talika 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- talika-0.1.0/.gitignore +10 -0
- talika-0.1.0/CHANGELOG.md +42 -0
- talika-0.1.0/CONTRIBUTING.md +37 -0
- talika-0.1.0/LICENSE +21 -0
- talika-0.1.0/PKG-INFO +433 -0
- talika-0.1.0/README.md +399 -0
- talika-0.1.0/pyproject.toml +111 -0
- talika-0.1.0/src/talika/__init__.py +132 -0
- talika-0.1.0/src/talika/__main__.py +15 -0
- talika-0.1.0/src/talika/annotations.py +168 -0
- talika-0.1.0/src/talika/checker.py +286 -0
- talika-0.1.0/src/talika/cli.py +382 -0
- talika-0.1.0/src/talika/context.py +127 -0
- talika-0.1.0/src/talika/dsl.py +508 -0
- talika-0.1.0/src/talika/errors.py +332 -0
- talika-0.1.0/src/talika/fields.py +487 -0
- talika-0.1.0/src/talika/group_expansion.py +624 -0
- talika-0.1.0/src/talika/introspection.py +238 -0
- talika-0.1.0/src/talika/parsers.py +616 -0
- talika-0.1.0/src/talika/parsing.py +90 -0
- talika-0.1.0/src/talika/py.typed +1 -0
- talika-0.1.0/src/talika/pytest_plugin.py +108 -0
- talika-0.1.0/src/talika/records.py +165 -0
- talika-0.1.0/src/talika/schema.py +2040 -0
- talika-0.1.0/src/talika/sources.py +96 -0
- talika-0.1.0/src/talika/table.py +236 -0
- talika-0.1.0/src/talika/transformers.py +162 -0
- talika-0.1.0/tests/data/invalid_users.feature +6 -0
- talika-0.1.0/tests/test_annotations.py +83 -0
- talika-0.1.0/tests/test_cell_dsl.py +156 -0
- talika-0.1.0/tests/test_checker.py +227 -0
- talika-0.1.0/tests/test_column_table.py +89 -0
- talika-0.1.0/tests/test_composition.py +121 -0
- talika-0.1.0/tests/test_custom_parsers.py +50 -0
- talika-0.1.0/tests/test_defaults_aliases_policies.py +145 -0
- talika-0.1.0/tests/test_error_collection.py +108 -0
- talika-0.1.0/tests/test_errors.py +40 -0
- talika-0.1.0/tests/test_field_components.py +35 -0
- talika-0.1.0/tests/test_fields.py +93 -0
- talika-0.1.0/tests/test_functional_api.py +80 -0
- talika-0.1.0/tests/test_group_expansion.py +270 -0
- talika-0.1.0/tests/test_introspection.py +32 -0
- talika-0.1.0/tests/test_output_factory.py +20 -0
- talika-0.1.0/tests/test_output_models.py +75 -0
- talika-0.1.0/tests/test_package_metadata.py +13 -0
- talika-0.1.0/tests/test_parsers.py +85 -0
- talika-0.1.0/tests/test_pydantic_output.py +34 -0
- talika-0.1.0/tests/test_pytest_plugin.py +18 -0
- talika-0.1.0/tests/test_record_sources.py +42 -0
- talika-0.1.0/tests/test_record_validation.py +119 -0
- talika-0.1.0/tests/test_references.py +104 -0
- talika-0.1.0/tests/test_row_table.py +92 -0
- talika-0.1.0/tests/test_table_data.py +98 -0
- talika-0.1.0/tests/test_table_transform.py +129 -0
- talika-0.1.0/tests/test_table_validation.py +62 -0
- talika-0.1.0/tests/test_variants.py +388 -0
- talika-0.1.0/tests/typing/public_api.py +38 -0
talika-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. Until the first
|
|
4
|
+
stable release, additions may refine APIs while preserving the documented
|
|
5
|
+
`0.1` behavior whenever practical.
|
|
6
|
+
|
|
7
|
+
## 0.1.0
|
|
8
|
+
|
|
9
|
+
The first public alpha release of Talika introduces typed, validated contracts
|
|
10
|
+
for BDD data tables without imposing a project-specific table vocabulary.
|
|
11
|
+
|
|
12
|
+
### Schemas and fields
|
|
13
|
+
|
|
14
|
+
- Parse both row-oriented and column-oriented data tables into typed schema records.
|
|
15
|
+
- Declare required, optional, aliased, and identifier fields with static or context-aware defaults.
|
|
16
|
+
- Infer converters from scalar, optional, enum, and string-literal type annotations.
|
|
17
|
+
- Control empty cells independently from missing fields with preserve, parse, null, and forbid policies.
|
|
18
|
+
- Compose reusable field groups and discriminated variants for tables containing multiple record shapes.
|
|
19
|
+
- Resolve single and multi-value references between records, including identifiers with custom parsers.
|
|
20
|
+
|
|
21
|
+
### Parsing and extension points
|
|
22
|
+
|
|
23
|
+
- Convert cells with built-in string, integer, float, decimal, boolean, choice, mapping, split, optional, compose, and collection parsers.
|
|
24
|
+
- Build project-owned cell languages from scoped tokens, regular-expression patterns, predicates, fallbacks, and composable `CellDSL` instances.
|
|
25
|
+
- Transform tables before parsing with custom hooks or left-to-right transformer pipelines while preserving original source locations.
|
|
26
|
+
- Expand compact column groups with numeric or alphabetic ranges and configurable prefix or suffix repetition rules.
|
|
27
|
+
- Return schema records, dataclasses, Pydantic models, keyword-constructed classes, or objects produced by custom output builders.
|
|
28
|
+
|
|
29
|
+
### Validation and diagnostics
|
|
30
|
+
|
|
31
|
+
- Validate individual records and complete record collections after conversion, defaults, variants, and references are resolved.
|
|
32
|
+
- Report table shape, field, parser, transformation, reference, validation, and output errors with stable codes and source coordinates.
|
|
33
|
+
- Choose fail-fast behavior or collect independent failures into an ordered `TableErrors` aggregate.
|
|
34
|
+
- Inspect immutable record and field source metadata, including row, column, item identifier, and original transformed cells.
|
|
35
|
+
|
|
36
|
+
### Integrations and tooling
|
|
37
|
+
|
|
38
|
+
- Parse tables in pytest-bdd steps through the registered `talika` fixture or the functional parsing API.
|
|
39
|
+
- Discover and statically validate Gherkin data tables through the Python checker API or the `talika check` command.
|
|
40
|
+
- Emit human-readable or JSON checker diagnostics and supply deterministic project context through CLI context factories.
|
|
41
|
+
- Inspect complete field and variant contracts through `describe()`, `talika describe`, or JSON output.
|
|
42
|
+
- Ship a dependency-free typed core for Python 3.10 through 3.13 with optional CLI, Pydantic, and testing extras.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Development setup
|
|
4
|
+
|
|
5
|
+
The project uses Python 3.10 or newer and `uv` for reproducible environments:
|
|
6
|
+
|
|
7
|
+
```powershell
|
|
8
|
+
uv sync --all-extras --dev
|
|
9
|
+
uv run pytest -p no:cacheprovider
|
|
10
|
+
uv run ruff check .
|
|
11
|
+
uv run ruff format --check .
|
|
12
|
+
uv run mypy src tests/typing/public_api.py
|
|
13
|
+
uv build
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Design principles
|
|
17
|
+
|
|
18
|
+
- Keep business actions and domain vocabulary outside the core package.
|
|
19
|
+
- Prefer small schema declarations over a general grammar framework.
|
|
20
|
+
- Preserve original feature-file locations through every transformation.
|
|
21
|
+
- Keep direct schema parsing independent from pytest and pytest-bdd.
|
|
22
|
+
- Make extension contracts explicit and test custom implementations.
|
|
23
|
+
- Add focused tests and documentation for every user-facing capability.
|
|
24
|
+
- Preserve missing-field versus explicitly-empty-cell behavior.
|
|
25
|
+
|
|
26
|
+
## Tests
|
|
27
|
+
|
|
28
|
+
Changes should include focused unit tests and, for user-facing behavior,
|
|
29
|
+
documentation under `docs/`. Test error messages through structured attributes
|
|
30
|
+
and stable codes where possible rather than relying only on complete strings.
|
|
31
|
+
|
|
32
|
+
## Compatibility
|
|
33
|
+
|
|
34
|
+
The supported baseline is Python 3.10+, pytest 8+, and pytest-bdd 8+. CI checks
|
|
35
|
+
multiple Python and pytest releases. Deprecations should be documented before
|
|
36
|
+
removal, and the package version must be changed only as part of an explicit
|
|
37
|
+
release decision.
|
talika-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 talika contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
talika-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: talika
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Typed, validated schemas for BDD data tables
|
|
5
|
+
Project-URL: Documentation, https://talikadev.github.io/talika/
|
|
6
|
+
Project-URL: Source, https://github.com/talikadev/talika
|
|
7
|
+
Project-URL: Issues, https://github.com/talikadev/talika/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/talikadev/talika/blob/master/CHANGELOG.md
|
|
9
|
+
Author: Chinmay Singh, Nishant Bhandari
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: bdd,gherkin,pytest,pytest-bdd,testing
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Framework :: Pytest
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Provides-Extra: cli
|
|
25
|
+
Requires-Dist: gherkin-official>=29.0; extra == 'cli'
|
|
26
|
+
Provides-Extra: pydantic
|
|
27
|
+
Requires-Dist: pydantic>=2.0; extra == 'pydantic'
|
|
28
|
+
Provides-Extra: test
|
|
29
|
+
Requires-Dist: gherkin-official>=29.0; extra == 'test'
|
|
30
|
+
Requires-Dist: pydantic>=2.0; extra == 'test'
|
|
31
|
+
Requires-Dist: pytest-bdd>=8.0; extra == 'test'
|
|
32
|
+
Requires-Dist: pytest>=8.0; extra == 'test'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# talika
|
|
36
|
+
|
|
37
|
+
`talika` adds small, dataclass-style schemas to BDD data tables. It parses the
|
|
38
|
+
raw list-of-lists supplied by tools such as `pytest-bdd`, validates the table
|
|
39
|
+
shape, converts cells with project-defined parsers, and returns typed schema
|
|
40
|
+
records.
|
|
41
|
+
|
|
42
|
+
It does not prescribe a table DSL or perform business actions. Projects define
|
|
43
|
+
their own readable table vocabulary while `talika` handles the repeatable
|
|
44
|
+
parts: shape validation, conversion, source-aware diagnostics, and optional
|
|
45
|
+
static checks for `.feature` files.
|
|
46
|
+
|
|
47
|
+
## Links
|
|
48
|
+
|
|
49
|
+
- Documentation: <https://talikadev.github.io/talika/>
|
|
50
|
+
- Source: <https://github.com/talikadev/talika>
|
|
51
|
+
- Issues: <https://github.com/talikadev/talika/issues>
|
|
52
|
+
- PyPI: <https://pypi.org/project/talika/>
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
`talika` supports Python 3.10 and newer. The core package has no runtime
|
|
57
|
+
dependencies:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install talika
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Install optional extras only for integrations you use:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install "talika[cli]" # static Gherkin feature-file checks
|
|
67
|
+
pip install "talika[pydantic]" # Pydantic output models
|
|
68
|
+
pip install "talika[test]" # test/integration dependencies
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The command-line tool is available as both `talika` and `python -m talika`.
|
|
72
|
+
|
|
73
|
+
## Quick Start
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from talika import RowTable, field
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def parse_bool(value, context):
|
|
80
|
+
return value.lower() == "true"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class UserTable(RowTable):
|
|
84
|
+
name = field("name", required=True)
|
|
85
|
+
role = field("role", required=True)
|
|
86
|
+
active = field("active", parser=parse_bool, default=True)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
users = UserTable.parse(
|
|
90
|
+
[
|
|
91
|
+
["name", "role", "active"],
|
|
92
|
+
["Alice", "admin", "true"],
|
|
93
|
+
]
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
assert users[0].name == "Alice"
|
|
97
|
+
assert users[0].active is True
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Table Shapes
|
|
101
|
+
|
|
102
|
+
Row-oriented tables use the first row as labels and every following row as one
|
|
103
|
+
record:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from talika import RowTable, field
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class ProductTable(RowTable):
|
|
110
|
+
sku = field("sku", required=True)
|
|
111
|
+
name = field("name", required=True)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Column-oriented tables use the first column as labels and every following
|
|
115
|
+
column as one record:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from talika import ColumnTable, field, id_field
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class ContentTable(ColumnTable):
|
|
122
|
+
id = id_field("IDs")
|
|
123
|
+
content_type = field("Type*", required=True)
|
|
124
|
+
headline = field("Headline*", required=True)
|
|
125
|
+
category = field("Category")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
items = ContentTable.parse(
|
|
129
|
+
[
|
|
130
|
+
["IDs", "1", "2"],
|
|
131
|
+
["Type*", "Article", "Poll"],
|
|
132
|
+
["Headline*", "Hello", "QA Poll"],
|
|
133
|
+
]
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
assert items[0].id == "1"
|
|
137
|
+
assert items[1].content_type == "Poll"
|
|
138
|
+
assert items[0].category is None
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Use `parse_records()` when you specifically want schema instances for static
|
|
142
|
+
typing or when a schema has an `output_model` but a test needs the intermediate
|
|
143
|
+
validated record:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
records: list[ContentTable] = ContentTable.parse_records(datatable)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Functional helpers are also available:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from talika import parse_table, parse_table_records
|
|
153
|
+
|
|
154
|
+
items = parse_table(ContentTable, datatable)
|
|
155
|
+
records = parse_table_records(ContentTable, datatable)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Conversion
|
|
159
|
+
|
|
160
|
+
Common field conversion does not require custom functions:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from talika import RowTable, boolean, compose, decimal, each, field, split, string
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class ProductTable(RowTable):
|
|
167
|
+
price = field("price", parser=decimal())
|
|
168
|
+
active = field("active", parser=boolean())
|
|
169
|
+
tags = field("tags", parser=compose(split(","), each(string(strip=True))))
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The package provides `string`, `integer`, `floating`, `decimal`, `boolean`,
|
|
173
|
+
`choice`, `split`, `map_value`, `optional`, `compose`, and `each`.
|
|
174
|
+
|
|
175
|
+
Supported annotations infer a parser when the field has no explicit parser:
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
class UserTable(RowTable):
|
|
179
|
+
name: str = field("name")
|
|
180
|
+
age: int | None = field("age")
|
|
181
|
+
active: bool = field("active")
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Inference supports `str`, `int`, `float`, `bool`, `Decimal`, enums, string
|
|
185
|
+
`Literal` values, and simple optionals. Collection annotations such as
|
|
186
|
+
`list[str]` do not imply a cell syntax; use an explicit parser such as
|
|
187
|
+
`split(",")` when one cell should become several values.
|
|
188
|
+
|
|
189
|
+
## Defaults, Aliases, And Policies
|
|
190
|
+
|
|
191
|
+
Missing optional fields can use static defaults or context-aware factories:
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
headline = field(
|
|
195
|
+
"Headline",
|
|
196
|
+
default_factory=lambda context: (
|
|
197
|
+
context.user_data["generator"].headline(context.item_id)
|
|
198
|
+
),
|
|
199
|
+
)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Aliases support intentional wording changes:
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
headline = field("Headline", aliases=("Title", "Name"))
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Unknown fields currently accept only the default `unknown_fields = "forbid"`
|
|
209
|
+
policy. Discriminated schemas also support `inapplicable_fields = "forbid"`
|
|
210
|
+
and `inapplicable_fields = "preserve"` for variant-specific values.
|
|
211
|
+
|
|
212
|
+
## Variants
|
|
213
|
+
|
|
214
|
+
Variants let one BDD table contain several related record shapes. The base
|
|
215
|
+
schema declares shared fields and a discriminator used to select the applicable
|
|
216
|
+
fields and behavior.
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
from talika import ColumnTable, TableFields, discriminator, field, id_field, split
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class ArticleFields(TableFields):
|
|
223
|
+
body = field("Body*", required=True)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class PollFields(TableFields):
|
|
227
|
+
options: list[str] = field("Options*", required=True, parser=split(","))
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class ContentTable(ColumnTable):
|
|
231
|
+
id = id_field("IDs")
|
|
232
|
+
content_type = discriminator(
|
|
233
|
+
"Type*",
|
|
234
|
+
variants={
|
|
235
|
+
"Article": ArticleFields,
|
|
236
|
+
"Poll": PollFields,
|
|
237
|
+
},
|
|
238
|
+
)
|
|
239
|
+
headline = field("Headline*", required=True)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The explicit decorator form is also available with `discriminator_field()` and
|
|
243
|
+
`@ContentTable.variant(...)`.
|
|
244
|
+
|
|
245
|
+
## Custom Cell Syntax
|
|
246
|
+
|
|
247
|
+
`CellDSL` groups exact tokens and full-match regular-expression rules into a
|
|
248
|
+
reusable field parser. The package owns dispatch; your project owns the
|
|
249
|
+
meaning.
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
from talika import CellDSL, ColumnTable, field, id_field
|
|
253
|
+
|
|
254
|
+
content_cells = CellDSL()
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@content_cells.token("random")
|
|
258
|
+
def random_value(context):
|
|
259
|
+
return context.user_data["generator"].random_for(context.field_name)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@content_cells.pattern(r"(?P<count>\d+):word")
|
|
263
|
+
def generated_words(match, context):
|
|
264
|
+
count = int(match["count"])
|
|
265
|
+
return context.user_data["generator"].words(count)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class ContentTable(ColumnTable):
|
|
269
|
+
id = id_field("IDs")
|
|
270
|
+
headline = field("Headline*", required=True, parser=content_cells)
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Exact tokens run before patterns. Patterns are tried in registration order and
|
|
274
|
+
must match the whole cell. Values that match no rule pass through unchanged.
|
|
275
|
+
Rules may be scoped by schema attribute name, and several DSLs may be composed
|
|
276
|
+
with `compose_cell_dsls(...)`.
|
|
277
|
+
|
|
278
|
+
## Validation And Diagnostics
|
|
279
|
+
|
|
280
|
+
Override `validate_record()` to check rules involving one parsed record:
|
|
281
|
+
|
|
282
|
+
```python
|
|
283
|
+
class ContentTable(ColumnTable):
|
|
284
|
+
id = id_field("IDs")
|
|
285
|
+
content_type = field("Type*", required=True)
|
|
286
|
+
headline = field("Headline*", required=True)
|
|
287
|
+
|
|
288
|
+
def validate_record(self, context):
|
|
289
|
+
if self.content_type == "Poll" and not self.headline.endswith("?"):
|
|
290
|
+
raise ValueError("Poll headlines must end with a question mark")
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Use `validate_records()` for relationships involving several records. Failures
|
|
294
|
+
become `TableError` instances with stable error codes and source coordinates.
|
|
295
|
+
In collect mode, independent diagnostics are grouped into `TableErrors`:
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
from talika import TableErrors
|
|
299
|
+
|
|
300
|
+
try:
|
|
301
|
+
ContentTable.parse(datatable, error_mode="collect")
|
|
302
|
+
except TableErrors as errors:
|
|
303
|
+
for error in errors:
|
|
304
|
+
print(error.code, error.row, error.column, error.message)
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Schema records expose immutable source metadata:
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
record.table_source.row
|
|
311
|
+
record.table_source.column
|
|
312
|
+
record.table_source.item_id
|
|
313
|
+
record.source_for("headline")
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Output Models
|
|
317
|
+
|
|
318
|
+
Set `output_model` to return project objects after schema and table validation:
|
|
319
|
+
|
|
320
|
+
```python
|
|
321
|
+
from dataclasses import dataclass
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
@dataclass(frozen=True)
|
|
325
|
+
class User:
|
|
326
|
+
name: str
|
|
327
|
+
age: int
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class UserTable(RowTable):
|
|
331
|
+
output_model = User
|
|
332
|
+
|
|
333
|
+
name = field("name")
|
|
334
|
+
age: int = field("age")
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Dataclasses and other keyword-constructed classes need no integration
|
|
338
|
+
dependency. Pydantic v2 works through the optional `talika[pydantic]` extra.
|
|
339
|
+
Override `build_output(record, context)` when construction needs a custom
|
|
340
|
+
signature, selected fields, source metadata, or project services.
|
|
341
|
+
|
|
342
|
+
## Table Transformations
|
|
343
|
+
|
|
344
|
+
`ColumnGroupExpander` handles a common compact table convention where one
|
|
345
|
+
source column describes one item or a group of items:
|
|
346
|
+
|
|
347
|
+
```python
|
|
348
|
+
from talika import ColumnGroupExpander, NumericRange, PrefixRepeat
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class ContentTable(ColumnTable):
|
|
352
|
+
table_transformer = ColumnGroupExpander(
|
|
353
|
+
key_row="IDs",
|
|
354
|
+
range_rule=NumericRange(separator=".."),
|
|
355
|
+
repeat_rule=PrefixRepeat(separator=":"),
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
id = id_field("IDs")
|
|
359
|
+
content_type = field("Type*", required=True)
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Projects can also implement compatible `RangeRule` and `RepeatRule` objects or
|
|
363
|
+
override `transform_table()` for table syntax that does not fit the reusable
|
|
364
|
+
grouped-column shape.
|
|
365
|
+
|
|
366
|
+
## pytest Integration
|
|
367
|
+
|
|
368
|
+
Installing the package registers a `talika` fixture:
|
|
369
|
+
|
|
370
|
+
```python
|
|
371
|
+
def content_exists(datatable, talika, faker):
|
|
372
|
+
return talika.parse(
|
|
373
|
+
datatable,
|
|
374
|
+
schema=ContentTable,
|
|
375
|
+
context={"faker": faker},
|
|
376
|
+
)
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
The fixture also exposes `parse_records()` for type-checker-friendly schema
|
|
380
|
+
records.
|
|
381
|
+
|
|
382
|
+
## Static Feature Checking
|
|
383
|
+
|
|
384
|
+
Install the optional CLI extra to validate feature tables without executing
|
|
385
|
+
pytest scenarios:
|
|
386
|
+
|
|
387
|
+
```powershell
|
|
388
|
+
pip install "talika[cli]"
|
|
389
|
+
talika check features/content.feature `
|
|
390
|
+
--schema tests/support/content_schema.py:ContentTable `
|
|
391
|
+
--step "the following content exists:"
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Machine-readable diagnostics are available for CI and editor integrations:
|
|
395
|
+
|
|
396
|
+
```powershell
|
|
397
|
+
talika check features/content.feature `
|
|
398
|
+
--schema tests/support/content_schema.py:ContentTable `
|
|
399
|
+
--step "the following content exists:" `
|
|
400
|
+
--format json
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Use `describe` to inspect a schema without parsing a feature file:
|
|
404
|
+
|
|
405
|
+
```powershell
|
|
406
|
+
talika describe tests/support/content_schema.py:ContentTable
|
|
407
|
+
talika describe tests/support/content_schema.py:ContentTable --format json
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
The checker uses the official Gherkin parser and reports exact feature-file
|
|
411
|
+
coordinates. Importable `module:Schema` references are also supported. Use
|
|
412
|
+
`--context-factory module:function` for deterministic parser dependencies.
|
|
413
|
+
Scenario-outline substitutions are not expanded.
|
|
414
|
+
|
|
415
|
+
## Development
|
|
416
|
+
|
|
417
|
+
```powershell
|
|
418
|
+
uv sync --all-extras --dev
|
|
419
|
+
uv run pytest -p no:cacheprovider
|
|
420
|
+
uv run ruff check .
|
|
421
|
+
uv run ruff format --check .
|
|
422
|
+
uv run mypy src tests/typing/public_api.py
|
|
423
|
+
uv build
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
The documentation site is built with Zensical:
|
|
427
|
+
|
|
428
|
+
```powershell
|
|
429
|
+
uv run --group docs zensical build --strict
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
GitHub Pages is configured for <https://talikadev.github.io/talika/> through
|
|
433
|
+
the `Docs` workflow in `.github/workflows/docs.yml`.
|