A Python docstring linter that checks signature ↔ docstring consistency and auto-fixes violations.
Inspired by pydoclint, pydocfix goes further by automatically repairing the issues it finds.
Note
This project is in beta (v0.1.0b2). APIs and behavior may change before the stable release.
pydoclint pioneered fast signature ↔ docstring consistency checking for Python. However, it can only report violations — all corrections must be done by hand.
pydocfix is built on pydocstring-rs, a CST (Concrete Syntax Tree) parser for docstrings written in Rust by the same author. CST preserves every token's byte offset, whitespace, and formatting, enabling:
- Byte-level diagnostics — point to the exact token (parameter name, type annotation, section header), not just the line
- Surgical auto-fix — edits replace precise byte ranges, so fixes never corrupt adjacent content
- Iterative fix loop — apply non-overlapping fixes, re-parse, repeat until stable
- Auto-fix — Automatically repair docstring issues with safe/unsafe classification
- Many rules across multiple categories (Summary, Parameters, Returns, Yields, Raises, Docstring)
- Google & NumPy style support (powered by pydocstring-rs)
- Signature ↔ docstring consistency — type mismatches, missing/extra parameters, ordering
- Default value checking — detect missing
optional/defaultannotations - Precise diagnostics — byte-level position information for every violation
- Baseline — suppress existing violations so only new ones are reported
- noqa — suppress specific violations inline or file-wide
pydocfix performs linting and auto-fix generation in a single pass, yet is significantly faster than pydoclint (lint-only) thanks to parallel file processing and a Rust-based CST parser:
| Project | Files | Lines | pydocfix | pydoclint | Speedup |
|---|---|---|---|---|---|
| numpy | 425 | 252K | 0.74 sec | 2.93 sec | 4.0x |
| scikit-learn | 635 | 372K | 0.70 sec | 4.41 sec | 6.3x |
Median of 5 runs (+ 1 warmup). pydocfix automatically parallelises across CPU cores (
-jflag); pydoclint runs single-threaded.
| pydocfix | pydoclint | |
|---|---|---|
| Auto-fix (safe + unsafe) | ✅ | — |
| Google style | ✅ | ✅ |
| NumPy style | ✅ | ✅ |
| Sphinx style | — | ✅ |
| Parameter checking | ✅ | ✅ |
| Return type checking | ✅ | ✅ |
| Yield checking | ✅ | ✅ |
| Raises checking | ✅ | ✅ |
Class docstring / __init__ rules |
- | ✅ |
| Class attribute checking | - | ✅ |
Default value checking (optional / default) |
✅ | — |
| Byte-level diagnostics | ✅ | — |
| Baseline suppression | ✅ | ✅ |
Inline # noqa |
✅ | ✅ |
| flake8 plugin | — | ✅ |
| pre-commit hook | ✅ | ✅ |
| Parallel execution | ✅ | — |
pip install pydocfixRequires Python 3.11+.
# Check docstrings (report only)
pydocfix check src/
# Show diff of proposed fixes
pydocfix check src/ --diff
# Apply safe fixes
pydocfix check src/ --fix
# Apply safe + unsafe fixes
pydocfix check src/ --fix --unsafe-fixes
# Select / ignore specific rules or categories
pydocfix check src/ --select PRM --ignore RTN,YLD
# Parallel execution (auto-detected for ≥8 files; override with --jobs)
pydocfix check src/ --jobs 4Configure via pyproject.toml:
[tool.pydocfix]
# Rule selection (see "Rule selectors" for syntax details)
select = ["ALL"]
ignore = ["RIS"]
extend-safe-fixes = ["PRM"]
extend-unsafe-fixes = ["RTN", "YLD"]
# Type annotation style: "signature" | "docstring" | "both" | omitted (default)
# omitted — PRM103/RTN103/YLD103 and PRM104/RTN104/YLD104 are all disabled
# signature — redundant docstring types flagged (x104); missing signature annotations flagged (x105)
# docstring — missing docstring types flagged (x103); redundant signature annotations flagged (x106)
# both — missing docstring types flagged (x103); missing signature annotations flagged (x105)
type_annotation_style = "signature"
# Paths/patterns to exclude (in addition to built-in defaults)
exclude = ["tests/", "docs/"]
# Skip section-level rules (PRM001, RTN001, YLD001, RIS001) for one-line docstrings (default: true)
skip_short_docstrings = true
# Treat Optional[T], T | None, and Union[T, None] as equivalent to T
# when comparing types in PRM101/RTN101/YLD101 (default: false)
allow_optional_shorthand = false
# Path to the baseline file (relative to pyproject.toml)
baseline = ".pydocfix-baseline.json"Each rule is classified as safe fix, unsafe fix, or report-only.
- Safe fixes can be applied automatically with
--fix(no risk of changing semantics) - Unsafe fixes require
--fix --unsafe-fixes(may alter docstring meaning)
| Code | Default | Fix | Description |
|---|---|---|---|
| SUM001 | ✅ | — | Missing summary line |
| SUM002 | ✅ | safe | Summary doesn't end with period |
| Code | Default | Fix | Description |
|---|---|---|---|
| PRM001 | ✅ | unsafe | Missing Args/Parameters section |
| PRM002 | ✅ | safe | Unnecessary Args/Parameters section |
| PRM003 | ✅ | safe | self/cls documented in docstring |
| PRM004 | ✅ | unsafe | Parameter in signature missing from docstring |
| PRM005 | ✅ | unsafe | Parameter in docstring not in signature |
| PRM006 | ✅ | unsafe | Parameter order mismatch |
| PRM007 | ✅ | unsafe | Duplicate parameter name |
| PRM008 | ✅ | — | Parameter has no description |
| PRM009 | ✅ | safe | Missing */** prefix on *args/**kwargs |
| PRM101 | ✅ | unsafe | Docstring type doesn't match signature annotation |
| PRM102 | ✅ | unsafe | No type in docstring or signature |
| PRM103 | unsafe | No type in docstring | |
| PRM104 | safe | Redundant type in docstring (signature has annotation) | |
| PRM105 | — | No type annotation in signature (type_annotation_style = "signature" or "both") |
|
| PRM106 | — | Redundant type annotation in signature (type_annotation_style = "docstring") |
|
| PRM201 | ✅ | unsafe | Missing optional for parameter with default |
| PRM202 | unsafe | Missing default for parameter with default |
| Code | Default | Fix | Description |
|---|---|---|---|
| RTN001 | ✅ | unsafe | Missing Returns section |
| RTN002 | ✅ | safe | Unnecessary Returns section |
| RTN003 | ✅ | — | Returns entry has no description |
| RTN101 | ✅ | unsafe | Return type mismatch |
| RTN102 | ✅ | unsafe | No return type anywhere |
| RTN103 | unsafe | No return type in docstring | |
| RTN104 | safe | Redundant return type in docstring | |
| RTN105 | — | No return type annotation in signature (type_annotation_style = "signature" or "both") |
|
| RTN106 | — | Redundant return type annotation in signature (type_annotation_style = "docstring") |
| Code | Default | Fix | Description |
|---|---|---|---|
| YLD001 | ✅ | unsafe | Missing Yields section |
| YLD002 | ✅ | safe | Unnecessary Yields section |
| YLD003 | ✅ | — | Yields entry has no description |
| YLD101 | ✅ | unsafe | Yield type mismatch |
| YLD102 | ✅ | unsafe | No yield type anywhere |
| YLD103 | unsafe | No yield type in docstring | |
| YLD104 | safe | Redundant yield type in docstring | |
| YLD105 | — | No yield type annotation in signature (type_annotation_style = "signature" or "both") |
|
| YLD106 | — | Redundant yield type annotation in signature (type_annotation_style = "docstring") |
| Code | Default | Fix | Description |
|---|---|---|---|
| RIS001 | ✅ | unsafe | Missing Raises section |
| RIS002 | ✅ | safe | Unnecessary Raises section |
| RIS003 | ✅ | — | Raises entry has no description |
| RIS004 | ✅ | unsafe | Raised exception not documented |
| RIS005 | ✅ | unsafe | Documented exception not raised |
| Code | Default | Fix | Description |
|---|---|---|---|
| DOC001 | ✅ | unsafe | Section order doesn't match convention |
--select, --ignore, --extend-safe-fixes, --extend-unsafe-fixes (CLI) and their pyproject.toml equivalents all accept the same rule selector syntax:
| Format | Example | Matches |
|---|---|---|
| Exact code | PRM001 |
PRM001 only |
| Category prefix | PRM |
All PRM rules |
ALL |
ALL |
Every rule |
Add a # noqa comment on the closing """ line to suppress violations for that docstring.
def foo(x):
"""Short summary.""" # noqa # suppress all rules for this docstring
def bar(x):
"""Short summary.""" # noqa: PRM001 # suppress only PRM001
def baz(x):
"""Short summary.""" # noqa: PRM001, RTN001 # suppress multiple rules
# For multiline docstrings, put the comment on the closing """ line
def qux(x: int) -> int:
"""Short summary.
Args:
x: A value.
""" # noqa: RTN001
return xUnused # noqa codes are reported as NOQ001 (and removed by --fix).
Put a # pydocfix: noqa comment on its own line anywhere in the file to suppress violations for every docstring in the file.
# pydocfix: noqa # suppress all rules in this file
# pydocfix: noqa: PRM001 # suppress only PRM001 in this fileThe baseline lets you record the current violation state of a project and suppress those existing violations on future runs — so only new violations are reported. This makes gradual adoption easier: fix violations at your own pace.
# Record all current violations as the baseline
pydocfix check src/ --baseline .pydocfix-baseline.json --generate-baseline
# Future runs only report violations not in the baseline
pydocfix check src/ --baseline .pydocfix-baseline.jsonOr configure the baseline path in pyproject.toml so you don't need the flag every time:
[tool.pydocfix]
baseline = ".pydocfix-baseline.json"Then generate and use it:
pydocfix check src/ --generate-baseline # write baseline
pydocfix check src/ # only new violations reportedThe baseline file is a JSON file that records violations by symbol name (e.g. MyClass.my_method) rather than line number, so it stays stable when unrelated code is added or removed.
Fixed violations are automatically removed from the baseline on the next run.
Add to .pre-commit-config.yaml:
repos:
- repo: https://github.com/ryumasai/pydocfix
rev: v0.1.0b2
hooks:
- id: pydocfixTo enable auto-fix:
- id: pydocfix
args: [--fix]MIT