FP1: Hierarchical configuration¶
Rather than using YAML, which is prone to errors and ambiguous grammar, or JSON, which
is impossible* to read and modify by a user, this proposes a new config format based on
TOML format. This allows logically nested configuration without a
physically nested data format, and uses a better specified format with good support
in Python (as
tomli in PyPI, or
tomllib in stdlib with 3.11).
This specific proposal was designed with the need for overriding global or default values on any number of subpaths, so that individual paths (or submodules), within a multirepo or monorepo can have their own custom configuration or lint rules.
Furthermore, individual projects should be able to have their own config files (with relative paths) that are also read and override global config, to provide predictable and consistent linting results when linting the project separately or within the monorepo.
Configuration at any given path must be located either in the standardized
pyproject.toml file, or a separate
fixit.toml file. To follow PEP 518, everything
pyproject.toml must be under the
tool.<name> table, which would be
so we should use that same table name in
fixit.toml for consistency.
Selection of rules could ideally be a simple set of enabled and disabled rules,
but it also makes sense to allow specifying groups of rules by their package/module
name, as well as their fully qualified names. This is similar to enabling an entire
group of lint rules in flake8 with
select = E,F vs single rules with
ignore = E501.
Overriding global/default values should be possible both via additional tables in a top-level config file, or by separate config files within those subpaths. Overrides should be applied to all files within the relative subpaths, accounting for further nested overrides.
It may be worth supporting an
inherit = False or
root = true type of setting,
to ignore all parent/global configs, and prevent inconsistent results when, eg,
linting an OSS project exported to Github vs within the originating monorepo.
Overrides should share key names with the global/default values whenever possible. When inheriting parent values, subpath overrides should generally be set unions with parent values. Further semantics/heuristics may need to be applied when a subpath attempts to enable a rule that is otherwise disabled by global or parent configuration.
[tool.fixit] inherit = false # ignore all configs above this one enable = [ "fixit", # enable everything from a top-level package "fixit.core", # only rules from a specific module "fixit.core.OneRule", # enable a specific rule by fully qualified name ] disable = [ "fixit.opinions", # disable an entire module "fixit.style.LineLength", # disable a specific rule by fully qualified name ]
Overrides, option A, array-of-tables:
[[tool.fixit.overrides]] path = "foo" # add to the set of enabled/disabled rules enable = [ "foo.rules", # enable a local module with multiple rules ] disable = [ "fixit.core.RuleFour", # disable a core rule by fully qualified name ] [[tools.fixit.overrides]] path = "foo/bar/baz" enable = ["..."] disable = ["..."]
Overrides, option B, paths-within-table-names:
[tool.fixit.overrides.foo] enable = ["..."] disable = ["..."] [tool.fixit.overrides."foo/bar/baz"] enable = ["..."] disable = ["..."]