CLI Reference¶
The PyCodeCommenter command-line interface provides three subcommands: generate, validate, and coverage. This page documents every argument and flag for each, derived directly from cli.py.
The entry point is installed as pycodecommenter when you run pip install pycodecommenter.
pycodecommenter generate¶
Reads a Python source file or directory, generates Google-style docstrings for every undocumented function and class, and either prints, saves, or patches the result. Given a directory, it recursively collects every .py file (skipping the built-in and --excluded paths described below) and processes each one the same way it would a single file.
If a function or class already has a docstring, the existing content is merged, not discarded. The existing summary, parameter descriptions, and return description are preserved and used as the base; only missing sections are filled in.
Usage¶
Arguments¶
| Argument | Required | Description |
|---|---|---|
file |
Yes | Path to a Python source file, or a directory to process recursively |
Options / Flags¶
| Flag | Type | Default | Description |
|---|---|---|---|
-i, --inplace |
flag | off | Modify the file(s) in place. Without this flag or --output, the result is printed to stdout |
-o, --output |
string | none | Write the result to this file path instead of stdout. Single-file targets only — errors out if the target is a directory |
--dry-run |
flag | off | Print a unified diff of what would change and exit. Does not write any files. Exits with code 1 if there are changes, 0 if not |
--backup |
flag | off | Before modifying a file in place, copy it to <file>.bak. Has no effect without --inplace (a warning is printed) |
-e, --exclude |
list | .pycodecommenter.yaml's top-level exclude list, if set |
Patterns to exclude, added to the built-in defaults (directory targets only). Built-in defaults: __pycache__, .git, .venv, venv, env, .tox, .nox, __pypackages__, site-packages, build, dist, .eggs, .egg-info, .mypy_cache, .pytest_cache, node_modules |
--ai-draft |
flag | off | Have an AI model draft the parts the code can't state: function summaries and argument, return and exception descriptions, and class summaries and Attributes: entries, that would otherwise be TODO markers or type-only. Only gaps are filled; every drafted line is labelled (AI-drafted, unreviewed) |
--ai-provider |
choice | hosted |
Where --ai-draft sends code: hosted (free, daily limit, no key), or your own key with gemini (GEMINI_API_KEY), openai (OPENAI_API_KEY), anthropic (ANTHROPIC_API_KEY), deepseek (DEEPSEEK_API_KEY) or openai-compatible (OPENAI_COMPATIBLE_API_KEY). Install the SDK with pip install "pycodecommenter[gemini]", [openai] or [anthropic] |
--ai-model |
string | per provider | The model to use. Defaults: gemini-3.8-flash (with gemini-3.5-flash-lite, then gemini-2.5-flash, tried in turn if Google says an earlier one is unavailable to your key; a Gemini model you name yourself is never replaced), gpt-6-luna, claude-sonnet-5-5, deepseek-flash; these are only defaults, and any model your key can access works. Required for openai-compatible |
--ai-base-url |
string | none | API endpoint for openai-compatible (e.g. Mistral, Groq, or a local Ollama server) |
--accept-ai-drafts |
flag | off | Required with --ai-draft --inplace: an explicit acknowledgment that unreviewed AI drafts are written to your files |
--yes-send-code-to-ai |
flag | off | Record consent for the chosen provider without asking (CI), and skip the "Continue?" question before a directory run. --yes-send-code-to-hosted-ai still works as an alias |
--max-drafts |
integer | none | With --ai-draft, send at most N requests in the whole run (one per function or class with gaps). The rest keep their TODO markers and the summary says how many were not tried |
Note: An
--excludepattern matches a path component (directory or file name) exactly; a dot-prefixed pattern (e.g..egg-info) also matches a component it's a suffix of (covers the<name>.egg-infoconvention). This is exact-component matching, not a substring check against the whole path —rebuild_index.pyis not skipped just because it containsbuild, andenvironment_config.pyis not skipped just because it containsenv. (Thecoveragecommand's-e/--exclude, documented below, adds one more rule on top of these two — see its note.)
End-of-run summary¶
Every generate run ends with a summary on stderr (stdout carries only generated code, so generate app.py > out.py stays clean):
Summary: 4 docstrings would be written.
3 details taken straight from the code
5 gaps left as "TODO(pycodecommenter)" for you to fill
Next: fill in the gaps, or add --ai-draft to have them drafted; then run `pycodecommenter validate`.
It counts docstrings written, updated (your text kept) or already complete; details taken straight from the code; lines drafted by AI; docstrings taken from the comment above a definition; and gaps left. For a directory, the numbers cover the whole run. AI status messages and consent prompts also go to stderr. On a terminal, --ai-draft also shows a live status line while each request is in flight ([3/12] product_service.py - drafting create_product (hosted), or waiting 30 s for the rate limit), so a slow first request from the free hosted service does not look like a hang. It is not shown when stderr is redirected or in CI.
Before a directory run with --ai-draft, the tool counts the requests it would make (nothing is sent to find out) and prints, for example, AI drafting: 32 files, 121 functions and classes have gaps to draft (one request each). In a terminal it then asks Continue? [y/N] (the default is no), so running it in the wrong directory does not quietly spend your allowance. --yes-send-code-to-ai or a run without a terminal skips the question, and --max-drafts caps the total.
Why gaps can remain after an AI run. The summary says so: parts the AI declined to write (the code did not make them clear), requests that failed (network or service error), functions and classes not tried because drafting stopped (a spent limit or --max-drafts), and any not sent because their source looks like it holds a secret. Nothing that looks like a key, password or token is ever sent.
Output modes (mutually used in order)¶
- If
--dry-runis given, always print a diff (per file, for a directory target) and exit. No other flag matters. - If
--inplaceis given, write back to the original file(s) (and create.bakper file if--backupis set). - If
--output <path>is given (without--inplace, single-file targets only), write to the specified path. - If none of the above, print the result to stdout (per file, prefixed with
# File: <path>, for a directory target).
Examples¶
Preview what would be added (safe, no writes):
Write docstrings directly into the file:
Create a backup first, then patch:
Write to a separate output file:
Patch every file in a directory, excluding migrations:
Exit Codes¶
| Code | Meaning |
|---|---|
0 |
Success — file(s) processed (or, in --dry-run mode, no changes detected) |
1 |
Parse error — a file could not be read or parsed (in --dry-run mode: changes were detected). For a directory target, any file failing to parse is enough to exit 1. |
pycodecommenter review¶
Go through what needs a person's attention after generate, one item at a time.
Usage¶
What it asks about¶
| Item | Choices |
|---|---|
A line drafted by AI ((AI-drafted, unreviewed)) |
accept (keeps the text, removes the label), edit (type your own wording), skip |
A gap (TODO(pycodecommenter): describe) |
fill (type the text), skip |
A # comment the docstring below it now repeats |
yes to remove it, or no (the default) to keep it |
When a function has two or more AI-drafted lines, review first shows its whole docstring, and offers A (capital) to accept all of that function's remaining AI lines at once, after you have read them together. It covers that one function only; gaps and comments are still asked one by one, and there is deliberately no option to accept everything in a file, so removing the label always means a person looked at the text.
q quits at any point; what you decided so far is saved.
Safety¶
- Only docstring lines change, plus comment blocks you said yes to removing.
- Before a file is saved, it's checked to still parse and to have exactly the same code (only docstrings and comments may differ). If either check fails, the file is left unchanged.
- Typed text can't contain triple quotes or backslashes (they would break the docstring); you're asked again.
- The file's line endings are kept.
Options / Flags¶
| Flag | Type | Default | Description |
|---|---|---|---|
--list |
flag | off | Only list what needs review; change nothing. This is also what happens without a terminal (e.g. in CI) |
--exclude |
list | .pycodecommenter.yaml's exclude |
Patterns to exclude (directory targets only) |
Example¶
$ pycodecommenter review app.py --list
app.py:2 AI-drafted in add(): Add two numbers.
app.py:6 gap in add(): b (Any): TODO(pycodecommenter): describe.
1 AI-drafted line, 1 gap, 0 repeated comments to review in 1 file.
pycodecommenter validate¶
Reads a Python source file or directory and checks every function and class docstring for consistency with the actual code. Given a directory, it recursively validates every .py file it finds (skipping the same built-in and --excluded paths generate does).
Usage¶
Arguments¶
| Argument | Required | Description |
|---|---|---|
file |
Yes | Path to a Python source file, or a directory to validate recursively |
Options / Flags¶
| Flag | Type | Default | Description |
|---|---|---|---|
-e, --exclude |
list | .pycodecommenter.yaml's top-level exclude list, if set |
Patterns to exclude, added to the built-in defaults (directory targets only) — same matching rules and defaults as generate's -e/--exclude, above |
--fail-on-todo |
flag | off | Exit with code 1 if any docstring still holds a TODO(pycodecommenter) marker or other placeholder text (by default these are only warnings) |
--fail-on-ai-draft |
flag | off | Exit with code 1 if any docstring still holds an unreviewed (AI-drafted, unreviewed) line (see review) |
--output-format |
text | json |
text |
Output format. text prints one human-readable report per file. json prints a single JSON array of per-file report objects to stdout for a directory target (a single object for a single-file target). |
What the validator checks¶
Six categories of checks are run on every documented function:
- Signature matching — every parameter in the function signature must appear in the
Args:section, and vice versa. - Type consistency — if a parameter has a type annotation and is not in the docstring, that is flagged.
- Exception documentation — if the function body contains
raisestatements, aRaises:section is expected (Google or Sphinx style). - Return documentation — if the function has a
return <value>statement, aReturns:section is expected, and vice versa. - Format compliance — the docstring must have a summary line; non-standard section headers are flagged.
- Content quality — placeholder text (
TODO,FIXME,Description of, etc.), very short summaries, and duplicate parameter descriptions are flagged.
Output format¶
Text mode (default):
============================================================
VALIDATION REPORT
============================================================
File: mymodule.py
Coverage: 75.0%
Total Issues: 3
- Errors: 1
- Warnings: 2
- Info: 0
ISSUES:
[ERROR] mymodule.py:12:process_data: Parameter 'timeout' is not documented in docstring
→ Suggestion: Add 'timeout' to the Args section
JSON mode (--output-format json):
{
"file": "mymodule.py",
"stats": {
"total": 3,
"errors": 1,
"warnings": 2,
"info": 0,
"coverage_percentage": 75.0
},
"issues": [
{
"line": 12,
"severity": "ERROR",
"check": "signature",
"message": "Parameter 'timeout' is not documented in docstring"
}
]
}
Examples¶
Validate a single file:
Use in a shell script with exit code check:
Validate an entire directory:
Exit Codes¶
| Code | Meaning |
|---|---|
0 |
No ERROR-level issues found (warnings and info do not trigger a non-zero exit) |
1 |
One or more ERROR-level issues were found. For a directory target, this is true if any file in the tree has an ERROR-level issue. |
pycodecommenter coverage¶
Analyzes documentation coverage for a Python file or an entire directory tree.
Usage¶
Arguments¶
| Argument | Required | Description |
|---|---|---|
path |
Yes | A Python file or a directory to analyze recursively |
Options / Flags¶
| Flag | Type | Default | Description |
|---|---|---|---|
-e, --exclude |
list | none | One or more patterns to exclude, added to the built-in defaults (they don't replace them). Built-in defaults when not provided: __pycache__, .git, .venv, venv, env, .tox, .nox, __pypackages__, site-packages, build, dist, .eggs, .egg-info, .mypy_cache, .pytest_cache, node_modules, tests, test_ |
--output-format |
text | json |
text |
Output format. text prints the human-readable coverage table. json prints a machine-readable JSON object to stdout. |
--strict |
flag | off | Count a function or class as documented only if its docstring holds no TODO(pycodecommenter) placeholder and no unreviewed AI-drafted line. By default any non-empty docstring counts, so a project of generated stubs reads as 100% |
--fail-below |
float | none (or coverage.threshold from .pycodecommenter.yaml, if set) |
Exit with code 1 if the overall coverage percentage is below THRESHOLD |
--badge-output |
path | none | Write a shields.io endpoint-badge JSON file for the coverage percentage to PATH |
Note: An
--excludepattern matches a path component (directory or file name) exactly; a dot-prefixed pattern (e.g..egg-info) also matches a component it's a suffix of, and an underscore-suffixed pattern (e.g.test_) also matches a component it's a prefix of. They are not glob expressions, and they are not arbitrary substring matches against the full path either — matching is per path component.
Output¶
Directory mode prints a per-file table followed by a project total:
================================================================================
DOCUMENTATION COVERAGE REPORT
================================================================================
[OK] api.py 100.0%
[!!] utils.py 50.0%
[!!] models.py 0.0%
--------------------------------------------------------------------------------
TOTAL 60.0%
================================================================================
Single file mode prints one line:
--badge-output writes a shields.io endpoint-badge JSON file, independent of --output-format (the text/JSON console output above is unaffected):
color is brightgreen at ≥90%, green at ≥75%, yellow at ≥50%, and red below that.
Examples¶
Analyze a whole project:
Analyze only the src subdirectory:
Exclude test and migration files:
Analyze a single file:
Exit Codes¶
| Code | Meaning |
|---|---|
0 |
Analysis complete, and either --fail-below was not given or coverage was at or above the threshold |
1 |
--fail-below THRESHOLD was given and the overall coverage percentage is below it |
Global behaviour¶
If pycodecommenter is called with no subcommand, or with an unrecognised subcommand, it prints the top-level help message and exits with code 0.
--version prints the installed version and exits 0:
Related¶
- Getting Started — walkthrough with real examples
- Python API — programmatic access to the same functionality
- Recipes — complete CI/CD and pre-commit integrations