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?"
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.
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.
>> (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.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.
>> 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.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.
>> 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.
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_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.
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:
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.