Skip to content

Getting started

Installation

Install the synchronous package:

python -m pip install scriptdb

For the asynchronous API:

python -m pip install 'scriptdb[async]'

For a bundled modern SQLite build:

python -m pip install 'scriptdb[pysqlite]'

The package supports Python 3.8 and newer.

Define a database class

Subclass SyncBaseDB or AsyncBaseDB and implement migrations():

from scriptdb import SyncBaseDB


class StoreDB(SyncBaseDB):
    def migrations(self):
        return [
            {
                "name": "create_items",
                "sql": "CREATE TABLE items (id INTEGER PRIMARY KEY, value TEXT)",
            },
        ]

Migrations are applied once and tracked in ScriptDB's migration table. Keep migration names stable after they have been applied.

Opening and closing

The recommended form is a context manager:

with StoreDB.open("store.db") as db:
    db.insert_one("items", {"value": "hello"})

The equivalent manual form is useful when the database lifetime spans several parts of a program:

db = StoreDB.open("store.db")
try:
    db.init()
    db.insert_one("items", {"value": "hello"})
finally:
    db.close()

Calling a method that requires a connection before init() or after close() raises a descriptive RuntimeError.

open() options

Both database classes accept these common options:

Option Default Meaning
auto_create True Create the database file if it does not exist.
use_wal True Enable SQLite WAL journaling during initialization.
row_factory sqlite3.Row Return mapping-like SQLite rows or plain dict values.
legacy_sqlite_support False Emulate upserts on old SQLite; slower than native upsert.

AsyncBaseDB and AsyncCacheDB also accept daemonize_thread. Set it to True if an aiosqlite worker thread prevents process exit, but remember that a daemon thread can be terminated while work is still in flight.

They also accept handle_signals, which defaults to False. Enable it only when ScriptDB should own the process SIGINT and SIGTERM handlers; otherwise close the database from the application's existing shutdown path.

auto_create=False is useful when opening an existing database is required:

with StoreDB.open("must-exist.db", auto_create=False) as db:
    ...

It raises RuntimeError if the file does not exist.

Row factories

The default sqlite3.Row supports both numeric and named access:

row = db.query_one("SELECT 7 AS answer")
assert row["answer"] == 7

Use row_factory=dict when JSON-friendly plain dictionaries are more useful:

with StoreDB.open("store.db", row_factory=dict) as db:
    row = db.query_one("SELECT 7 AS answer")
    assert row == {"answer": 7}

The setting affects query_one, query_many, query_many_gen, and the row values used by query_dict, as well as post-processing callbacks.

Sync/async conversion

When the schema uses SQL strings or Builder objects, conversion helpers can reuse migrations without copying them:

from scriptdb.conversion import async_from_sync, sync_from_async

AsyncStoreDB = async_from_sync(StoreDB)
SyncStoreDB = sync_from_async(AsyncStoreDB)

Callable (function) migrations cannot be converted because their sync/async behavior is application-specific. The source class must be the corresponding SyncBaseDB or AsyncBaseDB subclass.