A minimalist Python ORM built directly on top of sqlite3 — no SQLAlchemy, no Django dependency, no metaclass magic. The entire library fits in a single afternoon of reading.
Most Python ORMs hide what they do. FineSQL exposes it.
If you've ever wondered:
- How does an ORM turn a class definition into a
CREATE TABLEstatement? - Where does a lazy-loaded foreign-key attribute actually come from?
- How much of "ORM magic" can be replaced with explicit, readable Python?
FineSQL is that — in code you can fork, read end-to-end, and break in interesting ways.
- Explicit over magic — no hidden descriptors doing invisible work
- Small surface area — one
Database, oneTable, oneColumn, oneForeignKey - Zero runtime dependencies —
sqlite3is the entire engine - Readable internals — designed to be understood, not just used
pip install finesqlfrom finesql import Database, Table, Column, ForeignKey
class User(Table):
username = Column(str)
age = Column(int)
class Post(Table):
title = Column(str)
body = Column(str)
author = ForeignKey(User)db = Database("app.db")
db.create(User)
db.create(Post)
# Create
user = User(username="Alice", age=25)
db.save(user)
post = Post(title="Hello World", body="First post", author=user)
db.save(post)
# Read
all_users = db.all(User)
alice = db.get(User, id=1)
match = db.get_by_field(User, field_name="username", value="Alice")
# Update
alice.age = 26
db.update(alice)
# Delete
db.delete(User, id=1)post = db.get(Post, id=1)
print(post.author.username) # triggers a SELECT on User on attribute accessA brief tour of the internals — the whole library is around 500 lines and structured so you can read it top to bottom:
- Schema introspection —
Tablecollects class-levelColumnandForeignKeydefinitions at class creation time and stores them in a schema dictionary. - DDL generation —
Database.create()walks that schema and emitsCREATE TABLEwith Python-to-SQLite type mapping (str→TEXT,int→INTEGER,bool→INTEGER,bytes→BLOB). - Parameterised inserts —
Database.save()builds prepared statements from instance attributes and assignsidback fromcursor.lastrowid. - Lazy relationships —
ForeignKeyresolves on attribute access rather than via JOINs, keeping the query layer dead simple.
If you want to see how a small ORM is actually structured, the finesql/ directory is the documentation.
| Method | Description |
|---|---|
Database(path) |
Open or create a SQLite database |
db.create(table) |
Issue CREATE TABLE IF NOT EXISTS for the model |
db.save(instance) |
Insert row and assign generated id |
db.all(table) |
Return all rows as model instances |
db.get(table, id) |
Fetch a single instance by primary key |
db.get_by_field(table, field_name, value) |
LIKE search on a single column |
db.update(instance) |
Persist changes back to the row |
db.delete(table, id) |
Delete by primary key |
Each item targets a specific internal mechanism worth implementing from scratch:
- Migrations — schema diffing and incremental
ALTERstatements - Chainable query builder —
.filter()/.order_by()/.limit()instead of bespoke methods - Async support — non-blocking I/O via
aiosqlite - mypy plugin — inferred column types on model instances
FineSQL is not a SQLAlchemy replacement. It targets a different niche: small scripts, internal tools, and developers who want ORM ergonomics without the dependency footprint. For multi-database production systems with migrations, connection pooling, and complex query optimisation, use SQLAlchemy or the Django ORM.
MIT © Abdulmajid Yunusov