Skip to main content

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")

Security

Obele builds SQL with bound parameters and validates every identifier it interpolates (table, column, pragma, and annotation names), so ordinary queries are safe against injection. Two features deserve care:

  • PickleField and the KVStore default "auto" serializer fall back to pickle, and reading those values deserializes them - which executes code. Point them only at databases you control. Use JSONField or KVStore(..., serializer="json") when the data may come from elsewhere.
  • RawSQL and Database.execute() pass your SQL through verbatim. Bind user input as parameters rather than formatting it into the string.

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.

Metadata

Release files for obele 1.0.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for obele 1.0.2
File Size Uploaded Provenance Actions
obele-1.0.2.tar.gz 112.3 kB Without provenance Details for obele-1.0.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for obele 1.0.2
File Interpreter ABI Platform Actions
obele-1.0.2-py3-none-any.whl Python 3 none any Details for obele-1.0.2-py3-none-any.whl

Total release size: 179.3 kB

Release files / obele-1.0.2.tar.gz

Download URL obele-1.0.2.tar.gz
Size 112.3 kB
Tags Source
SHA-256 checksum
How to use checksums
5ae8a966d0a50f9689ddf92caff8b2c99d1d7e7414fc76278707ed8d7b4f3a79
BLAKE2b-256 checksum
How to use checksums
e575bff52a1aea296efe159aaab79c2af6ad840aa88dfd148be2414b6db0b95c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.5

Release files / obele-1.0.2-py3-none-any.whl

Download URL obele-1.0.2-py3-none-any.whl
Size 67.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
34c965e8ae60b96ea19f0b2a980fc2443e3cfaa90b19bd15d80d0bce17ceade7
BLAKE2b-256 checksum
How to use checksums
6c7990d91d47ee5b615f42bf4fa66d221096d1090bdc65b9e23f6c78ba22b0f8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.5

Security for obele 1.0.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page