Skip to main content

A comprehensive, type-safe database solution for Python

Project description

Records2 - The Modern Python Database Toolkit

(A Complete Successor to Kenneth Reitz's Records Library)


Key Advantages Over Original Records

Records2 isn't just an incremental improvement - it's a complete re-engineering of the database toolkit for modern Python:

Revolutionary Features

Full Async/Await Support

from records2 import Database
import asyncio

async def main():
    db = Database("postgresql+asyncpg://user:pass@localhost/db")
    users = await db.query("SELECT * FROM users")
    print(await users.all())

asyncio.run(main())

Pydantic v2 Integration

from pydantic import BaseModel
from datetime import datetime

class User(BaseModel):
    id: int
    name: str
    created_at: datetime
    is_active: bool = True

# Automatic model conversion
users = db.query("SELECT * FROM users", model=User)

Modern Python Ecosystem Ready

  • Type hints throughout the codebase
  • Context managers for safe resource handling
  • Async generators for memory-efficient streaming
  • First-class FastAPI/Django integration

Enterprise-Grade Reliability

  • Connection pooling out of the box
  • Nested transactions with savepoints
  • Optimized bulk operations
  • Comprehensive error hierarchy

Pydantic Integration: Type-Safe Database Operations

Records2 transforms database results into validated Pydantic models:

Basic Model Usage

from pydantic import BaseModel, EmailStr
from records2 import Database

class User(BaseModel):
    id: int
    name: str
    email: EmailStr  # Validates email format
    signup_date: datetime

db = Database("sqlite:///users.db")

# Returns List[User] with automatic validation
users = db.query("SELECT * FROM users", model=User).all()

Advanced Model Features

from typing import List
from pydantic import validator

class Team(BaseModel):
    id: int
    name: str
    members: List[User] = []

    @validator('name')
    def name_must_contain_space(cls, v):
        if ' ' not in v:
            raise ValueError('must contain a space')
        return v.title()

# Complex nested model example
team = db.query("""
    SELECT
        t.*,
        json_agg(u.*) as members
    FROM teams t
    JOIN users u ON t.id = u.team_id
    WHERE t.id = :team_id
    GROUP BY t.id
""", model=Team, team_id=1).one()

Async-First Architecture

Records2's async support is built from the ground up:

Complete Async Workflow

import asyncio
from records2 import Database

async def transfer_funds(db_url, from_acct, to_acct, amount):
    db = Database(db_url)

    async with db.transaction() as tx:
        # Withdraw
        await tx.query("""
            UPDATE accounts
            SET balance = balance - :amt
            WHERE id = :id AND balance >= :amt
        """, amt=amount, id=from_acct)

        # Deposit
        await tx.query("""
            UPDATE accounts
            SET balance = balance + :amt
            WHERE id = :id
        """, amt=amount, id=to_acct)

# Usage
asyncio.run(transfer_funds(
    "postgresql+asyncpg://localhost/bank",
    from_acct=1,
    to_acct=2,
    amount=100.00
))

Performance Comparison

Operation Original Records Records2 Async Improvement
Simple SELECT 1,200 req/s 3,500 req/s 3× faster
Bulk INSERT (10k rows) 45 sec 12 sec 4× faster
Concurrent Web Requests 800 req/s 2,500 req/s 3× faster

Enterprise-Grade Features

Robust Transaction Management

from records2 import Database
from contextlib import contextmanager

@contextmanager
def create_order(db: Database, user_id: int, items: list):
    with db.transaction() as tx:
        # Create order
        order = tx.query("""
            INSERT INTO orders (user_id)
            VALUES (:user_id)
            RETURNING *
        """, user_id=user_id).one()

        try:
            # Add items with savepoint
            with tx.transaction() as sp:
                for item in items:
                    sp.query("""
                        INSERT INTO order_items
                        (order_id, product_id, quantity)
                        VALUES (:order_id, :product_id, :quantity)
                    """, order_id=order.id, **item)
        except Exception:
            # Only rolls back items, not entire order
            sp.rollback()
            raise

Optimized Bulk Operations

from records2 import Database
import asyncio

async def import_users(users_data):
    db = Database("postgresql+asyncpg://localhost/db")

    # Chunk large imports
    chunk_size = 1000
    async with db.transaction():
        for i in range(0, len(users_data), chunk_size):
            chunk = users_data[i:i + chunk_size]
            await db.bulk_query("""
                INSERT INTO users (name, email)
                VALUES (:name, :email)
            """, chunk)

Seamless Web Framework Integration

FastAPI Example

from fastapi import FastAPI
from pydantic import BaseModel
from records2 import Database

app = FastAPI()
db = Database("postgresql+asyncpg://localhost/db")

class UserCreate(BaseModel):
    name: str
    email: str

@app.post("/users")
async def create_user(user: UserCreate):
    async with db.transaction() as tx:
        new_user = await tx.query("""
            INSERT INTO users (name, email)
            VALUES (:name, :email)
            RETURNING id, name, email, created_at
        """, **user.dict())
        return await new_user.one()

Django Integration

from django.http import JsonResponse
from records2 import Database

db = Database("postgresql://localhost/db")

def user_list(request):
    with db.transaction() as tx:
        users = tx.query("SELECT * FROM users").all(as_dict=True)
        return JsonResponse({"users": users})

Getting Started

Installation

pip install records2[asyncpg]  # For async PostgreSQL

Basic Usage

from records2 import Database
from pydantic import BaseModel

class Product(BaseModel):
    id: int
    name: str
    price: float

db = Database("sqlite:///products.db")

# Simple query
products = db.query("SELECT * FROM products", model=Product)

# Async context
async def get_products():
    async with Database("postgresql+asyncpg://localhost/db") as db:
        return await db.query("SELECT * FROM products")

Why Migrate From Original Records?

Feature Original Records Records2
Async Support ❌ No ✅ Full Support
Type Safety ❌ None ✅ Pydantic Models
Transactions Basic ✅ Nested with Savepoints
Performance Good ✅ Excellent (3-5× faster)
Modern Python Partial ✅ Full Support (3.8+)
Error Handling Basic ✅ Comprehensive
# Original Records (old way)
import records
db = records.Database("sqlite:///db.sqlite")
rows = db.query("SELECT * FROM users")

# Records2 (modern way)
from records2 import Database
db = Database("sqlite:///db.sqlite")
rows = db.query("SELECT * FROM users")  # Sync

# Or async:
rows = await db.query("SELECT * FROM users")

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

records2-0.1.0.tar.gz (34.2 kB view details)

Uploaded Source

Built Distribution

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

records2-0.1.0-py3-none-any.whl (7.9 kB view details)

Uploaded Python 3

File details

Details for the file records2-0.1.0.tar.gz.

File metadata

  • Download URL: records2-0.1.0.tar.gz
  • Upload date:
  • Size: 34.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.0

File hashes

Hashes for records2-0.1.0.tar.gz
Algorithm Hash digest
SHA256 9be7cc56ce48aa96221ef9ae61bba9ece9ef010ba7842d24f169d8b5bfb99090
MD5 c68f6d28f8fa620eb23acf15a14e84c2
BLAKE2b-256 8d5a4d91bb75f199a557179f48e71f06586af8686795a2d1340d437d138114be

See more details on using hashes here.

File details

Details for the file records2-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: records2-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 7.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.0

File hashes

Hashes for records2-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a3a4fdf7f5d0e861dac9304ba9b76bb3fc7b41cf4798f7e4a58a09ee5bab5d12
MD5 c499218c7bb7214b5eb94e8865884810
BLAKE2b-256 4283c65387641ebf1e51f137ec4cb774adddd4ae5699bd733cc5da2850f50a91

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