A statically type-safe DataFrame abstraction layer
Project description
Colnade
A statically type-safe DataFrame abstraction layer for Python.
Colnade replaces string-based column references (pl.col("age")) with typed descriptors (Users.age), so column misspellings, type mismatches, and schema violations are caught by your type checker — before your code runs.
Works with ty, mypy, and pyright. No plugins, no code generation.
Installation
pip install colnade colnade-polars
Colnade requires Python 3.10+. The colnade-polars package provides the Polars backend adapter.
Quick Start
1. Define a schema
from colnade import Column, Schema, UInt64, Float64, Utf8
class Users(Schema):
id: Column[UInt64]
name: Column[Utf8]
age: Column[UInt64]
score: Column[Float64]
2. Read typed data
from colnade_polars import read_parquet
df = read_parquet("users.parquet", Users)
# df is DataFrame[Users] — the type checker knows the schema
3. Transform with full type safety
# Column references are attributes, not strings
result = (
df.filter(Users.age > 25)
.sort(Users.score.desc())
.select(Users.name, Users.score)
)
4. Bind to an output schema
class UserSummary(Schema):
name: Column[Utf8]
score: Column[Float64]
output = result.cast_schema(UserSummary)
# output is DataFrame[UserSummary]
Key Features
Type-safe column references
Column references are class attributes verified by the type checker at lint time:
Users.name # Column[Utf8] — valid
Users.naem # ty error: Class `Users` has no attribute `naem`
Schema-preserving operations
Operations that don't change the schema (filter, sort, limit, with_columns) preserve the type parameter:
def process(df: DataFrame[Users]) -> DataFrame[Users]:
return df.filter(Users.age > 25).sort(Users.score.desc())
Typed expressions
Column descriptors build an expression tree with typed operators:
Users.age > 18 # Expr[Bool] — comparison
Users.score * 2 # Expr[Float64] — arithmetic
(Users.age > 18) & (Users.score > 80) # Expr[Bool] — logical
Users.name.str_starts_with("A") # Expr[Bool] — string method
Aggregations
result = df.group_by(Users.name).agg(
Users.score.mean().alias(UserStats.avg_score),
Users.id.count().alias(UserStats.user_count),
)
Null handling
# Fill nulls, filter nulls, check nulls
df.with_columns(Users.score.fill_null(0.0).alias(Users.score))
df.filter(Users.score.is_not_null())
df.drop_nulls(Users.score)
Joins with typed output
joined = users.join(orders, on=Users.id == Orders.user_id)
# JoinedDataFrame[Users, Orders] — both schemas accessible
class UserOrders(Schema):
user_name: Column[Utf8] = mapped_from(Users.name)
amount: Column[Float64]
result = joined.cast_schema(UserOrders)
Schema-polymorphic utility functions
Write generic functions that work with any schema:
from colnade.schema import S
def first_n(df: DataFrame[S], n: int) -> DataFrame[S]:
return df.head(n)
# Works with any schema — type preserved
users_subset: DataFrame[Users] = first_n(users_df, 10)
Struct and List support
class Address(Schema):
city: Column[Utf8]
zip_code: Column[Utf8]
class UserProfile(Schema):
name: Column[Utf8]
address: Column[Struct[Address]]
tags: Column[List[Utf8]]
# Access nested data
df.filter(UserProfile.address.field(Address.city) == "New York")
df.with_columns(UserProfile.tags.list.len().alias(tag_count_col))
Lazy execution
from colnade_polars import scan_parquet
lazy = scan_parquet("users.parquet", Users)
# LazyFrame[Users] — builds a query plan
result = lazy.filter(Users.age > 25).sort(Users.score.desc()).collect()
# Executes the optimized query plan
Untyped escape hatch
When you need to drop down to untyped operations:
untyped = df.untyped() # UntypedDataFrame — string-based columns
retyped = untyped.to_typed(Users) # Back to DataFrame[Users]
Type Checker Error Showcase
Colnade catches real errors at lint time. Here are actual error messages from ty:
Misspelled column name
x = Users.agee
error[unresolved-attribute]: Class `Users` has no attribute `agee`
Schema mismatch at function boundary
df: DataFrame[Users] = read_parquet("users.parquet", Users)
wrong: DataFrame[Orders] = df
error[invalid-assignment]: Object of type `DataFrame[Users]` is not assignable
to `DataFrame[Orders]`
Nullability mismatch in mapped_from
class Bad(Schema):
age: Column[UInt8] = mapped_from(Users.age) # Users.age is Column[UInt8 | None]
error[invalid-assignment]: Object of type `Column[UInt8 | None]` is not
assignable to `Column[UInt8]`
Comparison with Existing Solutions
| Feature | Colnade | Pandera | StaticFrame | Patito |
|---|---|---|---|---|
| Column refs checked statically | Yes | No | No | No |
| Schema preserved through ops | Yes | Nominal only | No | No |
| Works with existing engines | Yes | Yes | No | Polars only |
| No plugins or code gen | Yes | No (mypy plugin) | Yes | Yes |
| Generic utility functions | Yes | No | No | No |
| Struct/List typed access | Yes | No | No | No |
Documentation
Full documentation is available at colnade.com, including:
- Getting Started — installation and quick start
- User Guide — concepts, schemas, expressions, joins
- Tutorials — worked examples with real data
- API Reference — auto-generated from source
Examples
Runnable examples are in the examples/ directory:
basic_usage.py— Schema definition, filter, select, aggregatenull_handling.py— Nullable columns, fill_null, drop_nullsjoins.py— Joining DataFrames, JoinedDataFrame, cast_schemageneric_functions.py— Schema-polymorphic utility functionsnested_types.py— Struct and List column operationsfull_pipeline.py— Complete ETL pipeline example
License
MIT
Project details
Release history Release notifications | RSS feed
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 colnade-0.1.2.tar.gz.
File metadata
- Download URL: colnade-0.1.2.tar.gz
- Upload date:
- Size: 174.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3948975f483df36d03a1274e33d6d30d1f1a24c95fc858c175c3da2b787c1e70
|
|
| MD5 |
d2c706bf147f954eb763959bc51d24b1
|
|
| BLAKE2b-256 |
9d60eea3b49b11a9218074619237ccd51ebda96f9d510528e32f181e9d2cc578
|
Provenance
The following attestation bundles were made for colnade-0.1.2.tar.gz:
Publisher:
publish.yml on jwde/colnade
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
colnade-0.1.2.tar.gz -
Subject digest:
3948975f483df36d03a1274e33d6d30d1f1a24c95fc858c175c3da2b787c1e70 - Sigstore transparency entry: 952305862
- Sigstore integration time:
-
Permalink:
jwde/colnade@d8d2380525155def0c421ceafd3ecc4448a4effa -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/jwde
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d8d2380525155def0c421ceafd3ecc4448a4effa -
Trigger Event:
release
-
Statement type:
File details
Details for the file colnade-0.1.2-py3-none-any.whl.
File metadata
- Download URL: colnade-0.1.2-py3-none-any.whl
- Upload date:
- Size: 22.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cd830edf5442a150bf0b3063528a11b96925e7d449b8160fcacbc90eac665edc
|
|
| MD5 |
09f33dc11deb4f58ed3bbf1cd445393f
|
|
| BLAKE2b-256 |
f885410057df4d8cd068fd0eecebfd36b782a1bc48e915a21aec58c4f13d09ab
|
Provenance
The following attestation bundles were made for colnade-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on jwde/colnade
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
colnade-0.1.2-py3-none-any.whl -
Subject digest:
cd830edf5442a150bf0b3063528a11b96925e7d449b8160fcacbc90eac665edc - Sigstore transparency entry: 952305863
- Sigstore integration time:
-
Permalink:
jwde/colnade@d8d2380525155def0c421ceafd3ecc4448a4effa -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/jwde
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d8d2380525155def0c421ceafd3ecc4448a4effa -
Trigger Event:
release
-
Statement type: