Quick start#

@Data: the all-in-one decorator#

from inito import Data


@Data
class User:
    name: str
    age: int = 0


user = User("Ada", age=30)
print(user)              # User(name='Ada', age=30)
print(user == User("Ada", 30))   # True
print(user.get_name())   # Ada
user.set_age(31)

@Data generates a constructor, __repr__, __eq__, __hash__, and get_<field>()/set_<field>(value) accessors for every declared field — required fields first, defaulted fields after, matching normal Python parameter ordering rules.

Options#

@Data(frozen=True)             # genuinely immutable: no setters, assignment/deletion raise
@Data(include_getters=False)   # omit getters
@Data(include_setters=False)   # omit setters, keep getters (not immutable - direct assignment still works)

@Value: a genuinely immutable data class#

from inito import Value


@Value
class Point:
    x: int
    y: int


point = Point(1, 2)
print(point)              # Point(x=1, y=2)
print(point.get_x())      # 1
point.x = 5                # raises dataclasses.FrozenInstanceError

@Value is @Data without setters, and genuinely immutable: constructor, __repr__, __eq__, __hash__, and get_<field>() accessors — no set_<field>(value) is ever generated, and attribute assignment/deletion always raise dataclasses.FrozenInstanceError after construction. No @dataclass(frozen=True) stacking is needed — @Value enforces this itself. @Data(frozen=True) gets the same real enforcement.

Dependency injection#

from inito import Inject, Service, Singleton


@Singleton
class Repo:
    def __init__(self) -> None:
        self.ages = {"Ada": 30}


@Service
class UserService:
    def __init__(self, repo: Repo, retries: int = 3) -> None:
        self.repo = repo
        self.retries = retries


@Inject
def main(service: UserService) -> None:
    print(service.repo.ages["Ada"])   # 30


main()

@Service registers a class’s constructor dependency types into a Container (default_container unless you pass container=) at decoration time only — it never instantiates anything and never mutates the class, so UserService(repo=Repo(), retries=5) still works exactly like an ordinary Python class. Dependency graphs are resolved and built lazily, bottom-up, on the first container.get(cls)/@Inject-resolved call. @Singleton is sugar for @Service(scope=Scope.SINGLETON) — the default scope; pass @Service(scope=Scope.TRANSIENT) for a fresh instance on every resolution instead.

@Service also composes with the constructor decorators — a plain field annotation (repo: Repo) is enough, no hand-written __init__ required:

from inito import RequiredArgsConstructor, Service


@Service
@RequiredArgsConstructor
class Repo:
    pass


@Service
@RequiredArgsConstructor
class UserService:
    repo: Repo

That is the tour. The Dependency injection guide covers the rest in depth: autowiring vs. plain config, singleton vs. transient scope, custom containers, the error types, and the thread-safety and performance guarantees.

Composing atomic decorators#

Every capability @Data bundles is also available on its own, so you can pick exactly what you need:

from inito import AllArgsConstructor, EqualsAndHashCode, Getter, Setter, ToString


@AllArgsConstructor
@ToString
@EqualsAndHashCode
@Getter
@Setter
class User:
    name: str
    age: int

This is functionally equivalent to @Data — each atomic decorator resolves the exact same underlying generator @Data uses internally, so there’s no duplicated logic, only a more explicit spelling.

@Builder: a fluent builder#

from dataclasses import dataclass
from inito import builder


@builder(to_builder=True)
@dataclass
class Request:
    prompt: str
    temperature: float = 0.7


request = Request.builder().prompt("hello").build()
revised = request.to_builder().temperature(0.9).build()

@builder works standalone on a plain class too — it doesn’t require @dataclass or @Data, since build() constructs instances directly rather than depending on __init__. Pair it with @ToString if you also want a readable repr without pulling in @Data’s constructor/eq/hash:

from inito import ToString, builder


@builder
@ToString
class Point:
    x: int
    y: int


point = Point.builder().x(1).y(2).build()
print(point)   # Point(x=1, y=2)

See API reference for the full decorator list and Examples for a runnable script per decorator.