ArchUnitPython

repository·main·Indexed 42 days ago

https://github.com/lukasniessen/archunitpython

An architecture testing library for Python projects (version 1.7.0) used to enforce structural rules, detect circular dependencies, and monitor code metrics such as LCOM and lines of code. It features a fluent API for defining dependency rules, support for named layers, and the ability to generate dependency graph reports in formats like HTML, Mermaid, and DOT. It integrates with pytest via `assert_passes()` or can be used with other frameworks via the `.check()` method.

Tokens
14.4K
Snippets
56
Records
78
Agent score
60%

What's inside ArchUnitPython

  1. Supported Python versions and testing frameworks

    main

    ArchUnitPython supports Python 3.10 and above.

    It is compatible with any Python testing framework, including unittest and pytest. For the best experience, it is recommended to use pytest along with the assert_passes() method.

  2. Use code metrics for architectural enforcement

    main

    ArchUnitPython provides a metrics() API to evaluate code quality metrics like lines of code, method counts, and cohesion. Metrics can be applied to the whole project or filtered using .for_classes_matching("pattern").

    # Example: Enforcing lines of code limit
    def test_no_large_files():
        rule = metrics("src/").count().lines_of_code().should_be_below(1000)
        assert_passes(rule)
    
    # Example: Enforcing class cohesion (LCOM)
    def test_high_class_cohesion():
        rule = metrics("src/").lcom().lcom96b().should_be_below(0.3)
        assert_passes(rule)
  3. Core capabilities of ArchUnitPython

    main

    ArchUnitPython provides three primary surfaces for architecture testing, making it a broad tool for enforcing structural rules and measuring code quality:

    1. File-level architecture rules (via project_files(...)): Used for enforcing dependency direction, detecting cycles, enforcing naming/path conventions, checking external dependencies, and applying custom file predicates.
    2. Slice-level architecture rules (via project_slices(...)): Used for extracting slices from path patterns or regexes, validating adherence to PlantUML diagrams, and enforcing forbidden slice dependencies.
    3. Code metrics (via metrics(...)): Provides various metrics including count metrics, LCOM (Lack of Cohesion of Methods) variants, abstractness, instability, distance from the main sequence, zone checks, and custom metrics.
  4. Use pattern matching for targeting files and classes

    main

    ArchUnitPython provides three primary targeting options for pattern matching across all modules, plus a specialized option for the metrics module:

    • with_name(pattern): Matches against the filename (e.g., service.py).
    • in_path(pattern): Matches against the full relative path (e.g., src/services/service.py).
    • in_folder(pattern): Matches against the path excluding the filename (e.g., src/services).
    • for_classes_matching(pattern): (Metrics module only) Matches against class names, regardless of file path.

    Patterns can be strings with glob support or compiled regular expressions via re.compile().

    # String patterns with glob support
    .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$"))
    
    # Metrics: Class name matching
    .for_classes_matching("*Service*")
    .for_classes_matching(re.compile(r"^User.*"))
  5. Handling false positives in architecture rules

    main

    If your architecture rules trigger false positives, you can use filtering and targeting capabilities to refine your rules. You can exclude specific files or patterns by filtering via:

    • File paths
    • Class names
    • Custom predicates
  6. Enforce architecture rules with pytest

    main

    To use ArchUnitPython with pytest, use the assert_passes() function. This provides clean assertion messages when a rule is violated. You can define rules using the fluent API starting with project_files().

    from archunitpython import project_files, assert_passes
    
    def test_my_architecture():
        rule = project_files("src/").should().have_no_cycles()
        assert_passes(rule)
  7. Use glob patterns for file and folder targeting

    main

    When using string patterns, ArchUnitPython supports standard glob wildcards:

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

    Common usage patterns include:

    • *.py: All Python files.
    • **/services: Any folder named services at any depth.
    • src/**/*.py: All Python files anywhere under the src directory.
    # Filename patterns
    .with_name("*.py")
    .with_name("*_service.py")
    
    # Folder patterns
    .in_folder("**/services")
    .in_folder("src/services")
    
    # Path patterns
    .in_path("src/**/*.py")
  8. Enforce architecture rules with other testing frameworks

    main

    If you are not using pytest, you can use the .check() method directly on a rule. This returns a list of violations. You can then assert that the length of this list is zero.

    from archunitpython import project_files
    
    rule = project_files("src/").should().have_no_cycles()
    violations = rule.check()
    assert len(violations) == 0
  9. Load architecture rules from a JSON configuration

    main

    For shared rules, you can define them in a JSON file and load them using rules_from_config().

    Supported rule types:

    • no_cycles
    • forbidden_dependency
    • forbidden_external_dependency

    Example JSON structure:

    {
      "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)