The oslo_db.sqlalchemy.session Module

Session Handling for SQLAlchemy backend.

Recommended ways to use sessions within this framework:

  • Use the enginefacade system for connectivity, session and transaction management:

    from oslo_db.sqlalchemy import enginefacade
    
    @enginefacade.reader
    def get_foo(context, foo):
        return (model_query(models.Foo, context.session).
                filter_by(foo=foo).
                first())
    
    @enginefacade.writer
    def update_foo(context, id, newfoo):
        (model_query(models.Foo, context.session).
                filter_by(id=id).
                update({'foo': newfoo}))
    
    @enginefacade.writer
    def create_foo(context, values):
        foo_ref = models.Foo()
        foo_ref.update(values)
        foo_ref.save(context.session)
        return foo_ref
    

    In the above system, transactions are committed automatically, and are shared among all dependent database methods. Ensure that methods which “write” data are enclosed within @writer blocks.

    Note

    Statements in the session scope will not be automatically retried.

  • If you create models within the session, they need to be added, but you do not need to call model.save():

    @enginefacade.writer
    def create_many_foo(context, foos):
        for foo in foos:
            foo_ref = models.Foo()
            foo_ref.update(foo)
            context.session.add(foo_ref)
    
    @enginefacade.writer
    def update_bar(context, foo_id, newbar):
        foo_ref = (model_query(models.Foo, context.session).
                    filter_by(id=foo_id).
                    first())
        (model_query(models.Bar, context.session).
                    filter_by(id=foo_ref['bar_id']).
                    update({'bar': newbar}))
    

    The two queries in update_bar can alternatively be expressed using a single query, which may be more efficient depending on scenario:

    @enginefacade.writer
    def update_bar(context, foo_id, newbar):
        subq = (model_query(models.Foo.id, context.session).
                filter_by(id=foo_id).
                limit(1).
                subquery())
        (model_query(models.Bar, context.session).
                filter_by(id=subq.as_scalar()).
                update({'bar': newbar}))
    

    For reference, this emits approximately the following SQL statement:

    UPDATE bar SET bar = ${newbar}
        WHERE id=(SELECT bar_id FROM foo WHERE id = ${foo_id} LIMIT 1);
    

    Note

    create_duplicate_foo is a trivially simple example of catching an exception while using a savepoint. Here we create two duplicate instances with same primary key, must catch the exception out of context managed by a single session:

    @enginefacade.writer
    def create_duplicate_foo(context):
        foo1 = models.Foo()
        foo2 = models.Foo()
        foo1.id = foo2.id = 1
        try:
            with context.session.begin_nested():
                session.add(foo1)
                session.add(foo2)
        except exception.DBDuplicateEntry as e:
            handle_error(e)
    
  • The enginefacade system eliminates the need to decide when sessions need to be passed between methods. All methods should instead share a common context object; the enginefacade system will maintain the transaction across method calls.

    @enginefacade.writer
    def myfunc(context, foo):
        # do some database things
        bar = _private_func(context, foo)
        return bar
    
    def _private_func(context, foo):
        with enginefacade.using_writer(context) as session:
            # do some other database things
            session.add(SomeObject())
        return bar
    
  • Avoid with_lockmode('UPDATE') when possible.

    FOR UPDATE is not compatible with MySQL/Galera. Instead, an “opportunistic” approach should be used, such that if an UPDATE fails, the entire transaction should be retried. The @wrap_db_retry decorator is one such system that can be used to achieve this.

Enabling soft deletes:

  • To use/enable soft-deletes, SoftDeleteMixin may be used. For example:

    class NovaBase(models.SoftDeleteMixin, models.ModelBase):
        pass
    

Efficient use of soft deletes:

  • While there is a model.soft_delete() method, prefer query.soft_delete(). Some examples:

    @enginefacade.writer
    def soft_delete_bar(context):
        # synchronize_session=False will prevent the ORM from attempting
        # to search the Session for instances matching the DELETE;
        # this is typically not necessary for small operations.
        count = model_query(BarModel, context.session).\
            find(some_condition).soft_delete(synchronize_session=False)
        if count == 0:
            raise Exception("0 entries were soft deleted")
    
    @enginefacade.writer
    def complex_soft_delete_with_synchronization_bar(context):
        # use synchronize_session='evaluate' when you'd like to attempt
        # to update the state of the Session to match that of the DELETE.
        # This is potentially helpful if the operation is complex and
        # continues to work with instances that were loaded, though
        # not usually needed.
        count = (model_query(BarModel, context.session).
                    find(some_condition).
                    soft_delete(synchronize_session='evaulate'))
        if count == 0:
            raise Exception("0 entries were soft deleted")
    
oslo_db.sqlalchemy.session.EngineFacade

alias of LegacyEngineFacade

oslo_db.sqlalchemy.session.create_engine(sql_connection, sqlite_fk=False, mysql_sql_mode=None, idle_timeout=3600, connection_debug=0, max_pool_size=None, max_overflow=None, pool_timeout=None, sqlite_synchronous=True, connection_trace=False, max_retries=10, retry_interval=10, thread_checkin=True, logging_name=None, json_serializer=None, json_deserializer=None)

Return a new SQLAlchemy engine.

oslo_db.sqlalchemy.session.get_maker(engine, autocommit=True, expire_on_commit=False)

Return a SQLAlchemy sessionmaker using the given engine.

class oslo_db.sqlalchemy.session.Query(entities, session=None)

Bases: sqlalchemy.orm.query.Query

Subclass of sqlalchemy.query with soft_delete() method.

soft_delete(synchronize_session='evaluate')
update_on_match(specimen, surrogate_key, values, **kw)

Emit an UPDATE statement matching the given specimen.

This is a method-version of oslo_db.sqlalchemy.update_match.update_on_match(); see that function for usage details.

update_returning_pk(values, surrogate_key)

Perform an UPDATE, returning the primary key of the matched row.

This is a method-version of oslo_db.sqlalchemy.update_match.update_returning_pk(); see that function for usage details.

class oslo_db.sqlalchemy.session.Session(bind=None, autoflush=True, expire_on_commit=True, _enable_transaction_accounting=True, autocommit=False, twophase=False, weak_identity_map=True, binds=None, extension=None, info=None, query_cls=<class 'sqlalchemy.orm.query.Query'>)

Bases: sqlalchemy.orm.session.Session

oslo.db-specific Session subclass.