Rule selection specification¶
This document specifies how pydocfmt discovers rule definitions and resolves rule-selection, per-file-ignore, and fixability settings, and how rule selection information is exposed via the CLI.
Ruff compatibility deltas¶
- D1: Rule catalog and selector prefixes.
pydocformatter has its own rule catalog and requires selectors to use complete pydocformatter rule prefixes such as
PDForPCF. The selectorPdoes not matchPDForPCFrules. - D2: Supported settings.
pydocformatter implements
select,ignore,extend-select,per-file-ignores,extend-per-file-ignores,fixable,unfixable, andextend-fixable. Ruff settings that have no pydocformatter equivalent, such as deprecatedextend-ignore, preview-rule settings, fix-safety settings, andexternal, are outside this compatibility surface. - D3: Selector errors. pydocformatter records selector validation problems as operational errors so one run can report all rule-selection issues together. Ruff may fail earlier while parsing or resolving its own configuration.
- D4: Fixability selector validation.
pydocformatter reports an operational error when a
fixableorextend-fixableselector matches only rules whoseFixAvailabilityisNever. Ruff accepts such selectors and simply has no fix to apply for those rules. - D5: Command-line comma-list whitespace.
pydocformatter strips whitespace around entries in dedicated command-line comma-list options, so
--select "PDF200, PDF110"is accepted asPDF200andPDF110. Ruff rejects whitespace-containing selector entries such asF841.
Rule definitions¶
Rules and rule categories live under pydocformatter.rules.definitions. Importing pydocformatter.rules.collection imports each category module before its rule modules and builds the default RULE_COLLECTION.
Each rule category is a RuleCategoryBase subclass in a module named after its rule-code prefix, with adjacent category documentation:
src/pydocformatter/rules/definitions/PDF/PDF.py
src/pydocformatter/rules/definitions/PDF/PDF.md
The class is also named after the prefix and defines RuleCategoryMetadata containing its prefix, user-facing name, and optional url. RuleCategoryBase rejects missing or invalid metadata at class definition time and provides ordered_rules() and ordered_code_class_map() views.
Each implemented rule is collected as one RuleBase subclass, normally in a module grouped by prefix:
src/pydocformatter/rules/definitions/PDF/PDF101_docstring_reflow.py
Rule classes register with their category through @register_rule_to(PDF). Rule selection uses the rule's meta class attribute, a RuleMetadata record containing:
code: ARuleCode, such asPDF101.name: A stable machine-readable name, such asdocstring-reflow.message: The default diagnostic message shown in rule listings and findings.fix_availability: AFixAvailabilityvalue describing whether automatic fixes areAlways,Usually,Sometimes, orNeveravailable at the rule level.stable_since: The pydocformatter version in which the rule became stable.setting_effects: Immutable metadata mapping resolved setting fields and triggering values toIgnoredorDisabledselection effects.incompatible_with: An immutable tuple ofRuleCodevalues for rules that cannot be selected together with this rule.check_kind: ARuleCheckKindvalue used to distinguish standard source-check rules from suppression-audit rules during rule collection and execution ordering.
RuleBase rejects subclasses without meta, or with non-RuleMetadata metadata, at class definition time. RuleMetadata rejects non-RuleCode codes, non-FixAvailability fix availability values, non-RuleCheckKind check kinds, empty names, messages, or stable versions, malformed setting-effect records, malformed incompatibility tuples, and duplicate incompatible codes. Category registration rejects rules whose code prefix differs from the category prefix and rejects duplicate rule codes from different classes.
Rule codes¶
Rule codes are the canonical lookup keys.
A valid rule code:
- Uses one or more uppercase ASCII letters followed by one or more digits.
- Does not start with the reserved selector tag
ALL. - Is parsed into
prefix,number_str, and numericnumber.
Examples:
PDF001is valid with prefixPDF, number string001, and number1.PCF100is valid with prefixPCF, number string100, and number100.bad,001, andALL001are invalid.
Collection¶
RuleCollection stores category classes in deterministic prefix order in categories, exposes the same order through category_class, and flattens their rules into deterministic rule-code order in rules and rule_class.
Collection behavior:
- Every collected object must inherit from
RuleCategoryBase; rules cannot be registered directly with a registry or collection. - Duplicate category prefixes are rejected unless they refer to the same category class.
- Categories expose their rules in rule-code order and allow idempotent re-registration of the same rule class.
matching_rules(selector)returns matching rule classes in collection order.matching_rules_exist(selector)returns whether any collected rule matches the selector.- Incompatibility declarations must reference collected rules, cannot reference the declaring rule itself, and must be declared mutually by both rules.
Built-in categories use the default registry through @register_rule_category, and their rules use @register_rule_to(category). Tests and custom rule packages can use an isolated RuleRegistry with register_rule_category_to(registry), pass that registry to import_package_rule_categories, then call RuleCollection.from_registry(registry).
Definition package loading validates the complete built-in layout: the definitions package contains only category packages; each category package is flat and contains a matching prefix-named category module and class; each rule module contains exactly one registered local rule whose code matches its filename and category; registered rules originate from and have modules in their category package; and every category and rule has adjacent Markdown documentation with no orphan Markdown files.
With default settings, a custom empty catalog resolves to an empty active ruleset without errors.
Selector grammar¶
Rule selectors are:
ALL: Matches all collected rules.- Full rule code:
PDF101. - Complete prefix:
PDF. - Complete prefix plus leading digits:
PDF10, matching rule codes whose numeric string starts with10.
Selectors are case-sensitive and must use complete rule prefixes. For example, P does not match rules with prefix PDF.
Selectors outside the grammar are operational errors. Selectors that match no collected rule are operational errors, except ALL, which may match an empty collection without error. Invalid or unknown selectors resolve to no rules and resolution continues.
Source priority and specificity¶
Selection conflicts are resolved first by configuration source priority, then by selector specificity. Selector order inside one setting is not significant.
Source priority values:
- Defaults have priority
0. - Auto-discovered or explicit config-file settings have priority
1. - Inline
--config "<KEY> = <VALUE>"settings have priority2. - Dedicated command-line options have priority
3. - In-process field overrides used by tests and callers have priority
4.
Specificity values:
ALLhas specificity0.- Any other selector has specificity equal to
len(selector).
Resolution rule:
- The strongest matching enabling selector is compared with the strongest matching disabling selector.
- A selector with a higher source priority beats a selector with a lower source priority, even if the lower-priority selector is more specific.
- When both sides have the same source priority, the more specific selector wins.
- The disabling side wins equal-priority, equal-specificity ties.
- Lower-priority adjustment settings that do not participate in the final decision are still validated and can still produce operational errors.
Examples:
select = ["PDF14"]andignore = ["PDF1"]enablesPDF142.select = ["PDF1"]andignore = ["PDF14"]disablesPDF142but can still enablePDF150.select = ["PDF14"]andignore = ["PDF14"]disablesPDF142.- With default
select = ["ALL"],extend-select = ["PDF14"]andignore = ["PDF1"]enablesPDF142but disablesPDF150. - A command-line
--select PDF1enablesPDF142even when a lower-priority config file hasignore = ["PDF142"]. - A command-line
--ignore PDF1disablesPDF142even when a lower-priority config file hasselect = ["PDF142"].
Setting effects¶
After normal select and ignore precedence is resolved, each selected rule's metadata is evaluated against the resolved CheckSettings values. Effects from all declared setting fields are combined:
Disabledtakes precedence overIgnored.Ignoredremoves a rule selected throughALL, a category prefix, or a partial selector, but any participating exact rule-code selector restores it, including one retained alongside a higher-priority broadextend-select.Disabledalways removes a rule, including one selected by exact rule code.- Configured
ignoreselectors and matching per-file ignores still suppress an exactly restored ignored rule. - Setting effects do not change effective fixability.
- A metadata field name that is not present on
CheckSettingsis a programming error identifying the rule and unknown field.
Global rule selection¶
Defaults:
select = ["ALL"]ignore = []extend-select = []require-explicitdefaults to the exact rule-code selectors in the runtimeDEFAULT_REQUIRE_EXPLICITsetting.
Global enabled rules are resolved per rule:
- Use the resolved
selectvalue as the active rule-selection basis. The source priority of thisselectvalue is the active select priority. - Keep
extend-selectonly when its source priority is greater than or equal to the active select priority. Lower-priorityextend-selectsettings are ignored. - Keep
ignoreonly when its source priority is greater than or equal to the active select priority. Lower-priorityignoresettings are ignored. - For each rule, track the strongest matching enabling selector by source priority and specificity.
- For each rule, track the strongest matching disabling selector by source priority and specificity.
- Select the rule only when the enabling selector strength is greater than the disabling selector strength.
- Resolve
require-explicitas rule selectors. Any selected rule matched byrequire-explicitis removed unless an exact rule-code selector also participated in enabling it.
The output rules tuple preserves deterministic rule-code order after filtering.
require-explicit is intended for rules that should remain available through exact selection without being enabled by broad selectors such as ALL, PDF, or PCF. For a rule matched by require-explicit, select = ["ALL"] does not enable it, while including the rule's exact code in extend-select enables it. Setting require-explicit = [] lets broad selectors enable all selected rules.
Rule incompatibilities¶
After normal selection precedence, setting effects, and explicit-selection requirements are resolved, selected rule incompatibilities are resolved once for the complete settings profile:
- Rules are considered in deterministic
RuleCollection.rulesorder. - A rule is retained unless it is incompatible with an earlier retained rule.
- A discarded rule does not prevent a later compatible rule from being retained.
- Selector source priority and specificity do not override collection ordering during this final pass.
- Each discarded rule produces one operational error listing all earlier retained rules that conflict with it.
- Per-file ignores run later and do not restore rules discarded by incompatibility resolution.
The built-in opposing pairs (e.g. PDF106/PDF107 and PDF108/PDF109) are mutually incompatible. Convention-specific setting effects keep each built-in convention profile conflict-free, but exact selection can restore ignored rules. Selecting both rules in either pair retains the lower ordered rule and reports the higher ordered rule as disabled.
docstring-convention = "pep257" is the default named convention profile. It does not parse Google sections, NumPy sections, or reStructuredText fields, and it applies PEP 257/pydocstyle-compatible broad-rule carve-outs. docstring-convention = "none" also avoids convention-specific parsing, but keeps the stricter generic no-convention rule profile for rules that can act without convention parsing.
Per-file ignores¶
Defaults:
per-file-ignores = {}extend-per-file-ignores = {}
Per-file ignores are resolved from per-file-ignores followed by extend-per-file-ignores. Both settings are TOML-style mappings from file patterns to lists of rule selectors.
Each field uses the resolved highest-priority value for that field. For example, a command-line --per-file-ignores value replaces configured per-file-ignores, while a command-line --extend-per-file-ignores value contributes through the separate extension field and does not replace configured per-file-ignores.
Each per-file ignore entry stores:
pattern: The file pattern exactly as configured.base_path: The setting source base used for matching the pattern.rule_codes: The set of rule codes matched by the entry's selectors.rule_specificities: The strongest selector specificity for each matched rule code.
RuleSelection.for_path(path):
- Converts
pathto a POSIX-style relative path from the per-file-ignore entry'sbase_path. - Matches per-file ignore patterns with
GlobPatternSet.compile((pattern,), match_parent_segments_for_bare=False). - Treats a leading
!in the pattern as negation: the entry applies to files that do not match the pattern after!is removed. - Removes any globally selected rule whose code is matched by a matching per-file-ignore entry.
- Does not compare per-file-ignore selector specificity against the selector that enabled the rule.
Per-file-ignore bases follow file-selection glob bases: auto-discovered config patterns are relative to their config directory, and explicit config, inline config, and CLI patterns are relative to the current working directory.
Examples:
select = ["PDF14"]withper-file-ignores = {"tests/*.py" = ["PDF1"]}removesPDF142for matching files.select = ["PDF1"]withper-file-ignores = {"tests/*.py" = ["PDF14"]}removesPDF142for matching files and keepsPDF150.select = ["PDF14"]withper-file-ignores = {"tests/*.py" = ["PDF14"]}removesPDF142for matching files.select = ["PDF"]withper-file-ignores = {"!src/*.py" = ["PDF101"]}removesPDF101everywhere except files matchingsrc/*.py.- A lower-priority config-file
per-file-ignoresentry still suppresses a rule selected by a higher-priority command-line--select; per-file ignores apply after global rule selection.
pydocfmt check --show-rules prints the global active rules and does not apply per-file ignores.
Per-file settings boundary¶
Per-file settings are specified in Settings specification. They are intentionally outside rule selection: they apply after global rule selection, do not re-run selector resolution, and cannot change which rules are active. pydocfmt check --show-rules therefore remains global/cwd-oriented and does not apply path-specific per-file setting overrides.
CLI list options¶
Rule-selection CLI list options accept comma-separated selector values per option occurrence:
pydocfmt check --select PDF200,PDF110 --ignore PDF203
Whitespace around command-line comma-list entries is stripped before validation. This is a pydocformatter CLI parsing delta from Ruff, which treats the whitespace as part of the selector.
Repeated list option occurrences append values within the command-line layer:
pydocfmt check --select PDF200 --select PDF110
Repeated TOML-map option occurrences merge entries within the command-line layer. If the same pattern appears more than once in repeated --per-file-ignores or --extend-per-file-ignores values, the selector lists are appended:
pydocfmt check --per-file-ignores '{"tests/*.py" = ["PDF200"]}' --per-file-ignores '{"tests/*.py" = ["PDF110"]}'
As with other settings, the resolved command-line value for a field replaces lower-priority values for the same field.
Fixability¶
Defaults:
fixable = ["ALL"]unfixable = []extend-fixable = []
Effective fixability uses the same source-priority and specificity model as rule selection:
- Use the resolved
fixablevalue as the active fixability basis. The source priority of thisfixablevalue is the active fixable priority. - Keep
extend-fixableonly when its source priority is greater than or equal to the active fixable priority. Lower-priorityextend-fixablesettings are ignored. - Keep
unfixableonly when its source priority is greater than or equal to the active fixable priority. Lower-priorityunfixablesettings are ignored. - For each rule, track the strongest matching fixable selector by source priority and specificity.
- For each rule, track the strongest matching unfixable selector by source priority and specificity.
- Treat the rule as configured-fixable only when the fixable selector strength is greater than the unfixable selector strength.
- Intersect configured fixability with the rule's
RuleMetadata.fix_availability.
Settings cannot make a rule with fix_availability = FixAvailability.NEVER fixable. Rules with FixAvailability.ALWAYS, FixAvailability.USUALLY, and FixAvailability.SOMETIMES all have available fixes for selector purposes.
A selector in fixable or extend-fixable that matches only rules with no available fixes is an operational error unless the selector is ALL. This means the default fixable = ["ALL"] does not warn merely because some collected rules have FixAvailability.NEVER.
Examples:
fixable = ["PDF14"]andunfixable = ["PDF1"]makesPDF142configured-fixable.fixable = ["PDF1"]andunfixable = ["PDF14"]makesPDF142configured-unfixable but can leavePDF150configured-fixable.fixable = ["PDF14"]andunfixable = ["PDF14"]makesPDF142configured-unfixable.- A command-line
--fixable PDF1makesPDF142configured-fixable even when a lower-priority config file hasunfixable = ["PDF142"]. - A command-line
--unfixable PDF1makesPDF142configured-unfixable even when a lower-priority config file hasfixable = ["PDF142"].
Operational errors¶
Rule selection is tolerant of selector errors. select_rules() accumulates nonfatal error strings and continues with invalid or unknown selectors resolving to no rules.
Current error wording:
- Invalid selector:
"{context} contains invalid selector: {selector}" - Unknown selector:
"{context} contains unknown selector: {selector}" - Fixability selector that only matches rules with no available fixes:
"{context} selector {selector!r} only matches rules with no available fixes" - Incompatible selected rule:
"Selected rule {rule} is incompatible with earlier selected rule[s] {conflicts}; {rule} has been disabled"
Contexts include:
rule selectionignored rulesfixable rulesunfixable rulesper-file ignores for {pattern!r}
When pydocfmt check --show-rules sees rule-selection errors, it prints them before the rule list and exits with status 1. Normal check execution includes these errors in grouped output handling.
Resolved selection API¶
select_rules(settings, collection=None) resolves a CheckSettings object against a RuleCollection. If no collection is passed, it uses pydocformatter.rules.collection.RULE_COLLECTION. Callers can pass a SettingsProfile to preserve source bases and source priorities from configuration loading; when no profile or explicit field priorities are provided, all selectors are treated as coming from the same source priority.
It returns RuleSelection:
rules: A tuple ofSelectedRuleobjects in rule-code order.per_file_ignores: A tuple of resolvedPerFileRuleIgnoreobjects.errors: A tuple of nonfatal operational error strings.collection: The collection used for resolution.
SelectedRule contains:
rule: The rule'sRuleMetadata.fixable: The effective fixability after configured and inherent fixability are combined.enabled_priority: The source priority of the selector that enabled the rule.enabled_specificity: The global selector specificity that enabled the rule.
PerFileRuleIgnore contains:
pattern: The configured path pattern.rule_codes: A frozenset of matched rule codes.rule_specificities: A sorted tuple of(RuleCode, specificity)pairs.
RuleSelection.for_path(path) returns the selected rules after applying matching per-file ignores to the normalized path.
CLI behavior¶
pydocfmt check --show-rules:
- Resolves rule selection using the loaded settings.
- Prints operational errors first, prefixed with
ERROR:. - Prints each globally active rule as
{code}{* if fixable} {name} ({message}). - Prints
No active rules.when no global rules are selected. - Exits with status
1if rule-selection errors were reported, otherwise0.
Only global rule selection is displayed. Per-file ignores are intentionally not reflected in --show-rules output because they depend on a path.
CLI rule explanations¶
pydocfmt rule [RULE|--all] exposes Ruff-style rule explanations.
RULEmust be an exact uppercase rule code, such asPDF101; rule names and lowercase codes are invalid.--allprints all collected rules in rule-code order and cannot be combined withRULE.--output-format textprints the adjacent Markdown rule document directly.--output-format jsonprints Ruff-style metadata for one rule, or a list of metadata objects with--all; itsexplanationfield omits the Markdown title and fixability paragraph.
Rule documents live next to their rule definition modules with the same basename and a .md extension. For example, src/pydocformatter/rules/definitions/PDF/PDF101_docstring_reflow.py is documented by src/pydocformatter/rules/definitions/PDF/PDF101_docstring_reflow.md.
CLI linter listing¶
pydocfmt linter exposes Ruff-style linter metadata translated from collected rule categories. The public command retains Ruff's terminology even though the internal model uses categories.
- Text output prints a right-aligned prefix/name table.
- JSON output prints a list of objects with
prefix,name, andurlwhen a URL is configured. - The command reports metadata from
RuleCollection.categoriesand does not resolve project configuration.