Skip to main content

A lightweight, async-ready ORM and key-value store for SQLite

Project description

Obele

Obele is a lightweight SQLite ORM and key-value store for modern Python applications. It keeps a small, Django-like API for models and queries while supporting both synchronous code and native asyncio workflows.

Highlights

  • Sync and async APIs for models, queries, raw SQL, search, and KV operations. Every method has an a-prefixed async twin that runs on a worker thread, so transactions and scoped bindings behave identically in both worlds.
  • Thread-safe SQLite connection management: per-thread connections under WAL mode, transactions with savepoint nesting, scoped database bindings, and query logging. Plain ":memory:" databases are shared across threads.
  • Declarative models with field validation, foreign keys, reverse relations, expressions (F("views") + 1), lazy slicing, prefetching, mixins, signals, pagination, and FTS5 search indexes.
  • Persistent dict-like KVStore with namespaces, TTL, atomic operations, range/prefix/scan queries, memoization, serializers, and async methods.

Requirements

  • Python 3.13 or newer.
  • SQLite with FTS5 enabled for full-text search features.

Obele has no required runtime dependencies outside the Python standard library.

Installation

pip install obele

For local development:

git clone https://github.com/ichinga-samuel/obele.git
cd obele
python -m pip install -e ".[dev]"
pytest -q

Quickstart

import asyncio
from obele import Database, Model, TextField, IntegerField

Database.configure("app.sqlite3")

class User(Model):
    table_name = "users"
    name = TextField()
    age = IntegerField(nullable=True)

async def main():
    await User.acreate_table()

    await User.acreate(name="Alice", age=30)
    await User.acreate(name="Bob", age=25)

    users = await User.filter(age__gte=18).order_by("name").aall()
    for user in users:
        print(user.name, user.age)

    await Database.aclose_all()

asyncio.run(main())

The same API is available synchronously by omitting the a prefix:

User.create_table()
User.create(name="Ada", age=36)
adults = User.filter(age__gte=18).all()

Direct SQL

Use Database helpers when you need lower-level access:

cursor = Database.execute(
    "INSERT INTO users (name, age) VALUES (?, ?)",
    ["Grace", 40],
)
print(cursor.lastrowid)

row = Database.fetchone("SELECT name FROM users WHERE id = ?", [cursor.lastrowid])

Async helpers return fully materialized results - no cursors to manage:

result = await Database.aexecute(
    "INSERT INTO users (name, age) VALUES (?, ?)",
    ["Linus", 33],
)
print(result.lastrowid)

row = await Database.afetchone("SELECT name FROM users WHERE age > ?", [30])

Database.aexecute() and Database.aexecutemany() return an ExecResult containing rows, rowcount, and lastrowid; there is no async cursor to close. The former obele.asqlite and raw async-connection exports are no longer part of the public API.

Transactions And Scoped Databases

with Database.transaction(mode="IMMEDIATE"):
    User.create(name="One")
    User.create(name="Two")

async with Database.transaction():
    await User.acreate(name="Async One")
    await User.acreate(name="Async Two")

Transaction modes are DEFERRED, IMMEDIATE (the default), and EXCLUSIVE. Nested transactions use savepoints.

Use scoped bindings for tests, tenants, or short-lived alternate databases:

with Database.using("tenant.sqlite3"):
    User.create_table()
    User.create(name="Tenant User")

Key-Value Store

from obele import KVStore

settings = KVStore("settings", key_type=str, namespace="app")
settings["theme"] = "dark"
settings.set("session", {"user_id": 1}, ttl=3600)

print(settings["theme"])
print(settings.prefix("sess"))

Async KV methods are prefixed with a:

await settings.aset("feature:search", True)
enabled = await settings.aget("feature:search")

Documentation

To preview the docs locally:

mkdocs serve

Testing

The test suite is organized by public behavior and uses the local checkout by default via pyproject.toml.

pytest -q

Current suite coverage includes ORM CRUD, queries, async behavior, direct database access, FTS search, pagination, signals, field types, and KVStore features.

License

Obele is released under the MIT License. See LICENSE.

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

obele-1.0.0.tar.gz (86.8 kB view details)

Uploaded Source

Built Distribution

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

obele-1.0.0-py3-none-any.whl (61.1 kB view details)

Uploaded Python 3

File details

Details for the file obele-1.0.0.tar.gz.

File metadata

  • Download URL: obele-1.0.0.tar.gz
  • Upload date:
  • Size: 86.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.5

File hashes

Hashes for obele-1.0.0.tar.gz
Algorithm Hash digest
SHA256 f45b2f16b9e373f113869e41cebec9836a94ed76be4ae8c47f6e06c4d30008fc
MD5 3283418a26e8084aa416ef7ae90162e4
BLAKE2b-256 59b856c8f14dcd2fe983eba8e4f092abdb190f66a8ada4e5135bd86abc5792c0

See more details on using hashes here.

File details

Details for the file obele-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: obele-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 61.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.5

File hashes

Hashes for obele-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7c80ab49afd1a4a5f0b59f75ac004b009f9c70d133d65c1ead89c93e1f4421fb
MD5 a2f54647610d0ddc3b6216a06cad98df
BLAKE2b-256 11e0cdb4828b0f0543f13eeee29d5bfc8c567cc792816f6dbb355c725a5897bc

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