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.

Pydantic v2 is auto-detected#

InitO recognizes a Pydantic v2 model (by duck-typing — it never imports Pydantic, so the zero-dependency promise holds) and adapts so the right thing happens automatically:

  • @Builder just works — bare @Builder on a Pydantic model constructs through Pydantic’s validating __init__ (no need to pass use_init=True) and reads each field’s default and required-ness from the model, so a Pydantic-defaulted field is optional in the builder rather than wrongly required:

    from inito import Builder
    from pydantic import BaseModel
    
    
    @Builder
    class User(BaseModel):
        name: str
        age: int = 0          # Pydantic default -> optional in the builder
    
    
    user = User.builder().name("Ada").build()     # age -> Pydantic's default (0)
    user.__pydantic_fields_set__                    # {"name"} — a fully-valid model
    User.builder().name("Ada").age("nope").build()  # raises pydantic.ValidationError
    
  • Constructor-owning decorators are rejected — applying @Data, @Value, @AllArgsConstructor, @NoArgsConstructor, or @RequiredArgsConstructor to a Pydantic model would overwrite Pydantic’s validating __init__ and silently disable validation, so InitO raises DecoratorConfigurationError at decoration time and points you at @Builder plus the additive decorators.

  • Additive decorators (@Getter/@Setter/@ToString/@EqualsAndHashCode) compose freely, as shown above.

Builders that respect a framework constructor: use_init=True#

For a Pydantic model this is automatic (above). For SQLAlchemy, Django, or any hand-written __init__, opt in explicitly with 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 class whose constructor does real work that means it would be skipped. Pass use_init=True and build() instead constructs through the class’s own __init__, so that logic runs:

from inito import Builder


@Builder(use_init=True)
class Temperature:
    celsius: float

    def __init__(self, celsius: float) -> None:
        self.celsius = round(celsius, 2)   # constructor logic must run


Temperature.builder().celsius(3.14159).build().celsius   # 3.14

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, RequiredArgsConstructor, Service, Singleton


@Singleton
class Repository:
    users = {"ada": 30}          # seed data — no constructor needed


@Service
@RequiredArgsConstructor         # inito writes __init__(self, repo)
class UserService:
    repo: Repository


# 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.