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.11.1.tar.gz (31.4 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.11.1-py3-none-any.whl (25.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: ducttapedb-2025.2.11.1.tar.gz
  • Upload date:
  • Size: 31.4 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.11.1.tar.gz
Algorithm Hash digest
SHA256 bf83de3ed77eb7a8a4773b07eecb2a102f69a282102d3c79230817c40bae13fa
MD5 83a1a635ba2e97016d566ddf43d7a5b2
BLAKE2b-256 82f40549b3b33376bd990980e6e064d8bbaade046fd7a5fb4b0cc4df0c19bada

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for ducttapedb-2025.2.11.1-py3-none-any.whl
Algorithm Hash digest
SHA256 4ada27ab824d653086b99a6a6f7ee6d668645f7732129f2e68d6ef4b7e0c8a5d
MD5 4a63948347e4a7165376fe56f74f9438
BLAKE2b-256 fbd7221d78f2445ea332f214f4b436c0175dd81b6064390145001182345ef95f

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