DC011: DocstringCoverageByModule
Overview
| Property | Value |
|---|---|
| ID | DC011 |
| Name | DocstringCoverageByModule |
| Group | documentation |
| Severity | NOTE |
Description
Checks docstring coverage on a per-module basis and reports modules that fall below a configurable threshold.
Per-module coverage is valuable because:
- It identifies specific files that need documentation attention
- It prevents new undocumented code from being added
- It helps prioritize documentation efforts
- It provides granular feedback compared to package-wide metrics
What it checks
The check walks the AST of each Python module and counts documented vs undocumented items:
- PASSED: All modules meet the coverage threshold (default: 80%)
- FAILED: One or more modules are below threshold (reports file, percentage, and missing docstrings)
- NOT_APPLICABLE: Package directory not found or no documentable items exist
What counts as “documentable”
- Public functions (not starting with
_) - Public classes (not starting with
_) - Public async functions (not starting with
_)
What is excluded
- Private functions and classes (names starting with
_) - Module-level docstrings (checked separately by DC001)
- Nested classes and functions within private items
How to fix
Add docstrings to public functions
# Bad: missing docstring
def calculate_total(items):
return sum(item.price for item in items)
# Good: with docstring
def calculate_total(items):
"""Calculate the total price of all items.
Args:
items: Iterable of items with a price attribute.
Returns:
The sum of all item prices.
"""
return sum(item.price for item in items)Add docstrings to public classes
# Bad: missing docstring
class ShoppingCart:
def __init__(self):
self.items = []
# Good: with docstring
class ShoppingCart:
"""A shopping cart that holds items for purchase.
Attributes:
items: List of items in the cart.
"""
def __init__(self):
self.items = []Make internal functions private
If a function is implementation detail, make it private:
# Before: public but undocumented helper
def format_price(cents):
return f"${cents / 100:.2f}"
# After: private helper (doesn't need docstring for coverage)
def _format_price(cents):
return f"${cents / 100:.2f}"Focus on high-impact modules first
The check reports which modules have low coverage:
FAILED: 3 module(s) below 80% docstring coverage
src/mypackage/core.py: 45% coverage (threshold: 80%)
- missing docstring: function process_data
- missing docstring: class DataProcessor
- missing docstring: function validate_input
Start with the most important modules for your users.
Why NOTE severity?
This check is a NOTE because:
- Documentation completeness is a quality goal, not a hard requirement
- Some internal modules may intentionally have minimal documentation
- Coverage thresholds are project-specific
- It encourages improvement without blocking releases
Configuration
Set coverage threshold
[tool.pycmdcheck]
coverage_threshold = 80 # percent (default)Lower threshold for legacy codebases:
[tool.pycmdcheck]
coverage_threshold = 50Higher threshold for public APIs:
[tool.pycmdcheck]
coverage_threshold = 95Skip this check
[tool.pycmdcheck]
skip = ["DC011"]CLI
pycmdcheck --skip DC011Best practices
- Document public APIs first: Focus on user-facing code
- Use consistent style: Choose Google, NumPy, or Sphinx style
- Include examples: Add usage examples in docstrings
- Generate API docs: Use sphinx-autodoc or mkdocstrings
- Review in PRs: Check new code has docstrings
Use docstring templates
Configure your editor to insert docstring templates:
def function_name(arg1, arg2):
"""Short description.
Longer description if needed.
Args:
arg1: Description of arg1.
arg2: Description of arg2.
Returns:
Description of return value.
Raises:
ValueError: When invalid input is provided.
"""
passEnforce in CI
# .github/workflows/docs.yml
jobs:
docstring-coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- run: pip install pycmdcheck
- run: pycmdcheck --only DC011