Dependency Injection

Vorte includes a built-in dependency injection container that manages object creation, lifecycle, and wiring. It integrates seamlessly with FastAPI's Depends system while adding scopes, auto-wiring, and child containers.

Container

The Container is the central registry for all services and dependencies. Register services with a scope, and the container handles instantiation and lifecycle automatically.

example.py
python
Copied!
1from vorte.di import Container
2
3container = Container()
4
5container.register(DatabaseService, scope="singleton")
6container.register(UserRepository, scope="transient")
7container.register(EmailClient, scope="request")

Scopes

Scopes control how often a dependency is instantiated. Vorte supports three scopes:

ScopeLifetimeUse Case
singletonOne instance for the entire applicationDatabase pools, configuration, cache clients
requestOne instance per HTTP requestRequest context, current user, unit of work
transientNew instance on every resolutionDTOs, validators, stateless processors

Singleton Scope

databaseservice.py
python
Copied!
1from vorte.di import Container
2
3container = Container()
4
5@container.singleton
6class DatabaseService:
7 def __init__(self, url: str):
8 self.url = url
9 self._pool = None
10
11 async def connect(self):
12 self._pool = await create_pool(self.url)
13
14 async def query(self, sql: str):
15 return await self._pool.execute(sql)
16
17# Registration with factory
18container.register(
19 DatabaseService,
20 factory=lambda: DatabaseService(url=config.database_url),
21 scope="singleton",
22)

Request Scope

requestcontext.py
python
Copied!
1@container.request_scope
2class RequestContext:
3 def __init__(self):
4 self.user: User | None = None
5 self.request_id: str = ""
6 self.start_time: float = time.time()
7
8 def elapsed_ms(self) -> float:
9 return (time.time() - self.start_time) * 1000

Transient Scope

uservalidator.py
python
Copied!
1@container.transient
2class UserValidator:
3 def validate(self, data: dict) -> list[str]:
4 errors = []
5 if not data.get("email"):
6 errors.append("Email is required")
7 if not data.get("name"):
8 errors.append("Name is required")
9 return errors

@wire Decorator

Use @wire to auto-inject dependencies into class constructors. The container inspects type annotations and resolves them automatically.

userservice.py
python
Copied!
1from vorte.di import wire, Container
2
3container = Container()
4container.register(DatabaseService, scope="singleton")
5container.register(CacheService, scope="singleton")
6
7@wire
8class UserService:
9 db: DatabaseService
10 cache: CacheService
11
12 async def get_user(self, user_id: int):
13 cached = await self.cache.get(f"user:{user_id}")
14 if cached:
15 return cached
16 user = await self.db.query("SELECT * FROM users WHERE id = $1", user_id)
17 await self.cache.set(f"user:{user_id}", user, ttl=300)
18 return user
19
20container.register(UserService, scope="request")

Depends Integration

Vorte integrates with FastAPI's Depends for route-level dependency injection. Use container.depends to resolve services in route handlers.

get_user.py
python
Copied!
1from vorte.di import Container
2from fastapi import Depends
3
4container = Container()
5
6@router.get("/users/{user_id}")
7async def get_user(
8 user_id: int,
9 user_service: UserService = Depends(container.depends(UserService)),
10):
11 user = await user_service.get_user(user_id)
12 return success_response(data=user)

@inject Decorator

Use @inject on functions that need dependency resolution outside of route handlers, such as background tasks or module initialization.

process_payment.py
python
Copied!
1from vorte.di import inject, Container
2
3container = Container()
4
5@inject
6async def process_payment(
7 order_id: str,
8 payment_gateway: PaymentGateway = None,
9 notification_service: NotificationService = None,
10):
11 result = await payment_gateway.charge(order_id)
12 await notification_service.send(
13 recipient=result.customer_email,
14 message=f"Payment {result.status} for order {order_id}",
15 )
16 return result
17
18# Called without arguments — container resolves dependencies
19await process_payment(order_id="ord_123")

Child Containers

Create isolated child containers for modules or request scopes. Child containers inherit registrations from their parent but can override them locally.

example.py
python
Copied!
1from vorte.di import Container
2
3root = Container()
4root.register(DatabaseService, scope="singleton")
5
6# Create a child container for a module
7ai_container = root.create_child()
8ai_container.register(AIService, scope="singleton")
9ai_container.register(PromptManager, scope="singleton")
10
11# Create a request-scoped child
12request_container = root.create_child()
13request_container.register(RequestContext, scope="request")
14request_container.override(DatabaseService, TestDatabaseService)
15
16# Resolve from child — falls back to parent if not found locally
17db = await request_container.resolve(DatabaseService)
18ai = await ai_container.resolve(AIService)

Manual Resolution

example.py
python
Copied!
1# Resolve a single dependency
2db = await container.resolve(DatabaseService)
3
4# Resolve multiple at once
5db, cache, email = await container.resolve_all(
6 DatabaseService, CacheService, EmailClient
7)
8
9# Check if registered
10if container.has(PaymentGateway):
11 gateway = await container.resolve(PaymentGateway)

Testing with DI

container.py
python
Copied!
1import pytest
2from vorte.di import Container
3
4@pytest.fixture
5def container():
6 c = Container()
7 c.register(DatabaseService, scope="singleton")
8 return c
9
10def test_user_service(container):
11 container.override(DatabaseService, MockDatabaseService)
12 service = container.resolve(UserService)
13 assert service.db.__class__ == MockDatabaseService
Stay in the loop

Get Vorte release notes, module guides, and developer deep-dives. No spam — unsubscribe anytime.