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

Classes

class  SectionItem

Functions

list[SectionItem]|None dependency_items (str|None docstring)
str|None entry_marker (str purpose)
list[tuple[str, bool]] dependency_entries (str|None docstring)
str|None _extract_module_docstring (str source)
list[str] extract_dependencies (str source, *, bool eager_only=False)
bool _should_skip (Path path, Path root)
str module_name_for (Path py_file, Path root)
tuple[dict[str, list[str]], dict[str, list[str]]] build_dependency_graphs (Path root)
dict[str, list[str]] build_dependency_graph (Path root, *, bool eager_only=False)
list[list[str]] detect_cycles (dict[str, list[str]] graph)
str to_json (dict[str, list[str]] graph, list[list[str]] cycles)
str to_mermaid (dict[str, list[str]] graph)
str to_dot (dict[str, list[str]] graph)
int audit_pins (Path root, bool json_output=False)
dict _parse_toml_deps_fallback (Path toml_path)
int run_deps (Path root, list[str]|None argv=None)

Variables

 _DEP_SECTION_RE
 _BULLET_RE = re.compile(r'^[ \t]*[-*][ \t]+(.*)$')
 _DEP_NAME_RE = re.compile(r'^`?(\S+?)`?[ \t]*(?:[-—:(].*)?$')
 _MARKER_RE = re.compile(r'^(lazy|not imported)(?![\w-])')

Detailed Description

Purpose
-------
Analyse inter-module dependencies within an Option C project by parsing
the ``Dependencies`` docstring section from all Python files.

Responsibilities
----------------
- Walk the project tree and extract ``Dependencies`` sections from module
  docstrings. The section parsing (:func:`dependency_items`,
  :func:`entry_marker`) is shared with the ``dependencies_match`` lint
  rule in :mod:`oct.deps.dependency_check`, so both read an entry alike.
- Build a directed dependency graph (adjacency list).
- Detect circular dependencies via depth-first search, over the EAGER
  entries only: an entry whose purpose starts with ``lazy`` or ``not
  imported`` (operator ruling 2026-09-28) is drawn but never part of a
  cycle, because lazy imports are how the code breaks import cycles.
- Output the graph in multiple formats: terminal summary, JSON, Mermaid,
  and DOT.

Diagnostics
-----------
Domain: OCT-DEPS
Levels:
    L2 - lifecycle
    L3 - semantic details
    L4 - deep tracing

Contracts
---------
- Must not import project modules; analysis is purely static.
- Must exit 1 if circular dependencies are detected.
- Must handle missing or malformed Dependencies sections gracefully.

Dependencies
------------
- oct.core.exclusions (directory exclusion lists)
- oct.core.diagnostics (_dbg structured logger)
- tomli (lazy; TOML parsing for audit_pins on Python before 3.11)

Function Documentation

◆ _extract_module_docstring()

str | None oct.deps.oct_deps._extract_module_docstring ( str source)
protected
Extract the module-level docstring from Python source.

Definition at line 139 of file oct_deps.py.

Here is the caller graph for this function:

◆ _parse_toml_deps_fallback()

dict oct.deps.oct_deps._parse_toml_deps_fallback ( Path toml_path)
protected
Minimal fallback TOML parser - extracts dependency lines only.

Definition at line 411 of file oct_deps.py.

Here is the caller graph for this function:

◆ _should_skip()

bool oct.deps.oct_deps._should_skip ( Path path,
Path root )
protected
Check if a path should be excluded.

The exclusion lists name DIRECTORIES, and are matched against the
directory components only, as the linter's ``is_excluded_dir`` does.
Until 2026-09-27 the file name was matched too, so the ``cache``,
``logs`` and ``venv`` wildcards silently dropped any module whose name
merely started with one of them: ``oct.testing.cache``,
``oc_chat.core.cache_usage`` and three others were missing from
``oct deps``, every dependency overview page and every call graph.

Definition at line 165 of file oct_deps.py.

Here is the caller graph for this function:

◆ audit_pins()

int oct.deps.oct_deps.audit_pins ( Path root,
bool json_output = False )
Audit pyproject.toml dependencies for unpinned version specifiers.

Reads ``[project.dependencies]`` and
``[project.optional-dependencies]`` from ``pyproject.toml``. Reports
dependencies that use open ranges (``>=``, ``>``) with no exact or
upper-bound constraint alongside those that are strictly pinned.

Parameters
----------
root
    Project root containing ``pyproject.toml``.
json_output
    If ``True``, prints a JSON summary. Otherwise prints a human-
    readable table.

Returns
-------
Exit code: ``0`` if all dependencies have upper-bound or exact pins,
``1`` if any open-ended ``>=``-only or ``>``-only specs are found,
``2`` if ``pyproject.toml`` could not be read.

Definition at line 307 of file oct_deps.py.

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

◆ build_dependency_graph()

dict[str, list[str]] oct.deps.oct_deps.build_dependency_graph ( Path root,
* ,
bool eager_only = False )
One graph of :func:`build_dependency_graphs`: every entry, or the eager ones.

Definition at line 236 of file oct_deps.py.

Here is the call graph for this function:

◆ build_dependency_graphs()

tuple[dict[str, list[str]], dict[str, list[str]]] oct.deps.oct_deps.build_dependency_graphs ( Path root)
Walk the project once; return ``(every entry, eager entries only)`` graphs.

Keys are dotted import paths (OI-521), so modules sharing a stem
across packages no longer overwrite one another.

Definition at line 208 of file oct_deps.py.

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

◆ dependency_entries()

list[tuple[str, bool]] oct.deps.oct_deps.dependency_entries ( str | None docstring)
``(name, eager)`` for every bulleted entry of *docstring*'s Dependencies section.

The name comes from the entry's first line; the marker from the joined
text, so ``oct deps`` and the ``dependencies_match`` rule read a
wrapped entry the same way. An entry marked ``lazy`` or ``not
imported`` is not eager.

Definition at line 119 of file oct_deps.py.

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

◆ dependency_items()

list[SectionItem] | None oct.deps.oct_deps.dependency_items ( str | None docstring)
The items of *docstring*'s Dependencies section, or ``None`` if it has none.

A non-bullet line after a bullet continues it; a non-bullet line before
any bullet is an item of its own (``bulleted`` false).

Definition at line 92 of file oct_deps.py.

Here is the caller graph for this function:

◆ detect_cycles()

list[list[str]] oct.deps.oct_deps.detect_cycles ( dict[str, list[str]] graph)
Detect circular dependencies using DFS. Returns list of cycle paths.

Definition at line 244 of file oct_deps.py.

Here is the caller graph for this function:

◆ entry_marker()

str | None oct.deps.oct_deps.entry_marker ( str purpose)
``"lazy"``, ``"not imported"`` or ``None`` for an entry's purpose text.

Definition at line 113 of file oct_deps.py.

Here is the caller graph for this function:

◆ extract_dependencies()

list[str] oct.deps.oct_deps.extract_dependencies ( str source,
* ,
bool eager_only = False )
Parse the Dependencies section from a module docstring.

Returns a list of dependency module names. With *eager_only*, entries
marked ``lazy`` or ``not imported`` are left out (cycle detection).

Definition at line 153 of file oct_deps.py.

Here is the call graph for this function:

◆ module_name_for()

str oct.deps.oct_deps.module_name_for ( Path py_file,
Path root )
Compute a dotted import-path key for *py_file* relative to *root*.

OI-521: using ``py_file.stem`` collided whenever two packages contain
modules of the same name (e.g. ``a/util.py`` and ``b/util.py`` both
reduced to ``util``). Returning the dotted import path makes each key
unique and aligns with the dotted names typically used in
``Dependencies`` docstring sections.

``__init__.py`` resolves to its parent package's dotted name; a file
directly under *root* keeps its stem as the fallback.

Definition at line 186 of file oct_deps.py.

Here is the caller graph for this function:

◆ run_deps()

int oct.deps.oct_deps.run_deps ( Path root,
list[str] | None argv = None )
Run dependency analysis on the given project root.

Returns exit code: 0 if no cycles, 1 if circular dependencies found.

Definition at line 432 of file oct_deps.py.

Here is the call graph for this function:

◆ to_dot()

str oct.deps.oct_deps.to_dot ( dict[str, list[str]] graph)
Format dependency graph as DOT (Graphviz) syntax.

Definition at line 294 of file oct_deps.py.

Here is the caller graph for this function:

◆ to_json()

str oct.deps.oct_deps.to_json ( dict[str, list[str]] graph,
list[list[str]] cycles )
Format dependency graph as JSON.

Definition at line 276 of file oct_deps.py.

Here is the caller graph for this function:

◆ to_mermaid()

str oct.deps.oct_deps.to_mermaid ( dict[str, list[str]] graph)
Format dependency graph as Mermaid diagram syntax.

Definition at line 285 of file oct_deps.py.

Here is the caller graph for this function:

Variable Documentation

◆ _BULLET_RE

oct.deps.oct_deps._BULLET_RE = re.compile(r'^[ \t]*[-*][ \t]+(.*)$')
protected

Definition at line 63 of file oct_deps.py.

◆ _DEP_NAME_RE

oct.deps.oct_deps._DEP_NAME_RE = re.compile(r'^`?(\S+?)`?[ \t]*(?:[-—:(].*)?$')
protected

Definition at line 71 of file oct_deps.py.

◆ _DEP_SECTION_RE

oct.deps.oct_deps._DEP_SECTION_RE
protected
Initial value:
1= re.compile(
2 r'Dependencies\s*\n\s*[-]+\s*\n(.*?)(?:\n\s*\n|\Z)',
3 re.DOTALL,
4)

Definition at line 59 of file oct_deps.py.

◆ _MARKER_RE

oct.deps.oct_deps._MARKER_RE = re.compile(r'^(lazy|not imported)(?![\w-])')
protected

Definition at line 75 of file oct_deps.py.