Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .releaserc.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
[
"@semantic-release/exec",
{
"prepareCmd": "python -c \"import re, pathlib; p=pathlib.Path('pyproject.toml'); p.write_text(re.sub(r'version = \\\"[^\\\"]+\\\"', f'version = \\\"${nextRelease.version}\\\"', p.read_text()))\""
"prepareCmd": "python -c \"import re, pathlib; files=[('pyproject.toml', r'(?m)^version = \\\"[^\\\"]+\\\"$', 'version = \\\"${nextRelease.version}\\\"'), ('src/archunitpython/__init__.py', r'(?m)^__version__ = \\\"[^\\\"]+\\\"$', '__version__ = \\\"${nextRelease.version}\\\"')]; [pathlib.Path(path).write_text(re.sub(pattern, replacement, pathlib.Path(path).read_text())) for path, pattern, replacement in files]\""
}
],
[
"@semantic-release/git",
{
"assets": ["pyproject.toml", "CHANGELOG.md"],
"assets": ["pyproject.toml", "src/archunitpython/__init__.py", "CHANGELOG.md"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
],
Expand Down
62 changes: 62 additions & 0 deletions BACKLOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Backlog

This backlog collects product and maintenance ideas from project research.

## P0 - Maintenance And Correctness

- Keep package metadata synchronized across `pyproject.toml`, `CHANGELOG.md`, and `src/archunitpython/__init__.py`.
- Keep tool configuration valid for the supported Python range, especially mypy and Ruff target versions.
- Add a release metadata check that fails when the exported `__version__` differs from the project version.
- Add CI jobs that run tests, Ruff, mypy, and a package build from a clean checkout.

## P1 - Adoption Workflow

- Add an `.archignore` or similar file, modeled after `.gitignore`, for files that should never be analyzed.
- Add a `.because(...)` API so rules can carry user-facing rationale into failure messages and generated architecture documentation.
- Add configuration-file support for common rules, while keeping the fluent Python API as the primary interface.
- Add support for monorepo and multi-package Python projects.

## P1 - Python Import Semantics

- Add support for namespace packages that do not contain `__init__.py`.
- Detect dynamic imports such as `importlib.import_module()` and `__import__()`.
- Detect conditional imports such as `try/except ImportError`.
- Add better `TYPE_CHECKING` import handling, including options to ignore, include, or report type-only imports separately.
- Improve external dependency rules so users can express allowed and forbidden third-party packages at module or slice level.
- Consider a public-interface rule inspired by Tach, where modules may only import through declared package APIs.

## P1 - Reporting And Documentation

- Add comprehensive HTML reports with dependency graphs, metric charts, and zone visualization.
- Auto-generate architecture documentation based on tests and rule rationales.
- Make logged paths clickable in IDEs and common terminal integrations.
- Add PlantUML or Mermaid export for discovered architecture graphs.
- Improve metric export examples and document how metric thresholds should be selected.

## P2 - Rule Surface

- Add first-class layered architecture helpers so common clean/hexagonal/layered rules require less boilerplate.
- Add slice isolation helpers for bounded contexts and modular monoliths.
- Add richer custom rule hooks for dependency edges, files, classes, and metrics.
- Add transitive dependency checks, especially for "domain must not transitively reach infrastructure" style rules.
- Add naming and placement conventions for classes/functions, not only files.

## P2 - Performance And Scale

- Improve performance for very large projects through parallel file parsing.
- Add persistent graph caching with invalidation based on file mtimes or content hashes.
- Add benchmarks for small, medium, large, and monorepo-style projects.

## P2 - Metrics

- Add more LCOM edge case handling.
- Add metric documentation with examples for good, suspicious, and failing values.
- Add trend-friendly metric exports for CI artifacts.
- Consider additional architecture metrics such as coupling counts per slice, fan-in/fan-out summaries, and instability per package.

## P3 - Packaging And Docs

- Publish to PyPI as part of the release pipeline if this is not already automated.
- Add a Sphinx or MkDocs documentation site.
- Add a complete example repository or examples folder covering pytest, unittest, PlantUML, metrics, and CI.
- Add contribution guidance for new rule types and metric implementations.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Releases are fully automated. When a PR is merged to `main`:
1. CI runs lint + type checking + tests (across Python 3.10-3.13)
2. If CI passes, [semantic-release](https://github.com/semantic-release/semantic-release) analyzes commit messages since the last release
3. If there are `fix:` or `feat:` commits, it automatically:
- Bumps the version in `pyproject.toml`
- Bumps the version in `pyproject.toml` and `src/archunitpython/__init__.py`
- Updates `CHANGELOG.md`
- Publishes to PyPI
- Creates a GitHub release with release notes
Expand Down
18 changes: 0 additions & 18 deletions TODO.md

This file was deleted.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@ python_classes = ["Test*"]
python_functions = ["test_*"]

[tool.mypy]
python_version = "1.1.0"
python_version = "3.10"
strict = true
warn_return_any = true
warn_unused_configs = true

[tool.ruff]
target-version = "1.1.0"
target-version = "py310"
line-length = 100

[tool.ruff.lint]
Expand Down
56 changes: 56 additions & 0 deletions research/product-direction/architecture-testing-landscape.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Architecture Testing Landscape

Research date: 2026-05-23

This document compares ArchUnitPython with architecture testing tools in the broader ecosystem and with Python-specific alternatives. It focuses on user-facing features that can inform product direction.

## Current ArchUnitPython Position

ArchUnitPython already covers three valuable surfaces:

- File-level architecture rules through `project_files(...)`: dependency direction, cycle detection, naming/path conventions, external dependency checks, and custom file predicates.
- Slice-level architecture rules through `project_slices(...)`: slice extraction from path patterns or regexes, PlantUML diagram adherence, and forbidden slice dependencies.
- Code metrics through `metrics(...)`: count metrics, LCOM variants, abstractness, instability, distance from the main sequence, zone checks, and custom metrics.

That breadth makes the project closer to ArchUnit-style libraries than to pure import linters. The main gaps are around configuration ergonomics, reporting, incremental adoption, IDE/CI workflow polish, and deeper Python import semantics.

## General Architecture Testing Players

| Tool | Ecosystem | Rule definition style | Dependency/layer rules | Cycles | Diagram support | Metrics | Reports/visualization | Notable strengths | ArchUnitPython comparison |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| [ArchUnit](https://www.archunit.org/userguide/html/000_Index.html) | Java | Fluent Java API, JUnit integrations | Yes: packages, classes, layers, slices | Yes | PlantUML component diagrams | Yes: software architecture metrics | Strong test failure output; mature JUnit support | Mature reference implementation with a broad rule model and mature JUnit workflow | ArchUnitPython mirrors the core idea and already has files/slices/metrics, but lacks class/member/annotation richness and mature import options |
| [ArchUnitNET](https://github.com/TNG/ArchUnitNET) | .NET | Fluent C# API with xUnit/NUnit/MSTest integrations | Yes: assemblies, namespaces, types, members | Yes | PlantUML support and diagram generation in the project ecosystem | Limited compared with ArchUnit | Test framework integrations | Strong .NET analogue to ArchUnit with many framework adapters | ArchUnitPython has stronger built-in code metrics, but less type/member-level expressiveness |
| [NetArchTest](https://github.com/BenMorris/NetArchTest) | .NET | Fluent C# API | Yes: namespace/type dependency and conventions | Not a primary differentiator | No first-class PlantUML in core | No | Policy results; test framework agnostic | Simple, widely adopted dependency/convention tests for .NET | ArchUnitPython is broader on cycles, slices, PlantUML, and metrics |
| [ts-arch](https://github.com/ts-arch/ts-arch) | TypeScript/JavaScript | Fluent API, Jest matcher | Yes: files, folders, slices | Yes | PlantUML diagram adherence | No | Jest-oriented failure output | Very close conceptual sibling: file API, slice API, PlantUML, NX monorepo support | ArchUnitPython has a similar surface plus metrics; ts-arch has stronger monorepo/NX positioning |
| [jQAssistant](https://jqassistant.github.io/jqassistant/current/) | Mostly JVM, plugin-based | Scanner + graph database + rule concepts/constraints | Yes, via graph rules and plugins | Yes, rule-dependent | Documentation validation and living documentation workflows | Software analytics via graph queries | Reports, server/explore mode, graph-backed analysis | Enterprise-scale scanning, graph exploration, living documentation, baseline management | ArchUnitPython is much lighter and easier to embed in tests, but lacks graph database exploration and living-doc workflows |

## Python Architecture Testing Libraries

| Tool | Rule definition style | Dependency/layer rules | Cycles | External dependency rules | Diagram support | Metrics | Reports/visualization | Adoption workflow | How ArchUnitPython stacks up |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| [ArchUnitPython](https://github.com/LukasNiessen/ArchUnitPython) | Fluent Python API used from pytest/unittest/any test framework | Yes: file dependencies, external modules, slices, forbidden slice dependencies | Yes: file-level cycles | Yes: external module matching | Yes: PlantUML slice adherence | Yes: count, LCOM, distance, custom metrics | Experimental metric export; no full HTML report yet | Plain test functions; `assert_passes()` or `.check()` | Broadest feature mix among the Python tools reviewed, especially because metrics and PlantUML are built in |
| [Import Linter](https://import-linter.readthedocs.io/en/stable/index.html) | `.importlinter` configuration contracts plus CLI | Yes: forbidden, protected, layers, independence, acyclic siblings, custom contracts | Yes: acyclic siblings | Primarily first-party import contracts | No first-class PlantUML | No | Browser-based architecture UI | CLI, CI, config file, caching | More mature for config-driven import contracts and exploration; ArchUnitPython is more test-native and broader on metrics/PlantUML |
| [Tach](https://docs.gauge.sh/getting-started/introduction/) | `tach.toml` plus CLI commands | Yes: module dependencies and public interfaces | Dependency graph focused | Yes: `check-external` for third-party imports | No | No | `show`, `map`, `report`, VS Code integration | Incremental CLI workflow, sync, pre-commit/CI, public interface enforcement | Tach is stronger for modular-boundary workflow and public APIs; ArchUnitPython is stronger for test-suite rules, metrics, and diagram validation |
| [PyTestArch](https://zyskarch.github.io/pytestarch/latest/features/module_import_checks/) | Python query language evaluated in pytest | Yes: module dependency query language and layered architecture rules | Implicit through dependency rules; not positioned as a primary cycle API | Not a main focus | Yes: generates rules from PlantUML component diagrams | No | Optional matplotlib visualization | Pytest-centered evaluation structures | Similar to ArchUnitPython on Python tests and PlantUML; ArchUnitPython has simpler fluent API and built-in metrics |
| [pytest-archon](https://github.com/jwbargsten/pytest-archon) | Pytest-oriented architecture assertions | Yes: forbidden dependencies and project structure rules | Yes, positioned around avoiding cycles | Not a main differentiator | No | No | Pytest failure output | Lightweight pytest helper | ArchUnitPython is broader and more ArchUnit-like; pytest-archon is smaller and focused |

## Product Takeaways

- ArchUnitPython should lean into being the ArchUnit-style Python test library, not just another import linter.
- The nearest feature gap against Python tools is not raw rule breadth; it is workflow: config files, ignore files, public interfaces, incremental adoption, reporting, and IDE/CI polish.
- Against ArchUnit and ArchUnitNET, the biggest long-term gaps are richer semantic model support, import semantics, and mature documentation/reporting.
- Metrics are a meaningful differentiator in the Python space and should be kept visible in docs and examples.

## Sources

- ArchUnit User Guide: https://www.archunit.org/userguide/html/000_Index.html
- ArchUnitNET repository: https://github.com/TNG/ArchUnitNET
- NetArchTest repository: https://github.com/BenMorris/NetArchTest
- ts-arch repository: https://github.com/ts-arch/ts-arch
- jQAssistant User Manual: https://jqassistant.github.io/jqassistant/current/
- Import Linter documentation: https://import-linter.readthedocs.io/en/stable/index.html
- Tach documentation: https://docs.gauge.sh/getting-started/introduction/
- PyTestArch documentation: https://zyskarch.github.io/pytestarch/latest/features/module_import_checks/
- PyTestArch PlantUML documentation: https://zyskarch.github.io/pytestarch/latest/features/plantuml/
- PyTestArch visualization documentation: https://zyskarch.github.io/pytestarch/latest/features/visualization/
- pytest-archon repository: https://github.com/jwbargsten/pytest-archon
2 changes: 1 addition & 1 deletion src/archunitpython/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""ArchUnitPython - Architecture testing library for Python projects."""

__version__ = "1.0.0"
__version__ = "1.1.0"

# Files API
# Common
Expand Down
4 changes: 1 addition & 3 deletions tests/integration/test_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@
format_violations,
metrics,
project_files,
project_slices,
)
from archunitpython.common.assertion.violation import EmptyTestViolation
from archunitpython.files.assertion.cycle_free import ViolatingCycle

FIXTURES_DIR = os.path.join(
Expand Down Expand Up @@ -54,7 +52,7 @@ def test_import_format_violations(self):
def test_version(self):
import archunitpython

assert archunitpython.__version__ == "1.0.0"
assert archunitpython.__version__


class TestAssertPasses:
Expand Down
16 changes: 15 additions & 1 deletion tests/test_setup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
"""Verify project setup is correct."""

import re
from pathlib import Path


def test_import():
import archunitpython

assert archunitpython.__version__ == "1.0.0"
assert archunitpython.__version__


def test_project_version_matches_package_version():
import archunitpython

project_root = Path(__file__).resolve().parents[1]
pyproject = (project_root / "pyproject.toml").read_text()
match = re.search(r'^version = "([^"]+)"$', pyproject, re.MULTILINE)

assert match is not None
assert archunitpython.__version__ == match.group(1)