python context layer

A Python Context Layer: Graph Structure in Django, FastAPI, and Beyond

Dynamic imports and duck typing make Python hard to analyse statically. How a structural graph handles it, and what grep and LSP miss.

Illustration for: A Python Context Layer: Graph Structure in Django, FastAPI, and Beyond

Python is the dominant language for AI, data science, and a growing share of backend services. It is also one of the harder languages to analyse statically: dynamic imports, duck typing, decorators that rewrite function signatures, and a packaging ecosystem that evolves faster than any static analyser can track.

A structural graph approach sidesteps most of these problems. Rather than trying to resolve every runtime type, it maps what Python code imports, which is statically deterministic even when what it does with those imports is not.

The import graph is the graph

Python’s import system is explicit. Every import and from ... import is a declared dependency edge. This is enough to build a structural graph that captures the architecture of any Python project, regardless of how dynamic its runtime behaviour is.

What the import graph gives you:

  • Module-to-module dependency edges (direct and transitive)
  • Blast radius for any module: which other modules import it
  • Community detection: which modules form tightly coupled clusters
  • Load-bearing modules: those that many others depend on

What the import graph doesn’t give you:

  • Dynamic dependency resolution (importlib.import_module)
  • Runtime monkey-patching
  • Dependency injection containers that wire things at startup

For most production Python code, the static import graph covers 90%+ of the actual dependency surface.

Django-specific patterns

Django’s application structure maps well to the graph:

  • models.py files are almost universally high-blast-radius, they’re imported by views, serializers, migrations, admin, signals, and tests. Graph analysis confirms what experienced Django developers know intuitively.
  • signals.py is the classic Django god object candidate: it imports from models, couples disparate parts of the application, and creates invisible dependencies.
  • urls.py files are entry-point nodes, high in-degree (imported by the root URLconf), low out-degree (they import views, not business logic).

Community detection on a Django project almost always reveals: a cluster per domain area (users, billing, content), a shared utilities community, and a data access community (models + queries).

FastAPI-specific patterns

FastAPI’s dependency injection system creates structural relationships that pure import analysis would miss, but the router-to-handler-to-dependency chain is usually visible in the import graph.

The most structurally interesting pattern in FastAPI projects is the Depends() tree: a dependency that pulls in database sessions, auth objects, and rate limiters. These show up as high-fan-in nodes in the graph, everything depends on them, making them high-blast-radius.

What Spiderbrain finds that grep misses

Transitive dependencies: grep -r "from auth.models import User" finds direct imports. A graph traversal finds every module reachable from auth.models, including the ones that import something that imports something that imports User.

Community boundaries: no grep query tells you that your reporting/ and analytics/ packages have drifted into a single structural community and should probably be merged (or explicitly decoupled).

Blast radius on rename: changing a class name in a core module requires knowing every transitive importer. The graph computes this in milliseconds. Grepping gives you the first degree; the graph gives you all degrees.

Python + Spiderbrain in practice

Spiderbrain parses Python via its AST layer, extracting import edges and building the structural graph alongside JS/TS projects. For monorepos that mix Python services with JavaScript frontends, the cross-language blast radius is tracked separately, a Python change cannot reach a TypeScript caller through static imports, but both graphs are queryable together.

The practical workflow: add Spiderbrain to your CI pipeline on your Python service. Before each merge, surface the blast radius of the changed modules. For Django projects, a change to models.py with a blast radius over 50 is routine, which means it deserves routine structural scrutiny, not just test coverage.

★ Read next
Webby
Spiderbrain’s support assistant
Hi, I’m Webby. What are you building, or what brought you to Spiderbrain today?