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

Classes

class  _ModuleInfo
class  _FunctionInfo
class  _ClassInfo
class  CallStats
class  StaticCallGraph
class  ProjectIndex

Functions

dict[str, str] _bindings_from (list[ast.stmt]|ast.AST stmts, str module, bool is_package)
str|None _dotted_name (ast.AST node)
tuple[str, str] _classify (ProjectIndex index, _FunctionInfo fn, ast.Call call, dict[str, str] local)
StaticCallGraph build_call_graph (Path root)
CallGraph to_callgraph (StaticCallGraph graph)

Variables

frozenset _BUILTINS = frozenset(dir(builtins))
int _MAX_RESOLVE_DEPTH = 12

Detailed Description

Purpose
-------
A static call graph for a Python project, resolved from the AST through
the project's real import bindings. Chart C of the overview-charts plan
(docs/DOCS_OVERVIEW_CHARTS_PLAN_2026-09-27.md in the workspace) uses it to
draw the full graph of an entry point such as ``oct git commit``.

It exists because Doxygen cannot do this for Python. Measured on oct on
2026-09-27, Doxygen's call analysis resolves a call only when caller and
callee share a file: ``oct git commit`` calls 48 distinct names, Doxygen
resolved 14, and every call into the quality gate - imported inside the
function - was invisible to it. This module follows the imports.

Responsibilities
----------------
- :class:`ProjectIndex` - every module's functions, classes, methods and
  import bindings (module level and function local, aliases, relative
  imports), with re-export chains followed on lookup.
- :func:`build_call_graph` - resolve each function's call sites into
  internal edges, external calls and unresolved calls, each counted.
- :func:`to_callgraph` - adapt the result to
  :class:`oct.docs.callgraph.CallGraph` so the Doxygen chart's closure and
  DOT renderer are reused unchanged.

Diagnostics
-----------
Domain: OCT-DEPS
Levels:
    L2 - build entry / exit with totals
    L3 - per-module indexing decisions
    L4 - individual unresolved call sites

Contracts
---------
- Purely static: no project module is imported or executed.
- Nothing is guessed. A call becomes an internal edge only when its target
  resolves, through a binding or a definition, to a function or method
  that exists in the project. A call on a receiver whose type cannot be
  known statically (``result.stdout.split()``) is counted as unresolved
  with that reason, never matched by name to some function that happens
  to share it - which is the mistake a textual resolver makes.
- Calls inside nested functions and lambdas are attributed to the
  enclosing top-level function or method, because that is where they run.
- A project function passed as an argument to a call is an edge too
  (a callback: a thread target, a sort key, a handler), since it runs
  because of that call. Counted separately as ``callbacks``.
- A project function placed in a tuple, list, set or dict literal is an
  edge too (a dispatch-table entry, later called through a loop
  variable no static resolver can follow). Counted separately as
  ``references``. Found by the runtime trace of ``oct git commit``
  (2026-09-27): ``format_file``'s fixer table was the whole of the
  difference between the trace and this graph.
- Decorators and default-argument expressions are not callees: they run
  when the function is defined, not when it is called.
- Cross-checked against Doxygen on oct (2026-09-27): of the 681 Doxygen
  call edges that map to a function here by file and line, every one is
  in this graph except six that Doxygen got wrong - four ``self.m()``
  calls matched by name to a same-named method of an unrelated class,
  and two module-level calls credited to the function above them.
- Tests and the top-level ``docs`` directory are skipped, as in the
  import graph.

Dependencies
------------
- oct.core.diagnostics (_dbg structured logger)
- oct.deps.import_graph (file walk, relative-import resolution, test skip)
- oct.deps.oct_deps (module_name_for - the shared module key scheme)
- oct.docs.callgraph (CallGraph / Function - the shared rendering model)

Function Documentation

◆ _bindings_from()

dict[str, str] oct.deps.call_graph._bindings_from ( list[ast.stmt] | ast.AST stmts,
str module,
bool is_package )
protected
Import bindings found in *stmts* (walked), as ``local name -> dotted target``.

Definition at line 145 of file call_graph.py.

Here is the caller graph for this function:

◆ _classify()

tuple[str, str] oct.deps.call_graph._classify ( ProjectIndex index,
_FunctionInfo fn,
ast.Call call,
dict[str, str] local )
protected
Return ``(kind, detail)``: kind is internal / external / unresolved.

Definition at line 292 of file call_graph.py.

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

◆ _dotted_name()

str | None oct.deps.call_graph._dotted_name ( ast.AST node)
protected
``a.b.c`` for a pure Name/Attribute chain, else ``None``.

Definition at line 280 of file call_graph.py.

Here is the caller graph for this function:

◆ build_call_graph()

StaticCallGraph oct.deps.call_graph.build_call_graph ( Path root)
Resolve every call site in the project at *root*.

Definition at line 326 of file call_graph.py.

Here is the call graph for this function:

◆ to_callgraph()

CallGraph oct.deps.call_graph.to_callgraph ( StaticCallGraph graph)
Adapt to the shared :class:`CallGraph` so closure and rendering are reused.

Definition at line 397 of file call_graph.py.

Variable Documentation

◆ _BUILTINS

frozenset oct.deps.call_graph._BUILTINS = frozenset(dir(builtins))
protected

Definition at line 90 of file call_graph.py.

◆ _MAX_RESOLVE_DEPTH

int oct.deps.call_graph._MAX_RESOLVE_DEPTH = 12
protected

Definition at line 93 of file call_graph.py.