Skip to main content

PyCodeCommenter — Python Docstring Generator & Validator

Tests PyPI version Documentation Python Support License: MIT Docs Coverage GitHub Stars

PyCodeCommenter writes and checks Python docstrings. It reads your code to fill in everything the code itself can prove — parameter names, types, defaults, when an exception is raised, what a boolean function tests — keeps anything you already wrote, and clearly marks what only a person can say. If you opt in, an AI model drafts those parts too, each line labelled for review. It also validates existing docstrings against the code and measures documentation coverage.

Install: pip install pycodecommenter · Python 3.10+ · MIT License


See It Work

Given this file (no docstrings, one comment):

# Calculate what the customer owes after VAT and any discount.
def total_due(invoice_id: str, discount: float = 0.0) -> float:
    invoice = INVOICES.get(invoice_id)
    if invoice is None:
        raise KeyError(invoice_id)
    return invoice.amount * 1.18 - discount


def is_overdue(invoice, today) -> bool:
    return today > invoice.due_date

pycodecommenter generate billing.py writes (real output):

# Calculate what the customer owes after VAT and any discount.
def total_due(invoice_id: str, discount: float = 0.0) -> float:
    """Calculate what the customer owes after VAT and any discount.

    Args:
        invoice_id (str): Unique identifier for the invoice.
        discount (float): float value. (default: 0.0)

    Returns:
        float: TODO(pycodecommenter): describe

    Raises:
        KeyError: If `invoice is None`.
    """
    ...


def is_overdue(invoice, today) -> bool:
    """Is overdue.

    Args:
        invoice (Any): TODO(pycodecommenter): describe.
        today (Any): TODO(pycodecommenter): describe.

    Returns:
        bool: True if `today > invoice.due_date`, otherwise False.
    """
    ...

The summary comes from your comment, which stays where it is. The raise condition and the boolean check are read straight from the code. Nothing is guessed: what the code can't say is marked TODO. Every run ends with a summary:

Summary: 2 docstrings written.
  3 details taken straight from the code
  1 docstring taken from the comment above it (comment left in place)
  3 gaps left as "TODO(pycodecommenter)" for you to fill
Next: fill the gaps with `pycodecommenter review`, or add --ai-draft to have them drafted; then run `pycodecommenter validate`.

Add --ai-draft and the gaps are drafted, each line labelled (real output):

def is_overdue(invoice, today) -> bool:
    """Determine if an invoice is overdue. (AI-drafted, unreviewed)

    Args:
        invoice (Any): This is the invoice to check. (AI-drafted, unreviewed)
        today (Any): This is today's date. (AI-drafted, unreviewed)

    Returns:
        bool: True if `today > invoice.due_date`, otherwise False.
    """

Then pycodecommenter review billing.py goes through each drafted line: accept it, edit it, or skip it.


What PyCodeCommenter Does

Task Command
Write missing docstrings (preview first) pycodecommenter generate app.py --dry-run
Have AI draft what the code can't state pycodecommenter generate app.py --ai-draft --dry-run
Accept, edit or fill drafts and gaps pycodecommenter review app.py
Validate docstrings against the code pycodecommenter validate app.py
Measure documentation coverage pycodecommenter coverage ./src
JSON output for other tools pycodecommenter validate app.py --output-format json

It also catches documentation drift — code that changed while its docstring didn't: undocumented parameters, missing Returns: sections, entries for parameters that no longer exist, and mismatched types.


Quick Start

pip install pycodecommenter

# Preview what will be written (changes nothing)
pycodecommenter generate app.py --dry-run

# Write it
pycodecommenter generate app.py --inplace

# Optional: have AI draft the gaps, then go through the drafts
pycodecommenter generate app.py --ai-draft --dry-run
pycodecommenter review app.py

# Keep docstrings accurate, e.g. in CI
pycodecommenter validate app.py
pycodecommenter coverage ./src

Installing the development version

To work on PyCodeCommenter itself, or to try the unreleased code on main (Python 3.10 or newer):

git clone https://github.com/AmosQuety/PyCodeCommenter.git
cd PyCodeCommenter
python -m venv .venv && source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
pytest

pytest runs the test suite and the package's doctests; none of it needs a network connection or an AI SDK. Before sending a change, also run black . and flake8. See CONTRIBUTING.md for the full workflow (also on the documentation site).


Why PyCodeCommenter?

The Problem

  • Code changes quickly; docstrings lag behind and become stale.
  • Few tools check existing docstrings against the real signatures.
  • Tools that write docstrings for you tend to state guesses as if they were facts.
  • Most projects have no measure of how well they're documented.

The Solution

  • Facts first. Names, types, defaults, raise conditions and boolean checks are read from the code, so they're always right.
  • Your words are never overwritten. Existing summaries, descriptions and entries are kept, a # comment above an undocumented function becomes its docstring, and NumPy- or Sphinx-style docstrings keep their style.
  • Gaps are marked, not guessed. What the code can't state is left as TODO(pycodecommenter): describe — or, with --ai-draft, drafted by an AI model and labelled (AI-drafted, unreviewed) until you review it.
  • Validation and coverage catch drift and measure progress, with JSON output for CI.

PyCodeCommenter isn't alone in this space. pydoclint checks Args/Returns/Yields/Raises against the actual function signature across Google, NumPy, and Sphinx styles, is actively maintained, and is fast — if you only need validation, it's a strong choice. interrogate measures presence-only documentation coverage, the same metric shape as coverage.py here. PyCodeCommenter's actual differentiator is breadth in one place: it's the only one of the three that generates a docstring skeleton and validates and measures coverage, all through one importable Python API — not just a CLI or pre-commit hook.


Features

Six Validation Checks

Every documented function is checked for:

  1. Signature Matching — every parameter in the function signature must appear in Args:, and vice versa.
  2. Type Consistency — type annotations must match documented types.
  3. Exception Documentation — raise statements require a Raises: section (Google or Sphinx style).
  4. Return Documentation — return <value> requires a Returns: section.
  5. Format Compliance — docstring must have a summary line; non-standard section headers are flagged.
  6. Content Quality — placeholder text (TODO, FIXME, Description of), short summaries, and duplicate descriptions are caught.

Decorator-Aware Validation (v2.2.0)

  • @property getter — return check fires as normal.
  • @property setter / deleter — return check is skipped (no false-positive warnings).
  • @classmethod — cls is excluded from parameter checks.
  • @staticmethod — no self/cls stripping; all parameters validated.

Structured JSON Output (v2.2.0)

Both validate and coverage support --output-format json for machine-readable output:

pycodecommenter validate src/api.py --output-format json
{
  "file": "src/api.py",
  "stats": {
    "total": 3,
    "errors": 1,
    "warnings": 2,
    "info": 0,
    "coverage_percentage": 85.0
  },
  "issues": [
    {
      "line": 42,
      "severity": "ERROR",
      "check": "signature",
      "message": "Parameter 'timeout' is not documented in docstring"
    }
  ]
}

Modern Python Support

  • Python 3.10, 3.11, 3.12, 3.13.
  • async def functions.
  • Complex type hints: Union, Optional, Generic, list[int], int | str.
  • PEP 604 unions, PEP 585 generics.

Coverage Reporting

  • Per-file coverage percentages.
  • Project-wide totals.
  • JSON, Markdown, and console output.
  • CI/CD integration with exit codes.

AI Drafting (Optional)

Some parts of a docstring can't be read off the code: what a function is for, or what an untyped argument means. By default PyCodeCommenter leaves those as TODO(pycodecommenter): describe markers. Add --ai-draft and an AI model drafts them instead:

pycodecommenter generate app.py --ai-draft --dry-run
  • Only the gaps. Your own docstring text and facts read off the code (types, raise conditions) are never replaced; the model only fills parts that would otherwise be a TODO or say nothing beyond the type. That covers functions (summary, arguments, return, exceptions) and classes (summary and Attributes:).
  • Every drafted line is labelled (AI-drafted, unreviewed). The label stays until a person accepts the line in pycodecommenter review (review --list shows what is waiting), and pycodecommenter validate reports those lines until then.
  • Review first. --dry-run and --output-dir show the result without touching your files; writing with --inplace also requires --accept-ai-drafts.
  • Consent first. Before any code is sent anywhere, you're asked once per destination (--yes-send-code-to-ai for CI). What is sent is the source of each function with gaps and an outline of each such class, comments included; anything that looks like a key, password or token is left out, but check your comments hold no secrets. See Data and Privacy.
  • Know the cost first. For a directory, the tool counts the requests it would make (32 files, 121 functions and classes have gaps to draft) and asks before sending; --max-drafts N caps the whole run.

Providers and models

By default drafts come from PyCodeCommenter's free hosted service: no key needed (the service does not store your code, but it forwards it to Google's Gemini API on a free tier, and Google may use content sent on that tier to improve its products; use your own key if that is not acceptable), with a daily limit (25 drafts per caller per day in v2.6.0; the service sets the figure, and each run ends with a line such as Hosted AI drafts left today: 10 of 25., which is the number to trust). The day is a UTC day, so it resets at midnight UTC; the count is kept in the service's memory, so a restart of the service can also reset it. When the limit is reached mid-run you're asked whether to continue with your own key. You can also start with your own key:

--ai-provider Key read from Install Default model
hosted (default) — included chosen by the service
gemini GEMINI_API_KEY pip install "pycodecommenter[gemini]" gemini-3.8-flash, then gemini-3.5-flash-lite, then gemini-2.5-flash if the earlier ones are unavailable to your key
openai OPENAI_API_KEY pip install "pycodecommenter[openai]" gpt-6-luna
anthropic ANTHROPIC_API_KEY pip install "pycodecommenter[anthropic]" claude-sonnet-5-5
deepseek DEEPSEEK_API_KEY pip install "pycodecommenter[openai]" deepseek-flash
openai-compatible OPENAI_COMPATIBLE_API_KEY pip install "pycodecommenter[openai]" none: pass --ai-model and --ai-base-url

If the provider's SDK isn't installed, PyCodeCommenter says so before it asks for consent or a key, and prints the exact command to install it into the Python you are running (it never runs pip itself). During a run, if you choose your own key after the hosted limit and that provider can't be used, you are asked again until one works or you type skip.

How each provider has been tested. The automated tests run every provider against fake SDK clients and make no network calls, so they check what PyCodeCommenter sends and how it handles replies and errors, not what a vendor's service accepts today. Live runs so far: the hosted service and Gemini have been used end to end, and Anthropic was run with claude-haiku-4-5-20251001 (its current default, claude-sonnet-5-5, has not been run live). OpenAI and DeepSeek have not been run against real keys; their request shapes and default models follow each vendor's documentation as of September 2026. If a request is rejected, that function keeps its TODO(pycodecommenter) marker (or, for a bad key or a spent quota, drafting stops for the run) and no code is changed. Please open an issue if a provider or model does not work for you.

The default model is only a default. Pass --ai-model to use any model your key can access, for example a more capable one:

pycodecommenter generate app.py --ai-draft --ai-provider anthropic --ai-model claude-opus-5-5 --dry-run

Every run prints the provider and model it is using.

Why a TODO can still remain

--ai-draft is meant to leave no TODO(pycodecommenter) behind, and the run summary says why any are left: the model declined a part (the code did not make it clear, and it would rather say nothing than guess), a request failed, drafting stopped (a spent limit, or --max-drafts), or the function's source looked like it holds a secret and was not sent. Fill what is left with pycodecommenter review, or run again. To check that nothing is unfinished, use pycodecommenter coverage --strict (counts only docstrings with no placeholder and no unreviewed AI line) and pycodecommenter validate --fail-on-todo.

Setting your API key

Set the provider's variable in the shell before running (the names are in the table above). For the current terminal session only:

# bash / zsh
export ANTHROPIC_API_KEY="your-key"
# PowerShell
$env:ANTHROPIC_API_KEY = "your-key"
  • .env files are not read. Export the variable yourself, or load the file in your own shell before running.
  • No variable set? In a terminal you are asked for the key with the input hidden. That key is used for this run only and is never saved; the tool never reads or writes keys in project files.
  • Without a terminal (CI, a redirect) there is no prompt, so the variable must be set.

What it costs

Each function that still has gaps is one request to the provider, so a run over a large project makes many requests. With your own key the requests are billed by the provider. Bigger models cost more per request. The Anthropic default, claude-sonnet-5-5, is a mid-priced model asked for low effort, which is enough for one-sentence docstrings; a smaller one such as claude-haiku-4-5-20251001 costs less, and a larger one such as claude-opus-5-5 costs more, both chosen with --ai-model. The OpenAI default, gpt-6-luna, is that family's cheapest model and is asked for low reasoning effort. Check your provider's price list before a large run. Try --dry-run on one file first.


Usage Examples

Example 1: Generate Docstrings

from PyCodeCommenter import PyCodeCommenter

code = """
def calculate_discount(price: float, rate: float = 0.1) -> float:
    return price * (1 - rate)
"""

commenter = PyCodeCommenter().from_string(code)
print(commenter.get_patched_code())

Output (real):

def calculate_discount(price: float, rate: float = 0.1) -> float:
    """Calculate discount.

    Args:
        price (float): float value.
        rate (float): float value. (default: 0.1)

    Returns:
        float: TODO(pycodecommenter): describe
    """
    return price * (1 - rate)

The types and default are facts from the signature; "float value." says no more than the type, and the return value is marked for a person to describe. With --ai-draft (CLI) or PyCodeCommenter(description_provider=...) (API), those parts are drafted instead and labelled (AI-drafted, unreviewed).

Review what was drafted

pycodecommenter review app.py

Steps through every AI-drafted line (accept, edit or skip), every TODO gap (fill or skip), and every comment a new docstring now repeats (remove only if you say yes). Only docstrings and approved comments change, and a file is saved only if its code is exactly the same as before. --list just lists them.

Example 2: Validate in CI/CD

import sys
from PyCodeCommenter import PyCodeCommenter

commenter = PyCodeCommenter().from_file("src/main.py")
report = commenter.validate()

if report.stats.errors > 0:
    report.print_summary()
    sys.exit(1)  # Fail the CI build

print(f"✓ Documentation validated: {report.stats.coverage_percentage:.1f}% coverage")

Example 3: Enforce Coverage Threshold

from PyCodeCommenter import CoverageAnalyzer

analyzer = CoverageAnalyzer()
project = analyzer.analyze_directory("./src", exclude_patterns=["tests"])

if project.total_coverage < 80.0:
    print(f"❌ Coverage {project.total_coverage:.1f}% is below the 80% threshold")
    project.print_report()
    sys.exit(1)

print(f"✓ Coverage {project.total_coverage:.1f}% meets the threshold")

Example 4: JSON Export

import json
from PyCodeCommenter import PyCodeCommenter

commenter = PyCodeCommenter().from_file("mycode.py")
report = commenter.validate()

with open("validation_report.json", "w") as f:
    json.dump(report.to_dict(), f, indent=2)

CI/CD Integration

GitHub Actions

# .github/workflows/docs.yml
name: Documentation Check

on: [push, pull_request]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - run: pip install pycodecommenter
      - run: pycodecommenter validate src/

Pre-commit Hook

# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: validate-docstrings
        name: Validate Docstrings
        entry: pycodecommenter validate
        language: system
        types: [python]

Frequently Asked Questions

What is PyCodeCommenter? PyCodeCommenter is a Python command-line tool and library for automatically generating Google-style docstrings and validating existing docstrings against real function signatures.

How do I install PyCodeCommenter? Run pip install pycodecommenter. Python 3.10 or later is required.

Does PyCodeCommenter use AI or LLMs? Not unless you ask it to. By default it is fully deterministic: it uses Python's built-in ast module, makes no network requests, and gives the same output for the same input. With --ai-draft, an AI model drafts only the parts the code can't state, every drafted line is labelled (AI-drafted, unreviewed), and nothing is sent without your consent. See AI Drafting (Optional).

Does PyCodeCommenter overwrite my hand-written docstrings? No. Existing summaries and parameter, return, exception and attribute descriptions are preserved and merged. Only missing sections are filled in automatically.

What docstring styles does PyCodeCommenter support? New docstrings are Google style. Existing NumPy-style (dash-underlined Parameters/Returns/Raises) and Sphinx-style (:param:, :type:, :returns:, :raises:) docstrings keep their style: gaps are filled in the same convention, and nothing is converted.

Can I use PyCodeCommenter in CI/CD? Yes. The validate subcommand exits with code 1 when any ERROR-level issue is found, making it suitable for blocking CI builds. The --output-format json flag enables integration with any downstream tooling.

What is documentation drift? Documentation drift is when code is updated but the corresponding docstrings are not. Parameters get added or renamed, return types change, and exceptions get added — but the docstring stays the same. PyCodeCommenter detects and fixes this.

How does PyCodeCommenter measure documentation coverage? Coverage is (documented_functions + documented_classes) / (total_functions + total_classes) × 100. A function counts as documented if its first body statement is a non-empty string literal.


Configuration

Create .pycodecommenter.yaml in your project root:

style: google
validation:
  level: strict
  check_types: true
  check_exceptions: true
coverage:
  threshold: 80
  fail_below: true
exclude:
  - "*/tests/*"
  - "*/migrations/*"
  - "*/__pycache__/*"

Documentation

Full documentation: https://amosquety.github.io/PyCodeCommenter/


Supported Platforms & Environments

  • OS: Linux, macOS, Windows
  • Python: 3.10, 3.11, 3.12, 3.13
  • Environments: local, CI/CD (GitHub Actions, GitLab CI, Jenkins), pre-commit hooks
  • Dependencies: ruamel.yaml (config files), libcst (docstring patching); AI provider SDKs only as optional extras ([gemini], [openai], [anthropic])

Known Limitations

  • Python 2.x is not supported (EOL).
  • match statements (Python 3.10+) have basic support.
  • Without --ai-draft, prose that can't be extracted from the AST (what a parameter or function means, as opposed to its name/type/default) is a marked placeholder, TODO(pycodecommenter): describe, for a human to fill in. With --ai-draft, it is an AI draft labelled (AI-drafted, unreviewed) — still to be reviewed, never presented as finished documentation.

Roadmap

  • VS Code extension
  • Smart docstring updates that preserve human-written content
  • Optional AI drafting (v2.6.0) — opt-in (--ai-draft), fills only the gaps the code can't state, labels every drafted line (AI-drafted, unreviewed), review-first by default (--dry-run/--output-dir), and writing in place needs a separate --accept-ai-drafts
  • NumPy and full Sphinx style support (v2.3.0)
  • GitHub Action for automated documentation PRs
  • --fail-below flag for coverage threshold enforcement in CLI (v2.3.0)

Creator

PyCodeCommenter was created and is actively maintained by Nabasa Amos (Amos Quety), a software engineer focused on developer tooling, documentation automation, and software quality.


License

MIT License — see LICENSE for details.

Contributing

Contributions are welcome. Please read CONTRIBUTING.md before submitting a pull request.

Issues & Discussions


If PyCodeCommenter is useful in your workflow, a ⭐ on GitHub helps other Python developers discover it.

Release files for pycodecommenter 2.6.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pycodecommenter 2.6.1
File Size Uploaded
pycodecommenter-2.6.1.tar.gz 189.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pycodecommenter 2.6.1
File Interpreter ABI Platform
pycodecommenter-2.6.1-py3-none-any.whl Python 3 none any Details

Total release size: 318.5 kB

Release files / pycodecommenter-2.6.1.tar.gz

Download URL pycodecommenter-2.6.1.tar.gz
Size 189.3 kB
Tags Source
SHA-256 checksum
How to use checksums
205073a3d6f005b9d0013e624ec1fedd87ed4e15621fb1244557f3e00ce7d560
BLAKE2b-256 checksum
How to use checksums
69df45ec032f23d9b25501cec032f0883205ac2dc6ffa03c8adecbc48c71dff4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.

Transparency log

Release files / pycodecommenter-2.6.1-py3-none-any.whl

Download URL pycodecommenter-2.6.1-py3-none-any.whl
Size 129.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b71316abea33facb22b14760b6fe8b97c4dadda9b7e2ed1bd361817c451fb62f
BLAKE2b-256 checksum
How to use checksums
e885a31570879073b36338cf172f549d5469342cc9f7b97e932ffcc1362c4d0b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

2.6.1 This release

2 release files

2.6.0

2 release files

2.5.0

2 release files

2.4.0

2 release files

2.3.0

2 release files

2.2.0

2 release files

2.1.0

2 release files

2.0.3

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

1 release file

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page