from etielle.core import field_of
class User:
id: str
str
email:
print(field_of(User, lambda u: u.email))
email
What you’ll learn: How to use type-safe field references instead of strings, when to choose each approach, and how to catch typos at type-check time.
Field selectors let you refer to model fields in a way static type checkers can verify, while the runtime resolves the field name without reflection.
Field selectors provide three key benefits over plain strings:
If you’re working with plain dicts or don’t use type checkers, plain strings work fine.
Use field_of(Model, lambda m: m.field)
to produce the string field name at runtime. Type checkers validate the lambda, catching typos early.
Use field_of()
selectors when:
Use plain string field names when:
Selectors are used with FieldSpec
inside InstanceEmit
. They are resolved against the builder’s model.
from etielle.core import MappingSpec, TraversalSpec, field_of
from etielle.transforms import get
from etielle.instances import InstanceEmit, FieldSpec, PydanticBuilder
from pydantic import BaseModel
class UserModel(BaseModel):
id: str
email: str
emit = InstanceEmit[UserModel](
table="users",
join_keys=[get("id")],
fields=[
FieldSpec(selector=field_of(UserModel, lambda u: u.id), transform=get("id")),
FieldSpec(selector=field_of(UserModel, lambda u: u.email), transform=get("email")),
],
builder=PydanticBuilder(UserModel),
)
If you use a builder without a model
attribute, pass string field names instead of selectors.
Here’s a side-by-side comparison showing the tradeoffs:
from etielle.instances import InstanceEmit, FieldSpec, PydanticBuilder
from etielle.core import field_of
from etielle.transforms import get
from pydantic import BaseModel
class User(BaseModel):
id: str
email: str
# With field selectors (type-safe):
emit_safe = InstanceEmit[User](
table="users",
join_keys=[get("id")],
fields=[
FieldSpec(selector=field_of(User, lambda u: u.id), transform=get("id")),
FieldSpec(selector=field_of(User, lambda u: u.emial), transform=get("email")), # ❌ Type checker catches typo!
],
builder=PydanticBuilder(User),
)
# With plain strings (more flexible, less safe):
emit_strings = InstanceEmit[User](
table="users",
join_keys=[get("id")],
fields=[
FieldSpec(selector="id", transform=get("id")),
FieldSpec(selector="emial", transform=get("email")), # ⚠️ Caught at runtime only (with strict_fields=True)
],
builder=PydanticBuilder(User),
)
String field names remain supported. When strict_fields=True
(default), unknown fields are recorded with helpful suggestions, and you can opt into strict_mode="fail_fast"
to raise immediately.
from etielle.transforms import get
from etielle.instances import InstanceEmit, FieldSpec, TypedDictBuilder
emit = InstanceEmit[dict](
table="users",
join_keys=[get("id")],
fields=[
FieldSpec(selector="emali", transform=get("email")), # typo on purpose
],
builder=TypedDictBuilder(lambda d: d),
strict_fields=True,
# strict_mode="fail_fast", # enable to raise instead of collect
)
etielle.core.field_of(model, selector)
→ str
field nameFieldSpec[T](selector: Callable[[T], Any] | str, transform)
model
(e.g., PydanticBuilder
).