Types¶
didactic understands three kinds of type:
- Scalars: built-in primitives plus a small set of stdlib types.
- Containers:
tuple,frozenset, anddict[str, V](immutable only). - Constrained scalars:
Annotated[T, ...]annotations whose metadata didactic interprets.
Mutable containers (list, set, plain dict) are rejected. Use
tuple[T, ...], frozenset[T], or dict[str, V].
Built-in scalars¶
| annotation | encoded as | decoded back to |
|---|---|---|
str |
string | str |
int |
string | int |
float |
string | float |
bool |
string "true" / "false" |
bool |
bytes |
hex string | bytes |
datetime.datetime |
ISO 8601 | datetime |
datetime.date |
ISO 8601 | date |
datetime.time |
ISO 8601 | time |
datetime.timedelta |
seconds | timedelta |
decimal.Decimal |
numeric string | Decimal |
uuid.UUID |
canonical | UUID |
pathlib.Path |
string | Path |
enum.Enum |
member name | Enum |
The encoded form is what panproto stores. JSON output uses the same
encoded form, with one extra step that turns hex bytes into a
JSON string and frozenset into a sorted list.
Optional types¶
T | None is supported directly. The encoded form is the value when
present, the JSON null when absent.
Containers¶
| annotation | notes |
|---|---|
tuple[T, ...] |
variable-length tuple of T |
tuple[A, B, C] |
fixed-shape tuple |
frozenset[T] |
order-insensitive set |
dict[str, V] |
string-keyed map; string keys only |
Literal["a", "b", ...] |
enumerated literal |
A field whose annotation contains a list, set, or plain dict raises
TypeNotSupportedError at class-creation time.
Constrained scalars from didactic.types¶
didactic.types re-exports a small library of constrained scalar
annotations:
| name | annotation | constraint |
|---|---|---|
EmailStr |
Annotated[str, ...] |
regex matching an email shape |
HttpUrl |
Annotated[str, ...] |
regex matching https?://... |
SecretStr |
wrapper class | masked repr and str |
Json[T] |
Annotated[str, ...] |
string field that holds JSON |
Use them as drop-in annotations:
from didactic.types import EmailStr, HttpUrl, SecretStr
class User(dx.Model):
id: str
email: EmailStr
homepage: HttpUrl | None = None
api_key: SecretStr
SecretStr does not derive from str; call .get_secret_value() to
retrieve the wrapped value.
Adding a custom scalar¶
If you want a type didactic does not natively understand, attach a
converter that produces an instance of a supported
type, or write a custom emitter (see Code generation).
A first-class dx.register_encoder(type, encoder) API for adding
new scalars is on the roadmap.
TypeAdapter for one-off validation¶
dx.TypeAdapter(T) validates values of an arbitrary type without
declaring a class:
RootModel for non-record payloads¶
dx.RootModel[T] wraps a single typed payload as a Model:
Use RootModel when your value is naturally a list, dict, or scalar
rather than a record.