Using InitO with your framework#
InitO has zero runtime dependencies and generates plain methods on plain classes, so it drops into any Python project — Django, FastAPI, Sanic, Flask, Litestar, or no framework at all. This page covers the two things worth knowing when you combine it with a framework: which decorators to reach for on framework model classes, and how the dependency-injection layer behaves in async request handlers.
The one rule: constructors vs. additive decorators#
InitO’s decorators fall into two groups:
Additive —
@Getter,@Setter,@ToString,@EqualsAndHashCode. These only add methods and never touch construction. They compose freely with any class, including framework models.Constructor-owning —
@Data,@Value,@NoArgsConstructor,@AllArgsConstructor,@RequiredArgsConstructor, and@Builder’s defaultbuild(). These generate (or bypass)__init__.
Frameworks like Pydantic, SQLAlchemy, and Django run important logic
in their own __init__ (validation, ORM instrumentation, field descriptors). So
on a framework’s model class, prefer the additive decorators, and let the
framework own construction.
from inito import Getter, ToString
from pydantic import BaseModel
@Getter
@ToString
class Settings(BaseModel):
host: str = "localhost"
port: int = 5432
s = Settings()
s.get_host() # "localhost" — inito accessor
repr(s) # "Settings(host='localhost', port=5432)" — inito __repr__
For your own domain / DTO / service objects — the classes that aren’t a
framework base class — use the full set (@Data, @Value, @Builder) exactly
as you would anywhere.
Builders that respect a framework constructor: use_init=True#
By default @Builder’s build() assigns fields directly and bypasses
__init__ — this is what keeps it fast and lets it work with InitO’s own
immutable classes. On a validating model that means the framework’s validation
would be skipped. Pass use_init=True and build() instead constructs
through the class’s own __init__, so the framework’s validation and
instrumentation run:
from inito import Builder
from pydantic import BaseModel
@Builder(use_init=True)
class User(BaseModel):
name: str
age: int = 0 # Pydantic default (InitO can't see it)
User.builder().name("Ada").build() # age -> Pydantic's default (0)
User.builder().name("Ada").age("nope").build() # raises pydantic.ValidationError
In use_init=True mode the builder is a keyword-argument accumulator: only the
fields you actually set are passed to the constructor, so the constructor’s
own defaults and required-argument errors apply — not InitO’s. It works the
same way with SQLAlchemy 2.0 declarative models and any hand-written __init__:
from inito import Builder
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
@Builder(use_init=True)
class Widget(Base):
__tablename__ = "widget"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column()
widget = Widget.builder().id(1).name("gadget").build() # fully instrumented ORM object
Dependency injection in async handlers (FastAPI / Sanic / Starlette)#
The DI container is safe to resolve from async code. container.get() and
@Inject are synchronous, do no I/O, and singleton construction is guarded by
double-checked locking, so concurrent resolution from many coroutines yields a
single shared instance with no deadlock.
@Inject wraps an async def transparently — it fills the container-known
parameters and returns the coroutine unchanged:
from inito import Inject, Service, Singleton
@Singleton
class Repository:
def __init__(self) -> None:
self.users = {"ada": 30}
@Service
class UserService:
def __init__(self, repo: Repository) -> None:
self.repo = repo
# FastAPI example — the handler's dependencies are wired by InitO, not the route.
@Inject
async def get_age(name: str, service: UserService) -> int:
return service.repo.users[name]
Register services at module import time (the decorator does this for you). Because DI resolves constructor type hints at import, define your services at module scope — a service class defined inside a function can’t have its annotations resolved against the module globals.
For a heavier request-scoped lifecycle, keep using your framework’s own
dependency system (FastAPI Depends, etc.) and let InitO wire the singletons
and services behind it.
A note on __slots__ and performance#
InitO’s generated constructors keep CPython’s key-sharing instance dict intact, so attribute access on decorated objects stays at handwritten speed — see Performance. This holds inside web frameworks too; there is no per-request or per-instance reflection cost, because all reflection happens once at import.