Skip to content

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.