Comment by JodieBenitez
3 months ago
> However, I've always felt some of the design choices didn't fit how I like to use an ORM. Notably:
I feel the same, hence why I prefer a Django-like ORM to SQLAlchemy in spite of all the praises it gets. The author says "SQLAlchemy is the best. I don't like the API or codebase of the others", but actually what he describes feels like the Django ORM (or Tortoise, or many others).
Also, sometimes just a thin layer above SQL is fine. For small personal projects I use my own wrapper above sqlite like so:
import oora
from dataclasses import dataclass
db = oora.DB(
db_path=":memory:", # or /path/to/your/db.sqlite3
# migrations are just pairs of key=>val where key is an arbitrary (but unique) label and val is a SQL script or a callable.
# If val is a callable, it must take a sqlite3.Cursor as first parameter.
# migrations are executed in order
migrations={
# here's an initial migration:
"0000": "CREATE TABLE IF NOT EXISTS user(id INTEGER PRIMARY KEY, name TEXT UNIQUE NOT NULL);",
# simulating a schema evolution, let's add a field:
"0001": "ALTER TABLE user ADD COLUMN email TEXT NULL;",
},
)
db.migrate()
db.insert("user", {"name": "John"})
db.insert("user", {"name": "Jack"})
db.insert("user", {"name": "Jill"})
# dataclasses are perfect to represent rows
# while still allowing custom behaviour
@dataclass
class User:
id: int
name: str
email: str
def __str__(self):
return self.name
# fetch a random instance
user = db.hydrate(User, db.execute("select * from user ORDER BY RANDOM() limit 1").fetchone())
print(f"User(id {user.id}), original name: {user}")
# change name and email
user.name = "Richard"
user.email = "richard@acme.tld"
db.save(user) # name of table is infered from the dataclass name
print(f"User(id {user.id}), updated name: {user} <{user.email}>")
# persist changes
db.commit()import oora
from dataclasses import dataclass
db = oora.DB(
db_path=":memory:", # or /path/to/your/db.sqlite3
# migrations are just pairs of key=>val where key is an arbitrary (but unique) label and val is a SQL script or a callable.
# If val is a callable, it must take a sqlite3.Cursor as first parameter.
# migrations are executed in order
migrations={
# here's an initial migration:
"0000": "CREATE TABLE IF NOT EXISTS user(id INTEGER PRIMARY KEY, name TEXT UNIQUE NOT NULL);",
# simulating a schema evolution, let's add a field:
"0001": "ALTER TABLE user ADD COLUMN email TEXT NULL;",
},
)
db.migrate()
db.insert("user", {"name": "John"})
db.insert("user", {"name": "Jack"})
db.insert("user", {"name": "Jill"})
# dataclasses are perfect to represent rows
# while still allowing custom behaviour
@dataclass
class User:
id: int
name: str
email: str
def __str__(self):
return self.name
# fetch a random instance
user = db.hydrate(User, db.execute("select * from user ORDER BY RANDOM() limit 1").fetchone())
print(f"User(id {user.id}), original name: {user}")
# change name and email
user.name = "Richard"
user.email = "richard@acme.tld"
db.save(user) # name of table is infered from the dataclass name
print(f"User(id {user.id}), updated name: {user} <{user.email}>")
# persist changes
db.commit()
No comments yet
Contribute on Hacker News ↗