Missing, Empty, Defaults⚓︎
Talika treats three cases differently:
- a field is missing because its label is not in the table
- a field is present, but one cell is explicitly empty
- a field is absent and the schema supplies a default
Those cases look similar when a test fails, but they mean different things to the person editing the feature file. A missing label is a table-shape choice. A blank cell is authored data. A default is schema-owned data.
First ask whether the label exists
Before thinking about defaults or empty strings, ask whether the field label appears in the table at all. Talika's behavior starts from that boundary.
The Field Parsing Lifecycle⚓︎
Whenever Talika processes a field in a Gherkin data table, it evaluates the presence of the field label, default configurations, empty policies, parsers, and validators in a strict, predictable sequence:
- Label Presence Check: Talika checks if the field's canonical label or any of its declared aliases appear in the table header for row tables, or in the field-label column for column tables.
- If absent: Talika looks for a
default_factory, then adefault. If neither is declared, the field becomesNone. - If present: Talika proceeds to the cell evaluation step.
- If absent: Talika looks for a
- Empty Value Check: If the field is present, Talika inspects the cell text.
- If non-empty: Talika runs the field parser when one is declared. If there is no parser, the text is returned as-is.
- If empty: Talika evaluates the field's
emptypolicy (raw,none,parse, orerror). Ifempty="parse", it passes the blank string to the parser. Otherwise, it sets the value based on the policy or raises aTableErrorifempty="error"or the field is required.
- Record-Level Validation: Once field values have been parsed, defaulted, or preserved as text, Talika runs
validate_record(self, context)on each record. - Table-Level Validation: Once all individual records have successfully passed record validation, Talika runs
validate_records(cls, records, context)to validate relationships across the entire collection.
Missing Optional Fields⚓︎
A field is missing when none of its accepted labels appear in the table. In a row table, that means the header is absent. In a column table, that means the field row is absent.
from talika import RowTable, field
def default_team(context):
return context.user_data["team"]
class UserDefaults(RowTable):
username = field("username", required=True)
role = field("role", default="viewer")
team = field("team", default_factory=default_team)
notes = field("notes")
This schema has four fields:
usernameis required.roleis optional and has a static default.teamis optional and has a context-aware default factory.notesis optional and has no default.
When the table only provides username, Talika fills the missing optional
fields:
user = UserDefaults.parse(
[
["username"],
["alice"],
],
context={"team": "platform"},
)[0]
assert user.username == "alice"
assert user.role == "viewer"
assert user.team == "platform"
assert user.notes is None
>> user
UserDefaults(username='alice', role='viewer', team='platform', notes=None)
>> user.as_dict()
{'username': 'alice', 'role': 'viewer', 'team': 'platform', 'notes': None}
The fallback order is direct:
- if
default_factoryis declared, call it - otherwise, if
defaultis declared, use it - otherwise, return
None
Defaults are final values. A field parser only converts authored cells; it
does not receive a static default or the result of a default factory. Static
defaults must be hashable and must not be mutable containers. Use
default_factory whenever each record needs a fresh list, dictionary, set, or
other mutable value.
Defaults run only for absent fields
Defaults do not run because a cell is blank. They run because the field is not present in the table.
Empty Cells Are Present Values⚓︎
Now compare the same schema with a table that includes every label but leaves some cells empty.
user = UserDefaults.parse(
[
["username", "role", "team", "notes"],
["alice", "", "", ""],
],
context={"team": "platform"},
)[0]
assert user.role == ""
assert user.team == ""
assert user.notes == ""
The role default is not used. The team factory is not called. The notes
field does not become None. Each field exists in the authored table, so the
blank cell is treated as an explicit value.
This distinction is the main thing to remember. A blank cell can be deliberate: it may mean "clear this value", "leave this optional text empty", or "this field is intentionally blank for this record." Talika does not silently replace that author choice with a default.
Do not use defaults as blank-cell cleanup
If a blank cell should become None, be parsed, or be rejected, configure
the field's empty-cell policy. A default is not a cleanup rule for visible
blank cells.
Missing and Empty Required Fields⚓︎
Required fields add two checks:
- the field label must be present
- the value must not be empty by default
When the required label is absent, Talika reports a missing required field:
Required field is missing from the table
(code=missing_required, schema=UserDefaults, field='username').
Hint: Add this field to the table, or make the schema field optional if the project should supply it.
When the label is present but the cell is blank, Talika reports the source cell:
Required field has an empty value
(code=empty_required, schema=UserDefaults, field='username',
row=2, column=1, value='').
Hint: Fill the cell, or remove required=True if an explicit empty value should be valid.
These diagnostics intentionally point to different fixes. For a missing label, the author adds a column or row. For an empty cell, the author fills the cell or the schema changes what blank means.
Use required for scenario meaning
Make a field required when the scenario would be unclear without it. Do not
use required=True just to get a Python type. Use parsers and validation
for conversion and business rules.
Default Factories⚓︎
Use default_factory when the fallback value depends on the parse operation.
The factory receives a DefaultContext, not a cell context, because there is
no source cell for a missing field.
from talika import id_field
def default_audit(context):
return f"audit-{context.item_id}-{context.user_data['suffix']}"
class RowWithId(RowTable):
user_id = id_field("user id")
audit = field("audit", default_factory=default_audit)
record = RowWithId.parse(
[
["user id"],
["U-7"],
],
context={"suffix": "qa"},
)[0]
assert record.audit == "audit-U-7-qa"
assert record.table_source.item_id == "U-7"
The factory can read:
context.schemacontext.field_namecontext.field_labelcontext.item_idcontext.user_data
There is no row, column, or source_value, because the field was absent.
Default factories are not parsers
A parser converts a present cell. A default factory creates a value when the field is missing. Keep those responsibilities separate.
Defaults in Column Tables⚓︎
In column tables, a missing optional row applies to each item column. If the field has a default factory, the factory runs once per item and receives that item's ID.
Given the content exists
| IDs | A-1 | P-1 |
| Status | draft | |
from talika import ColumnTable
class ContentDefaults(ColumnTable):
id = id_field("IDs")
headline = field("Headline", default_factory=default_audit)
status = field("Status")
The Headline row is absent, so headline is generated for both A-1 and
P-1. The Status row is present. A-1 has draft, and P-1 has an
explicit empty cell.
items = ContentDefaults.parse(
[
["IDs", "A-1", "P-1"],
["Status", "draft", ""],
],
context={"suffix": "qa"},
)
assert items[0].headline == "audit-A-1-qa"
assert items[0].status == "draft"
assert items[1].headline == "audit-P-1-qa"
assert items[1].status == ""
>> [item.as_dict() for item in items]
[
{'id': 'A-1', 'headline': 'audit-A-1-qa', 'status': 'draft'},
{'id': 'P-1', 'headline': 'audit-P-1-qa', 'status': ''},
]
This is the same rule as row tables, just turned sideways. Missing row means the field is absent. Empty item cell means the field is present for that item with a blank value.
A missing row and a blank item cell are not interchangeable
Removing the Status row would make every item's status None by default.
Keeping the row and leaving one item blank makes only that item hold an
explicit empty value.
Empty-Cell Policies⚓︎
Optional fields can choose what an explicit blank cell means.
from talika import integer
def parse_blank(value, context):
return "<blank>" if value == "" else value
class EmptyPolicies(RowTable):
raw_value = field("raw value", parser=integer(), empty="raw")
parsed_value = field("parsed value", parser=parse_blank, empty="parse")
none_value = field("none value", empty="none")
strict_value = field("strict value", empty="error")
The policies are:
empty="raw"preserves""empty="parse"sends""to the parserempty="none"returnsNoneempty="error"rejects""
Declaring empty="parse" without an explicit or inferred parser is invalid and
fails when the schema is created.
record = EmptyPolicies.parse(
[
["raw value", "parsed value", "none value"],
["", "", ""],
]
)[0]
assert record.raw_value == ""
assert record.parsed_value == "<blank>"
assert record.none_value is None
assert record.strict_value is None
>> record.as_dict()
{'raw_value': '', 'parsed_value': '<blank>', 'none_value': None, 'strict_value': None}
In this example, strict_value is absent from the table, so it is None. When
the strict value label is present and its cell is blank, Talika rejects it:
Optional field has an empty value
(code=empty_optional, schema=EmptyPolicies, field='strict value',
row=2, column=1, value='').
Hint: Fill the cell, omit the field, or choose a different empty-cell policy for this schema field.
Choosing a policy
Use raw when blank text is acceptable, none when blank means no value,
parse when your parser owns blank syntax, and error when the field may
be omitted but must not be written blank.
Required Fields Always Reject Blanks⚓︎
required=True is authoritative. A required field rejects an explicit empty
cell before any parser is called. Required fields cannot use empty="raw",
empty="none", or empty="parse"; empty="error" is implied.
class RequiredValue(RowTable):
value = field(required=True, parser=parse_blank)
RequiredValue.parse(
[
["value"],
[""],
]
)
Required field has an empty value
(code=empty_required, schema=RequiredValue, field='value', row=2, column=1, value='')
If blank text has domain meaning, make the field optional and choose
empty="parse" explicitly. Do not weaken a required field through a permissive
parser.
Default Errors and Invalid Declarations⚓︎
If a default factory fails, Talika reports a default-factory diagnostic. The error is tied to the field, but there is no row or column because the field was missing.
An ordinary factory exception becomes default_factory_failed and remains
available as the diagnostic cause. A deliberate TableError, TableErrors,
or SchemaDefinitionError passes through unchanged, preserving a project code
or hint.
def broken_default(context):
raise RuntimeError("team service unavailable")
class BrokenDefault(RowTable):
username = field("username")
team = field("team", default_factory=broken_default)
BrokenDefault.parse(
[
["username"],
["alice"],
]
)
Default factory failed: team service unavailable
(code=default_factory_failed, schema=BrokenDefault, field='team')
Some field declarations are invalid before parsing starts. A field cannot declare both a static default and a default factory. A required field cannot declare a default, because that would make missing required data appear valid.
field("value", default="x", default_factory=lambda context: "y")
field("value", required=True, default="x")
ValueError: field cannot declare both default and default_factory
ValueError: required fields cannot declare defaults
Keep defaults boring
Defaults are easiest to maintain when they are predictable. Use static defaults for stable values, factories for context-aware values, and validation for rules that need to inspect the completed record.