Troubleshooting#
FrozenInstanceError when calling a setter on a frozen instance#
from dataclasses import dataclass
from inito import Data
@Data
@dataclass(frozen=True)
class Point:
x: int
y: int
point = Point(1, 2) # works — @dataclass(frozen=True) is innermost
point.set_x(5) # raises dataclasses.FrozenInstanceError - expected
point.x = 5 # also raises - expected
This is expected, not a bug: generated setters remain plain attribute
assignment, so post-construction mutation on a frozen dataclass correctly
still fails — only construction itself is exempted from the frozen
check (for an immutable class, generated constructors use
object.__setattr__ internally, the same technique a real frozen
dataclass’s own __init__ uses).
Stacking order matters: put @dataclass(frozen=True) innermost
(closest to the class), as above. In the reverse order —
@dataclass(frozen=True) on the outside of @Data — construction itself
raises FrozenInstanceError, because inito generates its constructor
before the outer @dataclass installs the frozen __setattr__ and so
can’t know to bypass it. Use the innermost order, or @Value /
@Data(frozen=True) (which need no stacking), for an immutable class.
FrozenInstanceError from @Value/@Data(frozen=True) alone (no @dataclass needed)#
from inito import Value
@Value
class Point:
x: int
y: int
point = Point(1, 2)
point.x = 5 # raises dataclasses.FrozenInstanceError - expected
Also expected: @Value and @Data(frozen=True) are genuinely immutable
on their own, generating a __setattr__/__delattr__ pair that
unconditionally raises dataclasses.FrozenInstanceError — no
@dataclass(frozen=True) stacking required. This applies regardless of
how the instance was built (Point(1, 2), @Builder’s build(),
.to_builder(), …), since every inito constructor assigns fields via
object.__setattr__, bypassing the generated __setattr__ only for
that one internal call.
AnnotationResolutionError on a field annotation#
from inito import Data
@Data
class Sample:
value: DoesNotExist # raises AnnotationResolutionError - DoesNotExist isn't defined
inito resolves annotations eagerly, once, at decoration time (the core
performance rule: reflection happens once, at decoration time, never
later). If a name in a field’s annotation genuinely isn’t defined
anywhere reachable, this is the correct, expected error — fix the
annotation or make sure the referenced name is actually importable/defined
before the class is decorated.
Self-referential fields work fine (e.g. a linked-list next: Node) —
this used to be a limitation but no longer is; see the
README for how.
mypy flags user.get_name(), Point.builder(), etc. as unknown attributes#
Enable inito’s bundled mypy plugin — without it, mypy has no way to know
about members attached via setattr at decoration time:
[tool.mypy]
plugins = ["inito.typing.mypy_plugin"]
With the plugin enabled, mypy --strict correctly infers the real
__init__ signature, get_x/set_x accessor types, and the full
.builder()/Builder/.to_builder() chain for every decorator. One
cosmetic quirk: reveal_type() on a Builder instance itself may show a
doubled qualname (e.g. Point.Point.Builder) due to how mypy formats
synthetic nested classes — this doesn’t affect type-checking correctness,
only that one debug-output string.
pyright still flags get_x/set_x/@Builder members as unknown#
pyright has no third-party mypy-plugin equivalent, so the plugin above
doesn’t help it. This is a real, currently-unresolved gap — your code runs
correctly regardless, but pyright can’t verify it statically. See the
README’s known limitations section
for more, and TASKS.md Phase 17 for what closing this would require.
Exception: @Data, @AllArgsConstructor, and @Value’s constructors
are correctly typed under pyright too, via a .pyi stub marked with the
standard typing.dataclass_transform (PEP 681) — no inito-specific plugin
needed there, since both tools understand this marker natively. This
doesn’t extend to @NoArgsConstructor/@RequiredArgsConstructor, whose
real constructor signatures can’t be expressed by dataclass_transform
without misrepresenting them (see the README section above for why).
ValueError: '<field>' in __slots__ conflicts with class variable#
This is a native Python error, not an inito one — it happens at class-body
execution time, before any decorator runs, if you combine __slots__ with a
class-level default for the same name:
class Point:
__slots__ = ("x",)
x: int = 0 # raises ValueError - unrelated to inito
Slotted classes with required fields (no class-level default) work fine
with inito.
KeyError from GeneratorRegistry.get#
If you see KeyError: "No generator registered for capability '...'",
you’re calling internal machinery directly (core.attach.attach_capability)
with a capability name that was never registered. This shouldn’t happen
through any public decorator — if it does, please
open an issue.
CircularDependencyError from a DI Container#
from inito import Service, default_container
@Service
class A:
def __init__(self, b: "B"):
self.b = b
@Service
class B:
def __init__(self, a: A):
self.a = a
default_container.get(A) # raises CircularDependencyError: A -> B -> A
Two (or more) services depend on each other, directly or transitively —
the container can’t build either without first building the other. The
error message lists the exact cycle in resolution order. There’s no
setter-injection or lazy-proxy escape hatch in this release (that would
require exactly the kind of __getattr__/proxy machinery inito’s core
performance rule rules out); break the cycle by extracting the shared
behavior both classes need into a third service they both depend on
instead of on each other, or by restructuring one direction of the
dependency away.
UnresolvableDependencyError from a DI Container#
Raised in two situations: (1) container.get(cls) was called for a cls
that was never @Service/@Singleton-decorated (or registered manually
via container.register(cls)) into that specific container; (2) one of
cls’s constructor parameters has a type that isn’t registered and has
no default value, so the container has nothing to autowire and nothing to
fall back to — see
Dependency injection: how resolution works
for how to fix the second case (either register the missing type, or give
the parameter a default).
DependencyRegistrationError from @Service/@Singleton#
Raised at decoration time if a constructor parameter has no type
annotation at all (@Service needs every parameter’s type to build the
dependency graph — an unannotated parameter can’t be autowired or checked
against a default), or if you @Service-decorate the same class into the
same container twice.
Stacking @Service on top of another inito constructor decorator works
correctly and needs no extra annotations of your own:
from inito import RequiredArgsConstructor, Service
@Service
@RequiredArgsConstructor
class Repo:
pass
@Service
@RequiredArgsConstructor
class UserService:
repo: Repo # a plain field annotation, not a hand-written __init__
@RequiredArgsConstructor’s (and @AllArgsConstructor’s/@Data’s/
@NoArgsConstructor’s) generated __init__ carries no annotations in its
own source at all — @Service falls back to the ClassMetadata that
decorator already cached on the class (from the field’s own annotation,
repo: Repo above) rather than requiring you to hand-write an annotated
__init__ yourself. This only works when @Service is stacked directly
on a class that was itself already decorated — a plain, undecorated class
with an unannotated __init__ parameter still needs a real annotation.
Container.get()’s return type isn’t Any under mypy/pyright#
Container.get is a plain generic method (def get(self, cls: type[T]) -> T), so container.get(MyService) is correctly inferred as MyService by
both mypy and pyright natively — no plugin hook or .pyi stub needed here,
unlike get_x/set_x/@Builder. @Service/@Singleton also never
rewrite the decorated class’s constructor, so MyService’s own __init__
signature is exactly what you wrote, with no dataclass_transform marker
needed either.