Typed, validated schemas for BDD data tables
Project description
talika
talika adds small, dataclass-style schemas to BDD data tables. It parses the
raw list-of-lists supplied by tools such as pytest-bdd, validates the table
shape, converts cells with project-defined parsers, and returns typed schema
records.
It does not prescribe a table DSL or perform business actions. Projects define
their own readable table vocabulary while talika handles the repeatable
parts: shape validation, conversion, source-aware diagnostics, and optional
static checks for .feature files.
Links
- Documentation: https://talikadev.github.io/talika/
- Source: https://github.com/talikadev/talika
- Issues: https://github.com/talikadev/talika/issues
- PyPI: https://pypi.org/project/talika/
Installation
talika supports Python 3.10 and newer. The core package has no runtime
dependencies:
pip install talika
Install optional extras only for integrations you use:
pip install "talika[cli]" # static Gherkin feature-file checks
pip install "talika[pydantic]" # Pydantic output models
pip install "talika[test]" # test/integration dependencies
The command-line tool is available as both talika and python -m talika.
Quick Start
from talika import RowTable, field
def parse_bool(value, context):
return value.lower() == "true"
class UserTable(RowTable):
name = field("name", required=True)
role = field("role", required=True)
active = field("active", parser=parse_bool, default=True)
users = UserTable.parse(
[
["name", "role", "active"],
["Alice", "admin", "true"],
]
)
assert users[0].name == "Alice"
assert users[0].active is True
Table Shapes
Row-oriented tables use the first row as labels and every following row as one record:
from talika import RowTable, field
class ProductTable(RowTable):
sku = field("sku", required=True)
name = field("name", required=True)
Column-oriented tables use the first column as labels and every following column as one record:
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)
category = field("Category")
items = ContentTable.parse(
[
["IDs", "1", "2"],
["Type*", "Article", "Poll"],
["Headline*", "Hello", "QA Poll"],
]
)
assert items[0].id == "1"
assert items[1].content_type == "Poll"
assert items[0].category is None
Use parse_records() when you specifically want schema instances for static
typing or when a schema has an output_model but a test needs the intermediate
validated record:
records: list[ContentTable] = ContentTable.parse_records(datatable)
Functional helpers are also available:
from talika import parse_table, parse_table_records
items = parse_table(ContentTable, datatable)
records = parse_table_records(ContentTable, datatable)
Conversion
Common field conversion does not require custom functions:
from talika import RowTable, boolean, compose, decimal, each, field, split, string
class ProductTable(RowTable):
price = field("price", parser=decimal())
active = field("active", parser=boolean())
tags = field("tags", parser=compose(split(","), each(string(strip=True))))
The package provides string, integer, floating, decimal, boolean,
choice, split, map_value, optional, compose, and each.
Supported annotations infer a parser when the field has no explicit parser:
class UserTable(RowTable):
name: str = field("name")
age: int | None = field("age")
active: bool = field("active")
Inference supports str, int, float, bool, Decimal, enums, string
Literal values, and simple optionals. Collection annotations such as
list[str] do not imply a cell syntax; use an explicit parser such as
split(",") when one cell should become several values.
Defaults, Aliases, And Policies
Missing optional fields can use static defaults or context-aware factories:
headline = field(
"Headline",
default_factory=lambda context: (
context.user_data["generator"].headline(context.item_id)
),
)
Aliases support intentional wording changes:
headline = field("Headline", aliases=("Title", "Name"))
Unknown fields currently accept only the default unknown_fields = "forbid"
policy. Discriminated schemas also support inapplicable_fields = "forbid"
and inapplicable_fields = "preserve" for variant-specific values.
Variants
Variants let one BDD table contain several related record shapes. The base schema declares shared fields and a discriminator used to select the applicable fields and behavior.
from talika import ColumnTable, TableFields, discriminator, field, id_field, split
class ArticleFields(TableFields):
body = field("Body*", required=True)
class PollFields(TableFields):
options: list[str] = field("Options*", required=True, parser=split(","))
class ContentTable(ColumnTable):
id = id_field("IDs")
content_type = discriminator(
"Type*",
variants={
"Article": ArticleFields,
"Poll": PollFields,
},
)
headline = field("Headline*", required=True)
The explicit decorator form is also available with discriminator_field() and
@ContentTable.variant(...).
Custom Cell Syntax
CellDSL groups exact tokens and full-match regular-expression rules into a
reusable field parser. The package owns dispatch; your project owns the
meaning.
from talika import CellDSL, ColumnTable, field, id_field
content_cells = CellDSL()
@content_cells.token("random")
def random_value(context):
return context.user_data["generator"].random_for(context.field_name)
@content_cells.pattern(r"(?P<count>\d+):word")
def generated_words(match, context):
count = int(match["count"])
return context.user_data["generator"].words(count)
class ContentTable(ColumnTable):
id = id_field("IDs")
headline = field("Headline*", required=True, parser=content_cells)
Exact tokens run before patterns. Patterns are tried in registration order and
must match the whole cell. Values that match no rule pass through unchanged.
Rules may be scoped by schema attribute name, and several DSLs may be composed
with compose_cell_dsls(...).
Validation And Diagnostics
Override validate_record() to check rules involving one parsed record:
class ContentTable(ColumnTable):
id = id_field("IDs")
content_type = field("Type*", required=True)
headline = field("Headline*", required=True)
def validate_record(self, context):
if self.content_type == "Poll" and not self.headline.endswith("?"):
raise ValueError("Poll headlines must end with a question mark")
Use validate_records() for relationships involving several records. Failures
become TableError instances with stable error codes and source coordinates.
In collect mode, independent diagnostics are grouped into TableErrors:
from talika import TableErrors
try:
ContentTable.parse(datatable, error_mode="collect")
except TableErrors as errors:
for error in errors:
print(error.code, error.row, error.column, error.message)
Schema records expose immutable source metadata:
record.table_source.row
record.table_source.column
record.table_source.item_id
record.source_for("headline")
Output Models
Set output_model to return project objects after schema and table validation:
from dataclasses import dataclass
@dataclass(frozen=True)
class User:
name: str
age: int
class UserTable(RowTable):
output_model = User
name = field("name")
age: int = field("age")
Dataclasses and other keyword-constructed classes need no integration
dependency. Pydantic v2 works through the optional talika[pydantic] extra.
Override build_output(record, context) when construction needs a custom
signature, selected fields, source metadata, or project services.
Table Transformations
ColumnGroupExpander handles a common compact table convention where one
source column describes one item or a group of items:
from talika import ColumnGroupExpander, NumericRange, PrefixRepeat
class ContentTable(ColumnTable):
table_transformer = ColumnGroupExpander(
key_row="IDs",
range_rule=NumericRange(separator=".."),
repeat_rule=PrefixRepeat(separator=":"),
)
id = id_field("IDs")
content_type = field("Type*", required=True)
Projects can also implement compatible RangeRule and RepeatRule objects or
override transform_table() for table syntax that does not fit the reusable
grouped-column shape.
pytest Integration
Installing the package registers a talika fixture:
def content_exists(datatable, talika, faker):
return talika.parse(
datatable,
schema=ContentTable,
context={"faker": faker},
)
The fixture also exposes parse_records() for type-checker-friendly schema
records.
Static Feature Checking
Install the optional CLI extra to validate feature tables without executing pytest scenarios:
pip install "talika[cli]"
talika check features/content.feature `
--schema tests/support/content_schema.py:ContentTable `
--step "the following content exists:"
Machine-readable diagnostics are available for CI and editor integrations:
talika check features/content.feature `
--schema tests/support/content_schema.py:ContentTable `
--step "the following content exists:" `
--format json
Use describe to inspect a schema without parsing a feature file:
talika describe tests/support/content_schema.py:ContentTable
talika describe tests/support/content_schema.py:ContentTable --format json
The checker uses the official Gherkin parser and reports exact feature-file
coordinates. Importable module:Schema references are also supported. Use
--context-factory module:function for deterministic parser dependencies.
Scenario-outline substitutions are not expanded.
Development
uv sync --all-extras --dev
uv run pytest -p no:cacheprovider
uv run ruff check .
uv run ruff format --check .
uv run mypy src tests/typing/public_api.py
uv build
The documentation site is built with Zensical:
uv run --group docs zensical build --strict
GitHub Pages is configured for https://talikadev.github.io/talika/ through
the Docs workflow in .github/workflows/docs.yml.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file talika-0.1.0.tar.gz.
File metadata
- Download URL: talika-0.1.0.tar.gz
- Upload date:
- Size: 73.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e884e2ca5faa6c25fc3238dbfa4325bdec0c072cb727e36822aec7566196fafa
|
|
| MD5 |
3a551ce754c9ae44a23fb9bd86be451b
|
|
| BLAKE2b-256 |
bd1cacc9717f5a2f2b6a8600e110be69f937603c24ed5ed26021818ee342462c
|
Provenance
The following attestation bundles were made for talika-0.1.0.tar.gz:
Publisher:
publish-pypi.yml on talikadev/talika
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
talika-0.1.0.tar.gz -
Subject digest:
e884e2ca5faa6c25fc3238dbfa4325bdec0c072cb727e36822aec7566196fafa - Sigstore transparency entry: 2142699206
- Sigstore integration time:
-
Permalink:
talikadev/talika@63f92ec8799c19d0194e47dffb6a9bfff755f078 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/talikadev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@63f92ec8799c19d0194e47dffb6a9bfff755f078 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file talika-0.1.0-py3-none-any.whl.
File metadata
- Download URL: talika-0.1.0-py3-none-any.whl
- Upload date:
- Size: 67.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ef9767568b0f1ec64b82cbd47636b2ffc35963b40b07c5c2088149c1e8c3e805
|
|
| MD5 |
7d7d1d3ce2cad0bffd5cfef856bcb7be
|
|
| BLAKE2b-256 |
8ce34d9be254ef556a7e7c2553f833533c048086dbedd894269e0b2f647858eb
|
Provenance
The following attestation bundles were made for talika-0.1.0-py3-none-any.whl:
Publisher:
publish-pypi.yml on talikadev/talika
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
talika-0.1.0-py3-none-any.whl -
Subject digest:
ef9767568b0f1ec64b82cbd47636b2ffc35963b40b07c5c2088149c1e8c3e805 - Sigstore transparency entry: 2142699209
- Sigstore integration time:
-
Permalink:
talikadev/talika@63f92ec8799c19d0194e47dffb6a9bfff755f078 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/talikadev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@63f92ec8799c19d0194e47dffb6a9bfff755f078 -
Trigger Event:
workflow_dispatch
-
Statement type: