from sqlalchemy.orm import MapperExtension
class History(MapperExtension):
def __init__(self):
MapperExtension.__init__(self)
self.methods = ('before_update','before_delete')
def clone(self, mapper, connection, instance, action):
tablename=mapper.mapped_table.name
tablename_bak=tablename+'_bak'
rec = connection.execute(select([tbl[tablename]], tbl[tablename].c.id ==
getattr( instance, '%s_id'%tablename))).fetchone()
dd=dict()
modified=False
for k,v in rec.items():
dd[ k.lower() ] = v
if action=='D': #if delete request...
connection.execute(tbl[tablename_bak].insert(values=dd))
elif action=='U': #if update request...
for k in instance.c: #check for differences, to save it only if it was
modified...
if getattr( instance,str(k).replace('.','_')) != dd[str(k).split('.')[1]]:
modified=True
break
if modified is True:
connection.execute(tbl[tablename_bak].insert(values=dd))
return
def before_update(self, mapper, connection, instance):
return self.clone(mapper, connection, instance, 'U')
def before_delete(self, mapper, connection, instance):
return self.clone(mapper, connection, instance, 'D')
I call it in this way:
from ... import History
mapper(Anagrafica,
tbl['anagrafica'],
column_prefix = 'anagrafica_',
extension=History(),
)
> --
> You received this message because you are subscribed to the Google
> Groups "sqlalchemy" group.
> To view this discussion on the web visit
> https://groups.google.com/d/msg/sqlalchemy/-/IpLW9LroG6IJ.
> To post to this group, send email to sqlal...@googlegroups.com.
> To unsubscribe from this group, send email to
> sqlalchemy+...@googlegroups.com.
> For more options, visit this group at
> http://groups.google.com/group/sqlalchemy?hl=en.
--
Jose Soares _/_/
Sferacarta Net
Via Bazzanese 69 _/_/ _/_/_/
40033 Casalecchio di Reno _/_/ _/_/ _/_/
Bologna - Italy _/_/ _/_/ _/_/
Ph +39051591054 _/_/ _/_/ _/_/ _/_/
fax +390516131537 _/_/ _/_/ _/_/ _/_/
web:www.sferacarta.com _/_/_/ _/_/_/
Le informazioni contenute nella presente mail ed in ogni eventuale file allegato sono riservate e, comunque, destinate esclusivamente alla persona o ente sopraindicati, ai sensi del decreto legislativo 30 giugno 2003, n. 196. La diffusione, distribuzione e/o copiatura della mail trasmessa, da parte di qualsiasi soggetto diverso dal destinatario, sono vietate. La correttezza, l�integrit� e la sicurezza della presente mail non possono essere garantite. Se avete ricevuto questa mail per errore, Vi preghiamo di contattarci immediatamente e di eliminarla. Grazie.
This communication is intended only for use by the addressee, pursuant to legislative decree 30 June 2003, n. 196. It may contain confidential or privileged information. You should not copy or use it to disclose its contents to any other person. Transmission cannot be guaranteed to be error-free, complete and secure. If you are not the intended recipient and receive this communication unintentionally, please inform us immediately and then delete this message from your system. Thank you.
> For more options, visit this group at
> http://groups.google.com/group/sqlalchemy?hl=en.
>
> The problem I have run into is, listening for "after_insert" on a subclass extending DeclarativeBase, only results in calls after insert has been called for, but not after it has been executed.
I'm not familiar with this behavior, can you illustrate ? after_insert happens after all INSERT statements for that particular entity. are you not observing that?
MapperExtensions seem to be a deprecated interface to the same functionality that I am using with events http://docs.sqlalchemy.org/en/latest/orm/interfaces.html .
I guess my fundamental problem is that I don't see a straightforward way to get a reference to a model instance in an "after_insert" or "after_update" event, after the changes have actually been committed to the database. Updates aren't as big a problem as inserts. For inserts, you can't easily enqueue the id of the model, because that ID hasn't been assigned yet.
> I could have sworn I saw conflicting behaviour.
>
> My after_insert would run, and the instance would have an id, it would then enqueue a task in celery. On the other side (in another process), celery would run that task, make a query against that database for that ID, and the ID wouldn't be in the database.
oh, that's completely different. Your flush is inside of a transaction. Your newly generated id *will not* be exposed to the outside world until the transaction commits. If you're spawning off celery tasks that are dependent on the newly flushed data, you *must* use after_commit() for that. Not just for isolation purposes, but what if your transaction failed and was rolled back - then celery would be failing there as well.
Thanks, That was the problem. I think I have it working now. I will update my repo for you to take a look at later.
>
> It seems like I might have a common usecase here. What would you think of adding "after_insert_committed", "after_update_committed", and after "delete_committed" events to the Table object. I would be interested in taking a crack at it.
At some point I'll be adding features to the Session events such that filters can be applied, which will then emit a "sub-event" based on filtering for all objects with certain characteristics - a certain class, or their presence in .new, .dirty, .deleted (these are public lists). These will be available for events like before_flush and after_flush.
Within after_commit(), things are trickier as we have only a list of items that entered the session via .new and items that entered the session via .deleted, but not the public version of these lists, they're separate lists used just to maintain things in case we rollback. These collections don't cover objects that were garbage collected, so there really can't be such an event that will "work" the way everyone would expect it to.
So your best bet is to use before_flush, after_flush, or even after_insert/after_update, and populate a collection that's associated with the Session.