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
DuctTapeDBfor a straightforward sync workflow. - Use
HookLoopTable + AsyncSQLiteControllerfor async operations.
- Use
- Pydantic Integration:
- Define models by extending
DuctTapeModelor its variants (likeSafetyTapeModelorAutoSafetyTapeModel). - Automatic validation, model dumping, etc. courtesy of Pydantic.
- Define models by extending
- Optimistic Locking with a built-in version column (
SafetyTapeModel):- Prevent conflicting updates by incrementing a
versioneach time.
- Prevent conflicting updates by incrementing a
- 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
- Clone the repo
- Create a virtual environment
- Install dependencies:
pip install -e .[dev] - 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ducttapedb-2025.2.19.1.tar.gz.
File metadata
- Download URL: ducttapedb-2025.2.19.1.tar.gz
- Upload date:
- Size: 32.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.0.1 CPython/3.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
89d152b2c8d211d8e841d7810e802a8020ee74efb5da95f7124dde6ec486c7d5
|
|
| MD5 |
526d1645ea5731c288c2d436136b49f8
|
|
| BLAKE2b-256 |
45e1942602b288df5516f7704329fe551bce41fbea9415273659d4c7849a4993
|
File details
Details for the file ducttapedb-2025.2.19.1-py3-none-any.whl.
File metadata
- Download URL: ducttapedb-2025.2.19.1-py3-none-any.whl
- Upload date:
- Size: 25.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.0.1 CPython/3.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4c431769759f5167894c046bcb1ea6363915c8154ab9a1984aadd57381f9163a
|
|
| MD5 |
73e53d75d92d8b312f7a5f9bede012fa
|
|
| BLAKE2b-256 |
f8e66f115cb80e84d5147c663721737f802bd6b66063c36afb7d8866a7ba3c6b
|