From 979ea6b21f71605314dc0ac1231dd385eced98c4 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sat, 9 Oct 2021 18:56:25 -0400 Subject: [PATCH] update versioned rows examples for 1.4 Repaired the examples in examples/versioned_rows to use SQLAlchemy 1.4 APIs correctly; these examples had been missed when API changes like removing "passive" from ``Session.is_modified()`` were made as well as the ``do_orm_execute()`` event hook were added. Fixes: #7169 Change-Id: I30930a3b185dc0f73be4467faa2f97c5ebf1a36d --- doc/build/changelog/unreleased_14/7169.rst | 8 ++++ doc/build/conf.py | 1 + examples/versioned_rows/versioned_map.py | 4 +- examples/versioned_rows/versioned_rows.py | 2 +- .../versioned_rows_w_versionid.py | 2 +- .../versioned_update_old_row.py | 40 ++++++++----------- 6 files changed, 29 insertions(+), 28 deletions(-) create mode 100644 doc/build/changelog/unreleased_14/7169.rst diff --git a/doc/build/changelog/unreleased_14/7169.rst b/doc/build/changelog/unreleased_14/7169.rst new file mode 100644 index 0000000000..e737855457 --- /dev/null +++ b/doc/build/changelog/unreleased_14/7169.rst @@ -0,0 +1,8 @@ +.. change:: + :tags: bug, examples + :tickets: 7169 + + Repaired the examples in examples/versioned_rows to use SQLAlchemy 1.4 APIs + correctly; these examples had been missed when API changes like removing + "passive" from ``Session.is_modified()`` were made as well as the + ``do_orm_execute()`` event hook were added. diff --git a/doc/build/conf.py b/doc/build/conf.py index 2fb5b8e68c..1b434fad27 100644 --- a/doc/build/conf.py +++ b/doc/build/conf.py @@ -58,6 +58,7 @@ changelog_sections = [ "orm declarative", "orm querying", "orm configuration", + "examples", "engine", "sql", "schema", diff --git a/examples/versioned_rows/versioned_map.py b/examples/versioned_rows/versioned_map.py index 9abbb3e09c..c2fa6c2a91 100644 --- a/examples/versioned_rows/versioned_map.py +++ b/examples/versioned_rows/versioned_map.py @@ -53,9 +53,7 @@ def before_flush(session, flush_context, instances): """ for instance in session.dirty: - if hasattr(instance, "new_version") and session.is_modified( - instance, passive=True - ): + if hasattr(instance, "new_version") and session.is_modified(instance): # make it transient instance.new_version(session) diff --git a/examples/versioned_rows/versioned_rows.py b/examples/versioned_rows/versioned_rows.py index 01067431c8..7179d04749 100644 --- a/examples/versioned_rows/versioned_rows.py +++ b/examples/versioned_rows/versioned_rows.py @@ -34,7 +34,7 @@ def before_flush(session, flush_context, instances): for instance in session.dirty: if not isinstance(instance, Versioned): continue - if not session.is_modified(instance, passive=True): + if not session.is_modified(instance): continue if not attributes.instance_state(instance).has_identity: diff --git a/examples/versioned_rows/versioned_rows_w_versionid.py b/examples/versioned_rows/versioned_rows_w_versionid.py index ac5d0f58af..87d246da1d 100644 --- a/examples/versioned_rows/versioned_rows_w_versionid.py +++ b/examples/versioned_rows/versioned_rows_w_versionid.py @@ -64,7 +64,7 @@ def before_flush(session, flush_context, instances): for instance in session.dirty: if not isinstance(instance, Versioned): continue - if not session.is_modified(instance, passive=True): + if not session.is_modified(instance): continue if not attributes.instance_state(instance).has_identity: diff --git a/examples/versioned_rows/versioned_update_old_row.py b/examples/versioned_rows/versioned_update_old_row.py index 0da28cf42d..049c1a7a02 100644 --- a/examples/versioned_rows/versioned_update_old_row.py +++ b/examples/versioned_rows/versioned_update_old_row.py @@ -1,6 +1,6 @@ """Illustrates the same UPDATE into INSERT technique of ``versioned_rows.py``, but also emits an UPDATE on the **old** row to affect a change in timestamp. -Also includes a :meth:`.QueryEvents.before_compile` hook to limit queries +Also includes a :meth:`.SessionEvents.do_orm_execute` hook to limit queries to only the most recent version. """ @@ -8,22 +8,22 @@ to only the most recent version. import datetime import time +from sqlalchemy import and_ from sqlalchemy import Column from sqlalchemy import create_engine from sqlalchemy import DateTime from sqlalchemy import event from sqlalchemy import inspect from sqlalchemy import Integer -from sqlalchemy import literal from sqlalchemy import String from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import attributes from sqlalchemy.orm import backref from sqlalchemy.orm import make_transient from sqlalchemy.orm import make_transient_to_detached -from sqlalchemy.orm import Query from sqlalchemy.orm import relationship from sqlalchemy.orm import Session +from sqlalchemy.orm import with_loader_criteria Base = declarative_base() @@ -38,6 +38,9 @@ def current_time(): class VersionedStartEnd(object): + start = Column(DateTime, primary_key=True) + end = Column(DateTime, primary_key=True) + def __init__(self, **kw): # reduce some verbosity when we make a new object kw.setdefault("start", current_time() - datetime.timedelta(days=3)) @@ -88,7 +91,7 @@ def before_flush(session, flush_context, instances): for instance in session.dirty: if not isinstance(instance, VersionedStartEnd): continue - if not session.is_modified(instance, passive=True): + if not session.is_modified(instance): continue if not attributes.instance_state(instance).has_identity: @@ -100,27 +103,18 @@ def before_flush(session, flush_context, instances): session.add(instance) -@event.listens_for(Query, "before_compile", retval=True) -def before_compile(query): +@event.listens_for(Session, "do_orm_execute", retval=True) +def do_orm_execute(execute_state): """ensure all queries for VersionedStartEnd include criteria""" - for ent in query.column_descriptions: - entity = ent["entity"] - if entity is None: - continue - insp = inspect(ent["entity"]) - mapper = getattr(insp, "mapper", None) - if mapper and issubclass(mapper.class_, VersionedStartEnd): - query = query.enable_assertions(False).filter( - # using a literal "now" because SQLite's "between" - # seems to be inclusive. In practice, this would be - # ``func.now()`` and we'd be using PostgreSQL - literal( - current_time() + datetime.timedelta(seconds=1) - ).between(ent["entity"].start, ent["entity"].end) - ) - - return query + ct = current_time() + datetime.timedelta(seconds=1) + execute_state.statement = execute_state.statement.options( + with_loader_criteria( + VersionedStartEnd, + lambda cls: and_(ct > cls.start, ct < cls.end), + include_aliases=True, + ) + ) class Parent(VersionedStartEnd, Base): -- 2.47.3