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.
Files changed (24) hide show
  1. {typedframes-0.2.2 → typedframes-0.3.1}/PKG-INFO +67 -21
  2. {typedframes-0.2.2 → typedframes-0.3.1}/README.md +66 -20
  3. {typedframes-0.2.2 → typedframes-0.3.1}/pyproject.toml +1 -1
  4. {typedframes-0.2.2 → typedframes-0.3.1}/rust/Cargo.lock +3 -3
  5. {typedframes-0.2.2 → typedframes-0.3.1}/rust/Cargo.toml +1 -1
  6. {typedframes-0.2.2 → typedframes-0.3.1}/rust/src/lib.rs +1861 -117
  7. {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/__init__.py +1 -1
  8. {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/pandas.py +2 -2
  9. {typedframes-0.2.2 → typedframes-0.3.1}/rust/README.md +0 -0
  10. {typedframes-0.2.2 → typedframes-0.3.1}/rust/benches/parser_bench.rs +0 -0
  11. {typedframes-0.2.2 → typedframes-0.3.1}/rust/src/main.rs +0 -0
  12. {typedframes-0.2.2 → typedframes-0.3.1}/rust/tests/integration_test.rs +0 -0
  13. {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/base_schema.py +0 -0
  14. {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/cli.py +0 -0
  15. {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/column.py +0 -0
  16. {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/column_group.py +0 -0
  17. {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/column_group_error.py +0 -0
  18. {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/column_set.py +0 -0
  19. {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/missing_dependency_error.py +0 -0
  20. {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/mypy.py +0 -0
  21. {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/pandera.py +0 -0
  22. {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/polars.py +0 -0
  23. {typedframes-0.2.2 → typedframes-0.3.1}/src/typedframes/py.typed +0 -0
  24. {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.2.2
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.2.1) is currently an experimental proof-of-concept. The core static analysis and mypy/Rust
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 {order_id, amount, status}
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** (10 runs, 3 warmup, caches cleared between runs):
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 (488 files) |
456
+ | Tool | Version | What it does | typedframes (13 files) | great_expectations (482 files) |
413
457
  |------|---------|--------------|------------------------|--------------------------------|
414
- | typedframes | 0.2.1 | DataFrame column checker | 43ms ±531µs | 285ms ±4ms |
415
- | ruff | 0.15.4 | Linter (no type checking) | 27ms ±932µs | 251ms ±2ms |
416
- | ty | 0.0.19 | Type checker | 67ms ±1ms | 793ms ±14ms |
417
- | pyrefly | 0.54.0 | Type checker | 120ms ±2ms | 1.11s ±13ms |
418
- | mypy | 1.19.1 | Type checker (no plugin) | 3.47s ±15ms | 4.43s ±66ms |
419
- | mypy + typedframes | 1.19.1 | Type checker + column checker | 3.52s ±40ms | 4.79s ±113ms |
420
- | pyright | 1.1.408 | Type checker | 822ms ±55ms | 3.43s ±54ms |
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.2.1 | 0.29.0 | 1.18.0 | 0.3.7 | 3.0.3 | 0.1.2 | 1.1.3 | 4.1.0 | 2.22.1 | 2.10.1 | 0.8.6 |
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.29.0): Excellent runtime validation. Static analysis support exists
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.0): Official pandas type stubs. Provides
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)** (v4.1.0): Alternative immutable DataFrame library.
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.22.1): Compatibility layer that provides a unified API
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.0): Comprehensive data quality framework. Defines
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.10.1): Polars-only runtime validation library from Quantco.
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.2.1) is currently an experimental proof-of-concept. The core static analysis and mypy/Rust
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 {order_id, amount, status}
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** (10 runs, 3 warmup, caches cleared between runs):
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 (488 files) |
422
+ | Tool | Version | What it does | typedframes (13 files) | great_expectations (482 files) |
379
423
  |------|---------|--------------|------------------------|--------------------------------|
380
- | typedframes | 0.2.1 | DataFrame column checker | 43ms ±531µs | 285ms ±4ms |
381
- | ruff | 0.15.4 | Linter (no type checking) | 27ms ±932µs | 251ms ±2ms |
382
- | ty | 0.0.19 | Type checker | 67ms ±1ms | 793ms ±14ms |
383
- | pyrefly | 0.54.0 | Type checker | 120ms ±2ms | 1.11s ±13ms |
384
- | mypy | 1.19.1 | Type checker (no plugin) | 3.47s ±15ms | 4.43s ±66ms |
385
- | mypy + typedframes | 1.19.1 | Type checker + column checker | 3.52s ±40ms | 4.79s ±113ms |
386
- | pyright | 1.1.408 | Type checker | 822ms ±55ms | 3.43s ±54ms |
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.2.1 | 0.29.0 | 1.18.0 | 0.3.7 | 3.0.3 | 0.1.2 | 1.1.3 | 4.1.0 | 2.22.1 | 2.10.1 | 0.8.6 |
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.29.0): Excellent runtime validation. Static analysis support exists
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.0): Official pandas type stubs. Provides
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)** (v4.1.0): Alternative immutable DataFrame library.
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.22.1): Compatibility layer that provides a unified API
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.0): Comprehensive data quality framework. Defines
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.10.1): Polars-only runtime validation library from Quantco.
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.2.2"
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.18"
232
+ version = "0.9.20"
233
233
  source = "registry+https://github.com/rust-lang/crates.io-index"
234
- checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
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.2.2"
1112
+ version = "0.3.1"
1113
1113
  dependencies = [
1114
1114
  "anyhow",
1115
1115
  "criterion",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "typedframes_checker"
3
- version = "0.2.2"
3
+ version = "0.3.1"
4
4
  edition = "2021"
5
5
  description = "A high-performance Rust-based checker for typedframes DataFrame column schemas"
6
6
  license = "MIT"