Enforce architectural rules as code. Catch structural violations before they merge.
Project description
Architectural rules should not live in people’s heads
Architectural rules usually exist in engineers’ heads but nowhere in the codebase.
Archetype turns those rules into executable Python checks that run in archetype check and pytest.
# architecture.py
from archetype import imports, rule
@rule("api does not import db")
def api_not_db() -> None:
imports("myapp.api").must_not_import("myapp.db")
@rule("services only import db")
def services_only_db() -> None:
imports("myapp.services").must_only_import_from("myapp.db")
$ archetype check .
✓ api does not import db
✗ services only import db
- myapp.services.user -> myapp.cache: Module 'myapp.services.user' imports 'myapp.cache', which is outside the allowed set: ('myapp.db',).
Summary: 1 passed, 1 failed, 2 total rules.
Installation
pip install archetype-py
Quickstart
- Install Archetype.
pip install archetype-py
- Create
architecture.pyin your project root.
touch architecture.py
- Add your first rule with the imports DSL.
# architecture.py
from archetype import imports, rule
@rule("api does not import db")
def api_not_db() -> None:
imports("myapp.api").must_not_import("myapp.db")
- Run the checker.
archetype check .
- Read the output and fix violations.
✓ api does not import db
Summary: 1 passed, 0 failed, 1 total rules.
If your project already runs pytest, running pytest is sufficient because Archetype rules are collected and executed by the pytest plugin.
Why Archetype exists
Style tools enforce how code looks. Type tools enforce what values can flow through code. Architectural tools enforce which parts of the system are allowed to depend on which other parts.
Pylint and similar linters are strong at local code quality checks, and Mypy is strong at static type correctness. Neither is designed to express team-level dependency contracts like “API cannot import DB” or “internal modules are private outside their package boundary.”
Archetype keeps rules in architecture.py as normal Python functions, not static YAML declarations. That makes rules executable, reviewable, testable, and easy to evolve with the codebase using the same language and tooling your team already uses.
Built-in rules reference
layers
Enforces that lower layers do not import upper layers.
from archetype import rule
from archetype.rules import layers
@rule("layers are ordered")
def layer_order() -> None:
layers(["myapp.api", "myapp.services", "myapp.db"]).are_ordered()
module (module boundaries)
Enforces that a protected internal module is only imported from an allowed parent scope.
from archetype import rule
from archetype.rules import module
@rule("internal auth is private")
def auth_boundary() -> None:
module("myapp.auth.internal").only_imported_within("myapp.auth")
classes_in and functions_in (naming conventions)
Enforces class naming patterns and required top-level functions in matched modules.
from archetype import rule
from archetype.rules import classes_in, functions_in
@rule("service classes end with Service")
def class_names() -> None:
classes_in("myapp.services").all_match(r".*Service$")
@rule("api modules expose handle")
def api_handle_exists() -> None:
functions_in("myapp.api").must_include("handle")
no_cycles
Enforces that there are no import cycles in the whole project or in a selected module scope.
from archetype import rule
from archetype.rules import no_cycles
@rule("no cycles in services")
def services_no_cycles() -> None:
no_cycles("myapp.services")
Writing custom rules
from archetype import imports, rule
@rule("custom architecture policy")
def custom_policy() -> None:
imports("myapp.api").must_not_import("myapp.db")
imports("myapp.services").has_no_cycles()
imports("myapp.services").must_only_import_from("myapp.db", "myapp.shared")
Any Python function decorated with @rule that returns without raising is a passing rule. Rules can use the full Python language, so you can encode architecture constraints that do not fit generic linters or static config.
CI integration
name: Archetype Check
on:
push:
branches: [main]
pull_request:
jobs:
archetype:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: |
python -m pip install --upgrade pip
pip install archetype-py
- run: archetype check .
If your CI already runs pytest, no additional CI configuration is required.
Contributing
Source code and issue tracking are in the GitHub repository: https://github.com/MossabArektout/archetype-py.
Contributions are welcome; open an issue first to discuss scope before submitting a pull request.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file archetype_py-0.1.1.tar.gz.
File metadata
- Download URL: archetype_py-0.1.1.tar.gz
- Upload date:
- Size: 16.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: Hatch/1.16.5 cpython/3.11.15 HTTPX/0.28.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0499e8096709234e83c38c3d36ab0bea463bd476d583c44b1876d1fd6e993035
|
|
| MD5 |
3309dae4a53f3094ab2636c0cb20d044
|
|
| BLAKE2b-256 |
e9b829c584518e3d1044e974932460eac1b66b4d0ebb5fe879923e47005a1303
|
File details
Details for the file archetype_py-0.1.1-py3-none-any.whl.
File metadata
- Download URL: archetype_py-0.1.1-py3-none-any.whl
- Upload date:
- Size: 18.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: Hatch/1.16.5 cpython/3.11.15 HTTPX/0.28.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c6feedb56405ae82cd7a0f6c3c7de3e275bd53a4f291a02033f6d6c038a5d450
|
|
| MD5 |
fb32efde06e764fa86670293c44cde3e
|
|
| BLAKE2b-256 |
b7f4d59c1bd9b52f23d89d3b5b6168bf054a384fce54f0ed201d18594301e6e9
|