Skip to content

Schema Contracts⚓︎

describe() lets tools inspect a schema without parsing a table.

That is different from validation. Validation asks, "Does this authored table fit the schema?" A schema contract asks, "What does this schema expect?"

A schema with fields, aliases, defaults, and variants
from talika import RowTable, TableFields, discriminator, field


def split_choices(value, context):
    return [choice.strip() for choice in value.split(",")]


class ArticleFields(TableFields):
    body = field("Body", required=True)


class PollFields(TableFields):
    choices = field("Choices", parser=split_choices)


class ContentTable(RowTable):
    unknown_fields = "forbid"
    inapplicable_fields = "preserve"

    content_type = discriminator(
        "Type",
        variants={
            "Article": ArticleFields,
            "Poll": PollFields,
        },
    )
    headline = field("Headline", aliases=("Title",), required=True)
    status = field("Status", default="draft")

This schema can parse tables, but it can also describe itself.

Reading a schema contract
contract = ContentTable.describe()

describe() returns a frozen TableContract. It is safe to cache, compare, serialize, or use as input for documentation and editor tooling.

Talika builds this contract from the same immutable compiled schema plan used by parsing. Description therefore does not re-walk the class hierarchy or reinterpret mutable class attributes. The compiled plan itself is private; TableContract remains the supported introspection API.

Use contracts for tooling

A schema contract is metadata. It does not parse cells, run validators, hit references, or build output objects. It tells tools what the schema has declared.

Inspect Table Identity⚓︎

The contract starts with the table-level shape and policies.

Schema identity and policies
>> (contract.schema_name, contract.orientation)
('ContentTable', 'row')

>> (contract.unknown_fields, contract.inapplicable_fields)
('forbid', 'preserve')

orientation is row or column. The policies show how the schema treats unknown table labels and fields that belong to a different selected variant.

Contracts describe configuration

The contract records configured policies such as unknown_fields and inapplicable_fields. It does not know whether a particular feature table contains unknown or inapplicable fields.

Inspect Fields⚓︎

Each declared field becomes a FieldContract.

Field summary
>> [
...     (field.name, field.label, field.aliases, field.required, field.has_default)
...     for field in contract.fields
... ]
[
    ('content_type', 'Type', (), True, False),
    ('headline', 'Headline', ('Title',), True, False),
    ('status', 'Status', (), False, True),
]

Field contracts include the Python attribute name, authored table label, aliases, required flag, ID/discriminator flags, default information, parser name, reference target, and empty-cell policy.

One field as a dictionary
>> contract.as_dict()["fields"][1]
{
    'name': 'headline',
    'label': 'Headline',
    'aliases': ('Title',),
    'required': True,
    'is_id': False,
    'is_discriminator': False,
    'has_default': False,
    'default_repr': None,
    'default_factory': None,
    'parser': None,
    'reference_target': None,
    'reference_many': False,
    'empty': 'raw',
}

Use as_dict() when a tool wants ordinary containers instead of dataclass objects.

Callable names are display names

Parser, default factory, transformer, output model, and output builder names are meant for display and diagnostics. Do not treat them as import paths.

Inspect Variants⚓︎

Discriminator variants appear in the same contract.

Variant contracts
>> [(variant.value, variant.schema_name) for variant in contract.variants]
[('Article', 'ContentTable[Article]'), ('Poll', 'ContentTable[Poll]')]

>> [
...     (variant.value, [field.name for field in variant.fields])
...     for variant in contract.variants
... ]
[
    ('Article', ['body', 'content_type', 'headline', 'status']),
    ('Poll', ['choices', 'content_type', 'headline', 'status']),
]

Each item in contract.variants is a VariantContract. It includes the discriminator value, generated schema name, active fields, output model name, and output builder name.

A variant-specific field
>> contract.as_dict()["variants"][1]["fields"][0]
{
    'name': 'choices',
    'label': 'Choices',
    'aliases': (),
    'required': False,
    'is_id': False,
    'is_discriminator': False,
    'has_default': False,
    'default_repr': None,
    'default_factory': None,
    'parser': 'split_choices',
    'reference_target': None,
    'reference_many': False,
    'empty': 'raw',
}

This is useful for generated docs that need to show which fields apply to Article, which fields apply to Poll, and which fields are shared.

A good contract table

A generated schema page can list required labels, aliases, defaults, parser names, and variant-only fields without needing an example feature file.

Inspect Configured Hooks⚓︎

Contracts also name configured hooks such as table transformers and output models.

A schema with transformer and output hooks
from dataclasses import dataclass

from talika import ColumnGroupExpander, ColumnTable, NumericRange, PrefixRepeat
from talika import field, id_field


@dataclass
class ContentItem:
    id: str
    headline: str


class GroupedContentTable(ColumnTable):
    table_transformer = ColumnGroupExpander(
        "IDs",
        NumericRange(".."),
        PrefixRepeat(":"),
    )
    output_model = ContentItem

    id = id_field("IDs")
    headline = field("Headline")


hook_contract = GroupedContentTable.describe()
Hook metadata
>> (
...     hook_contract.transformer,
...     hook_contract.output_model,
...     hook_contract.output_builder,
... )
('ColumnGroupExpander', 'ContentItem', 'BaseTable.build_output')

This is enough for a tool to say, "This schema uses a grouped-column transformer and builds ContentItem objects", without importing private implementation details.

Use CLI Describe⚓︎

The CLI exposes the same contract for terminal workflows. This is useful when you want to inspect a schema outside Python, generate documentation in a build step, or compare schema metadata in CI.

The command imports the schema target and renders describe() output. Like talika check, the schema target should be an importable module path such as app.schemas:ContentTable.

Describe a schema
$ talika describe app.schemas:ContentTable
Human-readable contract
Schema: ContentTable
Orientation: row
Policies: unknown_fields=forbid, inapplicable_fields=preserve
Fields:
  - content_type: label='Type' (required, discriminator)
  - headline: label='Headline', aliases=['Title'] (required)
  - status: label='Status' (default)
Variants:
  - 'Article': ContentTable[Article]
    fields: body, content_type, headline, status
  - 'Poll': ContentTable[Poll]
    fields: choices, content_type, headline, status

Use JSON output when another tool should consume the contract:

Describe a schema as JSON
$ talika describe app.schemas:ContentTable --format json

The JSON shape is the same information as ContentTable.describe().as_dict().

Use text for humans and JSON for tools

Text output is meant for quick inspection. JSON output is better for schema drift checks, generated pages, and editor integrations.

Use Contracts Carefully⚓︎

Schema contracts are useful for:

  • generated reference pages
  • editor completions for table labels
  • schema drift checks in CI
  • comparing required labels across versions
  • documenting variants and aliases for feature authors

They are not a replacement for parsing real tables. A contract can tell you that Headline is required. Static checking or normal parsing tells you whether a specific feature table actually provided it.