Skip to main content

Convert Pydantic models to Polars schemas

Project description

🧩 Poldantic

Convert Pydantic models into Polars schemas — and back again.

Poldantic bridges the world of data validation (Pydantic) and blazing-fast computation (Polars). It's ideal for type-safe ETL pipelines, FastAPI response models, and schema round-tripping between Python classes and DataFrames.


✨ Features

  • 🔁 Bidirectional conversion — Pydantic models ⇄ Polars schemas
  • 🧠 Smart handling of nested models, containers (list, set, tuple), enums, Optional, and Annotated
  • 🛠 Sensible fallbacks (pl.Object) for ambiguous types like Union[int, str]
  • 🧪 Tested on a wide variety of primitives, structs, and container types
  • ⚙️ Minimal dependencies — Pydantic v2+, Polars ≥ 0.20 — production‑ready

📦 Install

pip install poldantic

Requires: Python ≥ 3.10, Pydantic ≥ 2.0, Polars ≥ 0.20.0


🚀 Usage

🔄 Pydantic ➜ Polars

from pydantic import BaseModel
from poldantic.infer_polars import to_polars_schema
from typing import Optional, List

class Person(BaseModel):
    name: str
    tags: Optional[List[str]]

schema = to_polars_schema(Person)
print(schema)
# {'name': String, 'tags': List(String)}

Initialize a DataFrame with the schema:

import polars as pl

data = [{"name": "Alice", "tags": ["x"]}, {"name": "Bob", "tags": None}]
df = pl.DataFrame(data, schema=schema)

🔄 Polars ➜ Pydantic

import polars as pl
from poldantic.infer_pydantic import to_pydantic_model

schema = {
    "name": pl.String,
    "tags": pl.List(pl.String())
}

Model = to_pydantic_model(schema)  # fields are Optional[...] by default
print(Model(name="Alice", tags=["x", "y"]))
# name='Alice' tags=['x', 'y']

Pass force_optional=False to require fields on the generated model:

StrictModel = to_pydantic_model(schema, "StrictModel", force_optional=False)

🧬 Nested Models

from pydantic import BaseModel
from poldantic.infer_polars import to_polars_schema

class Address(BaseModel):
    street: str
    zip: int

class Customer(BaseModel):
    id: int
    address: Address

print(to_polars_schema(Customer))
# {'id': Int64, 'address': Struct([('street', String), ('zip', Int64)])}

⚡ FastAPI Integration

from fastapi import FastAPI
from pydantic import BaseModel
import polars as pl
from poldantic.infer_polars import to_polars_schema
from poldantic.infer_pydantic import to_pydantic_model

class User(BaseModel):
    id: int
    name: str

schema = to_polars_schema(User)
UserOut = to_pydantic_model(schema, "UserOut", force_optional=False)

app = FastAPI()

@app.get("/users", response_model=list[UserOut])
def list_users():
    df = pl.DataFrame([{"id": 1, "name": "Ada"}, {"id": 2, "name": "Alan"}], schema=schema)
    return df.to_dicts()

⚙️ Settings

Both directions expose a settings object so you can tweak behavior without forking code.

Pydantic ➜ Polars (poldantic.infer_polars.settings)

from poldantic.infer_polars import settings

# Use pl.Enum for string-valued Python Enums when available (default: True)
settings.use_pl_enum_for_string_enums = True

# Default Decimal precision/scale when encountering `decimal.Decimal`
settings.decimal_precision = 38
settings.decimal_scale = 18

# Represent UUID as pl.String (True) or pl.Object (False)
settings.uuid_as_string = True

Polars ➜ Pydantic (poldantic.infer_pydantic.settings)

from poldantic.infer_pydantic import settings

# Map pl.Duration → datetime.timedelta (True) or int (False)
settings.durations_as_timedelta = True

# Default Decimal instance for reverse mapping (precision/scale)
settings.decimal_precision = 38
settings.decimal_scale = 18

Note: Settings are module‑level and affect conversions performed after they’re changed.


📚 Supported Type Mappings

Python / Pydantic ➜ Polars dtype ➜ back to Python
int pl.Int64() int
float pl.Float64() float
str pl.String() str
bool pl.Boolean() bool
bytes pl.Binary() bytes
datetime.date pl.Date() datetime.date
datetime.datetime pl.Datetime() datetime.datetime
datetime.time pl.Time() datetime.time
datetime.timedelta pl.Duration() datetime.timedelta
Decimal pl.Decimal(p,s) Decimal
Enum[str] pl.Enum([...]) or pl.String() str
list[T], set[T] pl.List(inner) list[T]
tuple[T, ...] pl.List(inner) list[T]
nested BaseModel pl.Struct([...]) nested Pydantic model
Union[int, str], Any pl.Object() Any
dict[...] pl.Object() Any

Ambiguous unions (e.g., Union[int, str]) intentionally map to pl.Object() and back to typing.Any.


🧭 Design Notes

  • Nullability: From-Polars conversion wraps all fields in Optional[...] by default; disable with force_optional=False.
  • Utf8 vs String: Normalized to pl.String for forward compatibility.
  • Structs: Works with tuple fields ("name", dtype) and polars.Field objects.
  • Classes vs Instances: Accepts both pl.Int64 and pl.Int64() in schema dicts.

🧪 Tests

pytest -q

Covers primitives, containers, structs, enums, optionals, and round‑trip inference.


💡 When to use Poldantic

  • You already have Pydantic models and want to validate Polars data against them.
  • You have Polars transformations and want an API response model without writing it by hand.
  • You want type-safe ETL: validate with Pydantic → transform with Polars → publish validated results.

📄 License

MIT © 2025 Odos Matthews

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

poldantic-0.2.2.tar.gz (13.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

poldantic-0.2.2-py3-none-any.whl (9.3 kB view details)

Uploaded Python 3

File details

Details for the file poldantic-0.2.2.tar.gz.

File metadata

  • Download URL: poldantic-0.2.2.tar.gz
  • Upload date:
  • Size: 13.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.7

File hashes

Hashes for poldantic-0.2.2.tar.gz
Algorithm Hash digest
SHA256 3c360c354c4d1d0d0e06adc1a102239617c7af12b08064fafdc02df021f09b52
MD5 4589cc8a49311beb0c153b6fdbc2e0be
BLAKE2b-256 a975ab59967de61e849e18d65578193eee5d291e53863bf22bd7e5345383572f

See more details on using hashes here.

File details

Details for the file poldantic-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: poldantic-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 9.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.7

File hashes

Hashes for poldantic-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 b74a8c63efdc530aa5694fcff2cec3adbd72e67a06742b3ed4a1416cf37d860d
MD5 c530f57d45aca662aefa07989f36869b
BLAKE2b-256 34843da957fb30b6688746e7212cdaf3ab878b47efdaaf2fc59db99c81baa2e3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page