On 09/23/2016 10:18 AM, HP3 wrote:
Hello all,
Couple of weeks back (see [*]), while discussing that history_meta.py
performs an update and an insert for each session.dirty object whenever
session.flush() happens, Simon suggested the following solution to
coalesce all changes within the same transaction into a single insert
and hence a single version increase:
1. In a before_flush hook, check to see if the object has really
changed (using the code from create_version). If it has, save it away in
a set somewhere.
2. In a before_commit hook, iterate over that set and create the
actual history entries.
I went ahead and produced a proof of concept of what he suggested.
Although I have not fully tested it, it seems to work well.
Nonetheless, I wanted to 2x check with you couple of points:
a) For step 1, I used `session.info` to stash `history_objects` - a
dictionary containing the newly created XYZHistory instances keyed by
their "parent" XYZ instance's id and version. Is that a good place to
store them? I guess I would need to "clear" it up on a
`session.rollback` and `session.commit` ?
session.info is great for that. If you want to clear it out when
transactions end, there's a few events you can use for that and
after_transaction_end might be a good one.
http://docs.sqlalchemy.org/en/latest/orm/events.html?highlight=after_transaction_end#sqlalchemy.orm.events.SessionEvents.after_transaction_end
b) For step 2, increase each XYZ instance's version `before_commit`
right when any newly added XYZHistory instance is being added to the
session.
before_commit() is invoked before the final flush() so this is a great
place to do that.
Any thoughts? suggestions? comments?
Thanks in advance
Here is a sneak peak of the changes:
```python
# history_meta.py
@event.listens_for(session, 'before_flush')
def versioned_session_before_flush_handler(session):
if "history-objects" not in session.info:
history_objects = dict()
session.info["history-objects"] = history_objects
...
@event.listens_for(session, 'before_commit')
def versioned_session_before_commit_handler(session):
for hist in history_objects.values():
session.add(hist)
vobj =
session.query(inspect(hist).class_.__versioning_cls__).get(hist.id) #
__versioning_cls__ is the class with the Versioned mixin
vobj.version = hist.version + 1
# near the bottom of `create_version`
...
hist_pk = (inspect(obj).identity, obj.version)
hist = history_objects.get(hist_pk, None)
if hist is None:
hist = hist_cls()
...
```
[*]
https://groups.google.com/forum/#!topic/sqlalchemy/jlEqIidnW_s
https://groups.google.com/forum/#!topic/sqlalchemy/b0tEjftCWkM
Here is the diff (against rel_0_9_8)
$ git diff rel_0_9_8..test_version_relationship_0_9_8 --
examples/versioned_history/history_meta.py
diff --git a/examples/versioned_history/history_meta.py
b/examples/versioned_history/history_meta.py
index f9e979a..84ba87b 100644
--- a/examples/versioned_history/history_meta.py
+++ b/examples/versioned_history/history_meta.py
@@ -7,6 +7,10 @@from sqlalchemy import Table, Column,
ForeignKeyConstraint, Integer, DateTime
from sqlalchemy import event
import datetime
from sqlalchemy.orm.properties import RelationshipProperty
+from sqlalchemy import inspect
+
+import logging
+log = logging.getLogger("History-Meta")
def col_references_table(col, table):
for fk in col.foreign_keys:
@@ -68,9 +72,11 @@def _history_mapper(local_mapper):
# "changed" column stores the UTC timestamp of when the
# history row was created.
# This column is optional and can be omitted.
+ """
cols.append(Column('changed', DateTime,
default=datetime.datetime.utcnow,
info=version_meta))
+ """
if super_fks:
cols.append(ForeignKeyConstraint(*zip(*super_fks)))
@@ -95,6 +101,8 @@def _history_mapper(local_mapper):
bases = local_mapper.base_mapper.class_.__bases__
versioned_cls = type.__new__(type, "%sHistory" % cls.__name__,
bases, {})
+ versioned_cls.__versioning_cls__ = cls
+
m = mapper(
versioned_cls,
table,
@@ -191,20 +210,46 @@def create_version(obj, session, deleted=False):
if obj_changed is True:
break
+ log.info("%r relationship changed ? %r" % (obj, obj_changed))
+
if not obj_changed and not deleted:
return
- attr['version'] = obj.version
- hist = history_cls()
- for key, value in attr.items():
- setattr(hist, key, value)
- session.add(hist)
- obj.version += 1
+ hist_pk = (inspect(obj).identity, obj.version)
+ hist = session.info["history-objects"].get(hist_pk, None)
+ if hist is None:
+ hist = history_cls()
+ attr['version'] = obj.version
+ session.info["history-objects"].update({ hist_pk : hist })
+
+ for key, value in attr.items():
+ setattr(hist, key, value)
+
+ #session.add(hist)
+ #obj.version += 1
def versioned_session(session):
@event.listens_for(session, 'before_flush')
def before_flush(session, flush_context, instances):
- for obj in versioned_objects(session.dirty):
- create_version(obj, session)
- for obj in versioned_objects(session.deleted):
- create_version(obj, session, deleted=True)
+ versioned_session_before_flush_handler(session)
+
+ @event.listens_for(session, 'before_commit')
+ def before_commit(session):
+ versioned_session_before_commit_handler(session)
+
+def versioned_session_before_flush_handler(session):
+ if "history-objects" not in session.info:
+ log.info("history-objects added to session.info")
+ session.info["history-objects"] = dict()
+ for obj in versioned_objects(session.dirty):
+ create_version(obj, session)
+ for obj in versioned_objects(session.deleted):
+ create_version(obj, session, deleted=True)
+
+def versioned_session_before_commit_handler(session):
+ for hist in session.info["history-objects"].values():
+ session.add(hist)
+ vobj =
session.query(inspect(hist).class_.__versioning_cls__).get(hist.id)
+ assert vobj and hist.version == vobj.version, "vobj: %r hist:
%r" % (vobj, hist)
+ vobj.version = hist.version + 1
--
You received this message because you are subscribed to the Google
Groups "sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send
an email to sqlalchemy+unsubscr...@googlegroups.com
<mailto:sqlalchemy+unsubscr...@googlegroups.com>.
To post to this group, send email to sqlalchemy@googlegroups.com
<mailto:sqlalchemy@googlegroups.com>.
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.