Skip to content

Python API Reference

PyCodeCommenter exposes a clean Python API for all three of its core capabilities — generation, validation, and coverage analysis. This page documents every public class exported from the package.

All classes listed here are importable directly from the top-level package:

from PyCodeCommenter import (
    PyCodeCommenter,
    DocstringValidator,
    ValidationReport,
    ValidationIssue,
    Severity,
    CoverageAnalyzer,
    FileCoverage,
    ProjectCoverage,
    TypeAnalyzer,
    DocstringParser,
)

PyCodeCommenter

The main class for loading Python code, generating docstrings, and patching the source. All methods return self or a computed value, enabling a fluent interface.

Constructor

PyCodeCommenter(description_provider=None, include_module_docstrings=False, progress=None, budget=None)
Parameter Type Default Description
description_provider DescriptionProvider or None None Opt-in AI drafting of the parts the code can't state (see AI drafting providers). None keeps generation fully deterministic, with no network calls
include_module_docstrings bool False Also write a module docstring for a file that has none
progress object with drafting(name) and clear(), or None None Told when a draft is requested and when it ends, so a slow request can be shown on screen (the CLI passes one; see progress.py)
budget object with take(), or None None Called before each AI request; a False answer skips it. Caps the requests of a whole run (the CLI's --max-drafts)

After construction, call from_string() or from_file() to load code.

Attribute Type Description
code str The raw source code (set by from_string / from_file)
parsed_code ast.Module or None The parsed AST; None if parsing failed
report GenerationReport Counts from the latest run: new, updated, unchanged docstrings, facts, ai_lines, todos, from_comments; summary_lines() gives the CLI's summary text
comment_docstrings list Definitions whose docstring came from the # comment above them (the comment is left in place)
drafting_stopped DraftingStopped or None Set if the description provider stopped drafting partway (for example, its daily allowance ran out)
comments list Populated by generate_docstrings()
tokenized_comments list Raw comment tokens extracted during load
type_analyzer TypeAnalyzer Instance used internally for type inference

Methods


from_string(code_string) -> PyCodeCommenter

Load code from a string. Returns self so calls can be chained.

Parameters:

Parameter Type Description
code_string str A string containing valid Python source code

Returns: PyCodeCommenter (the same instance)

Raises: Does not raise. Logs a warning on empty input; logs an error on SyntaxError and sets parsed_code = None.

Example:

from PyCodeCommenter import PyCodeCommenter

code = """
def greet(name: str) -> str:
    return f"Hello, {name}"
"""

commenter = PyCodeCommenter().from_string(code)

Runnable example (checked by the test suite):

>>> from PyCodeCommenter import PyCodeCommenter
>>> code = 'def greet(name: str) -> str:\n    return f"Hello, {name}"\n'
>>> commenter = PyCodeCommenter().from_string(code)
>>> commenter.parsed_code is not None
True
>>> PyCodeCommenter().from_string("def broken(:").parsed_code is None
True

from_file(file_path) -> PyCodeCommenter

Load code from a file on disk. Returns self.

Parameters:

Parameter Type Description
file_path str Absolute or relative path to a Python .py file

Returns: PyCodeCommenter (the same instance)

Raises: Does not raise. Logs an error and sets parsed_code = None on FileNotFoundError, IOError, or SyntaxError.

Example:

commenter = PyCodeCommenter().from_file("src/api.py")
if not commenter.parsed_code:
    print("Could not parse the file")

Runnable example (checked by the test suite):

>>> import os, tempfile
>>> with tempfile.TemporaryDirectory() as folder:
...     path = os.path.join(folder, "api.py")
...     with open(path, "w", encoding="utf-8") as handle:
...         _ = handle.write(code)
...     loaded = PyCodeCommenter().from_file(path)
>>> loaded.parsed_code is not None
True
>>> PyCodeCommenter().from_file("no/such/file.py").parsed_code is None
True

generate_docstrings() -> list

Walk the AST and generate docstring strings for every function and class. Returns a flat list of generated docstring strings (one per node). Does not modify the source.

Parameters: None

Returns: list of str — one entry per function/class found (including module docstring if present)

Example:

commenter = PyCodeCommenter().from_string(code)
docstrings = commenter.generate_docstrings()
for doc in docstrings:
    print(doc)

Runnable example (checked by the test suite):

>>> docstrings = PyCodeCommenter().from_string(code).generate_docstrings()
>>> len(docstrings)
1
>>> docstrings[0].splitlines()[0]
'"""Greet.'

get_patched_code() -> str

Return the full source code with all docstrings inserted or replaced. If a function or class already has a docstring, it is updated (the existing summary, parameter descriptions, and return text are preserved). New docstrings are inserted at the correct indentation level.

Parameters: None

Returns: str — the complete patched source code

Example:

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

# Write to disk yourself:
with open("output.py", "w", encoding="utf-8") as f:
    f.write(patched)

Runnable example (checked by the test suite):

>>> patched = PyCodeCommenter().from_string(code).get_patched_code()
>>> patched.splitlines()[:3]
['def greet(name: str) -> str:', '    """Greet.', '']
>>> 'name (str): The name.' in patched
True
>>> patched.endswith('return f"Hello, {name}"\n')
True

validate(strict=False) -> ValidationReport

Validate all docstrings in the loaded code against the actual AST signatures. Returns a full ValidationReport.

Parameters:

Parameter Type Default Description
strict bool False Parameter is accepted but does not yet affect behaviour. Reserved for future use

Returns: ValidationReport

Raises: Does not raise. Returns an empty ValidationReport if parsed_code is None.

Example:

import sys
from PyCodeCommenter import PyCodeCommenter

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

if report.stats.errors > 0:
    sys.exit(1)

Runnable example (checked by the test suite):

>>> report = PyCodeCommenter().from_string(code).validate()
>>> (report.stats.errors, report.stats.total_functions)
(1, 1)
>>> report.issues[0].message
"Function 'greet' has no docstring"

check_coverage() -> FileCoverage

Calculate documentation coverage for the loaded code.

Parameters: None

Returns: FileCoverage — coverage stats for the in-memory code (path is set to "<string>")

Example:

commenter = PyCodeCommenter().from_string(code)
coverage = commenter.check_coverage()
print(f"{coverage.coverage_percentage:.1f}%")
print(f"Functions: {coverage.documented_functions}/{coverage.total_functions}")
print(f"Classes:   {coverage.documented_classes}/{coverage.total_classes}")

Runnable example (checked by the test suite):

>>> coverage = PyCodeCommenter().from_string(code).check_coverage()
>>> (coverage.path, coverage.total_functions, coverage.documented_functions)
('<string>', 1, 0)
>>> coverage.coverage_percentage
0.0

DocstringValidator

Validates an existing Python source file or code string for documentation quality.

Constructor

DocstringValidator(code_string=None, file_path=None)

Provide exactly one of the two parameters.

Parameter Type Default Description
code_string str or None None Python source code as a string
file_path str or None None Path to a .py file to read and validate

If both are provided, code_string takes precedence.

Example:

from PyCodeCommenter import DocstringValidator

# From string
validator = DocstringValidator(code_string=my_code)

# From file
validator = DocstringValidator(file_path="src/models.py")

Methods


validate_all() -> ValidationReport

Run all six validation checks across all functions and classes in the parsed code.

Returns: ValidationReport

Example:

validator = DocstringValidator(file_path="src/models.py")
report = validator.validate_all()
report.print_summary()

Runnable example (checked by the test suite):

>>> from PyCodeCommenter import DocstringValidator
>>> validator = DocstringValidator(code_string=code)
>>> report = validator.validate_all()
>>> (report.stats.errors, report.stats.warnings, report.stats.info)
(1, 0, 0)
>>> DocstringValidator(file_path="no/such/file.py").validate_all().issues
[]

check_signature_match(func_node, docstring, location) -> List[ValidationIssue]

Check that every parameter in the function signature is documented and every documented parameter exists in the signature.

Parameters:

Parameter Type Description
func_node ast.FunctionDef or ast.AsyncFunctionDef The function AST node
docstring str The raw docstring text
location str Location string for issue reporting, e.g. "file.py:10:my_func"

Returns: List[ValidationIssue]

Runnable example (checked by the test suite):

>>> import ast
>>> source = '''
... def f(a: int, b: str = "x") -> int:
...     if a < 0:
...         raise ValueError("negative")
...     return a
... '''
>>> func = ast.parse(source).body[0]
>>> validator = DocstringValidator(code_string=source)
>>> issues = validator.check_signature_match(
...     func, "Do f.\n\nArgs:\n    a (int): The a.\n", "demo.py:2:f"
... )
>>> [(issue.severity.name, issue.message) for issue in issues]
[('ERROR', "Parameter 'b' is not documented in docstring")]

check_type_consistency(func_node, docstring, location) -> List[ValidationIssue]

Check that annotated parameters and return types are reflected in the docstring.

Parameters: Same structure as check_signature_match.

Returns: List[ValidationIssue]

Runnable example (checked by the test suite):

>>> issues = validator.check_type_consistency(
...     func, "Do f.\n\nArgs:\n    a (int): The a.\n", "demo.py:2:f"
... )
>>> [(issue.severity.name, issue.message) for issue in issues]
[('WARNING', "Function has return type hint 'int' but no Returns section in docstring"), ('INFO', "Parameter 'b' has type hint 'str' but is not documented")]

check_exception_documentation(func_node, docstring, location) -> List[ValidationIssue]

Check that any raise statements in the function body are matched by a recognised Raises section in the docstring — Google-style Raises:, a bare Raises header, or Sphinx-style :raises.

Parameters: Same structure as check_signature_match.

Returns: List[ValidationIssue]

Runnable example (checked by the test suite):

>>> issues = validator.check_exception_documentation(func, "Do f.", "demo.py:2:f")
>>> [issue.message for issue in issues]
["Function raises exceptions {'ValueError'} but has no Raises section"]

check_return_documentation(func_node, docstring, location) -> List[ValidationIssue]

Check that return <value> statements are matched by a Returns: section and vice versa.

Parameters: Same structure as check_signature_match.

Returns: List[ValidationIssue]

Runnable example (checked by the test suite):

>>> issues = validator.check_return_documentation(func, "Do f.", "demo.py:2:f")
>>> [issue.message for issue in issues]
['Function returns a value but has no Returns section in docstring']

check_format_compliance(docstring, location) -> List[ValidationIssue]

Check that the docstring has a summary line and only uses recognised Google-style section headers.

Parameters:

Parameter Type Description
docstring str The raw docstring text
location str Location string for issue reporting

Returns: List[ValidationIssue]

Runnable example (checked by the test suite):

>>> [issue.message for issue in validator.check_format_compliance("", "demo.py:2:f")]
['Docstring is empty']
>>> validator.check_format_compliance("Do f.", "demo.py:2:f")
[]

check_content_quality(docstring, location) -> List[ValidationIssue]

Check for placeholder text, very short summaries, and duplicate parameter descriptions.

Parameters: Same as check_format_compliance.

Returns: List[ValidationIssue]

Runnable example (checked by the test suite):

>>> issues = validator.check_content_quality("TODO: write this.", "demo.py:2:f")
>>> [issue.message for issue in issues]
["Placeholder text 'TODO' found in docstring"]

ValidationReport

Container for the results of a validate_all() run.

Attributes

Attribute Type Description
issues List[ValidationIssue] All issues found
stats ValidationStats Aggregated statistics
file_path str or None Source file path, if applicable

Methods


add_issue(issue) -> None

Add a ValidationIssue to the report and update stats counters.


Print a human-readable summary of the report to stdout, including all issues and suggestions.


to_dict() -> dict

Return the report as a Python dictionary, suitable for JSON serialisation.

Example:

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

to_markdown() -> str

Return the report as a Markdown-formatted string.

Example:

with open("report.md", "w") as f:
    f.write(report.to_markdown())

count_placeholders() -> int

Count the issues that report placeholder text (TODO, FIXME, this tool's own TODO(pycodecommenter) marker and the like). This is what the --fail-on-todo flag of validate checks.


count_ai_drafts() -> int

Count the docstrings that still hold an unreviewed (AI-drafted, unreviewed) line. This is what --fail-on-ai-draft checks.

Runnable example (checked by the test suite):

>>> import json
>>> from PyCodeCommenter import Severity, ValidationIssue, ValidationReport
>>> report = ValidationReport()
>>> report.add_issue(
...     ValidationIssue(Severity.WARNING, "quality", "f.py:1:f", "Placeholder text", "Replace it")
... )
>>> (report.stats.total_issues, report.stats.warnings)
(1, 1)
>>> report.to_dict()["issues"]
[{'line': 1, 'severity': 'WARNING', 'check': 'quality', 'message': 'Placeholder text'}]
>>> json.loads(json.dumps(report.to_dict()))["stats"]["warnings"]
1
>>> report.to_markdown().splitlines()[0]
'# Validation Report'
>>> (report.count_placeholders(), report.count_ai_drafts())
(1, 0)

ValidationStats

Dataclass holding aggregated counts from a validation run. Accessed via report.stats.

Field Type Description
total_functions int Total functions encountered
total_classes int Total classes encountered
documented_functions int Functions with a docstring
documented_classes int Classes with a docstring
total_issues int Total issues found
errors int ERROR-level issue count
warnings int WARNING-level issue count
info int INFO-level issue count (renamed from infos in v2.3.0 to match the "info" key in JSON/Markdown output; .infos still works as a backward-compatible property alias)
coverage_percentage float (property) (documented / total) * 100

Runnable example (checked by the test suite):

>>> report.stats.infos == report.stats.info
True
>>> round(report.stats.coverage_percentage, 1)
0.0

ValidationIssue

Dataclass representing a single documentation problem.

Field Type Description
severity Severity Severity.ERROR, Severity.WARNING, or Severity.INFO
category str One of "missing", "signature", "types", "exceptions", "returns", "format", "quality", "ai_draft"
location str "file.py:line:function_name"
message str Human-readable description of the issue
suggestion str or None How to fix the issue

Runnable example (checked by the test suite):

>>> issue = report.issues[0]
>>> (issue.severity, issue.category, issue.location)
(<Severity.WARNING: 'warning'>, 'quality', 'f.py:1:f')
>>> str(issue)
'[WARNING] f.py:1:f: Placeholder text'

Severity

Enum for issue severity levels.

from PyCodeCommenter import Severity

Severity.ERROR    # value: "error"   — Must fix; causes exit code 1
Severity.WARNING  # value: "warning" — Should fix; does not affect exit code
Severity.INFO     # value: "info"    — Nice to have; does not affect exit code

Runnable example (checked by the test suite):

>>> [severity.value for severity in Severity]
['error', 'warning', 'info']

CoverageAnalyzer

Analyses documentation coverage for individual files or entire directory trees.

Constructor

CoverageAnalyzer(strict=False)
Parameter Type Default Description
strict bool False Do not count a docstring that still holds a TODO(pycodecommenter) placeholder or an unreviewed AI-drafted line (the CLI's coverage --strict)

Methods


analyze_file(file_path) -> FileCoverage

Analyse a single Python source file.

Parameters:

Parameter Type Description
file_path str Path to the .py file

Returns: FileCoverage

Example:

from PyCodeCommenter import CoverageAnalyzer

analyzer = CoverageAnalyzer()
coverage = analyzer.analyze_file("src/api.py")
print(f"{coverage.coverage_percentage:.1f}%")

analyze_directory(directory, exclude_patterns=None) -> ProjectCoverage

Recursively analyse all .py files in a directory.

Parameters:

Parameter Type Default Description
directory str — Path to the directory to analyse
exclude_patterns List[str] or None None Extra patterns to exclude, added to the built-in defaults (__pycache__, .git, .venv, venv, env, .tox, .nox, __pypackages__, site-packages, build, dist, .eggs, .egg-info, .mypy_cache, .pytest_cache, node_modules, tests, test_) — never a replacement for them. A pattern matches a path component exactly, plus 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; it is not a substring match against the whole path

Returns: ProjectCoverage

Example:

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

if project.total_coverage < 80.0:
    print(f"Coverage {project.total_coverage:.1f}% is below threshold")

Runnable example (checked by the test suite):

>>> import os, tempfile
>>> from PyCodeCommenter import CoverageAnalyzer
>>> folder = tempfile.mkdtemp()
>>> path = os.path.join(folder, "module.py")
>>> with open(path, "w", encoding="utf-8") as handle:
...     _ = handle.write('def documented():\n    """Doc."""\n\n\ndef bare():\n    pass\n')
>>> analyzer = CoverageAnalyzer()
>>> analyzer.analyze_file(path).coverage_percentage
50.0
>>> analyzer.analyze_directory(folder).total_coverage
50.0
>>> analyzer.analyze_directory(folder, exclude_patterns=["module.py"]).files
{}

FileCoverage

Dataclass holding coverage statistics for one file.

Field Type Description
path str Path to the file
total_functions int Total functions found
documented_functions int Functions with docstrings
total_classes int Total classes found
documented_classes int Classes with docstrings
coverage_percentage float (property) (documented / total) * 100

Runnable example (checked by the test suite):

>>> file_coverage = analyzer.analyze_file(path)
>>> (file_coverage.total_functions, file_coverage.documented_functions)
(2, 1)
>>> (file_coverage.total_classes, file_coverage.documented_classes)
(0, 0)

ProjectCoverage

Dataclass holding coverage statistics across all analysed files.

Field Type Description
files Dict[str, FileCoverage] Mapping of file path to its FileCoverage
total_coverage float (property) Aggregate coverage across all files

Methods


Print a formatted coverage table to stdout.


to_json() -> dict

Export coverage data as a dictionary.

import json
data = project.to_json()
with open("coverage.json", "w") as f:
    json.dump(data, f, indent=2)

Runnable example (checked by the test suite):

>>> project = analyzer.analyze_directory(folder)
>>> list(project.files) == [path]
True
>>> project.to_json()["total_coverage"]
50.0
>>> project.to_json()["files"][path]["functions"]
'1/2'

shields_badge_dict

A standalone function, not a class — imported from the coverage submodule directly (it isn't in the top-level PyCodeCommenter package __all__):

from PyCodeCommenter.coverage import shields_badge_dict

shields_badge_dict(percentage, label="docs coverage") -> dict

Build a shields.io endpoint-badge dict for a coverage percentage. This is what backs the coverage CLI command's --badge-output flag.

Parameters:

Parameter Type Default Description
percentage float — Coverage percentage, 0-100
label str "docs coverage" Badge label text

Returns: dict with schemaVersion, label, message, and color keys. color is brightgreen at ≥90%, green at ≥75%, yellow at ≥50%, and red below that.

Example:

import json
from PyCodeCommenter import CoverageAnalyzer
from PyCodeCommenter.coverage import shields_badge_dict

project = CoverageAnalyzer().analyze_directory("./src")
badge = shields_badge_dict(project.total_coverage)
with open("coverage_badge.json", "w") as f:
    json.dump(badge, f, indent=2)

Runnable example (checked by the test suite):

>>> from PyCodeCommenter.coverage import shields_badge_dict
>>> shields_badge_dict(95)
{'schemaVersion': 1, 'label': 'docs coverage', 'message': '95%', 'color': 'brightgreen'}
>>> shields_badge_dict(50.0, label="docs")["color"]
'yellow'

TypeAnalyzer

Infers Python types from AST nodes. Used internally by PyCodeCommenter during docstring generation. Exposed publicly for advanced programmatic use.

Constructor

TypeAnalyzer(local_types=None)
Parameter Type Default Description
local_types Dict[str, str] or None {} Pre-known variable-to-type mappings for the current scope

Key Methods

infer_type(node) -> str

Infer type for an ast.arg node (with annotation) or any expression node.

infer_expr_type(expr) -> str

Infer type from an expression node (literal, list, dict, binary op, call, etc.).

get_annotation_type(annotation) -> str

Translate an annotation AST node (supports PEP 604 | and PEP 585 generics) into a readable string such as "list[str]" or "Union[int, None]".

Runnable example (checked by the test suite):

>>> import ast
>>> from PyCodeCommenter import TypeAnalyzer
>>> tree = ast.parse("def k(x: int | None, y: list[str]): ...\nz = 3")
>>> function, assignment = tree.body
>>> analyzer = TypeAnalyzer()
>>> analyzer.infer_type(function.args.args[0])
'Union[int, None]'
>>> analyzer.infer_type(function.args.args[1])
'list[str]'
>>> analyzer.infer_expr_type(assignment.value)
'int'
>>> analyzer.get_annotation_type(function.args.args[0].annotation)
'Union[int, None]'

DocstringParser

Parses existing Google-, Sphinx- or NumPy-style docstrings into structured components and records which style they use. Used internally during the merge step of get_patched_code(), so that author text is kept and the style is preserved.

Constructor

DocstringParser(docstring=None)
Parameter Type Default Description
docstring str or None "" The raw docstring text to parse. Parsing happens immediately on construction

Attributes (after construction)

Attribute Type Description
summary str The first paragraph of the docstring
description str Body text before the first section
params Dict[str, str] Parameter name → description
param_types Dict[str, str] Parameter name → type, where the docstring declares one
returns str The returns (or yields) text
raises Dict[str, str] Exception name → when it's raised
attributes Dict[str, str] Class attribute name → description
attribute_types Dict[str, str] Class attribute name → type, where declared
methods str An author's Google-style Methods: section, verbatim
style str "google", "numpy" or "sphinx"

Methods


get_info() -> dict

Return all parsed components as a dictionary.

{
    "summary": "...",
    "description": "...",
    "params": {"name": "description", ...},
    "param_types": {"name": "type", ...},
    "returns": "...",
    "raises": {"ValueError": "...", ...},
    "attributes": {"name": "description", ...},
    "attribute_types": {"name": "type", ...},
    "methods": "...",
    "style": "google",
}

Example:

from PyCodeCommenter import DocstringParser

docstring = """Process the data.

Args:
    data (str): The data to process.

Returns:
    str: The processed result.
"""

parser = DocstringParser(docstring)
info = parser.get_info()
print(info["summary"])      # "Process the data."
print(info["params"])       # {"data": "The data to process."}
print(info["returns"])      # "str: The processed result."

Runnable example (checked by the test suite):

>>> from PyCodeCommenter import DocstringParser
>>> parser = DocstringParser("Do it.\n\nArgs:\n    x (int): The x.\n")
>>> parser.get_info()["params"]
{'x': 'The x.'}
>>> (parser.style, parser.param_types)
('google', {'x': 'int'})
>>> DocstringParser().summary
''

AI drafting providers

Generation is deterministic unless you pass a description_provider. A provider is asked to draft only the parts the code can't state — the name-derived summary, a missing description, parameters with a TODO or type-only description, an undescribed return value, and exceptions without a readable condition. It is never asked to replace author text or facts read from the code. Every drafted line is written with the marker (AI-drafted, unreviewed), and each value is checked before it's written (no triple quotes, backslashes, placeholder or marker text).

Built-in providers

from PyCodeCommenter import PyCodeCommenter
from PyCodeCommenter.remote_provider import RemoteDescriptionProvider, DEFAULT_BACKEND_URL
from PyCodeCommenter.direct_providers import make_provider

# PyCodeCommenter's hosted service: no key, daily limit per user.
hosted = RemoteDescriptionProvider(backend_url=DEFAULT_BACKEND_URL)

# Your own key: "gemini", "openai", "anthropic", "deepseek" or "openai-compatible".
# Needs the matching extra, e.g. pip install "pycodecommenter[anthropic]".
own_key = make_provider("anthropic", api_key="...", model="claude-opus-5-5")

patched = PyCodeCommenter(description_provider=own_key).from_file("app.py").get_patched_code()

The CLI's --ai-draft also asks for consent before sending code anywhere; when you use a provider from the API, that decision is yours.

Runnable example (checked by the test suite):

>>> from PyCodeCommenter.description_provider import DescriptionProvider, DocstringDraft
>>> class OfflineProvider(DescriptionProvider):
...     def draft_docstring(self, context, known, slots):
...         return DocstringDraft(
...             summary="Compute the total." if slots.summary else None,
...             params={name: "The value." for name in slots.params},
...             returns="The total." if slots.returns else None,
...         )
>>> source = "def total(amount, tax):\n    return amount + tax\n"
>>> commenter = PyCodeCommenter(description_provider=OfflineProvider())
>>> patched = commenter.from_string(source).get_patched_code()
>>> patched.splitlines()[1]
'    """Compute the total. (AI-drafted, unreviewed)'
>>> commenter.report.ai_lines
4

Writing your own provider

Subclass DescriptionProvider (in PyCodeCommenter.description_provider) and implement draft_docstring:

from PyCodeCommenter.description_provider import (
    DescriptionProvider,
    DocstringDraft,
)


class MyProvider(DescriptionProvider):
    def draft_docstring(self, context, known, slots):
        # context: FunctionContext — name, parameters, return_type,
        #          is_generator, raised_exceptions, source (comments included)
        # known:   KnownText — text already settled, to stay consistent with
        # slots:   DraftSlots — what to draft: summary, description,
        #          params (names), returns, raises (names)
        return DocstringDraft(
            summary="Compute the total." if slots.summary else None,
            params={name: "..." for name in slots.params},
        )
Class Purpose
DraftSlots The parts requested: summary, description (bools), params, raises (tuples of names), returns (bool)
KnownText Settled params, returns and raises text, for consistency
DocstringDraft Your answer; leave anything you can't draft as None or out of the dicts. Unrequested parts are ignored
DraftingStopped(reason, message) Raise it to stop drafting for the rest of the run (for example, a rejected key or spent quota). Any other exception skips just that function

Providers written before draft_docstring existed, which implement only draft_function_description(context), still work: they fill the description paragraph.

Classes are drafted with draft_class_docstring(context, known, slots), which returns a ClassDraft(summary, attributes). context is a ClassContext (name, bases, attributes, and a source outline: header, class-level fields, __init__ in full, other methods as signatures), known maps attribute names to settled text, and slots is a ClassSlots (summary, and the attributes names to draft). The default declines everything, so a provider that only implements draft_docstring leaves class docstrings as they are. Set failed=True on a DocstringDraft or ClassDraft when the request itself failed, so the run summary can tell a failure from a decline.


  • CLI Reference — the command-line interface for all the same operations
  • Validation Checks — detailed descriptions of every check run by DocstringValidator
  • Recipes — complete CI/CD examples using the Python API