interrogate: explain yourself¶
Interrogate a codebase for docstring coverage.
Why Do I Need This?¶
interrogate checks your code base for missing docstrings.
Documentation should be as important as code itself. And it should live within code. Python standardized docstrings, allowing for developers to navigate libraries as simply as calling help() on objects, and with powerful tools like Sphinx, pydoc, and Docutils to automatically generate HTML, LaTeX, PDFs, etc.
Enter: interrogate.
interrogate will tell you which methods, functions, classes, and modules have docstrings, and which do not. Use interrogate to:
- Get an understanding of how well your code is documented;
- Add it to CI/CD checks to enforce documentation on newly-added code;
- Assess a new code base for (one aspect of) code quality and maintainability.
Let’s get started.
Requirements¶
interrogate supports Python 3.6 and above.
Installation¶
interrogate is available on PyPI and GitHub. The recommended installation method is pip-installing into a virtualenv:
$ pip install interrogate
Extras¶
interrogate provides a way to generate a shields.io-like coverage badge as an SVG file.
To generate a PNG file instead, install interrogate with the extras [png]:
$ pip install interrogate[png]
NOTICE: Additional system libraries/tools may be required in order to generate a PNG file of the coverage badge:
- on Windows, install Visual C++ compiler for Cairo;
- on macOS, install
cairoandlibffi(with Homebrew for example); - on Linux, install the
cairo,python3-devandlibffi-devpackages (names may vary depending on distribution).
Refer to the cairosvg documentation for more information.
Usage¶
Try it out on a Python project:
$ interrogate [PATH]
RESULT: PASSED (minimum: 80.0%, actual: 100.0%)
Add verbosity to see a summary:
$ interrogate -v [PATH]
================== Coverage for /Users/lynn/dev/interrogate/ ====================
------------------------------------ Summary ------------------------------------
| Name | Total | Miss | Cover | Cover% |
|---------------------------------------|---------|--------|---------|----------|
| src/interrogate/__init__.py | 1 | 0 | 1 | 100% |
| src/interrogate/__main__.py | 1 | 0 | 1 | 100% |
| src/interrogate/badge_gen.py | 6 | 0 | 6 | 100% |
| src/interrogate/cli.py | 2 | 0 | 2 | 100% |
| src/interrogate/config.py | 8 | 0 | 8 | 100% |
| src/interrogate/coverage.py | 27 | 0 | 27 | 100% |
| src/interrogate/utils.py | 10 | 0 | 10 | 100% |
| src/interrogate/visit.py | 18 | 0 | 18 | 100% |
| tests/functional/__init__.py | 1 | 0 | 1 | 100% |
| tests/functional/test_cli.py | 8 | 0 | 8 | 100% |
| tests/functional/test_coverage.py | 10 | 0 | 10 | 100% |
| tests/unit/__init__.py | 1 | 0 | 1 | 100% |
| tests/unit/test_badge_gen.py | 8 | 0 | 8 | 100% |
| tests/unit/test_config.py | 10 | 0 | 10 | 100% |
| tests/unit/test_utils.py | 13 | 0 | 13 | 100% |
|---------------------------------------|---------|--------|---------|----------|
| TOTAL | 124 | 0 | 124 | 100.0% |
---------------- RESULT: PASSED (minimum: 80.0%, actual: 100.0%) ----------------
Add even more verbosity:
$ interrogate -vv [PATH]
================== Coverage for /Users/lynn/dev/interrogate/ ====================
------------------------------- Detailed Coverage -------------------------------
| Name | Status |
|---------------------------------------------------------------------|---------|
| src/interrogate/__init__.py (module) | COVERED |
|---------------------------------------------------------------------|---------|
| src/interrogate/__main__.py (module) | COVERED |
|---------------------------------------------------------------------|---------|
| src/interrogate/badge_gen.py (module) | COVERED |
| save_badge (L42) | COVERED |
| get_badge (L87) | COVERED |
| should_generate_badge (L103) | COVERED |
| get_color (L160) | COVERED |
| create (L173) | COVERED |
|---------------------------------------------------------------------|---------|
| src/interrogate/cli.py (module) | COVERED |
| main (L258) | COVERED |
|---------------------------------------------------------------------|---------|
| src/interrogate/config.py (module) | COVERED |
| InterrogateConfig (L19) | COVERED |
| find_project_root (L61) | COVERED |
| find_project_config (L89) | COVERED |
| parse_pyproject_toml (L100) | COVERED |
| sanitize_list_values (L116) | COVERED |
| parse_setup_cfg (L139) | COVERED |
| read_config_file (L173) | COVERED |
|---------------------------------------------------------------------|---------|
| src/interrogate/coverage.py (module) | COVERED |
| BaseInterrogateResult (L23) | COVERED |
| BaseInterrogateResult.perc_covered (L37) | COVERED |
| InterrogateFileResult (L54) | COVERED |
| InterrogateFileResult.combine (L67) | COVERED |
| InterrogateResults (L81) | COVERED |
| InterrogateResults.combine (L93) | COVERED |
| InterrogateCoverage (L101) | COVERED |
| InterrogateCoverage._add_common_exclude (L121) | COVERED |
| InterrogateCoverage._filter_files (L128) | COVERED |
| InterrogateCoverage.get_filenames_from_paths (L141) | COVERED |
| InterrogateCoverage._filter_nodes (L168) | COVERED |
| InterrogateCoverage._filter_inner_nested (L194) | COVERED |
| InterrogateCoverage._get_file_coverage (L203) | COVERED |
| InterrogateCoverage._get_coverage (L231) | COVERED |
| InterrogateCoverage.get_coverage (L248) | COVERED |
| InterrogateCoverage._get_filename (L253) | COVERED |
| InterrogateCoverage._get_detailed_row (L264) | COVERED |
| InterrogateCoverage._create_detailed_table (L281) | COVERED |
| InterrogateCoverage._create_detailed_table._sort_nodes (L288) | COVERED |
| InterrogateCoverage._print_detailed_table (L315) | COVERED |
| InterrogateCoverage._create_summary_table (L338) | COVERED |
| InterrogateCoverage._print_summary_table (L381) | COVERED |
| InterrogateCoverage._sort_results (L399) | COVERED |
| InterrogateCoverage._get_header_base (L429) | COVERED |
| InterrogateCoverage._print_omitted_file_count (L438) | COVERED |
| InterrogateCoverage.print_results (L469) | COVERED |
|---------------------------------------------------------------------|---------|
| src/interrogate/utils.py (module) | COVERED |
| parse_regex (L21) | COVERED |
| smart_open (L40) | COVERED |
| get_common_base (L60) | COVERED |
| OutputFormatter (L80) | COVERED |
| OutputFormatter.should_markup (L90) | COVERED |
| OutputFormatter.set_detailed_markup (L105) | COVERED |
| OutputFormatter.set_summary_markup (L129) | COVERED |
| OutputFormatter._interrogate_line_formatter (L158) | COVERED |
| OutputFormatter.get_table_formatter (L226) | COVERED |
|---------------------------------------------------------------------|---------|
| src/interrogate/visit.py (module) | COVERED |
| CovNode (L15) | COVERED |
| CoverageVisitor (L44) | COVERED |
| CoverageVisitor._has_doc (L58) | COVERED |
| CoverageVisitor._visit_helper (L65) | COVERED |
| CoverageVisitor._is_nested_func (L112) | COVERED |
| CoverageVisitor._is_nested_cls (L121) | COVERED |
| CoverageVisitor._is_private (L133) | COVERED |
| CoverageVisitor._is_semiprivate (L141) | COVERED |
| CoverageVisitor._is_ignored_common (L151) | COVERED |
| CoverageVisitor._has_property_decorators (L168) | COVERED |
| CoverageVisitor._has_setters (L182) | COVERED |
| CoverageVisitor._is_func_ignored (L193) | COVERED |
| CoverageVisitor._is_class_ignored (L217) | COVERED |
| CoverageVisitor.visit_Module (L221) | COVERED |
| CoverageVisitor.visit_ClassDef (L228) | COVERED |
| CoverageVisitor.visit_FunctionDef (L237) | COVERED |
| CoverageVisitor.visit_AsyncFunctionDef (L246) | COVERED |
|---------------------------------------------------------------------|---------|
| tests/functional/__init__.py (module) | COVERED |
|---------------------------------------------------------------------|---------|
| tests/functional/test_cli.py (module) | COVERED |
| runner (L22) | COVERED |
| test_run_no_paths (L30) | COVERED |
| test_run_shortflags (L77) | COVERED |
| test_run_longflags (L106) | COVERED |
| test_run_multiple_flags (L124) | COVERED |
| test_generate_badge (L135) | COVERED |
| test_incompatible_options (L170) | COVERED |
|---------------------------------------------------------------------|---------|
| tests/functional/test_coverage.py (module) | COVERED |
| test_coverage_simple (L60) | COVERED |
| test_coverage_errors (L73) | COVERED |
| test_print_results (L101) | COVERED |
| test_print_results_omit_covered (L130) | COVERED |
| test_print_results_omit_none (L156) | COVERED |
| test_print_results_omit_all_summary (L174) | COVERED |
| test_print_results_omit_all_detailed (L198) | COVERED |
| test_print_results_ignore_module (L226) | COVERED |
| test_print_results_single_file (L253) | COVERED |
|---------------------------------------------------------------------|---------|
| tests/unit/__init__.py (module) | COVERED |
|---------------------------------------------------------------------|---------|
| tests/unit/test_badge_gen.py (module) | COVERED |
| test_save_badge (L26) | COVERED |
| test_save_badge_windows (L50) | COVERED |
| test_save_badge_no_cairo (L62) | COVERED |
| test_get_badge (L73) | COVERED |
| test_should_generate (L96) | COVERED |
| test_get_color (L115) | COVERED |
| test_create (L136) | COVERED |
|---------------------------------------------------------------------|---------|
| tests/unit/test_config.py (module) | COVERED |
| test_find_project_root (L29) | COVERED |
| test_find_project_config (L48) | COVERED |
| test_parse_pyproject_toml (L57) | COVERED |
| test_sanitize_list_values (L93) | COVERED |
| test_parse_setup_cfg (L98) | COVERED |
| test_parse_setup_cfg_raises (L123) | COVERED |
| test_read_config_file_none (L134) | COVERED |
| test_read_config_file (L193) | COVERED |
| test_read_config_file_raises (L207) | COVERED |
|---------------------------------------------------------------------|---------|
| tests/unit/test_utils.py (module) | COVERED |
| test_parse_regex (L32) | COVERED |
| test_smart_open (L39) | COVERED |
| test_get_common_base (L69) | COVERED |
| test_get_common_base_windows (L100) | COVERED |
| test_output_formatter_should_markup (L132) | COVERED |
| test_output_formatter_set_detailed_markup (L163) | COVERED |
| test_output_formatter_set_summary_markup (L206) | COVERED |
| test_output_formatter_interrogate_line_formatter (L258) | COVERED |
| test_output_formatter_interrogate_line_formatter_windows (L319) | COVERED |
| test_output_formatter_get_table_formatter (L343) | COVERED |
| test_output_formatter_get_table_formatter_py38 (L381) | COVERED |
| test_output_formatter_get_table_formatter_raises (L395) | COVERED |
|---------------------------------------------------------------------|---------|
------------------------------------ Summary ------------------------------------
| Name | Total | Miss | Cover | Cover% |
|---------------------------------------|---------|--------|---------|----------|
| src/interrogate/__init__.py | 1 | 0 | 1 | 100% |
| src/interrogate/__main__.py | 1 | 0 | 1 | 100% |
| src/interrogate/badge_gen.py | 6 | 0 | 6 | 100% |
| src/interrogate/cli.py | 2 | 0 | 2 | 100% |
| src/interrogate/config.py | 8 | 0 | 8 | 100% |
| src/interrogate/coverage.py | 27 | 0 | 27 | 100% |
| src/interrogate/utils.py | 10 | 0 | 10 | 100% |
| src/interrogate/visit.py | 18 | 0 | 18 | 100% |
| tests/functional/__init__.py | 1 | 0 | 1 | 100% |
| tests/functional/test_cli.py | 8 | 0 | 8 | 100% |
| tests/functional/test_coverage.py | 10 | 0 | 10 | 100% |
| tests/unit/__init__.py | 1 | 0 | 1 | 100% |
| tests/unit/test_badge_gen.py | 8 | 0 | 8 | 100% |
| tests/unit/test_config.py | 10 | 0 | 10 | 100% |
| tests/unit/test_utils.py | 13 | 0 | 13 | 100% |
|---------------------------------------|---------|--------|---------|----------|
| TOTAL | 124 | 0 | 124 | 100.0% |
---------------- RESULT: PASSED (minimum: 80.0%, actual: 100.0%) ----------------
Other Usage¶
Generate a shields.io badge (like this one! ):
$ interrogate --generate-badge PATH
RESULT: PASSED (minimum: 80.0%, actual: 100.0%)
Generated badge to /Users/lynn/dev/interrogate/docs/_static/interrogate_badge.svg
See below for more badge configuration.
Add it to your tox.ini file to enforce a level of coverage:
[testenv:doc]
deps = interrogate
skip_install = true
commands =
interrogate --quiet --fail-under 95 src tests
Or use it with pre-commit:
repos:
- repo: https://github.com/econchick/interrogate
rev: 1.5.0 # or master if you're bold
hooks:
- id: interrogate
args: [--quiet, --fail-under=95]
Use it within your code directly:
>>> from interrogate import coverage
>>> cov = coverage.InterrogateCoverage(paths=["src"])
>>> results = cov.get_coverage()
>>> results
InterrogateResults(total=68, covered=65, missing=3)
Use interrogate with GitHub Actions. Check out the action written & maintained by Jack McKew (thank you, Jack!).
Configuration¶
Configure within your pyproject.toml (interrogate will automatically detect a pyproject.toml file and pick up default values for the command line options):
$ interrogate -c pyproject.toml [OPTIONS] [PATHS]...
[tool.interrogate]
ignore-init-method = true
ignore-init-module = false
ignore-magic = false
ignore-semiprivate = false
ignore-private = false
ignore-property-decorators = false
ignore-module = false
ignore-nested-functions = false
ignore-nested-classes = true
ignore-setters = false
fail-under = 95
exclude = ["setup.py", "docs", "build"]
ignore-regex = ["^get$", "^mock_.*", ".*BaseClass.*"]
# possible values: 0 (minimal output), 1 (-v), 2 (-vv)
verbose = 0
quiet = false
whitelist-regex = []
color = true
omit-covered-files = false
generate-badge = "."
badge-format = "svg"
Or configure within your setup.cfg (interrogate will automatically detect a setup.cfg file and pick up default values for the command line options):
$ interrogate -c setup.cfg [OPTIONS] [PATHS]...
[tool:interrogate]
ignore-init-method = true
ignore-init-module = false
ignore-magic = false
ignore-semiprivate = false
ignore-private = false
ignore-property-decorators = false
ignore-module = false
ignore-nested-functions = false
ignore-nested-classes = true
ignore-setters = false
fail-under = 95
exclude = setup.py,docs,build
ignore-regex = ^get$,^mock_.*,.*BaseClass.*
; possible values: 0 (minimal output), 1 (-v), 2 (-vv)
verbose = 0
quiet = false
whitelist-regex =
color = true
omit-covered-files = false
generate-badge = .
badge-format = svg
Warning
The use of setup.cfg is not recommended unless for very simple use cases. .cfg files use a different parser than pyproject.toml which might cause hard to track down problems. When possible, it is recommended to use pyproject.toml to define your interrogate configuration.
Badge Options¶
Badge Format¶
The default file format is svg. Use the --badge-format flag to create a png file instead.
Note: interrogate must be installed with interrogate[png] in order to generate png files (see above).
$ interrogate --generate-badge PATH --badge-format png
RESULT: PASSED (minimum: 80.0%, actual: 100.0%)
Generated badge to /Users/lynn/dev/interrogate/docs/_static/interrogate_badge.png
Badge Style¶
The following badge styles are available via the --badge-style flag:
| option | example |
|---|---|
flat |
|
flat-square |
|
flat-square-modified (default) |
|
for-the-badge |
|
plastic |
|
social |
Command Line Options¶
-
--version¶ Show the version and exit.
-
-v,--verbose¶ Level of verbosity.
NOTE: When configuring verbosity in
pyproject.tomlorsetup.cfg,verbose=1maps to-v, andverbose=2maps to-vv.verbose=0is the equivalent of no verbose flags used, producing minimal output.
-
-q,--quiet¶ Do not print output [default:
False]
-
-f,--fail-underINT | FLOAT¶ Fail when coverage % is less than a given amount. [default:
80.0]
-
-e,--excludePATH¶ Exclude PATHs of files and/or directories. Multiple
-e/--excludeinvocations supported.
-
-i,--ignore-init-method¶ Ignore
__init__method of classes. [default:False]
-
-I,--ignore-init-module¶ Ignore
__init__.pymodules. [default:False]
-
-m,--ignore-magic¶ Ignore all magic methods of classes. [default:
False]NOTE: This does not include the
__init__method. To ignore__init__methods, use--ignore-init-method.
-
-M,--ignore-module¶ Ignore module-level docstrings. [default:
False]
-
-n,--ignore-nested-functions¶ Ignore nested functions and methods. [default:
False]
-
-C,--ignore-nested-classes¶ Ignore nested classes. [default:
False]
-
-p,--ignore-private¶ Ignore private classes, methods, and functions starting with two underscores. [default:
False]NOTE: This does not include magic methods; use
--ignore-magicand/or--ignore-init-methodinstead.
-
-P,--ignore-property-decorators¶ Ignore methods with property setter/getter decorators. [default:
False]
-
-S,--ignore-setters¶ Ignore methods with property setter decorators. [default:
False]
-
-s,--ignore-semiprivate¶ Ignore semiprivate classes, methods, and functions starting with a single underscore. [default:
False]
-
-r,--ignore-regexSTR¶ Regex identifying class, method, and function names to ignore. Multiple
-r/--ignore-regexinvocations supported.
-
-w,--whitelist-regexSTR¶ Regex identifying class, method, and function names to include. Multiple
-r/--ignore-regexinvocations supported.
-
-o,--outputFILE¶ Write output to a given
FILE. [default:stdout]
-
-c,--configFILE¶ Read configuration from
pyproject.tomlorsetup.cfg.
-
--color,--no-color¶ Toggle color output on/off when printing to stdout. [default: color]
-
--omit-covered-files¶ Omit reporting files that have 100% documentation coverage. This option is ignored if verbosity is not set. [default:
False]
-
-g,--generate-badgePATH¶ Generate a shields.io status badge (an SVG image) in at a given file or directory. Will not generate a badge if results did not change from an existing badge of the same path.
-
--badge-format[svg|png]¶ File format for the generated badge. Used with the
-g/--generate-badgeflag. [default:svg]NOTE: To generate a PNG file, interrogate must be installed with
interrogate[png], i.e.pip install interrogate[png].
-
--badge-style[flat|flat-square|flat-square-modified|for-the-badge|plastic|social]¶ Desired style of shields.io badge. Used with the -g/–generate-badge flag. [default: flat-square-modified]
Credits¶
interrogate was inspired by docstr-coverage, which was forked from Alexey “DataGreed” Strelkov’s docstring-coverage, which was inspired by a 2004 recipe from James Harlow (turtles…).
The cute
logo is by JustineW purchased via the Noun Project (but also available under the Creative Commons License with attribution).
Changelog¶
1.4.0 (2021-05-14)¶
1.3.1 (2020-09-03)¶
1.3.0 (2020-08-23)¶
Added¶
Fixed¶
-e/--excludedoesn’t error if a non-existent file/directory is passed (#38 - thank you MarcoGorelli!).
1.1.5 (2020-05-12)¶
Added¶
- Add
__main__.pymodule to allow for invocation viapython -m interrogate.
Fixed¶
Removed¶
- Removed
networkxdependency.
1.1.4 (2020-05-05)¶
Added¶
- Use
interrogatewith pre-commit (addresses Issue #10).
1.1.3 (2020-05-02)¶
Added¶
- New
-w/--whitelist-regexflag: whitelist regex identifying class, method, and function names to include.
Changed¶
-r/--ignore-regexnow supports multiple invocations.
Fixed¶
- Fix misleading bug where module info was still outputted even if
--ignore-modulewas used. - Fix output when interrogating a single file where filenames were not listed.
1.1.1 (2020-04-27)¶
Added¶
- Improve docstring content to reflect parameters, return values, and raised exceptions.
1.1.0 (2020-04-24)¶
Added¶
- New command to generate a status badge based off of shields.io.
1.0.0 (2020-04-23)¶
Initial release!