@Builder#
Generates a fluent, chainable builder:
Widget.builder().name("x").size(3).build().
The problem it solves#
A type with several optional fields leads to telescoping constructors —
long positional argument lists where the reader has to count commas
(Widget("x", 3, True, None, "blue")), or a call site cluttered with
keyword arguments. A builder makes construction readable and
order-independent, lets you set only the fields you care about, and — with
to_builder=True — lets you derive a modified copy of an existing instance.
Usage#
from dataclasses import dataclass
from inito import Builder
@Builder(to_builder=True)
@dataclass
class HttpRequest:
method: str
url: str
timeout: float = 30.0
retries: int = 0
request = (
HttpRequest.builder()
.method("GET")
.url("/users")
.timeout(5.0)
.build()
)
# Derive a variant without mutating the original:
with_retries = request.to_builder().retries(3).build()
@Builder works on a plain class too — it does not need @dataclass or
@Data, because build() constructs the instance directly (via __new__)
rather than calling __init__:
from inito import builder
@builder
class Point:
x: int
y: int
point = Point.builder().x(1).y(2).build()
What it generates#
Member |
Behaviour |
|---|---|
|
classmethod returning a fresh |
|
the nested builder class |
|
fluent setter; stores the value and returns the builder |
|
validates required fields are set, then returns a |
|
(only with |
build() raises BuilderValidationError if a required field (one
without a default) was never set. Defaulted fields you don’t set take their
default.
Options#
Option |
Default |
Effect |
|---|---|---|
|
|
also generate |
|
|
prefix the fluent setters, e.g. |
|
|
rename the terminal method, e.g. |
|
|
construct via the class’s own |
@Builder(setter_prefix="with_", build_method_name="create")
class Widget:
name: str
size: int = 1
Widget.builder().with_name("x").create()
use_init=True: construct through the real constructor#
By default build() is fast because it bypasses __init__ (see the gotcha
below). When you need the class’s own constructor to run — for a hand-written
__init__ with side effects, or a validating framework model (Pydantic,
SQLAlchemy, Django) — pass use_init=True:
from inito import Builder
from pydantic import BaseModel
@Builder(use_init=True)
class User(BaseModel):
name: str
age: int = 0
User.builder().name("Ada").build() # runs Pydantic validation
User.builder().name("Ada").age("nope").build() # raises pydantic.ValidationError
In this mode the builder only passes the fields you actually set, so the
constructor’s own defaults and required-argument errors apply rather than
InitO’s BuilderValidationError. See Using InitO with your
framework for the full guidance.
Notes & gotchas#
build()bypasses__init__by default. It creates the instance withcls.__new__(cls)and assigns fields directly, so any custom__init__logic (validation, computed attributes) is not run by the builder. Passuse_init=True(see the option above) to construct through the real constructor instead, or construct normally.No
repron its own.@Builderonly adds the builder machinery. Pair it with @ToString (or @Data) for a readablerepr.Composing with a frozen class works: stack
@dataclass(frozen=True)innermost, or use @Value;build()produces the immutable instance correctly. See Troubleshooting for the stacking-order rule.
See also#
@ToString — commonly paired for a readable
repr.Constructors — the non-fluent alternative.
Recipes — a request/response builder example.