Column Tables⚓︎
Use ColumnTable when the first column contains labels and each later column
describes one record.
This shape is useful when each item has several fields, when a record reads more naturally as a vertical card, or when row tables become too wide to scan. CMS content examples often fit this shape: an article, poll, or page section may have many properties, and each item is easier to read from top to bottom.
Given the content exists
| IDs | A-1 | P-1 |
| Type | Article | Poll |
| Headline | Market brief | Reader question |
| Status | draft | published |
In this table, A-1 and P-1 are the records. Type, Headline, and
Status are fields on each record.
When to choose a column table
Choose a column table when each item has enough fields that a row table would become wide or full of blanks. The first column becomes the list of labels, and each item gets its own vertical column.
Define the Column Contract⚓︎
A column table must declare exactly one id_field(...). That field identifies
each item column and must be the first row in the table.
from talika import ColumnTable, field, id_field
class ContentTable(ColumnTable):
id = id_field("IDs")
content_type = field("Type", required=True)
headline = field("Headline", required=True)
status = field("Status")
The contract says:
idmaps the ID rowIDsto the Python attributeid. Every item column must have a unique ID in that row.content_typemaps the required table rowTypeto the attributecontent_type.headlinemaps the required table rowHeadlineto the attributeheadline.statusmaps the optional table rowStatusto the attributestatus, which returnsNonewhen the whole row is absent.
ColumnTable requires an ID row
A row table may choose whether to declare an ID field. A column table must declare one, because the parser needs a stable identity for each item column.
Exactly-one cardinality is checked when the schema class is created. Put
incomplete reusable declarations on TableFields, then combine that
component with a concrete ColumnTable that declares its single ID.
Parse Item Columns⚓︎
The datatable shape mirrors the feature table. The first cell of each row is a label. Each later cell belongs to the item column above it.
datatable = [
["IDs", "A-1", "P-1"],
["Type", "Article", "Poll"],
["Headline", "Market brief", "Reader question"],
["Status", "draft", "published"],
]
Call parse() with the datatable:
items = ContentTable.parse(datatable)
assert [item.id for item in items] == ["A-1", "P-1"]
assert items[0].content_type == "Article"
assert items[0].headline == "Market brief"
assert items[1].content_type == "Poll"
assert items[1].status == "published"
The parsed result is ordered by item column. The first output record belongs to
A-1, and the second belongs to P-1.
>> items[0]
ContentTable(id='A-1', content_type='Article', headline='Market brief', status='draft')
>> items[0].as_dict()
{'id': 'A-1', 'content_type': 'Article', 'headline': 'Market brief', 'status': 'draft'}
>> items[0].table_source.item_id
'A-1'
The item ID is both a normal parsed field (item.id) and source metadata
(item.table_source.item_id). The normal field is useful in test setup. The
source metadata is useful when validation or diagnostics need to identify the
authored item.
Item Lookup and Column Metadata⚓︎
Column tables still return a sequence of parsed records, so index-based access works. The difference is what the index means. In a row table, record position usually points to a source row. In a column table, record position points to an item column.
Most tests become clearer if they do not rely on positional access for long. Parse the table, then build a small lookup by item ID for assertions and setup code.
table_source.columnstores the one-based source column for the parsed item.table_source.rowis not populated for the record itself because the item runs vertically through several rows.source_for("field_name")still points to the exact row and column for one field value.
# Create a fast lookup dict mapping ID to the parsed item
items_by_id = {item.id: item for item in items}
assert items_by_id["A-1"].content_type == "Article"
# Access column metadata
# Unlike row tables (which store .row), column records store .column (1-based index)
col_number = items[0].table_source.column
assert col_number == 2
Use IDs after parsing
Column tables are often authored by ID. Building by_id = {item.id: item}
makes later assertions read like the feature file instead of like a column
index calculation.
Missing Optional Rows⚓︎
When an optional field row is absent, Talika gives that field None unless the
field declares a default.
items = ContentTable.parse(
[
["IDs", "A-1", "P-1"],
["Type", "Article", "Poll"],
["Headline", "Market brief", "Reader question"],
]
)
assert [item.status for item in items] == [None, None]
This is different from an explicit empty cell. In the next table, the Status
row exists, and A-1 intentionally has a blank status cell:
items = ContentTable.parse(
[
["IDs", "A-1", "P-1"],
["Type", "Article", "Poll"],
["Headline", "Market brief", "Reader question"],
["Status", "", "published"],
]
)
assert [item.status for item in items] == ["", "published"]
Absent row and empty cell are different
An absent optional row means the field was not provided for any item. An empty cell means the field was provided, but one item has a blank value. Those two cases are often handled differently in real feature files.
Missing and Empty Required Values⚓︎
A required field row must be present, and every item column must have a non-empty value for that field.
If the table omits a required row, the error includes the item ID because the missing value affects a specific item column:
Required field is missing from the table
(code=missing_required, schema=ContentTable, field='Headline',
item_id='A-1').
Hint: Add this field to the table, or make the schema field optional if the project should supply it.
If the row exists but an item leaves the required cell empty, the error points to the exact row and column:
ContentTable.parse(
[
["IDs", "A-1", "P-1"],
["Type", "Article", "Poll"],
["Headline", "Market brief", ""],
]
)
Required field has an empty value
(code=empty_required, schema=ContentTable, field='Headline',
row=3, column=3, item_id='P-1', value='').
Hint: Fill the cell, or remove required=True if an explicit empty value should be valid.
This is one of the main benefits of preserving table source information. The author can go directly to the blank cell instead of searching through parsing code.
The First Row Must Be the ID Row⚓︎
For column tables, the first row controls item identity. The first cell of that
row must match the declared id_field(...) label or one of its aliases.
ContentTable.parse(
[
["Type", "Article"],
["IDs", "A-1"],
["Headline", "Market brief"],
]
)
The first row must be the declared id field
(code=table_error, schema=ContentTable, field='IDs',
row=1, column=1, value='Type').
Hint: Move the declared id_field label into the first cell.
Talika rejects this table before parsing records because it cannot safely know which values are item IDs.
The ID row is structural
The ID row is not just another optional field. It defines the item columns. Move it carefully when editing column-oriented feature files.
Duplicate Item IDs⚓︎
Every item column needs a unique ID. Duplicate IDs make references, defaults, diagnostics, and later validation ambiguous.
Uniqueness is checked after ID parsing, and IDs must be hashable. Consequently
1 and 01 collide when an integer parser converts both to 1; a parser that
returns a list fails with invalid_id at the authored ID cell.
ContentTable.parse(
[
["IDs", "A-1", "A-1"],
["Type", "Article", "Poll"],
["Headline", "Market brief", "Duplicate poll"],
]
)
Duplicate item ID
(code=duplicate_id, schema=ContentTable, field='IDs',
row=1, column=3, item_id='A-1', value='A-1').
Hint: Use one unique item ID per parsed column.
The diagnostic points to the second occurrence. That is the cell the author needs to change.
Rectangular Tables⚓︎
Every row in a column table must have the same number of cells as the ID row. If the ID row defines two item columns, every other row must also provide two item cells.
ContentTable.parse(
[
["IDs", "A-1", "P-1"],
["Type", "Article"],
["Headline", "Market brief", "Reader question"],
]
)
Ragged row: expected 3 cells, got 2
(code=ragged_row, schema=ContentTable, row=2).
Hint: Make every table row contain the same number of cells as the ID row.
Talika reports this as a table-shape error. Until the row is rectangular, the parser cannot reliably know which item a missing cell belongs to.
Keep column tables readable
Column tables are best when each item is worth reading vertically. If the table only has two or three simple fields, a row table may be shorter and easier to compare.