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 default build(). 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.