Home
developed by·Чудайкин Александр·Бюро автоматизации процессов
Who are you?
Your team grows. Each new service is wired differently. Every new hire reinvents the plumbing. Architecture docs drift from reality.
archtool gives the whole team one standard: declare an interface, write a concrete class, archtool wires it. Layer violations are caught at startup — not in a 3 AM incident. Architecture becomes something you can actually enforce, not just document.
You ship fast. But every shortcut in the foundation costs twice as much when you scale.
archtool handles the boilerplate so you focus on the product. Add a module, register it, done. The architecture scales with you — no rewrite when you hire engineer #10.
→ fractal_chunks (coming soon) — battle-tested modules for auth, users, payments, notifications. Plug in what you need; skip what you don't.
You generate code faster than you integrate it. The bottleneck is wiring.
archtool removes that step. AI writes the service, you declare the interface — archtool connects everything at startup. No boilerplate. No import chains to untangle.
→ fractal_chunks (coming soon) — a growing catalog of production-grade modules, each a Lego brick. Drop users, auth-jwt, notifications into a new project in minutes, not days.
You've cleaned up enough service-locator messes. You know what happens when DI is informal.
archtool makes the right patterns the only path. Interfaces are the only contract. Implementations are discovered, not registered. Layer violations fail fast at startup. Clean Architecture — without the ceremony.
The problem every Python project hits
You start clean. A service, a repo, maybe a controller. Then the project grows.
Somewhere around module 5, this happens:
# entrypoints/run.py — the graveyard of good intentions
import sys
sys.path.insert(0, "..") # ← why is this here again?
from app.users.repos import UserRepo
from app.users.services import UserService
from app.orders.repos import OrderRepo
from app.orders.services import OrderService
from app.payments.repos import PaymentRepo
from app.payments.services import PaymentService
from app.notifications.services import NotificationService
user_repo = UserRepo()
order_repo = OrderRepo()
payment_repo = PaymentRepo()
user_service = UserService()
user_service.repo = user_repo # ← don't forget this
order_service = OrderService()
order_service.repo = order_repo
order_service.user_service = user_service # ← or this
payment_service = PaymentService()
payment_service.repo = payment_repo
payment_service.order_service = order_service # ← or this
notification_service = NotificationService()
notification_service.payment_service = payment_service # ← or this
# ... 40 more lines ...
This is what archtool replaces — entirely.
The solution
# entrypoints/run.py with archtool
from pathlib import Path
from archtool.dependency_injector import DependencyInjector
from archtool.global_types import AppModule
injector = DependencyInjector(
modules_list=[
AppModule("app.users"),
AppModule("app.orders"),
AppModule("app.payments"),
AppModule("app.notifications"),
],
project_root=Path(__file__).parent.parent,
)
injector.inject()
archtool scans your modules, discovers every interface–implementation pair, instantiates them in dependency order, and wires everything together. No registration boilerplate. No sys.path hacks. No hidden wiring bugs.
What you get
Declare a dependency as a class annotation on your concrete class. archtool reads it and calls setattr at assembly time.
Layer violations — a service depending on a controller, a controller reaching into domain internals — are caught at startup, not in production at 3 AM.
The built-in layers follow Clean Architecture. But you define your own:
class IntegrationsLayer(Layer):
depends_on = InfrastructureLayer
class Components:
clients = ComponentPattern("clients", superclass=ABCClient)
Any filename, any base class — archtool adapts to your architecture, not the other way around.
- S — each
AppModuleowns one bounded context - O — add a module, nothing else changes
- L — swap a repo for a stub, consumers don't know
- I — interfaces stay minimal and focused
- D — everything depends on abstractions, never concretions
See it in action
# app/users/services.py from .interfaces import UserServiceABC, UserRepoABC class UserService(UserServiceABC): repo: UserRepoABC # ← archtool injects UserRepo here def get_name(self) -> str: return self.repo.find_all()[0] # entrypoints/run.py injector = DependencyInjector( modules_list=[AppModule("app.users")], project_root=Path(__file__).parent.parent, ) injector.inject() svc = injector.get_dependency(UserServiceABC) print(svc.get_name()) # → "alice"
# app/orders/services.py from app.users.interfaces import UserServiceABC # cross-module from .interfaces import OrderServiceABC, OrderRepoABC class OrderService(OrderServiceABC): repo: OrderRepoABC user_svc: UserServiceABC # ← wired from app.users automatically def place(self, user_id: int, items: list) -> dict: user = self.user_svc.get_name() return {"user": user, "items": items, "status": "placed"} # entrypoints/run.py injector = DependencyInjector( modules_list=[ AppModule("app.users"), AppModule("app.orders"), # cross-module dep resolved ], project_root=ROOT, ) injector.inject()
# app/fraud/services.py ← Domain layer from .interfaces import FraudServiceABC, FraudControllerABC class FraudService(FraudServiceABC): # Domain depending on Application layer — violation! controller: FraudControllerABC # entrypoints/run.py injector = DependencyInjector( modules_list=[AppModule("app.fraud")], layers=default_layers, # enforcement enabled project_root=ROOT, ) injector.inject() # ← raises here, not at 3 AM in production
Install
Supports Python 3.10 · 3.11 · 3.12 · 3.13.
Five-minute start
→ Quickstart — working project from scratch.
→ Why archtool? — the full problem statement and how we address it.
→ How it works — the two-pass injection algorithm explained.