Simple library that makes working with classes and databases more convenient in python.
omen2
will take a set of classes, and a database, and link them together.
omen2 allows the user to:
- serialize created objects to the db
- query the db and return objects, not rows
- query for related objects
- access/lock/update singleton objects across multiple threads
- roll back changes on exceptions
- create objects not-bound to the db, and later, bind them
- cache objects in use, flexible cache-control
omen2 is not fully flexible with regards to database structure:
- related tables must have primary keys
from notanorm import SqliteDb
from omen2 import Omen
class MyOmen(Omen):
version = 2
@staticmethod
def schema(version):
# use an omen2-compatible schema, which is a semicolon-delimited sqlite-compatible create statement
return """create table cars(id integer primary key, color text not null, gas_level double default 1.0);
create table doors(carid integer, type text, primary key (carid, type));"""
# or, just return a list of type-annotated classes derived from ObjBase
# or, don't have one at all, it's ok
# you don't have to codegen, you can also just derive from omen2.ObjBase
# but you have to match your database system to the model one way or another
# either manual, codegen, or dbgen
MyOmen.codegen()
# assuming this is example.py
import example_gen as gen_objs
# every table has a row_type, you can derive from it
class Car(gen_objs.cars_row):
def __init__(self, color="black", **kws):
self.not_saved_to_db = "some thing"
self.doors = gen_objs.doors_relation(self, kws.pop("doors", None), carid=lambda: self.id)
super().__init__(color=color, **kws)
@property
def gas_pct(self):
# read only props are fine
return self.gas_level * 100
# if you're using code generation, every db table has a type, you can derive from it
class Cars(gen_objs.cars):
# feel free to redefine the row_type used
row_type = Car
db = SqliteDb(":memory:")
mgr = MyOmen(db, cars=Cars)
# there's always a mapping from table class to instance
# so Omen knows what classes are in charge of what tables
mgr.cars = mgr[Cars]
# fine too (or stick in init)
mgr.cars = Cars(self)
# by default, you can always iterate on tables
assert mgr.cars.count() == 0
car = Car() # creates a default car (black, full tank)
car.color = "red"
car.gas_level = 0.5
# you don't need to create a class, if you use the code-generated one
car.doors.add(gen_objs.doors_row(type="a"))
car.doors.add(gen_objs.doors_row(type="b"))
car.doors.add(gen_objs.doors_row(type="c"))
car.doors.add(gen_objs.doors_row(type="d"))
mgr.cars.add(car)
# cars have ids, generated by the db
assert car.id
mgr.cars.add(Car(color="red", gas_level=0.3, doors=[gen_objs.doors_row(type=str(i)) for i in range(4)]))
assert sum(1 for _ in mgr.cars.select(color="red")) == 2 # 2
print("cars:", list(mgr.cars.select(color="red")))
car = mgr.cars.select_one(color="red", gas_level=0.3)
with car:
car.gas_level = 0.9
assert len(car.doors) == 4
To run codegen manually, rather than "inline", you can run: omen2-codegen my.package.MyClassName
.
Commiting this file, and running this as a git-hook on any change is a useful pattern.