ArchUnitPython

by LukasNiessenVerified

ArchUnitPython is an architecture testing library. Specify and ensure architecture rules in your Python app. Easy setup and pipeline integration.

225
Stars
5
Forks
Python
Language
8/23/2026
Added
View on GitHubDownload ZIP

⚠️ Third-Party Software Notice

This skill is third-party open-source software developed and hosted independently on GitHub. SkillTip is an informational directory and does not control or maintain the underlying repository. Any security checks displayed are automated and limited in scope. Review the source code before installing.

Read the Terms of Service

Installation

Add to your Claude Code skills directory:

# Add to your Claude Code skills
git clone https://github.com/LukasNiessen/ArchUnitPython

Getting Started

Guides for using skills like ArchUnitPython.

Security Report

Verified

Last scanned: —

{
  "status": "PASSED",
  "issues": []
}

README.md

ArchUnitPython - Architecture Testing

ArchUnitPython Logo

License: MIT Build & tests GitHub stars
PyPI downloads PyPI total downloads

Enforce architecture rules in Python projects. Check for dependency directions, detect circular dependencies, enforce coding standards and much more. Integrates with pytest and any other testing framework. Very simple setup and pipeline integration. Zero runtime dependencies.

The #1 ArchUnit-style architecture testing library for Python, measured by GitHub stars.

Inspired by the amazing ArchUnit library but we are not affiliated with ArchUnit.

SetupUse CasesFeaturesWhy ArchUnitPython?SponsorContributingDocumentation

⚡ 5 min Quickstart

Installation

pip install archunitpython

Add tests

Simply add tests to your existing test suites. The following is an example using pytest. First we ensure that we have no circular dependencies.

from archunitpython import project_files, metrics, assert_passes

def test_no_circular_dependencies():
    rule = project_files("src/").in_folder("src/**").should().have_no_cycles()
    assert_passes(rule)

Next we ensure that our layered architecture is respected.

def test_presentation_should_not_depend_on_database():
    rule = (
        project_files("src/")
        .in_folder("**/presentation/**")
        .should_not()
        .depend_on_files()
        .in_folder("**/database/**")
    )
    assert_passes(rule)

def test_business_should_not_depend_on_database():
    rule = (
        project_files("src/")
        .in_folder("**/business/**")
        .should_not()
        .depend_on_files()
        .in_folder("**/database/**")
    )
    assert_passes(rule)

# More layers ...

Lastly we ensure that some code metric rules are met.

def test_no_large_files():
    rule = metrics("src/").count().lines_of_code().should_be_below(1000)
    assert_passes(rule)

def test_high_cohesion():
    # LCOM metric (lack of cohesion of methods), low = high cohesion
    rule = metrics("src/").lcom().lcom96b().should_be_below(0.3)
    assert_passes(rule)

CI Integration

These tests run automatically in your testing setup, for example in your CI pipeline, so that's basically it. This setup ensures that the architectural rules you have defined are always adhered to!

# GitHub Actions
- name: Run Architecture Tests
  run: pytest tests/test_architecture.py -v

You can also export dependency graph reports as CI artifacts:

from archunitpython import project_graph

def test_generate_dependency_graph_reports():
    graph = project_graph("src/").titled("Application Architecture")

    graph.collapse_to_folder_depth(2).export_as_html(
        "reports/dependency-graph.html"
    )
    graph.export_as_mermaid("reports/dependency-graph.mmd")

    assert graph.snapshot().summary.node_count >= 0

🚐 Setup

Installation:

pip install archunitpython

That's it. Works with pytest, unittest, or any Python testing framework.

pytest (Recommended)

Use assert_passes() for clean assertion messages:

from archunitpython import project_files, assert_passes

def test_my_architecture():
    rule = project_files("src/").should().have_no_cycles()
    assert_passes(rule)

Any Other Framework

Use .check() directly and assert on the violations list:

from archunitpython import project_files

rule = project_files("src/").should().have_no_cycles()
violations = rule.check()
assert len(violations) == 0

Configuration Options

Both assert_passes() and .check() accept configuration options:

from archunitpython import CheckOptions

options = CheckOptions(
    allow_empty_tests=True,  # Don't fail when no files match
    clear_cache=True,        # Clear the graph cache
    ignore_type_checking_imports=True,  # Ignore imports inside if TYPE_CHECKING
)

violations = rule.check(options)

Excluding Files With .archignore

Add a .archignore file to your project root to permanently exclude generated or irrelevant files from architecture checks and file-based metrics:

# Generated code
generated/

# Migration scripts
migrations/*.py

# A single root-level file
/legacy_adapter.py

Patterns support comments, blank lines, glob syntax, root-relative paths, path patterns, and directory patterns with a trailing /.

Loading Common Rules From Config

For straightforward shared rules, you can load a JSON config file and still run the resulting rules in your normal test suite:

{
  "project_path": "src",
  "rules": [
    {
      "name": "controllers must not use services directly",
      "type": "forbidden_dependency",
      "source": "**/controllers/**",
      "target": "**/services/**"
    },
    {
      "name": "source files have no cycles",
      "type": "no_cycles"
    }
  ]
}
from archunitpython import assert_passes, rules_from_config

def test_configured_architecture_rules():
    for rule in rules_from_config("archunitpython.json"):
        assert_passes(rule)

Supported rule types are no_cycles, forbidden_dependency, and forbidden_external_dependency. The fluent Python API remains the primary and most flexible interface.

Explaining Rules With .because(...)

Attach a rationale to a rule so failing assertions explain why the rule exists:

rule = (
    project_files("src/")
    .in_folder("**/controllers/**")
    .should_not()
    .depend_on_files()
    .in_folder("**/database/**")
    .because("controllers should stay thin and delegate persistence")
)

assert_passes(rule)

When the rule fails, the rationale is included in the assertion message.

🐹 Use Cases

Here is an overview of common use cases.

Layered Architecture:

Enforce that higher layers don't depend on lower layers and vice versa.

Clean Architecture / Hexagonal:

Validate that domain logic doesn't depend on infrastructure.

Microservices / Modular:

Ensure services/modules don't have forbidden cross-dependencies.

🐲 Example Repository

Here is a repository with a fully functioning example that uses ArchUnitPython to ensure architectural rules:

🐣 Features

This is an overview of what you can do with ArchUnitPython.

Circular Dependencies

def test_services_cycle_free():
    rule = project_files("src/").in_folder("**/services/**").should().have_no_cycles()
    assert_passes(rule)

Layer Dependencies

def test_clean_architecture_layers():
    rule = (
        project_files("src/")
        .in_folder("**/presentation/**")
        .should_not()
        .depend_on_files()
        .in_folder("**/database/**")
    )
    assert_passes(rule)

def test_business_not_depend_on_presentation():
    rule = (
        project_files("src/")
        .in_folder("**/business/**")
        .should_not()
        .depend_on_files()
        .in_folder("**/presentation/**")
    )
    assert_passes(rule)

Named Layer Rules

from archunitpython import project_layers

def test_clean_architecture_layers():
    rule = (
        project_layers("src/")
        .layer("presentation").defined_by_folder("**/presentation/**")
        .layer("business").defined_by_folder("**/business/**")
        .layer("database").defined_by_folder("**/database/**")
        .where_layer("presentation")
        .may_only_depend_on_layers("business")
        .where_layer("business")
        .may_only_depend_on_layers()
        .where_layer("database")
        .may_only_depend_on_layers()
    )
    assert_passes(rule)

External Dependencies

def test_domain_does_not_import_requests():
    rule = (
        project_files("src/")
        .in_folder("**/domain/**")
        .should_not()
        .depend_on_external_modules()
        .matching("requests")
    )
    assert_passes(rule)

TYPE_CHECKING-aware Analysis

from archunitpython import CheckOptions

def test_type_only_dependencies_do_not_count_as_runtime_coupling():
    rule = (
        project_files("src/")
        .in_folder("**/api/**")
        .should_not()
        .depend_on_files()
        .in_folder("**/infrastructure/**")
    )
    assert_passes(rule, CheckOptions(ignore_type_checking_imports=True))

Dynamic Imports and Ignore Directives

ArchUnitPython detects string-based dynamic imports such as importlib.import_module("my_app.adapters.sql") and __import__("my_app.adapters.sql"). For known migration shims, you can suppress one import edge locally:

from my_app.adapters.sql import Repository  # archunit: ignore

Naming Conventions

def test_naming_patterns():
    rule = (
        project_files("src/")
        .in_folder("**/services/**")
        .should()
        .have_name("*_service.py")
    )
    assert_passes(rule)

Code Metrics

Metric rules evaluate every matching file or class independently. Use filters such as in_folder(), with_name(), and for_classes_matching() when different parts of the project need different limits.

def test_no_large_files():
    rule = metrics("src/").count().lines_of_code().should_be_below(1000)
    assert_passes(rule)

def test_high_class_cohesion():
    rule = metrics("src/").lcom().lcom96b().should_be_below(0.3)
    assert_passes(rule)

def test_method_count():
    rule = metrics("src/").count().method_count().should_be_below_or_equal(20)
    assert_passes(rule)

def test_field_count_for_data_classes():
    rule = (
        metrics("src/")
        .for_classes_matching("*Data*")
        .count()
        .field_count()
        .should_be(3)
    )
    assert_passes(rule)

Comparison Semantics

Metric comparisons are exact. In particular, should_be_below(20) means < 20, so a value of exactly 20 is a violation. Use the inclusive form when the limit itself should be accepted.

MethodPassing values
should_be_below(n)< n
should_be_below_or_equal(n)<= n
should_be_above(n)> n
should_be_above_or_equal(n)>= n
should_be(n)exactly n

Not every metric builder exposes every comparison. The available methods are shown by the fluent API after selecting a metric.

Choosing Thresholds

There is no universal correct limit for every codebase. Treat thresholds as architecture decisions that should reflect the role and maturity of the code:

  1. Measure the current project before enabling a new rule.
  2. Start at the current maximum, or slightly above it, to prevent further regression.
  3. Use narrower filters when generated code, data classes, or adapters need different limits.
  4. Lower the threshold gradually as existing violations are removed.
  5. Record the reason with .because(...) so future maintainers understand the limit.
MetricInterpretationUseful starting point
Lines of codeFile size and review burdenCurrent maximum for hand-written files
Method or field countClass responsibility and sizeCurrent maximum, split by class role
LCOMLack of class cohesion; lower is generally betterBaseline one LCOM variant and keep it consistent
InstabilityDependence on outgoing versus incoming dependenciesCompare files with similar architectural roles
Distance from main sequenceBalance between abstractness and instability; closer to zero is betterObserve the current distribution before tightening

Thresholds are guardrails, not quality scores. A metric violation is a prompt to inspect the design; it does not automatically mean the code is incorrect.

Distance Metrics

def test_proper_coupling():
    rule = metrics("src/").distance().distance_from_main_sequence().should_be_below(0.3)
    assert_passes(rule)

def test_not_in_zone_of_pain():
    rule = metrics("src/").distance().not_in_zone_of_pain()
    assert_passes(rule)

Custom Rules

You can define your own custom rules.

rule_desc = "Python files should have docstrings"

def has_docstring(file):
    return '"""' in file.content or "'''" in file.content

violations = (
    project_files("src/")
    .with_name("*.py")
    .should()
    .adhere_to(has_docstring, rule_desc)
    .check()
)

assert len(violations) == 0

Custom Metrics

You can define your own metrics as well.

def test_method_field_ratio():
    rule = (
        metrics("src/")
        .custom_metric(
            "methodFieldRatio",
            "Ratio of methods to fields",
            lambda ci: len(ci.methods) / max(len(ci.fields), 1),
        )
        .should_be_below(10)
    )
    assert_passes(rule)

Architecture Slices

import re
from archunitpython import project_slices

def test_adhere_to_diagram():
    diagram = """
@startuml
  component [controllers]
  component [services]
  [controllers] --> [services]
@enduml"""

    rule = (
        project_slices("src/")
        .defined_by_regex(re.compile(r"/([^/]+)/[^/]+\.py$"))
        .should()
        .adhere_to_diagram(diagram)
    )
    assert_passes(rule)

def test_no_forbidden_dependency():
    rule = (
        project_slices("src/")
        .defined_by("src/(**)/**")
        .should_not()
        .contain_dependency("services", "controllers")
    )
    assert_passes(rule)

Dependency Graph Reports

Generate dependency graph reports in multiple formats and narrow them to the part of the codebase you want to inspect.

Using requests library repo for example

from archunitpython import project_graph

def test_export_dependency_graph_reports():
    graph = project_graph("src/requests").titled("Application Architecture")

    graph.collapse_to_folder_depth(2).export_as_mermaid("reports/dependencies.mmd")

if __name__ == "__main__":
    test_export_dependency_graph_reports()

Exported Mermaid diagram

flowchart LR
  n0["__init__.py"]
  n1["__version__.py"]
  n2["_internal_utils.py"]
  n3["_types.py"]
  n4["adapters.py"]
  n5["api.py"]
  n6["auth.py"]
  n7["certs.py"]
  n8["compat.py"]
  n9["cookies.py"]
  n10["exceptions.py"]
  n11["help.py"]
  n12["hooks.py"]
  n13["models.py"]
  n14["packages.py"]
  n15["sessions.py"]
  n16["status_codes.py"]
  n17["structures.py"]
  n18["utils.py"]
  n0 --> n1
  n0 --> n5
  n0 --> n10
  n0 --> n13
  n0 --> n15
  n0 --> n16
  n2 --> n8
  n3 --> n6
  n3 --> n9
  n3 --> n13
  n3 --> n17
  n4 --> n0
  n4 --> n3
  n4 --> n6
  n4 --> n8
  n4 --> n9
  n4 --> n10
  n4 --> n13
  n4 --> n17
  n4 --> n18
  n5 --> n0
  n5 --> n13
  n6 --> n2
  n6 --> n8
  n6 --> n9
  n6 --> n13
  n6 --> n18
  n9 --> n2
  n9 --> n3
  n9 --> n8
  n9 --> n13
  n10 --> n8
  n10 --> n13
  n11 --> n0
  n12 --> n0
  n12 --> n13
  n13 --> n0
  n13 --> n2
  n13 --> n4
  n13 --> n6
  n13 --> n8
  n13 --> n9
  n13 --> n10
  n13 --> n12
  n13 --> n16
  n13 --> n17
  n13 --> n18
  n14 --> n8
  n15 --> n0
  n15 --> n2
  n15 --> n3
  n15 --> n4
  n15 --> n6
  n15 --> n8
  n15 --> n9
  n15 --> n10
  n15 --> n12
  n15 --> n13
  n15 --> n16
  n15 --> n17
  n15 --> n18
  n16 --> n17
  n17 --> n8
  n18 --> n0
  n18 --> n1
  n18 --> n2
  n18 --> n3
  n18 --> n8
  n18 --> n9
  n18 --> n10
  n18 --> n13
  n18 --> n17

Supported formats:

  • DOT (export_as_dot, to_dot)
  • Mermaid (export_as_mermaid, to_mermaid)
  • D2 (export_as_d2, to_d2)
  • CSV (export_as_csv, to_csv)
  • JSON (export_as_json, to_json)
  • HTML (export_as_html, to_html)

Graph exploration options:

  • focus_on(pattern, depth) keeps matching files and their neighbors.
  • reachable_from(pattern) keeps matching files and their transitive dependencies.
  • dependents_of(pattern) keeps files that transitively depend on the matching files.
  • collapse_to_folder_depth(depth) aggregates files to folder-level graph nodes.
  • collapse_by_pattern(pattern, replacement) maps files to custom graph nodes.
  • include_external_dependencies() includes imports to external modules such as requests or sqlalchemy.
  • include_self_dependencies() keeps self edges that are normally hidden in reports.

When you create reports through project_graph("src/"), internal file paths are displayed relative to that project root so the output stays readable.

Reports

Generate an HTML report from metric values collected by your tests or build tooling. MetricsExporter formats the supplied dictionary; it does not execute metric rules or calculate the values itself. This feature is in beta.

from archunitpython.metrics.fluentapi.export_utils import MetricsExporter, ExportOptions

metric_summary = {
    "Maximum method count": "18 (limit: <= 20)",
    "Maximum field count": "9 (limit: <= 10)",
    "Maximum lines of code": "420 (limit: < 500)",
    "Highest LCOM96b": "0.24 (limit: < 0.30)",
}

html = MetricsExporter.export_as_html(
    metric_summary,
    ExportOptions(
        output_path="reports/metrics.html",
        title="Architecture Metrics Dashboard",
        include_timestamp=False,
    ),
)

assert "Maximum method count" in html

Keep labels and units stable if these reports are stored as CI artifacts and compared between builds. Continue using executable metric rules with assert_passes() as the enforcement mechanism.

🔎 Pattern Matching System

We offer three targeting options for pattern matching across all modules:

  • with_name(pattern) - Pattern is checked against the filename (e.g. service.py from src/services/service.py)
  • in_path(pattern) - Pattern is checked against the full relative path (e.g. src/services/service.py)
  • in_folder(pattern) - Pattern is checked against the path without filename (e.g. src/services from src/services/service.py)

For the metrics module there is an additional one:

  • for_classes_matching(pattern) - Pattern is checked against class names. The filepath or filename does not matter here

Pattern Types

We support string patterns and regular expressions. String patterns support glob.

# String patterns with glob support (case sensitive)
.with_name("*_service.py")       # All files ending with _service.py
.in_folder("**/services")        # All files in any services folder
.in_path("src/api/**/*.py")      # All Python files under src/api

# Regular expressions
import re
.with_name(re.compile(r".*Service\.py$"))
.in_folder(re.compile(r"services$"))

# For metrics module: Class name matching
.for_classes_matching("*Service*")
.for_classes_matching(re.compile(r"^User.*"))

Glob Patterns Guide

Basic Wildcards

  • * - Matches any characters within a single path segment (except /)
  • ** - Matches any characters across multiple path segments
  • ? - Matches exactly one character

Common Glob Examples

# Filename patterns
.with_name("*.py")              # All Python files
.with_name("*_service.py")      # Files ending with _service.py
.with_name("test_*.py")         # Files starting with test_

# Folder patterns
.in_folder("**/services")       # Any services folder at any depth
.in_folder("src/services")      # Exact src/services folder
.in_folder("**/test/**")        # Any folder containing test in path

# Path patterns
.in_path("src/**/*.py")                # Python files anywhere under src
.in_path("**/test/**/*_test.py")       # Test files in any test folder

Recommendation

We generally recommend using string patterns with glob support unless you need very special cases. Regular expressions add extra complexity that is not necessary for most cases.

Supported Metric Types

LCOM (Lack of Cohesion of Methods)

The LCOM metrics measure how well the methods and fields of a class are connected. Lower values indicate better cohesion.

# LCOM96a (Henderson et al.)
metrics("src/").lcom().lcom96a().should_be_below(0.8)

# LCOM96b (Henderson et al.) - most commonly used
metrics("src/").lcom().lcom96b().should_be_below(0.7)

All 8 LCOM variants are available: lcom96a(), lcom96b(), lcom1() through lcom5(), and lcomstar().

The LCOM96b metric is calculated as:

LCOM96b = (1/a) * sum((1/m) * (m - mu(Ai)))

Where:

  • m is the number of methods in the class
  • a is the number of attributes (fields) in the class
  • mu(Ai) is the number of methods that access attribute Ai

The result is a value between 0 and 1:

  • 0: perfect cohesion (all methods access all attributes)
  • 1: complete lack of cohesion (each method accesses its own attribute)

Count Metrics

metrics("src/").count().method_count().should_be_below(20)
metrics("src/").count().field_count().should_be_below(15)
metrics("src/").count().lines_of_code().should_be_below(200)
metrics("src/").count().statements().should_be_below(100)
metrics("src/").count().imports().should_be_below(20)

Distance Metrics

metrics("src/").distance().abstractness().should_be_above(0.3)
metrics("src/").distance().instability().should_be_below(0.8)
metrics("src/").distance().distance_from_main_sequence().should_be_below(0.5)

Custom Metrics

metrics("src/").custom_metric(
    "complexityRatio",
    "Ratio of methods to fields",
    lambda ci: len(ci.methods) / max(len(ci.fields), 1),
).should_be_below(3.0)

📐 UML Diagram Support

ArchUnitPython can validate your architecture against PlantUML diagrams, ensuring your code matches your architectural designs.

Component Diagrams

def test_component_architecture():
    diagram = """
@startuml
component [UserInterface]
component [BusinessLogic]
component [DataAccess]

[UserInterface] --> [BusinessLogic]
[BusinessLogic] --> [DataAccess]
@enduml"""

    rule = (
        project_slices("src/")
        .defined_by("src/(**)/**")
        .should()
        .adhere_to_diagram(diagram)
    )
    assert_passes(rule)

Diagram from File

def test_from_file():
    rule = (
        project_slices("src/")
        .defined_by("src/(**)/**")
        .should()
        .adhere_to_diagram_in_file("docs/architecture.puml")
    )
    assert_passes(rule)

📊 Library Comparison

Here's how ArchUnitPython compares to other Python architecture-enforcement libraries.

ArchUnitPython is optimized for architecture rules as tests: rules live next to your normal unit tests, run in pytest/unittest/CI, and fail with test-style violation messages. Broader CLI-first tools such as Tach and Import Linter are excellent adjacent tools, but they solve the problem through separate configuration and commands rather than a test-native ArchUnit-style API.

FeatureArchUnitPythonTachImport LinterPyTestArch
Primary workflow✅ Architecture rules as unit tests⚠️ CLI + tach.toml⚠️ CLI + contracts config⚠️ pytest-oriented evaluable architecture
ArchUnit-style fluent API✅ Yes❌ No❌ No⚠️ Partial
Testing framework integration✅ pytest, unittest, any runner⚠️ CI/pre-commit CLI⚠️ CI/pre-commit CLI⚠️ pytest-focused
Zero runtime dependencies✅ Standard library only⚠️ No app runtime impact, Rust-backed tool❌ Tool dependencies❌ Tool dependencies
Circular dependency detection✅ First-class✅ First-class⚠️ Contract/graph based⚠️ Import-rule based
File/folder dependency rules✅ Glob + regex✅ Module config✅ Import contracts✅ Module rules
Named layer rulesproject_layers()✅ Supported✅ Supported✅ Supported
External dependency rulesdepend_on_external_modules()⚠️ Internal module focus⚠️ Import contract focus⚠️ Internal import focus
TYPE_CHECKING-aware analysis✅ Configurable⚠️ Not the core API⚠️ Not the core API⚠️ Not the core API
Dynamic import detectionimportlib + __import__ string calls⚠️ Not the core workflow⚠️ Not the core workflow⚠️ Import analysis focused
Inline ignore directives# archunit: ignore✅ Supported⚠️ Config-based ignores⚠️ Rule/exclusion based
Naming convention checks✅ Files and paths❌ No❌ No⚠️ Module-name oriented
Code metrics✅ Counts, LCOM, distance metrics❌ No❌ No❌ No
Custom rules and metrics✅ Full support❌ No⚠️ Custom contracts⚠️ Limited custom rule composition
PlantUML diagram validation✅ Supported❌ No❌ No❌ No
Empty test protection✅ Fails by default⚠️ Config validation⚠️ Contract validation⚠️ Not the main focus
Graph/reporting✅ DOT, Mermaid, D2, CSV, JSON, HTML graph reports + metrics HTML✅ DOT, JSON, web graph✅ Browser UI⚠️ Optional graph visualization
Best fitArchitecture tests, CI fitness functions, metrics, diagramsModular monolith dependency governanceConfig-driven import contractspytest import-boundary checks

The most important differences:

  • Test-native by design: ArchUnitPython rules are just Python tests, so architecture decisions are reviewed, run, and debugged in the same workflow as the rest of your test suite.
  • Broader rule surface: dependency direction, cycles, layer policies, external modules, type-only imports, dynamic imports, naming, metrics, custom rules, and PlantUML validation live in one API.
  • False-positive protection: empty checks fail by default, which helps catch typos in file and folder patterns before they silently make your architecture tests meaningless.
  • Quality beyond imports: ArchUnitPython can enforce code metrics such as LCOM cohesion, field/method counts, abstractness, instability, and distance from the main sequence.

📢 Informative Error Messages

When tests fail, you get helpful output with file paths and violation details:

Found 2 architecture violation(s):

  1. File dependency violation
     'src/api/bad_shortcut.py' depends on 'src/retrieval/vector_store.py'

  2. File dependency violation
     'src/api/bad_shortcut.py' depends on 'src/retrieval/embedder.py'

📝 Debug Logging & Configuration

We support logging to help you understand what files are being analyzed and troubleshoot test failures. Logging is disabled by default to keep test output clean.

Enabling Debug Logging

from archunitpython import CheckOptions
from archunitpython.common.logging.types import LoggingOptions

options = CheckOptions(
    logging=LoggingOptions(
        enabled=True,
        level="debug",       # "error" | "warn" | "info" | "debug"
        log_file=True,       # Creates logs/archunit-YYYY-MM-DD_HH-MM-SS.log
    ),
)

violations = rule.check(options)

CI Pipeline Integration

# GitHub Actions
- name: Run Architecture Tests
  run: pytest tests/test_architecture.py -v

- name: Upload Test Logs
  if: always()
  uses: actions/upload-artifact@v3
  with:
    name: architecture-test-logs
    path: logs/

🏈 Architecture Fitness Functions

The features of ArchUnitPython can very well be used as architectural fitness functions. See here for more information about that topic.

🔲 Core Modules

ModuleDescriptionStatus
FilesFile and folder based rulesStable
MetricsCode quality metricsStable
SlicesArchitecture slicingStable
GraphDependency graph reportsExperimental
TestingTest framework integrationStable
CommonShared utilitiesStable
ReportsGenerate HTML reportsExperimental

ArchUnitPython uses ArchUnitPython

We use ourselves to ensure the architectural rules for this repository.

🦊 Contributing

We highly appreciate contributions. See Contributing for the full workflow.

  • Use feature branches and open pull requests against main.
  • Use Conventional Commits so releases can be versioned automatically.
  • Do not bump versions manually for normal feature or fix work; semantic-release updates pyproject.toml, src/archunitpython/__init__.py, and CHANGELOG.md.
  • CI checks linting, typing, tests, package builds, and release metadata sync.

ℹ️ FAQ

Q: What Python testing frameworks are supported?

ArchUnitPython works with pytest, unittest, and any other testing framework. We recommend pytest with assert_passes().

Q: What Python versions are supported?

Python 3.10 and above.

Q: Does ArchUnitPython have any runtime dependencies?

No. ArchUnitPython uses only the Python standard library. Development dependencies (pytest, mypy, ruff) are optional.

Q: How does it analyze Python imports?

ArchUnitPython uses Python's built-in ast module to parse source files and resolve imports. It handles absolute imports, relative imports, and package imports.

Q: How do I handle false positives in architecture rules?

Use the filtering and targeting capabilities to exclude specific files or patterns. You can filter by file paths, class names, or custom predicates to fine-tune your rules.

📅 Plans

ArchUnitPython is the Python port of ArchUnitTS. We plan to keep it in sync with the TypeScript version's features, and extend it with Python-specific capabilities.

🐣 Origin Story

ArchUnitPython started as the Python port of ArchUnitTS. With the rise of LLMs and AI integration, enforcing architectural boundaries and QA in general has become more critical than ever -- especially in Python, the dominant language in the AI/ML ecosystem.

💟 Community

Maintainers

Contributors

Questions

Found a bug? Want to discuss features?

If ArchUnitPython helps your project, please consider:

  • Starring the repository 💚
  • Sponsoring development via GitHub Sponsors
  • Suggesting new features 💭
  • Contributing code or documentation ⌨️

Star History

Star History Chart

📄 License

This project is under the MIT license.


Go Back to Top

Frequently Asked Questions

What is ArchUnitPython?

ArchUnitPython is an open-source testing skill for AI coding assistants such as Claude Code, Codex CLI, and ChatGPT, built by LukasNiessen. ArchUnitPython is an architecture testing library. Specify and ensure architecture rules in your Python app. Easy setup and pipeline integration. It has 225 GitHub stars.

Is ArchUnitPython safe to use?

Yes. ArchUnitPython passed SkillsLLM's automated security scan — a dependency vulnerability audit plus prompt-injection heuristics — with no high-severity issues. You can read the full report in the Security Report section on this page.

How do I install ArchUnitPython?

Clone the repository with "git clone https://github.com/LukasNiessen/ArchUnitPython" and add it to your Claude Code skills directory (see the Installation section above).

What programming language is ArchUnitPython written in?

ArchUnitPython is primarily written in Python. It is open-source under LukasNiessen on GitHub, so you can review or fork the full source.

Are there alternatives to ArchUnitPython?

Yes. SkillsLLM lists many other Testing skills you can browse and compare side by side. Open the Testing category from the badge at the top of this page, or use the Related Skills and comparison links further down to weigh ArchUnitPython against similar tools.

Comments (0)

No comments yet. Be the first to share your thoughts!

Claude-BugHunter

by elementalsouls

A Claude Code skill bundle for bug hunting and external red-team work - 82 skills, 15 slash commands, 681 disclosed-report patterns curated across 24 core vulnerability classes, plus enterprise identity + infrastructure attack matrices.

3,740578Python
Testing
View details

Rapid-MLX

by raullenchai

The fastest local AI engine for Apple Silicon. 4.2x faster than Ollama, 0.08s cached TTFT, 100% tool calling. 17 tool parsers, prompt cache, reasoning separation, cloud routing. Drop-in OpenAI replacement. Works with Claude Code, Cursor, Aider.

3,530401Python
Testing
View details

playwright-skill

by lackeyjb

Claude Code Skill for browser automation with Playwright. Model-invoked - Claude autonomously writes and executes custom automation for testing and validation.

3,014229JavaScript
Testing
View details

src-hunter-skill

by MyuriKanao

实战 SRC / 众测 / Bug bounty 漏洞挖掘 Claude Code skill — 19 个攻击类 playbook、305 个结构化 payload、263 个 WAF/EDR 绕过、2887 份 HackerOne 真实案例、88,636 WooYun 案例统计

60187
Testing
View details

100 field-tested Claude Code recipes for knowledge workers — prompts, steps, and 6 installable graded skills.

38048
Testing
View details

Claude Code Skill that turns any idea into a cinematic, model-ready video prompt — Sora · Kling · Veo · Seedance. 21 genre templates, 5-stage structure, eval-tested. Distilled from the AI short Hollywood director PJ Ace called "one of the best short films I've seen in years."

36869Python
Testing
View details

Developers Also Liked

Based on votes and bookmarks from developers who liked this skill

ECC

by affaan-m

10

The agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond.

242,21936,702JavaScript
AI Agentsai-agentsanthropicclaude-code
View details
15

An agentic skills framework & software development methodology that works.

234,96620,863Shell
AI Agentsai-agentsbrainstorming
View details

n8n

by n8n-io

12

Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.

201,88160,308TypeScript
MCP Serversapisai-tools
View details

The agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond.

185,94028,768JavaScript
AI Agentsai-agentsanthropicclaude-code
View details

cc-switch

by farion1231

3

A cross-platform desktop All-in-One assistant for Claude Code, Codex, OpenCode, OpenClaw, Grok Build & Hermes Agent. Only official website: ccswitch.io

128,8688,826Rust
AI Agentsclaude-codeai-tools
View details