Supported types¶
Talea resolves annotations once when a Spec class is created. Construction
then executes specialized Python checks with no annotation reflection, adapter
registry, or coercion.
Strict values¶
The normal Spec constructor accepts Python objects that already satisfy the
declared contract. It does not parse strings into UUIDs, dates, paths, IP
objects, enum members, or decimal values.
from datetime import datetime, timezone
from uuid import UUID
from talea import Spec
class Event(Spec):
identifier: UUID
created_at: datetime
event = Event(
identifier=UUID("12345678-1234-5678-1234-567812345678"),
created_at=datetime.now(timezone.utc),
)
Passing the UUID or datetime as a string to ordinary construction or
from_mapping raises ValidationError unless that field declares an explicit
inbound transform. from_json owns a separate schema-aware representation
contract for JSON strings. Talea provides no global coercion policy. See
Input boundaries.
Type families¶
| Annotation | Accepted contract | Important boundary |
|---|---|---|
int, float, str, bool, bytes |
Exact built-in type | bool is not an int; subclasses are rejected |
Enum, IntEnum, StrEnum subclasses |
Exact declared enum class | Raw integer/string values and other enum classes are rejected |
UUID |
UUID instances and subclasses |
Strings are rejected |
date |
Exact date |
datetime and custom date subclasses are rejected |
datetime |
datetime instances and subclasses |
Naive and timezone-aware values are accepted |
time |
time instances and subclasses |
No timezone policy beyond type validation |
timedelta |
timedelta instances and subclasses |
Negative, zero, and large values are accepted |
Decimal |
Decimal instances and subclasses |
Integers, floats, and strings are rejected |
PurePath and Path families |
Nominal Python path relationships | Strings are rejected; concrete availability remains platform-specific |
| IPv4/IPv6 addresses, networks, and interfaces | Exact declared IP class | Versions and address/network/interface families never cross-match |
TypedDict |
Exact dict for strict validation; Mapping at external input |
Required/optional keys and unknown-key rejection follow structural declaration truth |
| stdlib dataclass | Exact declared class with current stored state | Mapping/JSON constructs the original class; init=False is output-only |
PEP 695 type aliases and NewType |
Underlying supported contract | Named identity is retained without runtime alias dispatch |
Annotated[A | B, Discriminator(name)] |
Required single-Literal Spec or TypedDict branches | Direct tag selection; see Tagged unions |
Annotated[T, Representation(...)] |
Strict internal T plus explicitly declared directional schemas |
Trusted callbacks run once and their results are validated; see custom representations |
date is intentionally exact because Python defines datetime as a subclass
of date. A field described as a calendar day should not silently accept a
timestamp. IP contracts are also exact because, for example,
IPv4Interface subclasses IPv4Address even though interfaces and addresses
are distinct data contracts.
Path contracts follow pathlib's nominal hierarchy. PurePath accepts pure
and concrete platform path descendants, while Path accepts concrete path
instances for the running platform. Portable code should normally annotate
PurePath, Path, PurePosixPath, or PureWindowsPath and avoid constructing
an incompatible WindowsPath or PosixPath on the host platform.
Enum values¶
from enum import StrEnum
from talea import Spec
class Status(StrEnum):
ACTIVE = "active"
DISABLED = "disabled"
class Account(Spec):
status: Status
account = Account(status=Status.ACTIVE)
Account(status="active") fails. IntEnum and StrEnum do not inherit the
acceptance rules of their underlying primitive values.
Literal¶
Literal supports strings, bytes, integers, booleans, None, and enum
members. Checks preserve both the value and its runtime type:
from typing import Literal
from talea import Spec
class Feature(Spec):
enabled: Literal[True]
mode: Literal["safe", "fast"]
feature = Feature(enabled=True, mode="safe")
Literal[True] rejects integer 1, even though True == 1 in ordinary Python
equality. Literal alternatives compose inside containers, unions, and optional
fields. Unsupported Literal categories fail when the class is declared.
Composition¶
Every supported type and Literal contract can appear inside Talea's existing containers, tuples, unions, nested Specs, defaults, and inherited fields. A failure retains the complete field and container location.
These rows describe already-Python construction. JSON input and output have schema-specific representations, including exact Decimal strings, ISO duration strings, and base64 bytes. See Input boundaries and Serialization and JSON output.
TypedDict supports total=False, Required, NotRequired, inheritance,
nested declarations, containers, unions, constraints on child fields, and
concrete generic specialization. ReadOnly metadata is retained but has no
runtime mutation semantics. Recursive type aliases and recursive TypedDict
graphs resolve through finite declaration-identity back-edges, including
mutual and concrete generic recursion. See Generics and
recursion.
Type and operation matrix¶
| Contract family | Strict Python | External Python | JSON input | Python output | JSON output | Schema |
|---|---|---|---|---|---|---|
| primitives | exact built-in values | same strict values | native JSON scalar where compatible | same scalar | JSON scalar; finite-number rules apply | scalar type |
| UUID, temporal, Decimal, Path, IP, bytes | declared Python object | same strict object | documented string representation | Python object | documented string representation | type plus format/content encoding where defined |
| Enum and Literal | exact member/value and runtime type | same | canonical JSON member/value | same Python value | canonical JSON value | enum/const |
| list/set/frozenset/dict/tuple | exact concrete container | recursively converts nested mappings/Specs | JSON array/object where representable | detached concrete Python containers | arrays/objects | array/object shapes |
| Spec | instance of the declared nominal type | Mapping constructs a Spec | object constructs a Spec | aliased detached mapping | object | named object definition |
| TypedDict | exact dict with closed keys | Mapping becomes a detached dict | object becomes a detached dict | detached dict | object | named closed object definition |
| stdlib dataclass | exact declared instance | Mapping constructs the original dataclass | object constructs the original dataclass | aliased detached dictionary | object | directional named object definition |
| untagged union | first strict branch that succeeds | branches attempted in canonical order | branches attempted in canonical order | selected runtime branch | selected branch representation | anyOf |
| tagged union | nominal Spec or exact tagged dict | direct discriminator dispatch | direct external-tag dispatch | selected branch | selected branch | oneOf; OpenAPI discriminator |
| recursive named graph | strict acyclic/cycle-aware graph | resource-governed traversal | resource-governed traversal | detached acyclic graph | acyclic JSON | finite definitions and references |
| represented custom type | strict internal type/schema | declared input= then one validated load |
same declared input after JSON decoding | one dump, declared-output validation, detached projection | same output through JSON projection/encoding | declared input/output by mode |
Transforms or serializers can deliberately change a boundary domain. If their
input or output cannot be expressed statically, the corresponding schema mode
raises SchemaProjectionError instead of guessing.
Important JSON representations and edge cases¶
Decimal and exact financial values¶
Strict Python construction accepts a Decimal; it rejects strings, integers,
and floats. JSON input and output use strings so decimal text survives without
binary floating-point loss:
from decimal import Decimal
from talea import Contract
amount = Contract[Decimal](Decimal)
assert amount.validate(Decimal("42.50")) == Decimal("42.50")
assert amount.from_json('"42.50"') == Decimal("42.50")
assert amount.to_json(Decimal("42.50")) == '"42.50"'
JSON Schema therefore describes Decimal as a string. Numeric Decimal constraints remain runtime-only because JSON Schema numeric keywords do not apply to numeric text. Use Decimal for representational exactness, then keep currency conversion, rounding policy, tick sizes, and accounting rules in the domain layer.
Date, datetime, and time¶
date rejects datetime despite Python's subclass relationship. JSON uses
ISO-formatted strings and reconstructs the declared temporal type. Talea
accepts both naive and timezone-aware datetime; it does not impose an
application timezone policy. API contracts that require instants should add a
check for tzinfo and normalize elsewhere deliberately.
Bytes¶
Python paths accept exact bytes. JSON uses padded base64 text; arbitrary plain text is not silently encoded. Schema output carries the content encoding, while length constraints describe base64 blocks conservatively and runtime validation owns exact decoded length. Keep streaming uploads outside a complete in-memory JSON field when their size warrants a streaming protocol.
IP addresses and paths¶
Address, interface, and network types remain distinct, as do IPv4 and IPv6.
JSON uses their standard string form, but external Python mappings still require
the declared Python object. Path annotations follow pathlib's nominal
hierarchy and JSON uses text; Talea does not check file existence, permissions,
or path traversal policy.
Tuples, sets, and dictionaries¶
Fixed tuples validate each declared position; variadic tuples validate every item against one contract. JSON represents both as arrays. Sets and frozensets also use arrays at JSON boundaries and reject duplicate decoded values rather than silently collapsing them. JSON dictionaries require representable string keys; a Python mapping contract with non-string keys cannot claim an ordinary JSON-object schema.
Financial composition example¶
The following application-shaped example uses UUID identifiers, Decimal quantity and price, currency/side enums, a timezone-aware datetime, aliases matching an external protocol, nested instrument and money Specs, constraints, serialization, schema output, and a whole-order currency invariant.
"""A realistic trading boundary without pretending validation is business logic."""
from datetime import UTC, datetime
from decimal import Decimal
from enum import StrEnum
from typing import Annotated, Literal, cast
from uuid import UUID
from talea import Alias, Gt, MaxLength, MinLength, Spec, ValidationError, check
class Currency(StrEnum):
CHF = "CHF"
EUR = "EUR"
USD = "USD"
class Side(StrEnum):
BUY = "buy"
SELL = "sell"
class Instrument(Spec):
isin: Annotated[str, MinLength(12), MaxLength(12)]
symbol: Annotated[str, MinLength(1), MaxLength(16)]
settlement_currency: Annotated[Currency, Alias("settlementCurrency")]
class Money(Spec):
amount: Annotated[Decimal, Gt(Decimal("0"))]
currency: Currency
class Order(Spec):
order_id: Annotated[UUID, Alias("orderId")]
instrument: Instrument
side: Side
quantity: Annotated[Decimal, Gt(Decimal("0"))]
limit_price: Annotated[Money, Alias("limitPrice")]
submitted_at: Annotated[datetime, Alias("submittedAt")]
time_in_force: Annotated[Literal["day", "gtc"], Alias("timeInForce")] = "day"
@check("limit_price", "instrument")
def currencies_match(limit_price: Money, instrument: Instrument) -> None:
if limit_price.currency is not instrument.settlement_currency:
raise ValueError("limit currency differs from settlement currency")
class Trade(Spec):
trade_id: Annotated[UUID, Alias("tradeId")]
order_id: Annotated[UUID, Alias("orderId")]
executed_quantity: Annotated[Decimal, Alias("executedQuantity"), Gt(Decimal("0"))]
executed_price: Annotated[Money, Alias("executedPrice")]
executed_at: Annotated[datetime, Alias("executedAt")]
class Counterparty(Spec):
legal_name: Annotated[str, Alias("legalName")]
lei: str
internal_rating: str
class TradeReport(Spec):
trade: Trade
instrument: Instrument
counterparty: Counterparty
reconciliation_note: str
order = Order.from_json(
"""{
"orderId": "12345678-1234-5678-1234-567812345678",
"instrument": {
"isin": "CH0000000001",
"symbol": "TALEA",
"settlementCurrency": "CHF"
},
"side": "buy",
"quantity": "10.250",
"limitPrice": {"amount": "42.50", "currency": "CHF"},
"submittedAt": "2026-08-26T10:15:00Z",
"timeInForce": "day"
}"""
)
assert order.order_id == UUID("12345678-1234-5678-1234-567812345678")
assert order.quantity == Decimal("10.250")
assert order.limit_price.amount == Decimal("42.50")
assert order.submitted_at == datetime(2026, 8, 26, 10, 15, tzinfo=UTC)
encoded = order.to_json()
assert '"quantity":"10.250"' in encoded
assert '"settlementCurrency":"CHF"' in encoded
try:
Order.from_json(encoded.replace('"currency":"CHF"', '"currency":"EUR"'))
except ValidationError as error:
assert error.errors()[0]["code"] == "spec_check"
else:
raise AssertionError("cross-field currency policy must reject the order")
trade = Trade(
trade_id=UUID("87654321-4321-8765-4321-876543218765"),
order_id=order.order_id,
executed_quantity=Decimal("5.125"),
executed_price=Money(amount=Decimal("42.40"), currency=Currency.CHF),
executed_at=datetime(2026, 8, 26, 10, 16, tzinfo=UTC),
)
assert trade.to_dict()["executedQuantity"] == Decimal("5.125")
assert '"executedQuantity":"5.125"' in trade.to_json()
report = TradeReport(
trade=trade,
instrument=order.instrument,
counterparty=Counterparty(
legal_name="Analytical Engines AG",
lei="529900EXAMPLE000001",
internal_rating="A",
),
reconciliation_note="operator-only",
)
assert report.to_dict(
include={
"trade": {"trade_id": True, "executed_quantity": True, "executed_price": {"amount": True}},
"instrument": {"isin": True, "symbol": True},
"counterparty": {"legal_name": True, "lei": True},
}
) == {
"trade": {
"tradeId": UUID("87654321-4321-8765-4321-876543218765"),
"executedQuantity": Decimal("5.125"),
"executedPrice": {"amount": Decimal("42.40")},
},
"instrument": {"isin": "CH0000000001", "symbol": "TALEA"},
"counterparty": {"legalName": "Analytical Engines AG", "lei": "529900EXAMPLE000001"},
}
order_schema = Order.json_schema()
assert order_schema["$schema"] == "https://json-schema.org/draft/2020-12/schema"
order_definitions = cast(dict[str, object], order_schema["$defs"])
assert "Order" in order_definitions
# Talea establishes representation and structural invariants. Venue calendars,
# tick sizes, market permissions, credit limits, and settlement rules remain
# application/domain responsibilities.
Talea establishes representation, type, constraints, and declared cross-field invariants. Venue calendars, market permissions, credit limits, regulatory classification, settlement behavior, and persistence remain domain concerns. This example makes no compliance claim.