Constraints
Design constraints
Zero external dependencies. The entire tool runs on Python’s standard
library (ast, pathlib, json, subprocess, concurrent.futures). Once
installed it works in any environment without pulling in a dependency tree.
Framework-agnostic output. The render functions return a string — they know nothing about how it will be served:
from dependaman import dependaman
# FastAPI
@app.get("/graph", response_class=HTMLResponse)
def dependency_graph():
return dependaman(".", in_memory=True)
Degrade, never abort. A file that cannot be read or parsed is reported as a
warning and skipped; the analysis continues and returns an answer for everything
else. Malformed files, non-UTF-8 bytes, a missing git and a directory outside
a repository are all expected conditions in a real repository.
Deterministic output. The same project analysed twice serializes identically, so the JSON can be diffed between commits or checked in CI. Order is normalized once on the finished payload rather than at every point where a set is materialized.
Parsing in two passes. The first pass collects every definition in the project; the second resolves references against that set, and cannot begin until the set is complete. Each pass reads and AST-parses a module once, so a module is parsed twice per analysis. This one is a constraint of the current design rather than a choice — see Planned.
Current limitations
Known limitations in the current release
Call resolution
obj.method() with local variables. When the root of an attribute chain is
a local name, there is nothing to resolve it against. The resolver falls back to
matching the trailing attribute against every class imported into or defined in
that module, and emits an edge to each one that defines a method with that
name.
Reference contexts beyond calls. Only callable-as-argument references are
tracked (pool.submit(fn), sorted(xs, key=fn)). Other ways a callable can be
referenced without being called are still missed:
- Assignment:
handler = my_func - List/dict literals:
handlers = [fn_a, fn_b] - Return values:
return my_func
A callable appearing only in these contexts will be incorrectly flagged as dead.
Libraries are different from applications. A library exists to be called from outside itself. Its public functions and methods are the product, and nothing inside the project calls them. So on a library the dead-code flags fire on exactly the public APIs that the creator shares.
Nothing else in the analysis depends on that flag — import cycles, fan-in and fan-out stay correct, because the graph does not care why a callable has no internal callers.
Naming
from pkg import module lands on the package. When the imported name is
itself a module, the import is emitted as pkg::module — a string claiming a
callable named module lives in pkg. Nothing matches it, so the edge is
lost and the import degrades to the package node. A large project can hold
hundreds of imports in this shape.
Every __init__.py reads as an orphan. A package’s __init__.py becomes a
module node named pkg.__init__, while imports of the package resolve to pkg.
The two never meet, so every __init__.py reports zero fan-in — including
DependaMan’s own, whose public API is from dependaman import dependaman.
Planned
Type-aware call resolution
Replace the naive name-matching fallback with annotation-driven type inference:
ast.AnnAssign— typed assignments (x: MyClass = ...)- Function argument annotations —
def foo(x: MyClass)mapsxtoMyClass - Return type annotations — propagate known types through call chains
This gives precise obj.method() resolution for annotated code without a full
type checker dependency.
Full reference tracking
Extend edge emission beyond ast.Call to cover assignments, list/dict literals
and return statements — closing the false-positive dead-callable cases listed
above.
Single-pass parsing
Collapse the two parse passes into one, so each module is read and parsed once. The obstacle is ordering: reference resolution needs the complete definition set, which the first pass is what produces. Either the trees are kept between passes, or resolution is deferred until every file has been visited.