typedframes 0.2.2__tar.gz → 0.3.1__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.
- {typedframes-0.2.2 → typedframes-0.3.1}/PKG-INFO +67 -21
- {typedframes-0.2.2 → typedframes-0.3.1}/README.md +66 -20
- {typedframes-0.2.2 → typedframes-0.3.1}/pyproject.toml +1 -1
- {typedframes-0.2.2 → typedframes-0.3.1}/rust/Cargo.lock +3 -3
- {typedframes-0.2.2 → typedframes-0.3.1}/rust/Cargo.toml +1 -1
- {typedframes-0.2.2 → typedframes-0.3.1}/rust/src/lib.rs +1861 -117
- {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/__init__.py +1 -1
- {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/pandas.py +2 -2
- {typedframes-0.2.2 → typedframes-0.3.1}/rust/README.md +0 -0
- {typedframes-0.2.2 → typedframes-0.3.1}/rust/benches/parser_bench.rs +0 -0
- {typedframes-0.2.2 → typedframes-0.3.1}/rust/src/main.rs +0 -0
- {typedframes-0.2.2 → typedframes-0.3.1}/rust/tests/integration_test.rs +0 -0
- {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/base_schema.py +0 -0
- {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/cli.py +0 -0
- {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/column.py +0 -0
- {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/column_group.py +0 -0
- {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/column_group_error.py +0 -0
- {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/column_set.py +0 -0
- {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/missing_dependency_error.py +0 -0
- {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/mypy.py +0 -0
- {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/pandera.py +0 -0
- {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/polars.py +0 -0
- {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/py.typed +0 -0
- {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/schema_algebra.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: typedframes
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -42,7 +42,7 @@ Project-URL: Repository, https://github.com/w-martin/typedframes
|
|
|
42
42
|
|
|
43
43
|
> ⚠️ **Project Status: Proof of Concept**
|
|
44
44
|
>
|
|
45
|
-
> `typedframes` (v0.
|
|
45
|
+
> `typedframes` (v0.3.1) is currently an experimental proof-of-concept. The core static analysis and mypy/Rust
|
|
46
46
|
> integrations work, but expect rough edges. The codebase prioritizes demonstrating the viability of static DataFrame
|
|
47
47
|
> schema validation over production-grade stability.
|
|
48
48
|
>
|
|
@@ -140,7 +140,7 @@ print(orders["revenue"]) # ✗ unknown-column — 'revenue' not in inferred set
|
|
|
140
140
|
|
|
141
141
|
```shell
|
|
142
142
|
typedframes check src/
|
|
143
|
-
# src/pipeline.py:7:8: error[unknown-column] Column 'revenue' not in inferred set
|
|
143
|
+
# src/pipeline.py:7:8: error[unknown-column] Column 'revenue' does not exist in inferred column set (defined at line 6)
|
|
144
144
|
# ✗ Found 1 error in 12 files (0.0s)
|
|
145
145
|
```
|
|
146
146
|
|
|
@@ -297,12 +297,53 @@ trimmed = df.drop(columns=["nonexistent"])
|
|
|
297
297
|
# ⚠ dropped-unknown-column: Dropped column 'nonexistent' does not exist in UserData
|
|
298
298
|
```
|
|
299
299
|
|
|
300
|
+
### Function Parameter Contracts
|
|
301
|
+
|
|
302
|
+
Beyond validating access at the point it happens, the checker infers a *contract* for any function's
|
|
303
|
+
first parameter: every column the function needs, drawn from what its body accesses or — taking priority —
|
|
304
|
+
from a schema annotation on the parameter itself. Calling that function with a DataFrame that doesn't
|
|
305
|
+
satisfy the contract is caught at the **call site**, across files:
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
# transforms.py
|
|
309
|
+
def contact_label(customers):
|
|
310
|
+
return customers["name"] + customers["email"]
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
```python
|
|
314
|
+
# pipeline.py
|
|
315
|
+
customers = load_customers(path) # inferred columns: {customer_id, name, region}
|
|
316
|
+
contact_label(customers)
|
|
317
|
+
# ✗ missing-column: 'customers' passed to contact_label (transforms.py:2) is missing
|
|
318
|
+
# column(s) {email} — available: {customer_id, name, region}, required: {email, name}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
The contract is resolved *transitively*: if a function only forwards its parameter to other functions
|
|
322
|
+
(`step1 = preprocess(df); step2 = enrich(step1)`), the checker follows the chain and unions their
|
|
323
|
+
requirements, catching a missing column even when no single function in the chain touches it directly.
|
|
324
|
+
Column-list slices (`df[["a", "b"]]`) contribute to the contract too.
|
|
325
|
+
|
|
326
|
+
**Known limitations:**
|
|
327
|
+
- Cross-file delegate/schema resolution follows `from module import name`, plain `import module` +
|
|
328
|
+
`module.helper(df)` attribute access, and `from module import *` wildcard imports. A dotted import with no
|
|
329
|
+
alias (`import a.b.c`) only binds the first segment (`a`), matching Python's own binding rules, so a
|
|
330
|
+
deeply nested submodule accessed without an alias is not tracked.
|
|
331
|
+
- Contract inference is a single top-to-bottom pass over a function body, not full control-flow analysis.
|
|
332
|
+
Deeply nested control flow (nested `try`/`except`, `match`, comprehensions) may under-report a function's
|
|
333
|
+
true requirements.
|
|
334
|
+
- A cycle in the delegate graph (mutually- or self-delegating helpers) contributes only each function's own
|
|
335
|
+
direct requirements to the cycle, not the full transitive union — conservative rather than exhaustive.
|
|
336
|
+
- If two plainly-imported modules both define a same-named function, an attribute-style delegate call
|
|
337
|
+
(`module.helper(df)`) resolves to whichever one is found first — the checker doesn't disambiguate by which
|
|
338
|
+
module the call site actually used.
|
|
339
|
+
|
|
300
340
|
### See Also
|
|
301
341
|
|
|
302
342
|
- [`examples/inference_example.py`](examples/inference_example.py) — single-file walkthrough of all four inference
|
|
303
343
|
scenarios with annotated ✓/✗ comments.
|
|
304
344
|
- [`examples/multi_file_inference/`](examples/multi_file_inference/) — multi-file project checked with
|
|
305
|
-
`typedframes check examples/multi_file_inference/`; no `BaseSchema` anywhere.
|
|
345
|
+
`typedframes check examples/multi_file_inference/`; no `BaseSchema` anywhere. Includes a function
|
|
346
|
+
parameter contract violation caught at the call site (`missing-column`).
|
|
306
347
|
- [`examples/multi_file_with_schema/`](examples/multi_file_with_schema/) — same scenario with `BaseSchema`
|
|
307
348
|
classes; the checker follows schemas across module boundaries via the project index.
|
|
308
349
|
|
|
@@ -327,6 +368,8 @@ typedframes check src/
|
|
|
327
368
|
**Features:**
|
|
328
369
|
- Catches column name errors
|
|
329
370
|
- Validates schema mismatches between functions
|
|
371
|
+
- Validates function parameter contracts across files, including transitively through chains of
|
|
372
|
+
helper functions (`missing-column`)
|
|
330
373
|
- Checks both pandas and polars code
|
|
331
374
|
- Significantly faster than mypy (see benchmarks below)
|
|
332
375
|
|
|
@@ -407,17 +450,18 @@ code reference.
|
|
|
407
450
|
|
|
408
451
|
Fast feedback reduces development time. The typedframes Rust binary provides near-instant column checking.
|
|
409
452
|
|
|
410
|
-
**Benchmark results** (
|
|
453
|
+
**Benchmark results** (20 runs, 3 warmup, caches cleared between runs):
|
|
454
|
+
*2026-07-13 · Darwin 25.5.0 · arm · CPython 3.14.4 · 64GiB RAM · Great Expectations pinned @ 1.9.3*
|
|
411
455
|
|
|
412
|
-
| Tool | Version | What it does | typedframes (13 files) | great_expectations (
|
|
456
|
+
| Tool | Version | What it does | typedframes (13 files) | great_expectations (482 files) |
|
|
413
457
|
|------|---------|--------------|------------------------|--------------------------------|
|
|
414
|
-
| typedframes | 0.
|
|
415
|
-
| ruff | 0.15.
|
|
416
|
-
| ty | 0.0.
|
|
417
|
-
| pyrefly |
|
|
418
|
-
| mypy |
|
|
419
|
-
| mypy + typedframes |
|
|
420
|
-
| pyright | 1.1.
|
|
458
|
+
| typedframes | 0.3.1 | DataFrame column checker | 43ms ±668µs | 213ms ±4ms |
|
|
459
|
+
| ruff | 0.15.21 | Linter (no type checking) | 26ms ±1ms | 193ms ±2ms |
|
|
460
|
+
| ty | 0.0.58 | Type checker | 72ms ±3ms | 181ms ±9ms |
|
|
461
|
+
| pyrefly | 1.1.1 | Type checker | 107ms ±4ms | 557ms ±16ms |
|
|
462
|
+
| mypy | 2.2.0 | Type checker (no plugin) | 2.77s ±41ms | 4.21s ±71ms |
|
|
463
|
+
| mypy + typedframes | 2.2.0 | Type checker + column checker | 2.78s ±34ms | 4.48s ±63ms |
|
|
464
|
+
| pyright | 1.1.411 | Type checker | 743ms ±8ms | 3.34s ±43ms |
|
|
421
465
|
|
|
422
466
|
*Run `uv run python benchmarks/benchmark_checkers.py` to reproduce.*
|
|
423
467
|
|
|
@@ -630,7 +674,7 @@ Comprehensive comparison of pandas/DataFrame typing and validation tools. **type
|
|
|
630
674
|
|
|
631
675
|
| Feature | typedframes | Pandera | Great Expectations | strictly_typed_pandas | pandas-stubs | dataenforce | pandas-type-checks | StaticFrame | narwhals | dataframely | patito |
|
|
632
676
|
|---------------------------------|------------------------|-------------|--------------------|-----------------------|--------------|-------------|--------------------|------------------|----------|------------------|------------------|
|
|
633
|
-
| **Version tested** | 0.
|
|
677
|
+
| **Version tested** | 0.3.1 | 0.32.1 | 1.18.2 | 0.3.7 | 3.0.3 | 0.1.2 | 1.1.3 | 5.0.0 | 2.23.0 | 2.13.0 | 0.8.6 |
|
|
634
678
|
| **Analysis Type** |
|
|
635
679
|
| When errors are caught | **Static (lint-time)** | Runtime | Runtime | Runtime | Static | Runtime | Runtime | Runtime | Runtime | Runtime | Runtime |
|
|
636
680
|
| **Static Analysis (our focus)** |
|
|
@@ -656,7 +700,7 @@ Comprehensive comparison of pandas/DataFrame typing and validation tools. **type
|
|
|
656
700
|
|
|
657
701
|
### Tool Descriptions
|
|
658
702
|
|
|
659
|
-
- **[Pandera](https://pandera.readthedocs.io/)** (v0.
|
|
703
|
+
- **[Pandera](https://pandera.readthedocs.io/)** (v0.32.1): Excellent runtime validation. Static analysis support exists
|
|
660
704
|
but has limitations—column access via `df["column"]` is not validated, and schema mismatches between functions may not
|
|
661
705
|
be caught.
|
|
662
706
|
|
|
@@ -664,30 +708,30 @@ Comprehensive comparison of pandas/DataFrame typing and validation tools. **type
|
|
|
664
708
|
hints for runtime validation via typeguard. Despite documentation implying mypy support, there is no mypy plugin —
|
|
665
709
|
column access errors are not caught statically. No standalone checker. No polars support.
|
|
666
710
|
|
|
667
|
-
- **[pandas-stubs](https://github.com/pandas-dev/pandas-stubs)** (v3.0.
|
|
711
|
+
- **[pandas-stubs](https://github.com/pandas-dev/pandas-stubs)** (v3.0.3): Official pandas type stubs. Provides
|
|
668
712
|
API-level types but no column-level checking.
|
|
669
713
|
|
|
670
714
|
- **[dataenforce](https://github.com/CedricFR/dataenforce)** (v0.1.2): Runtime validation via decorator. Marked as
|
|
671
|
-
experimental/not production-ready. Appears inactive.
|
|
715
|
+
experimental/not production-ready. Appears inactive. Broken on Python 3.13+ due to removal of internal typing APIs.
|
|
672
716
|
|
|
673
717
|
- **[pandas-type-checks](https://pypi.org/project/pandas-type-checks/)** (v1.1.3): Runtime validation decorator. No
|
|
674
718
|
static analysis.
|
|
675
719
|
|
|
676
|
-
- **[StaticFrame](https://github.com/static-frame/static-frame)** (
|
|
720
|
+
- **[StaticFrame](https://github.com/static-frame/static-frame)** (v5.0.0): Alternative immutable DataFrame library.
|
|
677
721
|
Not compatible with pandas/polars — requires a full rewrite to StaticFrame's own API. Column access is still
|
|
678
722
|
string-based; mypy does not catch column name typos. Type safety comes from immutability guarantees, not schema checking.
|
|
679
723
|
|
|
680
|
-
- **[narwhals](https://narwhals-dev.github.io/narwhals/)** (v2.
|
|
724
|
+
- **[narwhals](https://narwhals-dev.github.io/narwhals/)** (v2.23.0): Compatibility layer that provides a unified API
|
|
681
725
|
across pandas, polars, DuckDB, cuDF, and more. Solves a different problem—write-once-run-anywhere portability, not
|
|
682
726
|
type safety. See [Why Abstraction Layers Don't Solve Type Safety](#why-abstraction-layers-dont-solve-type-safety)
|
|
683
727
|
below.
|
|
684
728
|
|
|
685
|
-
- **[Great Expectations](https://greatexpectations.io/)** (v1.18.
|
|
729
|
+
- **[Great Expectations](https://greatexpectations.io/)** (v1.18.2): Comprehensive data quality framework. Defines
|
|
686
730
|
"expectations" (assertions) about data values, distributions, and schema properties. Excellent for runtime
|
|
687
731
|
validation, data documentation, and data quality monitoring. No static analysis or column-level type checking in
|
|
688
732
|
code. Supports pandas, Spark, and SQL backends.
|
|
689
733
|
|
|
690
|
-
- **[dataframely](https://github.com/Quantco/dataframely)** (v2.
|
|
734
|
+
- **[dataframely](https://github.com/Quantco/dataframely)** (v2.13.0): Polars-only runtime validation library from Quantco.
|
|
691
735
|
Schemas are defined as classes inheriting `dy.Schema` with typed descriptor fields (`dy.String()`, `dy.Float64()`)
|
|
692
736
|
and `@dy.rule()` decorators for cross-column and group-level constraints. Returns `dy.DataFrame[Schema]` generic
|
|
693
737
|
types that give call-site narrowing to type checkers, but does not validate column subscript access inside function
|
|
@@ -992,6 +1036,8 @@ MIT License - see [LICENSE](LICENSE)
|
|
|
992
1036
|
- [x] Pandera integration (`to_pandera_schema()`)
|
|
993
1037
|
- [x] Cross-file schema inference (project-level index, `--no-index` flag)
|
|
994
1038
|
- [x] Aggressive column inference (untracked-dataframe/dropped-unknown-column warnings, method chain propagation)
|
|
1039
|
+
- [x] Function parameter contracts (`missing-column`), resolved transitively across chains of helper
|
|
1040
|
+
functions and cross-file calls; schema-annotated parameters take priority over body-scanning
|
|
995
1041
|
|
|
996
1042
|
**Planned:**
|
|
997
1043
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
> ⚠️ **Project Status: Proof of Concept**
|
|
10
10
|
>
|
|
11
|
-
> `typedframes` (v0.
|
|
11
|
+
> `typedframes` (v0.3.1) is currently an experimental proof-of-concept. The core static analysis and mypy/Rust
|
|
12
12
|
> integrations work, but expect rough edges. The codebase prioritizes demonstrating the viability of static DataFrame
|
|
13
13
|
> schema validation over production-grade stability.
|
|
14
14
|
>
|
|
@@ -106,7 +106,7 @@ print(orders["revenue"]) # ✗ unknown-column — 'revenue' not in inferred set
|
|
|
106
106
|
|
|
107
107
|
```shell
|
|
108
108
|
typedframes check src/
|
|
109
|
-
# src/pipeline.py:7:8: error[unknown-column] Column 'revenue' not in inferred set
|
|
109
|
+
# src/pipeline.py:7:8: error[unknown-column] Column 'revenue' does not exist in inferred column set (defined at line 6)
|
|
110
110
|
# ✗ Found 1 error in 12 files (0.0s)
|
|
111
111
|
```
|
|
112
112
|
|
|
@@ -263,12 +263,53 @@ trimmed = df.drop(columns=["nonexistent"])
|
|
|
263
263
|
# ⚠ dropped-unknown-column: Dropped column 'nonexistent' does not exist in UserData
|
|
264
264
|
```
|
|
265
265
|
|
|
266
|
+
### Function Parameter Contracts
|
|
267
|
+
|
|
268
|
+
Beyond validating access at the point it happens, the checker infers a *contract* for any function's
|
|
269
|
+
first parameter: every column the function needs, drawn from what its body accesses or — taking priority —
|
|
270
|
+
from a schema annotation on the parameter itself. Calling that function with a DataFrame that doesn't
|
|
271
|
+
satisfy the contract is caught at the **call site**, across files:
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
# transforms.py
|
|
275
|
+
def contact_label(customers):
|
|
276
|
+
return customers["name"] + customers["email"]
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
# pipeline.py
|
|
281
|
+
customers = load_customers(path) # inferred columns: {customer_id, name, region}
|
|
282
|
+
contact_label(customers)
|
|
283
|
+
# ✗ missing-column: 'customers' passed to contact_label (transforms.py:2) is missing
|
|
284
|
+
# column(s) {email} — available: {customer_id, name, region}, required: {email, name}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
The contract is resolved *transitively*: if a function only forwards its parameter to other functions
|
|
288
|
+
(`step1 = preprocess(df); step2 = enrich(step1)`), the checker follows the chain and unions their
|
|
289
|
+
requirements, catching a missing column even when no single function in the chain touches it directly.
|
|
290
|
+
Column-list slices (`df[["a", "b"]]`) contribute to the contract too.
|
|
291
|
+
|
|
292
|
+
**Known limitations:**
|
|
293
|
+
- Cross-file delegate/schema resolution follows `from module import name`, plain `import module` +
|
|
294
|
+
`module.helper(df)` attribute access, and `from module import *` wildcard imports. A dotted import with no
|
|
295
|
+
alias (`import a.b.c`) only binds the first segment (`a`), matching Python's own binding rules, so a
|
|
296
|
+
deeply nested submodule accessed without an alias is not tracked.
|
|
297
|
+
- Contract inference is a single top-to-bottom pass over a function body, not full control-flow analysis.
|
|
298
|
+
Deeply nested control flow (nested `try`/`except`, `match`, comprehensions) may under-report a function's
|
|
299
|
+
true requirements.
|
|
300
|
+
- A cycle in the delegate graph (mutually- or self-delegating helpers) contributes only each function's own
|
|
301
|
+
direct requirements to the cycle, not the full transitive union — conservative rather than exhaustive.
|
|
302
|
+
- If two plainly-imported modules both define a same-named function, an attribute-style delegate call
|
|
303
|
+
(`module.helper(df)`) resolves to whichever one is found first — the checker doesn't disambiguate by which
|
|
304
|
+
module the call site actually used.
|
|
305
|
+
|
|
266
306
|
### See Also
|
|
267
307
|
|
|
268
308
|
- [`examples/inference_example.py`](examples/inference_example.py) — single-file walkthrough of all four inference
|
|
269
309
|
scenarios with annotated ✓/✗ comments.
|
|
270
310
|
- [`examples/multi_file_inference/`](examples/multi_file_inference/) — multi-file project checked with
|
|
271
|
-
`typedframes check examples/multi_file_inference/`; no `BaseSchema` anywhere.
|
|
311
|
+
`typedframes check examples/multi_file_inference/`; no `BaseSchema` anywhere. Includes a function
|
|
312
|
+
parameter contract violation caught at the call site (`missing-column`).
|
|
272
313
|
- [`examples/multi_file_with_schema/`](examples/multi_file_with_schema/) — same scenario with `BaseSchema`
|
|
273
314
|
classes; the checker follows schemas across module boundaries via the project index.
|
|
274
315
|
|
|
@@ -293,6 +334,8 @@ typedframes check src/
|
|
|
293
334
|
**Features:**
|
|
294
335
|
- Catches column name errors
|
|
295
336
|
- Validates schema mismatches between functions
|
|
337
|
+
- Validates function parameter contracts across files, including transitively through chains of
|
|
338
|
+
helper functions (`missing-column`)
|
|
296
339
|
- Checks both pandas and polars code
|
|
297
340
|
- Significantly faster than mypy (see benchmarks below)
|
|
298
341
|
|
|
@@ -373,17 +416,18 @@ code reference.
|
|
|
373
416
|
|
|
374
417
|
Fast feedback reduces development time. The typedframes Rust binary provides near-instant column checking.
|
|
375
418
|
|
|
376
|
-
**Benchmark results** (
|
|
419
|
+
**Benchmark results** (20 runs, 3 warmup, caches cleared between runs):
|
|
420
|
+
*2026-07-13 · Darwin 25.5.0 · arm · CPython 3.14.4 · 64GiB RAM · Great Expectations pinned @ 1.9.3*
|
|
377
421
|
|
|
378
|
-
| Tool | Version | What it does | typedframes (13 files) | great_expectations (
|
|
422
|
+
| Tool | Version | What it does | typedframes (13 files) | great_expectations (482 files) |
|
|
379
423
|
|------|---------|--------------|------------------------|--------------------------------|
|
|
380
|
-
| typedframes | 0.
|
|
381
|
-
| ruff | 0.15.
|
|
382
|
-
| ty | 0.0.
|
|
383
|
-
| pyrefly |
|
|
384
|
-
| mypy |
|
|
385
|
-
| mypy + typedframes |
|
|
386
|
-
| pyright | 1.1.
|
|
424
|
+
| typedframes | 0.3.1 | DataFrame column checker | 43ms ±668µs | 213ms ±4ms |
|
|
425
|
+
| ruff | 0.15.21 | Linter (no type checking) | 26ms ±1ms | 193ms ±2ms |
|
|
426
|
+
| ty | 0.0.58 | Type checker | 72ms ±3ms | 181ms ±9ms |
|
|
427
|
+
| pyrefly | 1.1.1 | Type checker | 107ms ±4ms | 557ms ±16ms |
|
|
428
|
+
| mypy | 2.2.0 | Type checker (no plugin) | 2.77s ±41ms | 4.21s ±71ms |
|
|
429
|
+
| mypy + typedframes | 2.2.0 | Type checker + column checker | 2.78s ±34ms | 4.48s ±63ms |
|
|
430
|
+
| pyright | 1.1.411 | Type checker | 743ms ±8ms | 3.34s ±43ms |
|
|
387
431
|
|
|
388
432
|
*Run `uv run python benchmarks/benchmark_checkers.py` to reproduce.*
|
|
389
433
|
|
|
@@ -596,7 +640,7 @@ Comprehensive comparison of pandas/DataFrame typing and validation tools. **type
|
|
|
596
640
|
|
|
597
641
|
| Feature | typedframes | Pandera | Great Expectations | strictly_typed_pandas | pandas-stubs | dataenforce | pandas-type-checks | StaticFrame | narwhals | dataframely | patito |
|
|
598
642
|
|---------------------------------|------------------------|-------------|--------------------|-----------------------|--------------|-------------|--------------------|------------------|----------|------------------|------------------|
|
|
599
|
-
| **Version tested** | 0.
|
|
643
|
+
| **Version tested** | 0.3.1 | 0.32.1 | 1.18.2 | 0.3.7 | 3.0.3 | 0.1.2 | 1.1.3 | 5.0.0 | 2.23.0 | 2.13.0 | 0.8.6 |
|
|
600
644
|
| **Analysis Type** |
|
|
601
645
|
| When errors are caught | **Static (lint-time)** | Runtime | Runtime | Runtime | Static | Runtime | Runtime | Runtime | Runtime | Runtime | Runtime |
|
|
602
646
|
| **Static Analysis (our focus)** |
|
|
@@ -622,7 +666,7 @@ Comprehensive comparison of pandas/DataFrame typing and validation tools. **type
|
|
|
622
666
|
|
|
623
667
|
### Tool Descriptions
|
|
624
668
|
|
|
625
|
-
- **[Pandera](https://pandera.readthedocs.io/)** (v0.
|
|
669
|
+
- **[Pandera](https://pandera.readthedocs.io/)** (v0.32.1): Excellent runtime validation. Static analysis support exists
|
|
626
670
|
but has limitations—column access via `df["column"]` is not validated, and schema mismatches between functions may not
|
|
627
671
|
be caught.
|
|
628
672
|
|
|
@@ -630,30 +674,30 @@ Comprehensive comparison of pandas/DataFrame typing and validation tools. **type
|
|
|
630
674
|
hints for runtime validation via typeguard. Despite documentation implying mypy support, there is no mypy plugin —
|
|
631
675
|
column access errors are not caught statically. No standalone checker. No polars support.
|
|
632
676
|
|
|
633
|
-
- **[pandas-stubs](https://github.com/pandas-dev/pandas-stubs)** (v3.0.
|
|
677
|
+
- **[pandas-stubs](https://github.com/pandas-dev/pandas-stubs)** (v3.0.3): Official pandas type stubs. Provides
|
|
634
678
|
API-level types but no column-level checking.
|
|
635
679
|
|
|
636
680
|
- **[dataenforce](https://github.com/CedricFR/dataenforce)** (v0.1.2): Runtime validation via decorator. Marked as
|
|
637
|
-
experimental/not production-ready. Appears inactive.
|
|
681
|
+
experimental/not production-ready. Appears inactive. Broken on Python 3.13+ due to removal of internal typing APIs.
|
|
638
682
|
|
|
639
683
|
- **[pandas-type-checks](https://pypi.org/project/pandas-type-checks/)** (v1.1.3): Runtime validation decorator. No
|
|
640
684
|
static analysis.
|
|
641
685
|
|
|
642
|
-
- **[StaticFrame](https://github.com/static-frame/static-frame)** (
|
|
686
|
+
- **[StaticFrame](https://github.com/static-frame/static-frame)** (v5.0.0): Alternative immutable DataFrame library.
|
|
643
687
|
Not compatible with pandas/polars — requires a full rewrite to StaticFrame's own API. Column access is still
|
|
644
688
|
string-based; mypy does not catch column name typos. Type safety comes from immutability guarantees, not schema checking.
|
|
645
689
|
|
|
646
|
-
- **[narwhals](https://narwhals-dev.github.io/narwhals/)** (v2.
|
|
690
|
+
- **[narwhals](https://narwhals-dev.github.io/narwhals/)** (v2.23.0): Compatibility layer that provides a unified API
|
|
647
691
|
across pandas, polars, DuckDB, cuDF, and more. Solves a different problem—write-once-run-anywhere portability, not
|
|
648
692
|
type safety. See [Why Abstraction Layers Don't Solve Type Safety](#why-abstraction-layers-dont-solve-type-safety)
|
|
649
693
|
below.
|
|
650
694
|
|
|
651
|
-
- **[Great Expectations](https://greatexpectations.io/)** (v1.18.
|
|
695
|
+
- **[Great Expectations](https://greatexpectations.io/)** (v1.18.2): Comprehensive data quality framework. Defines
|
|
652
696
|
"expectations" (assertions) about data values, distributions, and schema properties. Excellent for runtime
|
|
653
697
|
validation, data documentation, and data quality monitoring. No static analysis or column-level type checking in
|
|
654
698
|
code. Supports pandas, Spark, and SQL backends.
|
|
655
699
|
|
|
656
|
-
- **[dataframely](https://github.com/Quantco/dataframely)** (v2.
|
|
700
|
+
- **[dataframely](https://github.com/Quantco/dataframely)** (v2.13.0): Polars-only runtime validation library from Quantco.
|
|
657
701
|
Schemas are defined as classes inheriting `dy.Schema` with typed descriptor fields (`dy.String()`, `dy.Float64()`)
|
|
658
702
|
and `@dy.rule()` decorators for cross-column and group-level constraints. Returns `dy.DataFrame[Schema]` generic
|
|
659
703
|
types that give call-site narrowing to type checkers, but does not validate column subscript access inside function
|
|
@@ -958,6 +1002,8 @@ MIT License - see [LICENSE](LICENSE)
|
|
|
958
1002
|
- [x] Pandera integration (`to_pandera_schema()`)
|
|
959
1003
|
- [x] Cross-file schema inference (project-level index, `--no-index` flag)
|
|
960
1004
|
- [x] Aggressive column inference (untracked-dataframe/dropped-unknown-column warnings, method chain propagation)
|
|
1005
|
+
- [x] Function parameter contracts (`missing-column`), resolved transitively across chains of helper
|
|
1006
|
+
functions and cross-file calls; schema-annotated parameters take priority over body-scanning
|
|
961
1007
|
|
|
962
1008
|
**Planned:**
|
|
963
1009
|
|
|
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "typedframes"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.1"
|
|
8
8
|
description = "Static analysis for pandas and polars DataFrames. Catch column errors at lint-time, not runtime."
|
|
9
9
|
keywords = ["pandas", "polars", "type-checking", "static-analysis", "dataframe", "linter", "mypy-plugin"]
|
|
10
10
|
readme = "README.md"
|
|
@@ -229,9 +229,9 @@ dependencies = [
|
|
|
229
229
|
|
|
230
230
|
[[package]]
|
|
231
231
|
name = "crossbeam-epoch"
|
|
232
|
-
version = "0.9.
|
|
232
|
+
version = "0.9.20"
|
|
233
233
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
234
|
-
checksum = "
|
|
234
|
+
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
|
|
235
235
|
dependencies = [
|
|
236
236
|
"crossbeam-utils",
|
|
237
237
|
]
|
|
@@ -1109,7 +1109,7 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
|
|
1109
1109
|
|
|
1110
1110
|
[[package]]
|
|
1111
1111
|
name = "typedframes_checker"
|
|
1112
|
-
version = "0.
|
|
1112
|
+
version = "0.3.1"
|
|
1113
1113
|
dependencies = [
|
|
1114
1114
|
"anyhow",
|
|
1115
1115
|
"criterion",
|