Skip to content

quality_report

mkdocs_terok.quality_report

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 :class:QualityReportResult containing the Markdown and any companion files (e.g. SVGs) that the consumer should write alongside it.

QualityReportConfig 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/quality_report.py
 33
 34
 35
 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
@dataclass(frozen=True)
class QualityReportConfig:
    """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
    codecov_treemap_path: Path | None = None
    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/quality_report.py
57
58
59
60
61
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}")

QualityReportResult 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/quality_report.py
103
104
105
106
107
108
109
110
111
112
113
114
@dataclass
class QualityReportResult:
    """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_quality_report(config=None)

Assemble the full quality report.

Parameters:

Name Type Description Default
config QualityReportConfig | None

Report configuration. Uses defaults if None.

None

Returns:

Name Type Description
A QualityReportResult

class:QualityReportResult with the Markdown and companion files.

Source code in src/mkdocs_terok/quality_report.py
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
def generate_quality_report(config: QualityReportConfig | None = None) -> QualityReportResult:
    """Assemble the full quality report.

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

    Returns:
        A :class:`QualityReportResult` with the Markdown and companion files.
    """
    if config is None:
        config = QualityReportConfig()

    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 QualityReportResult(
        markdown="".join(sections),
        companion_files=companion,
    )