Compare commits
No commits in common. "b247c6cd840b28fb6b14c9acd4486519c28e6eb3" and "d95a2308489f2398cc3d42d834dda9811b2be171" have entirely different histories.
b247c6cd84
...
d95a230848
4 changed files with 5 additions and 104 deletions
|
|
@ -5,12 +5,6 @@ All notable changes to Wutta-Continuum will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## v0.3.3 (2026-03-04)
|
|
||||||
|
|
||||||
### Fix
|
|
||||||
|
|
||||||
- add `joins` param for `model_transaction_query()`
|
|
||||||
|
|
||||||
## v0.3.2 (2026-02-01)
|
## v0.3.2 (2026-02-01)
|
||||||
|
|
||||||
### Fix
|
### Fix
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "Wutta-Continuum"
|
name = "Wutta-Continuum"
|
||||||
version = "0.3.3"
|
version = "0.3.2"
|
||||||
description = "SQLAlchemy-Continuum versioning for Wutta Framework"
|
description = "SQLAlchemy-Continuum versioning for Wutta Framework"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
|
authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ def render_operation_type(operation_type):
|
||||||
return OPERATION_TYPES[operation_type]
|
return OPERATION_TYPES[operation_type]
|
||||||
|
|
||||||
|
|
||||||
def model_transaction_query(instance, session=None, model_class=None, joins=None):
|
def model_transaction_query(instance, session=None, model_class=None):
|
||||||
"""
|
"""
|
||||||
Make a query capable of finding all SQLAlchemy-Continuum
|
Make a query capable of finding all SQLAlchemy-Continuum
|
||||||
``transaction`` records associated with the given model instance.
|
``transaction`` records associated with the given model instance.
|
||||||
|
|
@ -66,35 +66,8 @@ def model_transaction_query(instance, session=None, model_class=None, joins=None
|
||||||
:param model_class: Optional :term:`data model` class to query.
|
:param model_class: Optional :term:`data model` class to query.
|
||||||
If not specified, will be obtained from the ``instance``.
|
If not specified, will be obtained from the ``instance``.
|
||||||
|
|
||||||
:param joins: Optional sequence of "join info tuples" - see
|
|
||||||
further explanation below.
|
|
||||||
|
|
||||||
:returns: SQLAlchemy query object. Note that it will *not* have an
|
:returns: SQLAlchemy query object. Note that it will *not* have an
|
||||||
``ORDER BY`` clause yet.
|
``ORDER BY`` clause yet.
|
||||||
|
|
||||||
The default logic looks for any "version" records for the given
|
|
||||||
instance, and returns the associated transactions. But sometimes
|
|
||||||
you need to look for more than one type of version record:
|
|
||||||
|
|
||||||
If e.g. a core table provides common fields but a custom table
|
|
||||||
adds extension fields for a record, you will want to find
|
|
||||||
transactions involving *either* version table when showing a
|
|
||||||
record's total history.
|
|
||||||
|
|
||||||
This is accomplished here via the ``joins`` param. If specified,
|
|
||||||
each item in the ``joins`` sequence must be a 3-tuple::
|
|
||||||
|
|
||||||
(related_class, related_attr, instance_attr)
|
|
||||||
|
|
||||||
The meaning of those is as follows; assuming ``User`` is the main
|
|
||||||
instance model class:
|
|
||||||
|
|
||||||
* ``related_class`` - model class which is "related" to the
|
|
||||||
instance model class, e.g. ``UserExtension``
|
|
||||||
* ``related_attr`` - attribute name on the related class which
|
|
||||||
serves as foreign key to the instance, e.g. ``"user_uuid"``
|
|
||||||
* ``instance_attr`` - attribute name on the main instance class
|
|
||||||
which serves as primary key, e.g. ``"uuid"``
|
|
||||||
"""
|
"""
|
||||||
if not session:
|
if not session:
|
||||||
session = orm.object_session(instance)
|
session = orm.object_session(instance)
|
||||||
|
|
@ -104,52 +77,9 @@ def model_transaction_query(instance, session=None, model_class=None, joins=None
|
||||||
txncls = continuum.transaction_class(model_class)
|
txncls = continuum.transaction_class(model_class)
|
||||||
vercls = continuum.version_class(model_class)
|
vercls = continuum.version_class(model_class)
|
||||||
|
|
||||||
# basic query is for the *transaction* table
|
query = session.query(txncls).join(
|
||||||
query = session.query(txncls)
|
|
||||||
|
|
||||||
# we'll do inner *or* outer join on main version table below
|
|
||||||
join_args = (
|
|
||||||
vercls,
|
vercls,
|
||||||
sa.and_(
|
sa.and_(vercls.uuid == instance.uuid, vercls.transaction_id == txncls.id),
|
||||||
vercls.uuid == instance.uuid,
|
|
||||||
vercls.transaction_id == txncls.id,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if joins:
|
|
||||||
|
|
||||||
# we must *outer* join on main version table, since we will
|
|
||||||
# also be joining on other version tables
|
|
||||||
query = query.outerjoin(*join_args)
|
|
||||||
|
|
||||||
# we'll collect "filter conditions" for use below...
|
|
||||||
conditions = [vercls.uuid != None]
|
|
||||||
|
|
||||||
# add join/filter for each requested by caller
|
|
||||||
for child_class, foreign_attr, primary_attr in joins:
|
|
||||||
child_vercls = continuum.version_class(child_class)
|
|
||||||
foreign_attr = getattr(child_vercls, foreign_attr)
|
|
||||||
query = query.outerjoin(
|
|
||||||
child_vercls,
|
|
||||||
sa.and_(
|
|
||||||
child_vercls.transaction_id == txncls.id,
|
|
||||||
foreign_attr == getattr(instance, primary_attr),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# and add the filter condition for use below...
|
|
||||||
conditions.append(foreign_attr != None)
|
|
||||||
|
|
||||||
# at this point we have *outer* joined on *all* version tables
|
|
||||||
# involved, but that means basically "all transactions" will
|
|
||||||
# match! so we add explicit filter to make sure at least one
|
|
||||||
# of them is related for a transaction to match
|
|
||||||
query = query.filter(sa.or_(*conditions))
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
# no joins were specified, so we can just do *inner* join on
|
|
||||||
# the main version table and call it good
|
|
||||||
query = query.join(*join_args)
|
|
||||||
|
|
||||||
return query
|
return query
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ class TestRenderOperationType(TestCase):
|
||||||
|
|
||||||
class TestModelTransactionQuery(VersionTestCase):
|
class TestModelTransactionQuery(VersionTestCase):
|
||||||
|
|
||||||
def test_inner_join(self):
|
def test_basic(self):
|
||||||
model = self.app.model
|
model = self.app.model
|
||||||
|
|
||||||
user = model.User(username="fred")
|
user = model.User(username="fred")
|
||||||
|
|
@ -38,26 +38,3 @@ class TestModelTransactionQuery(VersionTestCase):
|
||||||
UserVersion = continuum.version_class(model.User)
|
UserVersion = continuum.version_class(model.User)
|
||||||
version = self.session.query(UserVersion).one()
|
version = self.session.query(UserVersion).one()
|
||||||
self.assertIs(version.transaction, txn)
|
self.assertIs(version.transaction, txn)
|
||||||
|
|
||||||
def test_outer_joins(self):
|
|
||||||
model = self.app.model
|
|
||||||
|
|
||||||
person = model.Person(full_name="Fred Flintstone")
|
|
||||||
self.session.add(person)
|
|
||||||
user = model.User(username="fred", person=person)
|
|
||||||
self.session.add(user)
|
|
||||||
self.session.commit()
|
|
||||||
|
|
||||||
query = mod.model_transaction_query(
|
|
||||||
user, joins=[(model.Person, "uuid", "person_uuid")]
|
|
||||||
)
|
|
||||||
self.assertEqual(query.count(), 1)
|
|
||||||
txn = query.one()
|
|
||||||
|
|
||||||
vercls_user = continuum.version_class(model.User)
|
|
||||||
version = self.session.query(vercls_user).one()
|
|
||||||
self.assertIs(version.transaction, txn)
|
|
||||||
|
|
||||||
vercls_person = continuum.version_class(model.Person)
|
|
||||||
version = self.session.query(vercls_person).one()
|
|
||||||
self.assertIs(version.transaction, txn)
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue