oct
Loading...
Searching...
No Matches
oct.deps.import_graph Namespace Reference

Classes

class  ImportGraph

Functions

bool _is_test_path (Path py_file, Path root)
str|None _resolve_relative (str module, bool is_package, int level, str|None name)
str|None _resolve_in_project (str candidate, set[str] modules)
ImportGraph build_import_graph (Path root)
str _package_of (str module, int depth)
dict[tuple[str, str], int] collapse_to_packages (dict[str, list[str]] edges, int depth=2)
str _mermaid_id (str name)
str to_mermaid_flowchart (dict[tuple[str, str], int] package_edges)
str to_dot (dict[str, list[str]] edges, str title="modules")

Variables

frozenset _TEST_DIR_NAMES = frozenset({"tests", "test"})

Detailed Description

Purpose
-------
Build a project's dependency graph from what its modules actually
``import``, and collapse it to package level for an overview chart.

:mod:`oct.deps.oct_deps` builds a graph from the ``Dependencies`` section
each module docstring declares. That is a contract, and a useful one, but
a structural picture that can drift from the code is worse than none, so
the overview chart on every subproject's ``dependencies`` page is derived
from the AST instead: ``import x``, ``from x import y``, and relative
imports resolved against the importing module's package. Only imports
that resolve to a module inside the project become edges; third-party and
standard-library imports are counted, not drawn.

Responsibilities
----------------
- :func:`build_import_graph` - modules and edges from the AST, tests and
  the standard exclusions skipped.
- :func:`collapse_to_packages` - the package-level overview: one node per
  top-level package, edges weighted by how many module-level edges they
  stand for.
- :func:`to_mermaid_flowchart` and :func:`to_dot` - render either level.

Diagnostics
-----------
Domain: OCT-DEPS
Levels:
    L2 - build entry / exit with sizes
    L3 - per-module resolution decisions
    L4 - individual unresolved imports

Contracts
---------
- Purely static: no project module is ever imported or executed.
- A file that does not parse contributes no edges and is reported at L3;
  it never aborts the graph, because a docs build must not die on one
  broken module and the linter already blocks that module elsewhere.
- Module keys are dotted paths relative to the project root, the same
  scheme :func:`oct.deps.oct_deps.module_name_for` uses, so the two graphs
  can be compared key for key.
- ``__init__.py`` resolves to its package; an import of a package resolves
  to that package node, not to every module in it.

Dependencies
------------
- oct.core.diagnostics (_dbg structured logger)
- oct.deps.oct_deps (module_name_for, _should_skip - shared key scheme)

Function Documentation

◆ _is_test_path()

bool oct.deps.import_graph._is_test_path ( Path py_file,
Path root )
protected
True when the file sits under a test or non-source directory.

Only a top-level ``docs`` counts as non-source; a package that happens
to contain a ``docs`` subpackage (``oct.docs``) is source.

Definition at line 105 of file import_graph.py.

Here is the caller graph for this function:

◆ _mermaid_id()

str oct.deps.import_graph._mermaid_id ( str name)
protected

Definition at line 238 of file import_graph.py.

◆ _package_of()

str oct.deps.import_graph._package_of ( str module,
int depth )
protected
The package node *module* belongs to at *depth*.

The first *depth* dotted components; a name with fewer components is
its own node, so a package's ``__init__`` (``pkg.core``) and its
members (``pkg.core.git``) collapse to the same node. A dunder module
(``oct.__main__``) folds into its parent rather than standing alone.

Definition at line 206 of file import_graph.py.

Here is the caller graph for this function:

◆ _resolve_in_project()

str | None oct.deps.import_graph._resolve_in_project ( str candidate,
set[str] modules )
protected
Return the longest project module that *candidate* names, or ``None``.

``from oct.core import git`` may name the module ``oct.core.git`` or
the attribute ``git`` of the package ``oct.core``; the walk from the
longest prefix down picks whichever exists.

Definition at line 137 of file import_graph.py.

Here is the caller graph for this function:

◆ _resolve_relative()

str | None oct.deps.import_graph._resolve_relative ( str module,
bool is_package,
int level,
str | None name )
protected
Resolve ``from ..x import y`` to a dotted name, or ``None`` if it escapes the root.

*module* is the importing module's dotted name; a package's
``__init__`` imports relative to itself, a plain module relative to
its parent, which is why *is_package* matters.

Definition at line 120 of file import_graph.py.

Here is the caller graph for this function:

◆ build_import_graph()

ImportGraph oct.deps.import_graph.build_import_graph ( Path root)
Walk *root* and build the import-derived module graph.

Skips the standard exclusion directories and every test directory.
See the module docstring for the resolution rules.

Definition at line 152 of file import_graph.py.

Here is the call graph for this function:

◆ collapse_to_packages()

dict[tuple[str, str], int] oct.deps.import_graph.collapse_to_packages ( dict[str, list[str]] edges,
int depth = 2 )
Collapse module edges to package edges, weighted by count.

*depth* is how many dotted components make a package. For a project
laid out as ``<dist>/<pkg>/<subpkg>/module.py`` the natural overview
is depth 2 (``oct.core``, ``oct.git``); a flat project wants 1. Edges
inside one package are dropped - the overview is about the seams.

Definition at line 220 of file import_graph.py.

Here is the call graph for this function:

◆ to_dot()

str oct.deps.import_graph.to_dot ( dict[str, list[str]] edges,
str title = "modules" )
Render module-level edges as Graphviz DOT, clustered by package.

Clusters make the module graph readable at oct's size: dot lays each
package out as a box, and the cross-package edges are the picture.

Definition at line 258 of file import_graph.py.

Here is the call graph for this function:

◆ to_mermaid_flowchart()

str oct.deps.import_graph.to_mermaid_flowchart ( dict[tuple[str, str], int] package_edges)
Render package-level edges as a Mermaid ``flowchart LR``.

Edge labels carry the weight when it is more than one, so the chart
shows where the heavy coupling is without a second diagram.

Definition at line 242 of file import_graph.py.

Variable Documentation

◆ _TEST_DIR_NAMES

frozenset oct.deps.import_graph._TEST_DIR_NAMES = frozenset({"tests", "test"})
protected

Definition at line 69 of file import_graph.py.