DM + SQL storage how-to#
This guide shows how to persist DM models into a SQL database using RESTAlchemy.
You will:
- Define DM models with
ModelWithUUIDandSQLStorableMixin. - Configure a SQL engine (MySQL or PostgreSQL).
- Create and query data using
.save(),.delete()andModel.objects. - Use filters for queries.
Examples are based on examples/dm_mysql_storage.py and examples/dm_pg_storage.py.
Prerequisites#
- RESTAlchemy installed (see
installation.md). - A running database:
- MySQL or MariaDB, or
- PostgreSQL.
- Appropriate Python driver installed, for example:
mysql-connector-pythonfor MySQL.psycopg[binary]for PostgreSQL.- Tables created according to the model definitions (see the migration section below).
1. Define DM models for SQL#
The minimal pattern for a SQL-backed model is:
- Inherit from
models.ModelWithUUID(or anotherModelWithID). - Inherit from
orm.SQLStorableMixin. - Set
__tablename__to the table name. - Declare DM properties with types.
Example (simplified from dm_mysql_storage.py):
from restalchemy.dm import models, properties, relationships, types
from restalchemy.storage.sql import orm
class FooModel(models.ModelWithUUID, orm.SQLStorableMixin):
__tablename__ = "foos"
foo_field1 = properties.property(types.Integer(), required=True)
foo_field2 = properties.property(types.String(), default="foo_str")
class BarModel(models.ModelWithUUID, orm.SQLStorableMixin):
__tablename__ = "bars"
bar_field1 = properties.property(types.String(min_length=1, max_length=10))
foo = relationships.relationship(FooModel)
These models use DM validation and relationships while SQLStorableMixin adds persistence methods.
2. Configure the SQL engine#
Use restalchemy.storage.sql.engines.engine_factory to create an engine instance.
MySQL example#
from restalchemy.storage.sql import engines
engines.engine_factory.configure_factory(
db_url="mysql://user:password@127.0.0.1:3306/test",
)
PostgreSQL example#
from restalchemy.storage.sql import engines
engines.engine_factory.configure_factory(
db_url="postgresql://postgres:password@127.0.0.1:5432/ra_tests",
)
You should call configure_factory() once during application startup. Later, all models using SQLStorableMixin will obtain the engine via engine_factory.get_engine().
Optional arguments:
config— engine-specific configuration (pool sizes, timeouts, etc.).query_cache— enable per-session query cache.
3. Create tables and run migrations#
RESTAlchemy does not auto-create tables; instead, it relies on explicit migrations.
Use the migration commands described in README.rst:
ra-new-migration— create new migration files.ra-apply-migration— apply migrations to the target database.
The examples include commented SQL schemas, for example in dm_mysql_storage.py:
CREATE TABLE `foos` (
`uuid` CHAR(36) NOT NULL,
`foo_field1` INT NOT NULL,
`foo_field2` VARCHAR(255) NOT NULL,
PRIMARY KEY (`uuid`)
) ENGINE = InnoDB;
CREATE TABLE `bars` (
`uuid` CHAR(36) NOT NULL,
`bar_field1` VARCHAR(10) NOT NULL,
`foo` CHAR(36) NOT NULL,
CONSTRAINT `_idx_foo` FOREIGN KEY (`foo`) REFERENCES `foos`(`uuid`)
) ENGINE = InnoDB;
You can adapt these schemas to your environment or generate migrations that produce similar DDL.
4. Basic CRUD operations#
Once the engine is configured and tables exist, you can use the DM models as persistent entities.
Create and save#
foo1 = FooModel(foo_field1=10)
foo1.save() # INSERT into foos
bar1 = BarModel(bar_field1="test", foo=foo1)
bar1.save() # INSERT into bars
Read data#
# All bars
all_bars = list(BarModel.objects.get_all())
# One bar by primary key
same_bar = BarModel.objects.get_one(filters={"uuid": bar1.get_id()})
# All bars for a given FooModel instance
bars_for_foo = list(BarModel.objects.get_all(filters={"foo": foo1}))
# Convert to plain dict
print(bar1.as_plain_dict())
Update#
foo2 = FooModel(foo_field1=11, foo_field2="some text")
foo2.save()
# Modify and save again (UPDATE)
foo2.foo_field2 = "updated text"
foo2.save()
If a model is already saved, save() calls update(). Otherwise, it calls insert().
Delete#
This issues DELETE statements based on the model's ID properties.
5. Filtering#
Filters are defined in restalchemy.dm.filters and can be passed to get_all() and get_one().
Simple filters#
from restalchemy.dm import filters
# foo_field1 == 10
one = FooModel.objects.get_one(filters={"foo_field1": filters.EQ(10)})
# foo_field1 > 5
greater = list(
FooModel.objects.get_all(filters={"foo_field1": filters.GT(5)})
)
# foo_field1 IN (5, 6)
subset = list(
FooModel.objects.get_all(filters={"foo_field1": filters.In([5, 6])})
)
# foo_field1 NOT IN (1, 2)
not_subset = list(
FooModel.objects.get_all(filters={"foo_field1": filters.NotIn([1, 2])})
)
Complex expressions#
You can build complex conditions with AND and OR expressions:
from restalchemy.dm import filters
# WHERE ((foo_field1 = 1 AND foo_field2 = '2') OR (foo_field2 = '3'))
filter_expr = filters.OR(
filters.AND(
{
"foo_field1": filters.EQ(1),
"foo_field2": filters.EQ("2"),
}
),
filters.AND({"foo_field2": filters.EQ("3")}),
)
foo = FooModel.objects.get_one(filters=filter_expr)
The storage layer translates these filter structures into SQL WHERE clauses.
6. Transactions and explicit sessions#
By default, each operation uses an internal session and transaction.
If you need to group multiple operations in a single transaction, use engine.session_manager() or sessions.session_manager().
Using engine.session_manager()#
from restalchemy.storage.sql import engines
engine = engines.engine_factory.get_engine()
with engine.session_manager() as session:
foo = FooModel(foo_field1=42)
foo.save(session=session)
bar = BarModel(bar_field1="x", foo=foo)
bar.save(session=session)
# If any error happens here, both INSERTs are rolled back.
Inside the with block, all operations share the same session and transaction.
You can also reuse a session object obtained from the engine elsewhere in your code by passing session= to .save(), .delete() or collection methods.
Summary#
- Define DM models with
ModelWithUUID+SQLStorableMixinand a__tablename__. - Configure a SQL engine via
engine_factory.configure_factory(). - Use migrations to create/update the underlying tables.
- Use
.save(),.delete()andModel.objects.get_all() / get_one()to perform CRUD operations. - Use DM filters to express query conditions.
- Use explicit sessions and transactions when you need more control.