Skip to content

code_metrics

mkdocs_terok.code_metrics

Generate a code quality report as Markdown.

Runs complexipy, vulture, tach, scc, and docstr-coverage, then assembles the results into a single Markdown page with a Mermaid dependency diagram. Returns a CodeMetricsResult containing the Markdown and any companion files (e.g. SVGs) that the consumer should write alongside it.

CodeMetricsConfig dataclass

Configuration for quality report generation.

All paths are relative to root unless absolute. Sections gracefully degrade with warning admonitions when external tools are missing.

Source code in src/mkdocs_terok/code_metrics.py
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
@dataclass(frozen=True)
class CodeMetricsConfig:
    """Configuration for quality report generation.

    All paths are relative to ``root`` unless absolute. Sections gracefully
    degrade with warning admonitions when external tools are missing.
    """

    root: Path = field(default_factory=Path.cwd)
    src_dir: Path | None = None
    tests_dir: Path | None = None
    complexity_threshold: int = 15
    histogram_buckets: Sequence[tuple[int, int]] | None = None
    graph_depth: int = 3
    vulture_whitelist: Path | None = None
    vulture_min_confidence: int = 80
    coverage_json_path: Path | None = None
    treemap_group_depth: int = 3
    # codecov_repo is opt-in: when set *and* coverage_json_path is unavailable, the
    # report embeds Codecov's live treemap URL as a fallback. That image is fetched
    # by the visitor's browser, so it always reflects Codecov's *latest* master
    # coverage — it will not match the snapshot of the surrounding page if the page
    # was built from an older commit.
    codecov_repo: str = ""
    file_level_loc: bool = True
    include_layer_overview: bool = False
    include_graph_coarsening: bool = False
    src_label: str = "Source"
    tests_label: str = "Tests"

    def __post_init__(self) -> None:
        """Validate configuration values and normalize root to absolute."""
        object.__setattr__(self, "root", self.root.resolve())
        if self.graph_depth < 1:
            raise ValueError(f"graph_depth must be >= 1, got {self.graph_depth}")

    def _resolve(self, path: Path | None, default: str) -> Path:
        """Resolve a path relative to root, with a fallback default."""
        if path is None:
            return self.root / default
        return path if path.is_absolute() else self.root / path

    def _resolve_optional(self, path: Path | None) -> Path | None:
        """Resolve an optional path relative to root."""
        if path is None:
            return None
        return path if path.is_absolute() else self.root / path

    @property
    def resolved_src_dir(self) -> Path:
        """Return the source directory, falling back to ``src/`` under root."""
        return self._resolve(self.src_dir, "src")

    @property
    def resolved_tests_dir(self) -> Path:
        """Return the tests directory, falling back to ``tests/`` under root."""
        return self._resolve(self.tests_dir, "tests")

    @property
    def resolved_histogram_buckets(self) -> Sequence[tuple[int, int]]:
        """Return histogram buckets with default narrow bins if not configured."""
        if self.histogram_buckets is not None:
            return self.histogram_buckets
        return [
            (0, 3),
            (4, 6),
            (7, 9),
            (10, 12),
            (13, 15),
            (16, 18),
            (19, 21),
            (22, 25),
            (26, 999),
        ]

resolved_histogram_buckets property

Return histogram buckets with default narrow bins if not configured.

resolved_src_dir property

Return the source directory, falling back to src/ under root.

resolved_tests_dir property

Return the tests directory, falling back to tests/ under root.

__post_init__()

Validate configuration values and normalize root to absolute.

Source code in src/mkdocs_terok/code_metrics.py
66
67
68
69
70
def __post_init__(self) -> None:
    """Validate configuration values and normalize root to absolute."""
    object.__setattr__(self, "root", self.root.resolve())
    if self.graph_depth < 1:
        raise ValueError(f"graph_depth must be >= 1, got {self.graph_depth}")

CodeMetricsResult dataclass

Result of quality report generation.

Attributes:

Name Type Description
markdown str

The full Markdown report content.

companion_files dict[str, str]

Mapping of relative paths to file contents that should be written alongside the report (e.g. SVGs).

Source code in src/mkdocs_terok/code_metrics.py
112
113
114
115
116
117
118
119
120
121
122
123
@dataclass
class CodeMetricsResult:
    """Result of quality report generation.

    Attributes:
        markdown: The full Markdown report content.
        companion_files: Mapping of relative paths to file contents that
            should be written alongside the report (e.g. SVGs).
    """

    markdown: str
    companion_files: dict[str, str] = field(default_factory=dict)

generate_code_metrics(config=None)

Assemble the full quality report.

Parameters:

Name Type Description Default
config CodeMetricsConfig | None

Report configuration. Uses defaults if None.

None

Returns:

Type Description
CodeMetricsResult

A CodeMetricsResult with the Markdown and companion files.

Source code in src/mkdocs_terok/code_metrics.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
def generate_code_metrics(config: CodeMetricsConfig | None = None) -> CodeMetricsResult:
    """Assemble the full quality report.

    Args:
        config: Report configuration. Uses defaults if ``None``.

    Returns:
        A [`CodeMetricsResult`][mkdocs_terok.code_metrics.CodeMetricsResult] with the Markdown and companion files.
    """
    if config is None:
        config = CodeMetricsConfig()

    now = datetime.now(UTC).strftime("%Y-%m-%d %H:%M UTC")
    coverage_md, companion = _section_coverage_treemap(config)

    sections = [
        "# Code Metrics\n\n",
        f"*Generated: {now}*\n\n",
        "---\n\n",
        "## Lines of Code\n\n",
        _section_loc(config),
        "\n",
        "## Architecture\n\n",
    ]

    layer_overview = _section_layer_overview(config)
    if layer_overview:
        sections.append("### Layer Overview\n\n")
        sections.append(layer_overview)
        sections.append("\n")

    sections.extend(
        [
            "### Module Dependency Graph\n\n",
            _section_dependency_diagram(config),
            "\n",
            "### Module Boundaries\n\n",
            _section_boundary_check(config),
            "\n",
            "### Module Summary\n\n",
            _section_dependency_report(config),
            "\n",
            "## Test Coverage\n\n",
            coverage_md,
            "\n",
            "## Cognitive Complexity\n\n",
            f"Threshold: **{config.complexity_threshold}** (functions above this are listed below)\n\n",
            _section_complexity(config),
            "\n",
            "## Dead Code Analysis\n\n",
            _section_dead_code(config),
            "\n",
            "## Docstring Coverage\n\n",
            _section_docstring_coverage(config),
            "\n---\n\n",
            "*Generated by scc, complexipy, vulture, tach, and docstr-coverage.*\n",
        ]
    )

    return CodeMetricsResult(
        markdown="".join(sections),
        companion_files=companion,
    )