Skip to main content

Persist pydantic models. Convenient model management and storage.

This project has been archived.

The maintainers of this project have marked this project as archived. No new releases are expected.

Project description

DuctTapeDB

DuctTapeDB is a lightweight persistence layer for Pydantic models using SQLite (both sync & async). Think of it as a quick, no-frills doc store that supports partial updates, optimistic concurrency, and both synchronous and asynchronous usage.

Key Features

  • Synchronous & Asynchronous support out of the box:
    • Use DuctTapeDB for a straightforward sync workflow.
    • Use HookLoopTable + AsyncSQLiteController for async operations.
  • Pydantic Integration:
    • Define models by extending DuctTapeModel or its variants (like SafetyTapeModel or AutoSafetyTapeModel).
    • Automatic validation, model dumping, etc. courtesy of Pydantic.
  • Optimistic Locking with a built-in version column (SafetyTapeModel):
    • Prevent conflicting updates by incrementing a version each time.
  • Partial JSON Updates:
    • Update only changed fields in the JSON column, saving time and concurrency headaches.
  • Soft Deletes and extra auditing columns (created_at, updated_at, etc.) if you need them.
  • Extensive Test Suite showing concurrency handling, partial updates, and more.

Installation

pip install ducttapedb

(Or if you want to install from source, clone the repo and do pip install ..)

Quick Start (Sync)

Below is a simple synchronous example using DuctTapeDB:

from ducttapedb import DuctTapeDB, DuctTapeModel

# 1. Define your model
class Hero(DuctTapeModel):
    name: str
    level: int

# 2. Create a DB instance (in-memory by default)
db = DuctTapeDB.create_memory(table="heroes")
# Set the shared DB on the model class
Hero.set_db(db)

# 3. Create and save a model
erdrick = Hero(name="Erdrick", level=50)
erdrick.save()          # returns the auto-generated ID
print(erdrick.id)       # e.g. 1

# 4. Retrieve the same record
loaded = Hero.from_id(erdrick.id)
print(loaded.name)      # "Erdrick"
print(loaded.level)     # 50

# 5. Update & re-save
loaded.level = 99
loaded.save()

Quick Start (Async)

For asynchronous usage, you’ll typically work with HookLoopTable, an AsyncSQLiteController, and a Pydantic model that extends HookLoopModel:

import asyncio
from ducttapedb.hookloopdb import HookLoopModel, HookLoopTable
from ducttapedb.hookloopdb.controller import AsyncSQLiteController
from typing import Optional

class AsyncHero(HookLoopModel):
    name: str
    level: int
    hp: Optional[int] = 100  # default HP

async def main():
    controller = await AsyncSQLiteController.create_memory(shared_cache=True)
    table = HookLoopTable(controller, "async_heroes")
    await table.initialize(indexes=["name", "level"])

    # 1. Set table on the model
    AsyncHero.set_table(table)

    # 2. Create & save a model
    hero = AsyncHero(name="Async Erdrick", level=30)
    await hero.save()
    print("New Hero ID:", hero.id)

    # 3. Load by ID
    loaded = await AsyncHero.from_id(hero.id)
    print("Loaded Hero:", loaded)

asyncio.run(main())

SafetyTapeModel for Optimistic Locking

If you need concurrency protection and version checks, use SafetyTapeModel and SafetyTapeTable. Each update increments a version column, so if two processes (or tasks) try to update the same row simultaneously, the second one to save will raise a RuntimeError if the version is stale.

from ducttapedb.safetytapedb import SafetyTapeModel, SafetyTapeTable

class Monster(SafetyTapeModel):
    name: str
    level: int
    # version is auto-handled behind the scenes

# ...

# On save, if version doesn’t match, you get a RuntimeError

AutoSafetyTapeModel for Partial Updates

For even more convenience, use AutoSafetyTapeModel which tracks updated fields automatically. Only changed fields are written back to the DB:

from ducttapedb.safetytapedb import AutoSafetyTapeModel

class AutoMonster(AutoSafetyTapeModel):
    name: str
    level: int

monster = AutoMonster(name="Slime", level=5)
await monster.save()   # Full insert
monster.level = 6
await monster.save()   # Updates only level in JSON
# Or as a one-liner
await monster.asetattr(key="level", value=7)

Partial Updates in Action

With AutoSafetyTapeModel, you can see which fields changed by checking updated_fields. If none changed, no update is performed. That’s a big concurrency boost for heavily contended rows.

Testing

We use pytest (including pytest-asyncio). To run tests:

pytest

(We also have concurrency stress tests, partial update tests, version mismatch tests, etc.)

Contributing

  1. Clone the repo
  2. Create a virtual environment
  3. Install dependencies: pip install -e .[dev]
  4. Run black . && ruff check . && pytest

Pull requests are welcome! For major changes, please open an issue first to discuss what you’d like to change.

License

MIT License—see the LICENSE file for details.

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

ducttapedb-2025.2.13.1.tar.gz (31.5 kB view details)

Uploaded Source

Built Distribution

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

ducttapedb-2025.2.13.1-py3-none-any.whl (25.2 kB view details)

Uploaded Python 3

File details

Details for the file ducttapedb-2025.2.13.1.tar.gz.

File metadata

  • Download URL: ducttapedb-2025.2.13.1.tar.gz
  • Upload date:
  • Size: 31.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.0.1 CPython/3.12.6

File hashes

Hashes for ducttapedb-2025.2.13.1.tar.gz
Algorithm Hash digest
SHA256 7565f92b3cb9ff30275c973e76c6c2170470fc6fc5d73f059c8549ab11c1fb1c
MD5 610b2a0d78871aad3bce7da27990a37a
BLAKE2b-256 7e1dc023e70b009bc9bd03512bd550bb4bffdc513673ae2a98b3d074c0d9b17f

See more details on using hashes here.

File details

Details for the file ducttapedb-2025.2.13.1-py3-none-any.whl.

File metadata

File hashes

Hashes for ducttapedb-2025.2.13.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b0bee70236f2bbc09099bafdecb605cc0f0c55308542b097ec60449f17d122ed
MD5 f3d63ad84ac0ebf96259c13fc444dd31
BLAKE2b-256 384018b02a5407b37319a418bcee60e175c23728715248f808ed2a00ba6d8eb0

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