astral-sh / ruff

An extremely fast Python linter and code formatter, written in Rust.
https://docs.astral.sh/ruff
MIT License
30.99k stars 1.03k forks source link

[Feature Request / Proposal] Upstream / preset / profile to preconfigure options #809

Open pwoolvett opened 1 year ago

pwoolvett commented 1 year ago

Would something like preset or a profile be a feature you'd like to see?

for example:

[tool.ruff]
preset = "charliemarsh"

alternatively

[tool.ruff]
upstream = "https://.../ruff-profile.toml"

The idea would be to (1) add a new parameter [in the cli]() and [in the config]() , and (2) override defaults eg after this with the loaded contents of the profile / upstream file.

would a PR with mentioned implementation be accepted? Or would more discussion be required (eg as part of the eventual plugin system)? Or maybe it's just a non-goal?

charliermarsh commented 1 year ago

Yeah I like this idea, I think this could be quite useful. The main question that comes to mind is where / how the presets should be defined.

isort supports presets (called "profiles"), but they're all defined in isort itself, which is probably not what we want here.

eslint supports shareable configs, but they have to be released on npm (or defined and loaded from the local filesystem), which we also probably don't want.

A URL seems reasonable, but we'd definitely want to cache it.

Can we think of other examples to model on here? Maybe something pre-commit like, where you specify a URL and explicitly run install to fetch (or update) the config locally?

pwoolvett commented 1 year ago

Presets seem to involve more logic.

I think the most straightforward would be the "upstream" variant, which would just be a local or cached remote .toml with configuration to use as defaults, overriden by local ruff config+cli. Then most common "presets" could just be tomls hosted together with the documentation :).

In that case, the pre-commit hook could be used to invalidate cached url (stage 1), before actually running ruff (stage 2). Or actually just provide two ruff hooks here: one to invalidate remote (people not using upstream wont care about this), and the current one. (you were talking about pre-commit, not pre-commit, right?)

charliermarsh commented 1 year ago

Yeah. We could also just cache it in .ruff_cache and then users can always rm -rf .ruff_cache to clear it out. (I want to make regular purging of the .ruff_cache slightly more automatic anyway, right now it just gets bigger with every release.)

charliermarsh commented 1 year ago

@pwoolvett - Are you interested in working on it? :)

pwoolvett commented 1 year ago

sure! ill have a go at it and send a pr

charliermarsh commented 1 year ago

We now have extend = "/path/to/pyproject.toml" which is relevant here.

michaeloliverx commented 1 year ago

Maybe relevant, Something akin to tsconfig.json extends?

https://www.typescriptlang.org/tsconfig#extends

charliermarsh commented 1 year ago

Created a Discussion around this: https://github.com/charliermarsh/ruff/discussions/3363

Avasam commented 1 year ago

We now have extend = "/path/to/pyproject.toml" which is relevant here.

The more projects I move over to Ruff, the more I really wish I could extend some base configs cross-projects. Something like being able to use a URL in extend or a path relative to site-packages, like:

Maybe relevant, Something akin to tsconfig.json extends?

typescriptlang.org/tsconfig#extends

It's also the last major feature missing before I feel confident showing off (and integrating) Ruff at my workplace, where we love being able to reference a global base configuration for our tooling, massively simplifying other dev's work when it comes to following company-wide standards, staying up to date, and integrating/configuring the tools in all projects as easily as possible.

(tl;dr: adoption is easier when I can say "add these 3 lines to your configs" instead of "copy these config files that will soon be out of date")

gaborbernat commented 1 year ago

@charliermarsh we spoke about this at PyCon how for Python would generalize better if one could pull configuration via a plugin system of an additionaly installed Python package. Ideally, we'd not even need any configuration such as:

[tool.ruff]
preset = "tox_ruff_config"

When installed into a Python interpreter, we could just use https://docs.python.org/3/library/importlib.metadata.html#entry-points via the ruff key, and if a such library is detected load that configuration file as a base (still overwrite-able via pyproject.toml). How one woud use this is via pre-commit:

- repo: https://github.com/charliermarsh/ruff-pre-commit
  rev: "v0.0.263"
  hooks:
    - id: ruff
       additional_dependencies: [tox_ruff_config==1.0.1]

This solution would cache natively, would be versioned by design, and would work behind enterprise firewalls too. This feature would only work if ruff is installed via Python, and would be no-op should you run ruff as a dedicated binary.

yoann9344 commented 6 months ago

It would be nice to be able to extend the config from a main project on another git.

[tool.ruff]
extend = "../ruff/pyproject.toml"
# instead we could write something like this :
extend = { git = "https://github.com/astral-sh/ruff.git", tag="0.2.1" }
extend = { git = "https://github.com/astral-sh/ruff.git", branch="some_branch" }
# maybe with the possibility to configure the path to the `pyproject.toml`
extend = { git = "https://github.com/astral-sh/ruff.git", tag="0.2.1", path = "my_dir_configs/pyproject.toml" }

Could even imagine to retrieve a config from a dependency manager like pep compliant or poetry :

[project]
dependencies = [
    "my-repo @ git+https://github.com/my-repo/ruff.git@0.2.1"
]
[tool.poetry.dependencies]
my-repo = { git = "https://github.com/my-repo/ruff.git", tag="0.2.1" }

[tool.ruff]
extend = { pep = "my-repo", path = "my_dir_configs/pyproject.toml" }
extend = { poetry = "my-repo", path = "my_dir_configs/pyproject.toml" }

The logic behind will basically do something like this :

# With default values :
# extend_tag=""
# extend_branch=""
# extend_path="pyproject.toml"
position=`pwd`
git clone --no-checkout --depth=1 --no-tags $extend_git $somewhere_in_ruff_cache
cd $somewhere_in_ruff_cache
if [ "$extend_tag" != "" ]; then
    git fetch origin "+refs/tags/${extend_tag}:refs/tags/${extend_tag}" --no-tags
    git checkout $extend_tag -- $extend_path
else
    git checkout $extend_branch -- $extend_path  # empty branch is HEAD
fi
cd $position
# config ruff to point on the retrieved toml
config_extend "${somewhere_in_ruff_cache}/${extend_path}"

Then we could imagine that extend can include presets :

extend = { preset = "strict_without_doc" }
# Would be be an alias for :
extend = { git = "https://github.com/astral-sh/ruff.git", tag = "<running_ruff_version>", path = "presets_config/strict_without_doc.toml" }
# We could even imagine to block the config to an older version
extend = { preset = "strict_without_doc", tag = "0.2.0" }

I think it is better to keep the same key for adding other configs. So everything that involves joining a toml would be under extend, doc, config and code would be clearer this way. Then to have multiple configs, we could image a list, where order matters :

extend = [
    { preset = "Doc_permissive" },
    { preset = "Flake8_without_warnings" },
    { preset = "Import" },
    { preset = "Security_strict" },
]
hinricht commented 5 months ago

Also very interested in the feature to extend from a remote URL. Use-case is to settle on a sane base config for ruff which can get included in all projects, but individual rules could easly tuned without code duplication.

Avasam commented 5 months ago

Do keep in mind though that online shared configs will break pre-commit.ci as it doesn't allow internet acces whilst running, only on install.

This could be worked around if the pre-commit action can "dry-run" and cache the extended config file.

dprint currently has this issue.