Skip to main content

typewing

Type-safe Ibis table extensions with SQLModel-like interface. Create typed models for your database tables and get full IDE autocomplete support while preserving all of Ibis's powerful query building capabilities.

Features

  • SQLModel-like API: Define your table schemas using Python type annotations
  • Full IDE Autocomplete: Get intelligent code completion for your table columns and their types
  • Native Ibis Integration: All Ibis query operations work seamlessly
  • Type Safety: Leverage Python's type system for safer database queries
  • Field Aliases: Map Python field names to different database column names
  • Field Metadata: Add descriptions and other metadata to your fields

Installation

pip install typewing

Or with uv:

uv add typewing

Quick Start

from typewing import IbisModel, Field
import ibis

# Define your model with type annotations
class User(IbisModel):
    __tablename__ = "users"

    id: int
    name: str
    email: str
    age: int | None = Field(description="User's age in years")
    is_active: bool = Field(alias="active")

# Connect to your database
con = ibis.duckdb.connect()

# Bind the model to get a typed table
UserTable = User.bind(con)

# Use all of Ibis's query building with full type safety!
query = (
    UserTable
    .filter(UserTable.age > 18)
    .filter(UserTable.is_active == True)
    .select("name", "email")
    .order_by("name")
    .limit(10)
)

# Execute and get results
results = query.execute()

Detailed Usage

Defining Models

Models are defined by inheriting from IbisModel and using type annotations:

from typewing import IbisModel, Field

class Product(IbisModel):
    # Explicit table name (optional - defaults to lowercase class name)
    __tablename__ = "products"

    # Simple typed fields
    product_id: int
    name: str
    price: float

    # Optional fields using Union type
    description: str | None

    # Fields with metadata
    category: str = Field(description="Product category")

    # Fields with database column aliases
    is_available: bool = Field(alias="available")

Table Names

If you don't specify __tablename__, the table name defaults to the lowercase class name:

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

# Table name will be "user"

Field Aliases

Use the alias parameter to map Python field names to different database column names:

class User(IbisModel):
    user_id: int = Field(alias="id")
    full_name: str = Field(alias="name")
    is_active: bool = Field(alias="active")

# Now you can use Python-style names in your code:
UserTable = User.bind(con)
query = UserTable.filter(UserTable.is_active == True)
# But it maps to the actual database column "active"

Binding to a Database

Before you can query, bind your model to an Ibis connection:

import ibis

# Create a connection (DuckDB, PostgreSQL, etc.)
con = ibis.duckdb.connect()

# Bind the model
UserTable = User.bind(con)

# Optionally override the table name
CustomTable = User.bind(con, table_name="custom_users")

Query Building

The bound table supports all Ibis operations:

# Filtering
active_users = UserTable.filter(UserTable.is_active == True)

# Selecting columns
names = UserTable.select("name", "email")

# Chaining operations
query = (
    UserTable
    .filter(UserTable.age > 18)
    .filter(UserTable.name.like("A%"))
    .select("name", "email", "age")
    .order_by(UserTable.age.desc())
    .limit(10)
)

# Aggregations
user_count = UserTable.count().execute()
avg_age = UserTable.age.mean().execute()

# Grouping
by_category = (
    ProductTable
    .group_by("category")
    .aggregate(
        count=ProductTable.count(),
        avg_price=ProductTable.price.mean()
    )
)

# Joins (with other ibis tables)
orders = con.table("orders")
query = UserTable.join(orders, UserTable.id == orders.user_id)

Executing Queries

Execute queries using the .execute() method:

# Returns a pandas DataFrame
results = query.execute()

# Iterate over results
for index, row in results.iterrows():
    print(row["name"], row["email"])

Working with Multiple Backends

typewing works with any Ibis backend:

# DuckDB
con = ibis.duckdb.connect()

# PostgreSQL
con = ibis.postgres.connect(
    host="localhost",
    database="mydb",
    user="user",
    password="password"
)

# SQLite
con = ibis.sqlite.connect("database.db")

# And many more: BigQuery, Snowflake, Trino, etc.
UserTable = User.bind(con)

IDE Autocomplete Support

One of the key features is full IDE autocomplete support. When you access fields on your bound table, your IDE will:

  • Show all available fields
  • Display their types
  • Show field descriptions (from Field() metadata)
  • Provide autocomplete for Ibis methods
UserTable = User.bind(con)

# Your IDE will autocomplete:
UserTable.name       #  knows it's a string column
UserTable.age        #  knows it's an int | None column
UserTable.is_active  #  knows it's a bool column
UserTable.filter(    #  autocompletes Ibis methods

Advanced Usage

Getting Field Metadata

Access field information programmatically:

# Get all fields
fields = User.get_fields()

# Get field names
field_names = User.get_field_names()
# ['id', 'name', 'email', 'age', 'is_active']

# Get field types
field_types = User.get_field_types()
# {'id': int, 'name': str, 'email': str, ...}

# Get table name
table_name = User.get_table_name()
# 'users'

# Get column name (handles aliases)
col_name = User.get_column_name('is_active')
# 'active'

Using with Ibis Expressions

You can mix typed tables with regular Ibis expressions:

import ibis

UserTable = User.bind(con)

# Use ibis functions
query = UserTable.filter(
    ibis.and_(
        UserTable.age > 18,
        UserTable.name.length() > 3
    )
)

# Window functions
query = UserTable.mutate(
    rank=ibis.rank().over(order_by=UserTable.age.desc())
)

Comparison with SQLModel

If you're familiar with SQLModel, here's how typewing compares:

Feature SQLModel typewing
Type annotations  
IDE autocomplete  
Field metadata  
ORM-style queries   (uses Ibis)
Multiple backends Limited  (20+ via Ibis)
Complex queries Via SQLAlchemy  Native Ibis
Data validation  
Model instances   (returns DataFrames)

typewing is designed for:

  • Complex analytical queries where Ibis excels
  • Working with multiple database backends with a unified API
  • Type-safe query building without ORM overhead
  • Data engineering/analysis workflows

Examples

Example 1: User Analytics

from typewing import IbisModel, Field
import ibis

class User(IbisModel):
    __tablename__ = "users"

    id: int
    email: str
    signup_date: str  # Or use datetime if your backend supports it
    age: int | None
    country: str

con = ibis.duckdb.connect("analytics.db")
UserTable = User.bind(con)

# Find users by country
us_users = UserTable.filter(UserTable.country == "US")

# Age distribution
age_stats = UserTable.filter(UserTable.age.notnull()).aggregate(
    avg_age=UserTable.age.mean(),
    min_age=UserTable.age.min(),
    max_age=UserTable.age.max(),
    count=UserTable.count()
).execute()

print(f"Average age: {age_stats['avg_age'][0]:.1f}")

Example 2: E-commerce Products

class Product(IbisModel):
    __tablename__ = "products"

    id: int
    name: str
    category: str
    price: float
    stock: int
    is_active: bool = Field(alias="active")

con = ibis.postgres.connect(...)
ProductTable = Product.bind(con)

# Find affordable products in stock
query = (
    ProductTable
    .filter(ProductTable.is_active == True)
    .filter(ProductTable.stock > 0)
    .filter(ProductTable.price < 50)
    .order_by(ProductTable.price.asc())
)

affordable_products = query.execute()

Example 3: Sales Analytics

class Sale(IbisModel):
    __tablename__ = "sales"

    sale_id: int
    product_id: int
    quantity: int
    revenue: float
    sale_date: str

con = ibis.duckdb.connect()
SaleTable = Sale.bind(con)

# Revenue by day
daily_revenue = (
    SaleTable
    .group_by("sale_date")
    .aggregate(
        total_revenue=SaleTable.revenue.sum(),
        total_quantity=SaleTable.quantity.sum(),
        num_sales=SaleTable.count()
    )
    .order_by("sale_date")
)

results = daily_revenue.execute()

Testing

Run the test suite:

# Install dev dependencies
uv sync --extra dev

# Run tests
uv run pytest tests/ -v

# With coverage
uv run pytest tests/ --cov=typewing --cov-report=html

Contributing

Contributions are welcome! Please feel free to submit issues or pull requests.

License

This project is licensed under the MIT License.

Acknowledgments

  • Built on top of Ibis - the portable dataframe library
  • Inspired by SQLModel - the SQL database framework

Related Projects

  • Ibis - The portable Python dataframe library
  • SQLModel - SQL databases in Python, designed for simplicity
  • SQLAlchemy - The Python SQL toolkit

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

typewing-0.0.1.tar.gz (37.8 kB view details)

Uploaded Source

Built Distribution

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

typewing-0.0.1-py3-none-any.whl (7.5 kB view details)

Uploaded Python 3

File details

Details for the file typewing-0.0.1.tar.gz.

File metadata

  • Download URL: typewing-0.0.1.tar.gz
  • Upload date:
  • Size: 37.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.6

File hashes

Hashes for typewing-0.0.1.tar.gz
Algorithm Hash digest
SHA256 b4083589d70bd33ac7e23224872d6296cdea0e41a1190b1ab823b9db4897055f
MD5 d86f9c4d6d1932bd31c84af23a864354
BLAKE2b-256 592e45b560910ab2bcc962f6de5746adddb0c1e987add4f28e9a1c71bee36190

See more details on using hashes here.

File details

Details for the file typewing-0.0.1-py3-none-any.whl.

File metadata

  • Download URL: typewing-0.0.1-py3-none-any.whl
  • Upload date:
  • Size: 7.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.6

File hashes

Hashes for typewing-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 76cb7636c840e8196a99e76db524f29069357c6063b160462c7873f1727f5164
MD5 b2604ab21d519730666a68baa2e66ef0
BLAKE2b-256 6c183ff31a4945c8359035a12c77399f3c6c14951551ded90bc29de23ca18d01

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.0.1 This release

2 files

Supported by

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