ORM API#
Extended Declarative Base#
sqlalchemy_mate.api.ExtendedBase is a Mixin class that enables many convenient methods for your ORM model.
[1]:
import sqlalchemy as sa
import sqlalchemy.orm as orm
import sqlalchemy_mate.api as sam
from rich import print as rprint
Base = orm.declarative_base()
# add sqlalchemy_mate.ExtendedBase Mixin class
class User(Base, sam.ExtendedBase):
__tablename__ = "users"
id: orm.Mapped[int] = orm.mapped_column(sa.Integer, primary_key=True)
name: orm.Mapped[str] = orm.mapped_column(sa.String, nullable=True)
# you can also do this
# id: int = sa.Column(sa.Integer, primary_key=True)
# name: str = sa.Column(sa.String, nullable=True)
# put important columns here
# you can choose to print those columns only with ``.glance()`` method.
_settings_major_attrs = [
id,
]
Python dict liked interface#
A data model should be have a nicer print.
[2]:
user = User(id=1, name="Alice")
print(user)
User(id=1, name='Alice')
Convert data model to / from generic python data type should be easy.
[3]:
rprint(User.keys())
['id', 'name']
[4]:
rprint(user.keys())
['id', 'name']
[5]:
rprint(user.values())
[1, 'Alice']
[6]:
rprint(user.items())
[('id', 1), ('name', 'Alice')]
[7]:
rprint(user.to_dict())
{'id': 1, 'name': 'Alice'}
[8]:
rprint(user.to_OrderedDict())
OrderedDict([('id', 1), ('name', 'Alice')])
[9]:
rprint(user.pk_values())
(1,)
[10]:
user.glance() # only print important columns
User(id=1)
Absorb and Revise#
Python dict can update values based on another dict, A data model should be able to do it to.
[11]:
# update values based on another data model, it is similar to ``dict.update()``
user_bob = User(name="Bob")
user.absorb(user_bob)
rprint(user)
# update values based on generic python dict, it is similar to ``dict.update()``
user.revise({"name": "Cathy"})
rprint(user)
User(id=1, name='Bob')
User(id=1, name='Cathy')
Insert, Select, Update with ORM Model#
[12]:
engine = sa.create_engine("sqlite:///:memory:")
sam.test_connection(engine, timeout=3)
Base.metadata.create_all(engine)
# Delete all data, make sure we have a fresh start
User.delete_all(engine)
[13]:
# Count rows in a table
print(User.count_all(engine))
0
[14]:
# Bulk insert
user_list = [
User(id=57),
User(id=264),
User(id=697),
]
User.smart_insert(engine, user_list)
rprint(User.count_all(engine))
3
[15]:
# Get single object by primary key values
user = User.by_pk(engine, 57)
rprint(user) # User(id=57)
User(id=57, name=None)
Smart Insert Handles Primary Key conflicts efficiently#
[16]:
# Bulk insert, handle primary key conflicts efficiently
user_list = [User(id=id_) for id_ in range(1, 1000 + 1)]
User.smart_insert(engine, user_list)
rprint(User.count_all(engine)) # 1000
1000
Bulk Upsert (Insert + Update) should be easy#
[17]:
# before upsert
rprint(User.by_pk(engine, 999))
rprint(User.by_pk(engine, 1000))
rprint(User.by_pk(engine, 1001))
rprint(User.by_pk(engine, 1002))
User(id=999, name=None)
User(id=1000, name=None)
None
None
[18]:
# Bulk update + insert, locate rows by primary key values
user_list = [
User(id=999, name="Alice"),
User(id=1000, name="Bob"),
User(id=1001, name="Cathy"),
User(id=1002, name="David"),
]
User.upsert_all(engine, user_list)
[18]:
(2, 2)
[19]:
# after upsert
rprint(User.by_pk(engine, 999))
rprint(User.by_pk(engine, 1000))
rprint(User.by_pk(engine, 1001))
rprint(User.by_pk(engine, 1002))
User(id=999, name='Alice')
User(id=1000, name='Bob')
User(id=1001, name='Cathy')
User(id=1002, name='David')
Run raw SQL query#
The Class.by_sql() assumes that the returned row is the given ORM model. You cannot do this with JOIN.
[20]:
results = User.by_sql(
engine,
sql="""
SELECT *
FROM users
WHERE users.id >= 999
""",
)
rprint(results)
[User(id=999, name='Alice'), User(id=1000, name='Bob'), User(id=1001, name='Cathy'), User(id=1002, name='David')]
[ ]: