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

Classes

class  Entry
class  Findings
class  _Probe
class  _Resolver

Functions

tuple[list[Entry], list[str], bool] parse_entries (str docstring)
tuple[Path,...] _workspace_roots (Path project_root, _Probe probe)
tuple[Path, bool] _naming_root (Path py_file, tuple[Path,...] roots, _Probe probe)
_Resolver _resolver_for (tuple[Path,...] roots, tuple[Path,...] local_roots, _Probe probe)
str|None _type_checking_kind (ast.expr test)
list[tuple[ast.Import|ast.ImportFrom, bool]] _imports_with_laziness (ast.Module tree)
tuple[dict[str, bool], set[str]] collect_imports (ast.Module tree, str module, bool is_package, _Resolver resolver)
bool _covers (str entry, str dep, bool first_party)
bool _is_test_module (Path py_file, Path naming_root, Path project_root)
bool _stdlib_satisfied (str entry, set[str] stdlib)
tuple[bool, str] check_dependencies_match (Path py_file, Path project_root, ast.Module|None tree, str|None docstring=None, *, dict|None memo=None)

Variables

 _ENTRY_RE = re.compile(r"^`?([A-Za-z_][\w.]*)`?\s*\‍((.*)\‍)\s*$")
frozenset STDLIB = frozenset(sys.stdlib_module_names) | {"__future__"}
str _SHIM = "oct.core.diagnostics"
 _NOT_NAMESPACE

Detailed Description

Purpose
-------
Compare a module's ``Dependencies`` docstring section with what the module
actually imports. The advisory lint rule ``dependencies_match`` runs this
on every source module. The section parsing lives in
:mod:`oct.deps.oct_deps`, whose cycle detection leaves lazy and
not-imported entries out, so the rule and ``oct deps`` read an entry alike.

The operator ruled on 2026-09-28 (docs/CHART_C_TRACE_FOLLOWUPS_2026-09-28.md,
section 8, in the workspace):

1. Every import is declared. An import made only inside a function or
   under ``TYPE_CHECKING`` is declared with a purpose starting ``lazy``,
   and ``oct deps`` leaves lazy entries out of its cycle detection.
2. First-party packages (any package in the workspace) and third-party
   packages are both checked; standard-library modules are optional
   (declaring one is allowed; it is stale only if nothing of it is
   imported).
3. The rule runs at every profile, is advisory, and exempts test modules.

One addition, for the operator to veto: a dependency used without an
import (a CLI, a ``python -m`` subprocess, a pytest plugin, a module
loaded by name) is declared with a purpose starting ``not imported``; it
is never reported stale and never enters cycle detection.

Responsibilities
----------------
- :func:`parse_entries` - the ``Dependencies`` entries of a docstring, with
  their markers, and the lines that are not entries.
- :func:`collect_imports` - every dependency a module imports, each marked
  eager or lazy, standard library kept apart.
- :func:`check_dependencies_match` - the comparison: missing, stale,
  wrongly marked and unrecognised entries, as one verdict and message.

Diagnostics
-----------
Domain: OCT-DEPS
Levels:
    L2 - (none) called once per file by the linter; the linter logs verdicts
    L3 - workspace roots resolved for a project
    L4 - per-file comparison totals

Contracts
---------
- Never raises: any internal error becomes a passing verdict whose message
  says the check did not run. A raising rule would make the quality gate
  skip every rule for the file, the secret check included.
- A module's dotted name is taken from the workspace subproject root whose
  first path component is a package, so a file in a namespace directory of
  a package (``oct/oct/core``) is ``oct.core.git``; outside any package,
  from Python's own rule.
- First-party names are checked against the filesystem with exact case
  (directory listings memoised for one lint run), never by walking the
  project, so ``oct git status`` stays fast and Windows and Linux agree.
- An import is recorded as written: ``import a.b.c`` is ``a.b.c``; ``from X
  import y`` is ``X.y`` when that is a module, else ``X``. Nothing is
  shortened to whatever prefix exists on disk, so an absent generated
  module (a setuptools-scm version file) does not change the verdict.
- A first-party entry must name a module exactly, or exactly what an
  import names; an attribute (``pkg.mod.Class``) is reported as naming no
  module. A third-party or standard-library entry may name the package,
  which covers its submodules.
- An ``if TYPE_CHECKING:`` body is lazy and its ``else`` eager; ``if not
  TYPE_CHECKING:`` is the reverse; any other test is eager.
- An ``oc_diagnostics`` entry is satisfied by an import of oct's shim
  ``oct.core.diagnostics`` (Core spec: oc_diagnostics "should always be
  listed if the module uses _dbg").

Dependencies
------------
- oct.core.diagnostics (_dbg structured logger)
- oct.core.workspace (lazy; the workspace manifest's subproject roots)
- oct.deps.import_graph (_is_test_path, _resolve_relative - shared rules)
- oct.deps.oct_deps (module_name_for, dependency_items, entry_marker - the shared parsing)

Function Documentation

◆ _covers()

bool oct.deps.dependency_check._covers ( str entry,
str dep,
bool first_party )
protected

Definition at line 402 of file dependency_check.py.

Here is the caller graph for this function:

◆ _imports_with_laziness()

list[tuple[ast.Import | ast.ImportFrom, bool]] oct.deps.dependency_check._imports_with_laziness ( ast.Module tree)
protected
Every import statement with whether it is lazy, in one linear pass.

Definition at line 336 of file dependency_check.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _is_test_module()

bool oct.deps.dependency_check._is_test_module ( Path py_file,
Path naming_root,
Path project_root )
protected

Definition at line 408 of file dependency_check.py.

Here is the caller graph for this function:

◆ _naming_root()

tuple[Path, bool] oct.deps.dependency_check._naming_root ( Path py_file,
tuple[Path, ...] roots,
_Probe probe )
protected
The directory *py_file*'s dotted name is relative to, and whether it is packaged.

Preferred: the most specific workspace root under which the file's first
path component is a package. Otherwise Python's rule from the file's
own directory; ``False`` then means a script outside any package.

Definition at line 256 of file dependency_check.py.

Here is the caller graph for this function:

◆ _resolver_for()

_Resolver oct.deps.dependency_check._resolver_for ( tuple[Path, ...] roots,
tuple[Path, ...] local_roots,
_Probe probe )
protected

Definition at line 312 of file dependency_check.py.

Here is the caller graph for this function:

◆ _stdlib_satisfied()

bool oct.deps.dependency_check._stdlib_satisfied ( str entry,
set[str] stdlib )
protected
A declared standard-library entry is satisfied by an import of it,
of a submodule, or of its package (``import os`` gives ``os.path``).

Definition at line 413 of file dependency_check.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _type_checking_kind()

str | None oct.deps.dependency_check._type_checking_kind ( ast.expr test)
protected
``"body"`` for ``if TYPE_CHECKING:``, ``"orelse"`` for ``if not TYPE_CHECKING:``.

Definition at line 324 of file dependency_check.py.

Here is the caller graph for this function:

◆ _workspace_roots()

tuple[Path, ...] oct.deps.dependency_check._workspace_roots ( Path project_root,
_Probe probe )
protected
Every subproject root of the workspace holding *project_root*, then it.

Definition at line 236 of file dependency_check.py.

Here is the caller graph for this function:

◆ check_dependencies_match()

tuple[bool, str] oct.deps.dependency_check.check_dependencies_match ( Path py_file,
Path project_root,
ast.Module | None tree,
str | None docstring = None,
* ,
dict | None memo = None )
The ``dependencies_match`` verdict for one file: ``(ok, message)``.

*docstring* defaults to the module docstring of *tree*. *memo* holds the
filesystem probes across calls (the linter passes one per run); without
it every call probes afresh. Test modules are exempt. Never raises.

Definition at line 419 of file dependency_check.py.

Here is the call graph for this function:

◆ collect_imports()

tuple[dict[str, bool], set[str]] oct.deps.dependency_check.collect_imports ( ast.Module tree,
str module,
bool is_package,
_Resolver resolver )
``(non-stdlib dependency -> imported eagerly, standard-library names)``.

Names are recorded as written: ``import a.b`` is ``a.b``; ``from X import
y`` is ``X.y`` when that is a first-party module, else ``X``.

Definition at line 356 of file dependency_check.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ parse_entries()

tuple[list[Entry], list[str], bool] oct.deps.dependency_check.parse_entries ( str docstring)
Return ``(entries, unrecognised lines, section present)`` for *docstring*.

The items come from :func:`oct.deps.oct_deps.dependency_items`: a bullet
whose text continues on the next line is joined first, so a wrapped
purpose still parses and its marker is read from the joined text, as
``oct deps`` reads it.

Definition at line 152 of file dependency_check.py.

Here is the caller graph for this function:

Variable Documentation

◆ _ENTRY_RE

oct.deps.dependency_check._ENTRY_RE = re.compile(r"^`?([A-Za-z_][\w.]*)`?\s*\‍((.*)\‍)\s*$")
protected

Definition at line 96 of file dependency_check.py.

◆ _NOT_NAMESPACE

oct.deps.dependency_check._NOT_NAMESPACE
protected
Initial value:
1= frozenset({"tests", "test", "docs", "__pycache__", "node_modules",
2 ".venv", "build", "dist"})

Definition at line 104 of file dependency_check.py.

◆ _SHIM

str oct.deps.dependency_check._SHIM = "oct.core.diagnostics"
protected

Definition at line 101 of file dependency_check.py.

◆ STDLIB

frozenset oct.deps.dependency_check.STDLIB = frozenset(sys.stdlib_module_names) | {"__future__"}

Definition at line 98 of file dependency_check.py.