Skip to content

Recipes

This page routes application tasks to the page that owns their full executable example. The examples reuse account/identity, payment events, trading, and recursive document domains so each guide can build on familiar contracts.

Accept an API request body

Use Spec.from_json() at the raw-body seam, select a finite ResourcePolicy, and catch resource exhaustion separately from contract invalidity:

try:
    request = UserCreate.from_json(body, policy=request_policy)
except ResourceLimitError as error:
    return resource_failure(error)
except ValidationError as error:
    return invalid_request(error.errors())

When one source Spec carries read/write metadata, derive the request and response shapes once with derive_spec(..., mode="input") and derive_spec(..., mode="output"); normal source behavior remains unchanged. Do application work only after conversion succeeds, then construct the explicit response Spec and call to_json(). The production service boundary owns a complete asserted account flow with nested address/credentials, aliases, constraints, redaction, invalid input, oversized transport, output, and OpenAPI fragments.

Apply a REST PATCH

Derive the update contract once from the writable/input direction:

UserPatch = derive_spec(User, mode="input", partial=True)
patch = UserPatch.from_json(body)
updated = apply_patch(existing, patch)

Do not use None to mean absent and do not merge dictionaries. The PATCH and presence guide owns the executable empty/nullable/default-equal/aliased/sensitive/failure/schema flow.

Dispatch an event protocol

Use a tagged union when every message has one stable required literal protocol field. Discriminator("type") selects the branch directly at Mapping/JSON input and supplies OpenAPI's discriminator map.

The tagged event guide owns four real event branches, generic envelopes, UUID/datetime/Decimal representations, Sensitive data, invalid and nested failures, JSON output, JSON Schema, and OpenAPI. Keep untagged unions when no genuine protocol tag exists.

Shape a nested API or event response

Use the existing output methods with canonical-name mappings when an endpoint needs a finite subset of nested data:

body = account.to_json(
    include={
        "account_id": True,
        "profile": {
            "display_name": True,
            "address": {"city": True},
            "permissions": {"code": True},
        },
    }
)

The production service example executes account/profile/address/permission shaping. The event example projects an envelope, actor, and tagged payload while retaining its discriminator. The finance example projects a trade, instrument, and counterparty. Selectors use canonical names even when these examples emit aliases. They are output policy, not authorization policy.

Validate a TypedDict or arbitrary root

Retain a Contract instead of inventing a wrapper Spec:

payloads: Contract[list[PartnerPayload]] = Contract(list[PartnerPayload])
values = payloads.from_json(body)

The Contract boundary set covers primitive, UUID-list, Decimal-mapping, TypedDict, recursive alias, and generic specialization roots across validation, input, output, schemas, policy, and errors.

Protect a hostile external boundary

Choose limits from the actual endpoint shape and separate ResourceLimitError from a possibly truncated ValidationError. Mark secret fields Sensitive, but use explicit output contracts to prevent valid secrets from being serialized.

The security scenarios execute oversized JSON, excessive depth, node exhaustion, broad invalid input, redaction, and a hostile Mapping callback. The page also says what Talea cannot sandbox.

Model financial values without floats

Use UUID for identifiers, Decimal for exact quantities/prices, enums or Literals for protocol vocabulary, timezone-aware datetime when the domain requires instants, and aliases for external protocol names. Keep venue, permission, credit, accounting, settlement, and compliance rules in the domain layer.

The supported-type finance example owns the full Order/Instrument/Money/Trade flow with constraints, a cross-field currency invariant, failure handling, serialization, and schema projection.

When an existing Money class must keep its own identity while accepting or emitting a different boundary shape, annotate it with a reusable Representation. The custom representation guide owns an executable Money/Currency payment example plus ULID-like identifiers, one-way contracts, nested selection, schemas, and Sensitive failures.

Use @serialize("field", output=Summary) when one response field needs a stable summary that should not redefine the represented domain type everywhere. The serialization guide includes an executable account-summary recipe, nested selection, result enforcement, and the contrast with an opaque legacy serializer.

Represent a recursive document or AST

Choose the simplest recursive owner:

  • a recursive Spec for one nominal node shape;
  • a recursive PEP 695 alias for structural containers;
  • a recursive TypedDict for dictionary identity;
  • a tagged recursive alias for a protocol/grammar with node kinds.

The recursive AST combines TypedDict branches, direct tags, nested error paths, schemas, OpenAPI, and the important distinction between a recursive type graph and a cyclic runtime value.

Publish JSON Schema or OpenAPI

Use json_schema(mode="input" | "output") for Draft 2020-12. Use openapi_schema(...) when a framework needs a root Schema Object plus reachable components. Talea does not generate paths or operations.

The framework projection example combines nested objects, aliases, metadata, constraints, read/write annotations, Sensitive handling, a partial PATCH schema, a tagged event, and a discriminator map.

Build trusted declarations at runtime

Use create_spec() only when runtime configuration genuinely owns the fields. Pass evaluated annotations and keep callbacks in a trusted namespace. Prefer class syntax when fields are known in source so constructor typing stays precise.

The dynamic lifecycle executes creation, defaults, metadata, constraints, hooks, Mapping/JSON input, introspection, immutable replacement, failures, and schema projection.

Inspect a contract for framework tooling

Use inspect_spec() or inspect_contract() and consume frozen FieldInfo, SpecInfo, DerivationInfo, and ContractInfo. Do not reread annotations or reach into generated callables to reconstruct semantics.

The framework adapter example projects aliases, requiredness, omittability, constraints, metadata, Sensitive state, partial provenance, and arbitrary Contract truth into a smaller framework-owned descriptor.

Advanced composition lab

This compact executable lab intentionally places generics, a recursive alias, a recursive tagged Spec graph, metadata, aliases, Sensitive/read/write fields, ResourcePolicy, Mapping/JSON input, PATCH derivation/application, Contract, and OpenAPI in one declaration graph:

"""Generic, recursive, tagged, metadata, schema, and policy composition."""

from typing import Annotated, Literal, cast

from talea import (
    Alias,
    Contract,
    Deprecated,
    Description,
    Discriminator,
    Examples,
    ReadOnly,
    ResourcePolicy,
    Sensitive,
    Spec,
    Title,
    WriteOnly,
    apply_patch,
    derive_spec,
)


class Envelope[T](Spec):
    payload: T


type Tree[T] = T | list[Tree[T]]


class ValueNode(Spec):
    kind: Literal["value"]
    value: int


class GroupNode(Spec):
    kind: Literal["group"]
    children: list[Node]


type Node = Annotated[ValueNode | GroupNode, Discriminator("kind")]


class Document(Spec, metadata=(Title("Document"), Description("A recursive document."))):
    document_id: Annotated[int, Alias("documentId"), ReadOnly(), Examples(7)]
    tree: Tree[int]
    root: Node
    credential: Annotated[str, Sensitive(), WriteOnly()]
    legacy_label: Annotated[str | None, Deprecated()] = None


document = Document.from_mapping(
    {
        "documentId": 7,
        "tree": [1, [2]],
        "root": {"kind": "group", "children": [{"kind": "value", "value": 3}]},
        "credential": "token",
    },
    policy=ResourcePolicy(max_depth=16, max_nodes=1_000),
)
assert isinstance(document.root, GroupNode)
assert Envelope[Document](payload=document).payload is document
assert Contract[Tree[int]](Tree[int]).validate([1, [2]]) == [1, [2]]

DocumentPatch = derive_spec(Document, partial=True)
patched = apply_patch(document, DocumentPatch.from_json('{"tree":[9]}'))
assert patched.tree == [9]

document_schema = cast(dict[str, object], Document.openapi_schema()["schema"])
assert document_schema["title"] == "Document"

Use the focused pages above to learn each decision before adopting this density in application code. Composition is valuable when the domain requires it; putting every capability in every contract only increases cognitive and declaration cost.

Operation lookup

Goal API Canonical reference
Serialize an API response to_dict(), to_json() Serialization
Decode a custom JSON codec from_json(..., loads=codec) Input
Encode a custom JSON codec to_json(dumps=codec) Serialization
Constrain a value Annotated[T, Ge(...), MinLength(...)] Constraints
Handle machine failures ValidationError.errors() Errors
Perform trusted immutable change copy.replace() Immutable updates
Check exact signatures and failures root/domain APIs Public API
Diagnose common mistakes broken and corrected code Troubleshooting