Evolving Tables Safely⚓︎
Feature tables change. A team renames a label. A new optional field appears. A parser becomes stricter. Old scenarios still matter, but new scenarios should read better.
Table evolution is healthiest when compatibility is intentional.
Renaming language⚓︎
An old table might use Full name:
A newer table might prefer name and add active:
Both can be supported while the team migrates:
from talika import RowTable, boolean, field
class UserTable(RowTable):
name = field("name", aliases=("Full name",), required=True)
age: int = field("age", required=True)
active = field("active", parser=boolean(), default=True)
The important part is that the old wording is visible. It is accepted because the project chose to accept it, not because the parser ignored differences.
See field aliases for the concrete migration mechanism.
Compatibility should have a reason
Aliases are useful for migration, shared vocabulary, and external wording. They should not become a junk drawer for every spelling that ever appeared.
Adding fields⚓︎
Adding a required field breaks old tables immediately. Sometimes that is the right choice. Other times the field should start as optional with a default, so old scenarios remain valid while new scenarios can be more explicit.
Choose between those paths by asking whether an old table still describes a valid scenario without the new field. If it does not, make the field required and update the affected tables together. If omission still has a clear meaning, introduce the field as optional and document the fallback in the schema.
Defaults can hide change
A default is convenient, but it also makes omitted data look intentional. Use it when omission is genuinely acceptable, not just to avoid updating tables.
Preserve variant data during migration⚓︎
Variant tables sometimes contain a value that belonged to an older record shape. During a controlled migration, a project can preserve that known but inapplicable value instead of treating it as an active field:
from talika import ColumnTable, TableFields, discriminator, field, id_field
class ArticleFields(TableFields):
body = field("Body")
class PollFields(TableFields):
options = field("Options")
class LegacyContentTable(ColumnTable):
inapplicable_fields = "preserve"
id = id_field("IDs")
content_type = discriminator(
"Type",
variants={"Article": ArticleFields, "Poll": PollFields},
)
Preserved values live in record.table_extras; they do not appear in
record.as_dict() or become attributes on the selected variant. This policy
only applies to labels declared elsewhere in the variant family. Completely
unknown labels still follow the schema's unknown_fields policy.
Use preservation as a short-lived compatibility strategy while authors clean up old tables. The inapplicable-fields guide shows how to inspect and eventually remove the preserved values.
CI makes evolution safer⚓︎
When table rules are explicit, a checker can validate feature files before the scenario runs. That gives teams earlier feedback when a label changed, a parser became stricter, or a compatibility alias was removed.
The static-checking guide shows how to check feature tables from the CLI.
Evolve in public
Feature tables are shared language. When you change that language, make the transition visible in the contract and easy to detect in CI.