Skip to main content

Typed JSON fields for SQLAlchemy with automatic mutation tracking

Project description

sqlatypemodel

Tests Linting PyPi status PyPi Package Version Python versions Downloads MIT License

Typed JSON fields for SQLAlchemy with automatic mutation tracking

sqlatypemodel solves the "immutable JSON" problem in SQLAlchemy. It allows you to use strictly typed Python objects (Pydantic, Dataclasses, Attrs) as database columns while ensuring that every change—no matter how deep—is automatically saved.

Powered by orjson for blazing-fast performance and featuring a State-Based Architecture for universal compatibility.


📚 Documentation

ReadTheDocs

Full documentation is available in the docs/ directory:


✨ Key Features

  • 🏗️ State-Based Tracking (v0.8.0+):

    • Universal Compatibility: Works natively with unhashable objects (e.g., standard Pydantic models, eq=True Dataclasses).
    • Zero Monkey-Patching: No longer alters your class's __hash__ or __eq__ methods. Uses internal MutableState tokens for safe identity tracking.
  • ⚡ Maximum Performance (v0.8.3+ Optimized):

    • Hot Path Acceleration: Direct object.__getattribute__() calls and type dispatch tables reduce overhead by 40%+.
    • Lazy Loading: 2.1x faster DB loading and 35% less memory usage.
    • Pre-computed state eliminates repeated lookups.
    • O(1) type checks using frozenset membership for atomic types.
  • 🐢 -> 🐇 Lazy Loading:

    • Zero-cost loading: Objects loaded from the DB are raw Python dicts until you access them.
    • JIT Wrapping: Wrappers are created Just-In-Time.
    • 5.1x faster initialization compared to eager loading.
  • 🥒 Pickle & Celery Ready:

    • Full support for pickle. Pass your database models directly to Celery workers or cache them in Redis.
    • Tracking is automatically restored upon deserialization via MutableMethods.
  • 🚀 High Performance:

    • Powered by orjson: faster serialization than standard json.
    • Native Types: Supports datetime, UUID, and numpy out of the box.
    • Smart Caching: Introspection results are cached (O(1) overhead).
  • 🔄 Deep Mutation Tracking:

    • Detects changes like user.settings.tags.append("new") automatically.
    • No more flag_modified() or reassigning the whole object.

The Problem

By default, SQLAlchemy considers JSON columns immutable unless you replace the entire object.

# ❌ NOT persisted by default in SQLAlchemy
user.settings.theme = "dark"
user.settings.tags.append("new")

session.commit() # Nothing happens! Data is lost.

The Solution

With sqlatypemodel, in-place mutations are tracked automatically:

# ✅ Persisted automatically
user.settings.theme = "dark"
user.settings.tags.append("new")

session.commit() # UPDATE "users" SET settings = ...

Installation

pip install sqlatypemodel

To ensure you have orjson (recommended):

pip install sqlatypemodel[fast]

📚 Examples & Usage

We provide a comprehensive suite of ready-to-run examples in the examples/ directory:

  1. Basic Pydantic: The standard workflow for mutation tracking.

  2. Lazy Loading Benchmarks: Performance comparison between eager and lazy loading.

  3. Dataclasses: Using the safe dataclass wrapper.

  4. Attrs Support: Integration with the attrs library.

  5. Async SQLAlchemy: Integration with AsyncSession and aiosqlite.

  6. Deep Nesting: Tracking changes in lists of dictionaries of models.

  7. Pickle & Celery: Passing models to background workers.


Quick Start (Pydantic)

1. Standard Usage (MutableMixin)

Best for write-heavy workflows or when you always access the data immediately.

from typing import List
from pydantic import BaseModel, Field
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
from sqlatypemodel import ModelType, MutableMixin
from sqlatypemodel.util.sqlalchemy import create_engine

# 1. Define Pydantic Model (Inherit from MutableMixin)
class UserSettings(MutableMixin, BaseModel):
    theme: str = "light"
    tags: List[str] = Field(default_factory=list)

# 2. Define Entity
class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "users"
    id: Mapped[int] = mapped_column(primary_key=True)
    settings: Mapped[UserSettings] = mapped_column(ModelType(UserSettings))

# 3. Usage
# Use our helper to get free orjson configuration
engine = create_engine("sqlite:///")
Base.metadata.create_all(engine)

with Session(engine) as session:
    user = User(settings=UserSettings())
    session.add(user)
    session.commit()

    # Mutation works!
    user.settings.tags.append("python")
    session.commit()

2. High-Performance Usage (LazyMutableMixin)

Recommended for read-heavy, sparse-field applications. Objects are initialized "lazily". The overhead of change tracking is only paid when you actually access the attribute.

from sqlatypemodel import LazyMutableMixin

# Just swap MutableMixin -> LazyMutableMixin
class UserSettings(LazyMutableMixin, BaseModel):
    theme: str = "light"
    # ...

Performance Benchmarks (v0.8.3):

Metric Eager Lazy Improvement Notes
Initialization (per object) 593 µs 1.6 µs 376x faster Pure Python object init
DB Load (5,000 objects) 406ms 195ms 2.1x faster SQL query + deserialization
First Field Access 2.8ms 146ms 50x slower JIT wrapping overhead
Memory Usage (5k objects) 11.8MB 7.8MB 35% less Lower overhead

Key Insight: Lazy loading is exceptionally fast at initialization and reduces DB load time significantly (2.1x). Use it for large result sets where you only access a subset of data.


License

MIT

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

sqlatypemodel-0.8.4.tar.gz (26.7 kB view details)

Uploaded Source

Built Distribution

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

sqlatypemodel-0.8.4-py3-none-any.whl (33.0 kB view details)

Uploaded Python 3

File details

Details for the file sqlatypemodel-0.8.4.tar.gz.

File metadata

  • Download URL: sqlatypemodel-0.8.4.tar.gz
  • Upload date:
  • Size: 26.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for sqlatypemodel-0.8.4.tar.gz
Algorithm Hash digest
SHA256 974ff4f7d83daf3950b74430222856d16d56829ec0ca184ceb5e549f73427e07
MD5 f6bf0fdb1df54eddd39b79d8fb38c11b
BLAKE2b-256 9f4f845f847049dd37978fdb8ff540e99b4799c481b6e9b9cc3df13c769225cc

See more details on using hashes here.

File details

Details for the file sqlatypemodel-0.8.4-py3-none-any.whl.

File metadata

  • Download URL: sqlatypemodel-0.8.4-py3-none-any.whl
  • Upload date:
  • Size: 33.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for sqlatypemodel-0.8.4-py3-none-any.whl
Algorithm Hash digest
SHA256 2d56318eb7245c71b7ee228faf72663339502b7f94b965e2688002c50da28a25
MD5 e00b64a7f68dac50fba7ae776a5d62f3
BLAKE2b-256 e37a390a970a726783d0ead8a9da8f1a8be2c2d27201bb341a4d3a405400b1f7

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