patito

repository·main·Indexed 20 days ago

https://github.com/jakobgm/patito

A dataframe modelling library built on top of polars and pydantic. Patito enables type-annotated, schema-driven data frame logic by allowing developers to define data models that serve as both validation schemas for Polars DataFrames and object-oriented representations of individual rows. It provides tools for schema validation, field constraints via patito.Field, mock data generation, and a model-aware patito.DataFrame class that extends polars.DataFrame.

Tokens
16.4K
Snippets
62
Records
84
Agent score
69%

What's inside patito

  1. Overview of Patito features

    main

    Patito allows you to declare Pydantic data models that serve as schemas for Polars DataFrames. This provides several key capabilities:

    • Data Frame Validation: Performant validation of Polars DataFrames against your models.
    • Mock Data Generation: Easily generate valid mock DataFrames for testing purposes.
    • Object-Oriented Row Access: Retrieve and represent individual rows as Python objects.
    • Single Source of Truth: Maintain core data models in one place for your entire codebase.

    Patito features first-class support for the polars library.

  2. How patito.Model maps types to Polars

    main

    Patito automatically maps Python type hints to corresponding Polars data types:

    • str maps to pl.Utf8
    • int maps to pl.Int8, pl.Int16, pl.Int32, or pl.Int64 depending on the value.
    • float maps to pl.Float64 (or similar float types).
    • Fields wrapped in typing.Optional allow null values in the DataFrame, whereas bare types do not.
  3. Use patito.DataFrame for validated data manipulation

    main

    The patito.DataFrame is the core data structure in Patito. It extends standard DataFrame capabilities by integrating with Pydantic models to provide data validation and model-driven operations. You can use it to read data, cast columns to specific types, derive new columns based on models, and validate the entire dataset against a schema.

    Key capabilities include:

    • Validation: Ensure all rows conform to a specific Pydantic model using .validate().
    • Model Integration: Associate a Pydantic model with the DataFrame using .set_model() and iterate over validated objects using .iter_models().
    • Data Transformation: Perform common operations like .cast(), .derive(), .drop(), and .fill_null() while maintaining awareness of the underlying schema.
    import patito
    import pandas as "pandas"
    from pydantic import BaseModel
    
    class MyModel(BaseModel):
        name: str
        age: int
    
    # Create a patito.DataFrame from a pandas DataFrame
    df = patito.DataFrame(pandas.DataFrame({"name": ["Alice"], "age": [30]}))
    # Set the model for validation
    df.set_model(MyModel)
    # Validate the data
    df.validate()
  4. Represent rows as classes with Patito Models

    main

    Patito allows you to bridge the gap between vectorized data frame operations and row-level logic by representing rows as Python classes. By inheriting from patito.Model, you can define schema via type hints and embed business logic using standard Python methods or properties. This is useful when you need to perform complex operations on a single row that are difficult to express in vectorized form.

    import patito as pt
    
    class Product(pt.Model):
        product_id: int = pt.Field(unique=True)
        name: str
    
        @property
        def url(self) -> str:
            return (
                "https://example.com/no/products/"
                f"{self.product_id}-"
                f"{self.name.lower().replace(' ', '-')}"
            )
  5. Define a data schema using patito.Model

    main

    To define a schema for your Polars data frames, create a subclass of patito.Model. The class represents the schema of the data frame, while instances of the class represent individual rows. You can use standard Python type annotations and patito.Field for additional constraints.

    from typing import Literal
    import patito as pt
    
    class Product(pt.Model):
        product_id: int = pt.Field(unique=True)
        temperature_zone: Literal["dry", "cold", "frozen"]
        is_for_sale: bool
  6. Use patito.DataFrame for model-aware operations

    main

    The patito.DataFrame class extends polars.DataFrame to provide methods that interact with a patito.Model. You can attach a model to a DataFrame using .set_model(Model) or by using the shorthand Model.DataFrame(...).

    import patito as pt
    import polars as pl
    
    # Shorthand for pt.DataFrame(...).set_model(Product)
    df = Product.DataFrame({"product_id": [1], "is_for_sale": [True]})
  7. Use patito.Model for data validation and modeling

    main

    The patito.Model class is the core abstraction in Patito, used to define schemas and perform data validation on structured data. It provides methods to interface with both Pandas DataFrame and Polars LazyFrame objects, allowing you to enforce types, check for nullability, and validate data integrity against a defined model structure.

    import patito
    
    # Example of defining a model
    class MyModel(patito.Model):
        field_name: str
        age: int
  8. Define a data model with patito.Model

    main

    To validate data in Patito, define a class that inherits from patito.Model. Each field in the class represents a column in your Polars DataFrame. You can use standard Python type hints (e.g., int, str, float) and typing.Literal to enforce specific allowed values.

    Because Patito is built on top of Pydantic, your models support both singular object instance validation and collection (DataFrame) validation using the same class definition.

    from typing import Literal
    import patito as pt
    
    class Product(pt.Model):
        product_id: int
        name: str
        temperature_zone: Literal["dry", "cold", "frozen"]
        demand_percentage: float
  9. Define constraints and derived fields using Polars expressions

    main

    When defining constraints or derived_from in ColumnInfo, you can use standard Polars expressions.

    • Constraints: These are used for validation. Every row in the DataFrame must satisfy the expression. If you are using Patito's field referencing, pt.field can be used to automatically resolve to pl.col(<field_name>) during evaluation.
    • Derived Fields: The derived_from attribute accepts either a string (representing a column name) or a Polars expression. This expression is executed when the pt.DataFrame.derive method is invoked to populate the column based on other existing columns.
  10. Serialize ColumnInfo to JSON

    main

    The ColumnInfo object is designed to be serializable to JSON. During serialization:

    • dtype is converted to its string representation (e.g., pl.Float32 becomes 'Float32').
    • constraints and derived_from Polars expressions are serialized using Polars' internal JSON serialization format (e.meta.serialize(format="json")).
    • None values are represented as the JSON literal null.
  11. How DtypeResolver works

    main

    The DtypeResolver is an internal utility used by Patito to bridge the gap between Pydantic type annotations and Polars data types. It works by:

    1. Converting a Python annotation into a JSON schema using Pydantic's TypeAdapter (using mode='serialization').
    2. Inspecting the schema to determine which Polars types are valid (valid_polars_dtypes) and which single Polars type should be used as the default (default_polars_dtype).

    Key Capabilities:

    • Nested Models: Supports nested models by resolving them into Polars Struct types.
    • Lists/Arrays: Resolves Python lists (e.g., list[int]) into Polars List types.
    • Any Type: If an annotation is Any, it defaults to pl.String().