ArchUnitPython

Architecture testing for Python. Turn imports and source structure into rules that run beside pytest or unittest. ArchUnitPython checks boundaries, cycles, layers, external dependencies, code metrics, and architecture diagrams without becoming a production dependency.

Python projectsMIT

PyPI

pip install archunitpython
test_architecture.py
from archunitpython import project_files, assert_passes

def test_domain_stays_independent():
    rule = (
        project_files('src/')
        .in_folder('**/domain/**')
        .should_not()
        .depend_on_files()
        .in_folder('**/infrastructure/**')
    )
    assert_passes(rule)

Silent product demo

Watch architecture rules run in Python.

This silent walkthrough follows an ArchUnitPython rule from its test definition to the resulting architecture feedback. Playback starts muted, loops in place, and keeps standard controls available so you can inspect each step.

Watch on YouTube ↗

The ecosystem

Python, in context.

Python is a readable, dynamically typed language used across web services, automation, data engineering, machine learning, and scientific computing. Frameworks such as Django, FastAPI, and Flask make it quick to grow an application, while architecture tests help that speed stay compatible with clear module ownership.

01

Web applications, data platforms, automation, and AI

02

Django, FastAPI, Flask, pytest, and unittest

03

Dynamic imports analyzed without a runtime dependency

Adoption snapshot

Public adoption, in context.

PyPI Stats reports 191,664 downloads in its retained public history, excluding mirrors, with 45,927 in the latest completed 30-day window.

Package status
Published on PyPI
Public download totals
Reported history
191,664
Apr 6 to Oct 3, 2026
Latest 30 days
45,927
Sep 4 to Oct 3, 2026
GitHub stars
667
Snapshot of October 4, 2026

Capabilities

What ArchUnitPython brings to the test suite.

The family shares a recognizable architecture vocabulary while each implementation stays honest about its language, toolchain, and maturity.

01

Cycle and internal-dependency rules

02

Named layers and forbidden external imports

03

Counting, cohesion, and distance metrics

04

pytest, unittest, and framework-neutral checks

05

Ignore files and JSON-configured common rules

06

Dependency graph snapshots and exports

Go deeper

Protect the direction between named layers.

Named layers describe the intended flow once, then validate all matching imports together. This is useful for services that grew from a small script into routes, domain logic, and persistence adapters.

test_layers.py
from archunitpython import layers, assert_passes

def test_layer_direction():
    rule = (
        layers()
        .layer('API').defined_by('app/api/**')
        .layer('Domain').defined_by('app/domain/**')
        .layer('Data').defined_by('app/data/**')
        .where_layer('Domain')
        .may_not_depend_on_layers("API")
    )
    assert_passes(rule)

Documentation highlights

Work with ArchUnitPython beyond the quickstart.

ArchUnitPython analyzes source without importing the target application. It turns ordinary Python modules into a dependency graph, preserves useful import context, and lets pytest, unittest, or any caller assert on structured violations.

01

Analyze Python without executing it

The extractor walks .py files, parses them with Python ASTs, and resolves absolute and relative imports back to project files or external module names.

  • .archignore augments safe defaults for virtual environments, caches, builds, generated code, and project-specific exclusions.
  • Literal importlib.import_module() and __import__() calls are recognized, while non-literal dynamic behavior is not guessed.
  • TYPE_CHECKING and conditional ImportError paths retain their import kind, and scoped ignore comments can remove known compatibility edges.
02

Express files, layers, and slices

File rules check naming, placement, cycles, internal dependencies, external dependencies, or a custom predicate. Layer and slice rules project the same graph into larger architectural units.

  • Named layers support allowlists and blocklists, including sealed layers that may not depend on another named layer.
  • Slices derive names from paths or regular expressions and can enforce an allowed component diagram.
  • Selectors fail when they match nothing by default, preventing a renamed folder from turning a useful test into a silent pass.
03

Set thresholds from evidence

Metric comparisons are exact and evaluate each selected file or class independently. The practical starting point is the current project baseline, tightened deliberately over time.

  • Use strict or inclusive comparisons explicitly, such as should_be_below() versus should_be_below_or_equal().
  • Count, LCOM, coupling, instability, abstractness, and main-sequence distance cover both local size and graph-level design pressure.
  • Graph and metric results export to reviewable formats, while because() keeps the architectural reason beside the threshold.
Read the complete documentation ↗

Release and compatibility

Published package

Published on PyPI, designed with zero runtime dependencies, and compatible with ordinary Python testing workflows.

LanguagePython
ReleasePublished package
RuntimePython projects
LicenseMIT

Where it fits

Put boundaries next to the code they protect.

Common starting points for ArchUnitPython include the following project shapes.

Django boundariesFastAPI servicesData platformsRAG pipelines

Make the first boundary executable.

Add one meaningful rule, run it with the existing tests, and grow the architecture suite as the system reveals where it needs protection.

Start with ArchUnitPython ↗