Sustained.py
A Python query builder and lightweight ORM inspired by Objection.js.
You describe a query as chained Python methods. Sustained renders the SQL for your dialect, runs it parameterized against any DB-API 2.0 connection or pool, and hydrates the rows into your model classes.
adults = User.query().where(User.c.age >= 18).orderBy('name').run()
What it does
- SQL building for the default (ANSI), Postgres, MSSQL, Presto, AWS Athena, and DuckDB dialects: joins, CTEs (including recursive), unions, window functions, CASE expressions, and subqueries. Features a dialect lacks raise
DialectErrorat build time. - Safe execution: every statement runs parameterized. Transactions nest through savepoints.
update()anddelete()refuse to run without a WHERE clause. - Writes:
insert(),update(),delete(), upserts withonConflict(),INSERT ... SELECT, CTAS, and RETURNING. - Typed filters:
User.query().where((User.c.age > 21) & User.c.name.like('A%')). - Results as model instances, dicts, pandas DataFrames, or pyarrow Tables, with
withGraphFetched()eager loading. - Schema migrations: models declare typed columns and indexes;
Migrator.sync(models)diffs the live database, generates the migration, applies it, anddown()rolls it back. Destructive changes are gated behind explicit opt-ins. - Async: the same queries run through driver adapters (
asyncpg,aiosqlite, or any sync driver in a worker thread) withawait query.arun().
What it does not do
No lazy loading, no dirty tracking or save(), no identity map, no result caching, no cross-dialect emulation of missing features, and no guessed migrations: drops, renames, and NOT NULL backfills all require explicit opt-ins or hints. Writes and schema changes only happen when you spell them out.
Installation
python3 -m pip install sustained
Usage
from sustained import Model, RelationType
class Person(Model):
tableName = 'persons'
class Animal(Model):
tableName = 'animals'
relationMappings = {
'owner': {
'relation': RelationType.BelongsToOneRelation,
'modelClass': Person,
'join': {
'from': 'animals.ownerId',
'to': 'persons.id'
}
}
}
# Build a query
query = Animal.query().select('animals.name', 'persons.name').leftOuterJoinRelated('owner')
print(query)
# SELECT animals.name, persons.name
# FROM animals
# LEFT OUTER JOIN persons
# ON animals.ownerId = persons.id
# Execute against any DB-API 2.0 connection
import sqlite3
conn = sqlite3.connect('app.db')
Animal.bind(conn)
# Parameterized execution with model hydration
animals = Animal.query().where('species', '=', 'dog').orderBy('name').run()
# Or take the SQL and parameters and execute them yourself
sql, params = Animal.query().where('species', '=', 'dog').to_sql()
# sql: "SELECT * FROM animals WHERE species = ?"
# params: ('dog',)
Models can manage their own schema:
from sustained.migrations import Migrator
from sustained.schema import Integer, String, Text
class User(Model):
tableName = 'users'
tableColumns = {
'id': Integer(primary_key=True, autoincrement=True),
'email': String(120, unique=True, nullable=False),
}
migrator = Migrator(conn, [])
migrator.sync([User]) # creates the users table
User.tableColumns['bio'] = Text()
migrator.sync([User]) # adds only the bio column
migrator.down() # rolls it back
Documentation
The documentation covers models, queries, dialects and drivers, filtering, grouping, relations and joins, execution, pooling, and async, and schema and migrations. The API reference lists every public method by task.
Development
To install from source:
git clone https://github.com/wetherc/sustained.git
cd sustained
python3 -m pip install -e .
This project uses pre-commit to format code, lint, type check, and run the test suite before each commit:
pip install pre-commit
pre-commit install
Release files for sustained 2.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sustained-2.6.0.tar.gz | 145.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sustained-2.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 233.8 kB
Release files / sustained-2.6.0.tar.gz
| Download URL | sustained-2.6.0.tar.gz |
|---|---|
| Size | 145.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1728135f969e0a726b00883191fe5fab1c42a910829b7743a807fdd50d35859d
|
|
BLAKE2b-256 checksum How to use checksums |
fb2168152afeaa18613f1292c8285e05105ee2d96dfd96c3b3b0a39a6a30ecc9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.0
|
Release files / sustained-2.6.0-py3-none-any.whl
| Download URL | sustained-2.6.0-py3-none-any.whl |
|---|---|
| Size | 88.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f456bb6335a77dd4f6e3e695688996f246a846ca57ae4d5bf8ba778d8774d36b
|
|
BLAKE2b-256 checksum How to use checksums |
90db96bb1ed9c0f471924e80c36eb4f3d35205065b5ec13cddf68e040ded8fa5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.0
|