DB session in after_commit signal handler

Hi all :slight_smile:

To begin I would like to mention that I am really impressed with Indicos plugin system. Although it takes a bit of searching through code an plugin repos, once you find the code snippets you need, it is a really powerful and well designed asset <3

I am currently developing a plugin which performs some database operations everytime a NewsItem is created/modified/deleted. However, I’m having some issues with the SQLAlchemy session (referenced by indico.core.db.db.session). Specifically, the plugin attaches to the after_commit signal at which point the session is already expired. I found out that it can be refreshed by running

db.session.__init__(db.session.session_factory)

although this does not seem optimal it does the trick. The issue is that I cannot distinguish between insert/update/delete operations this way because I don’t know how to access the transaction information. I don’t find anything in the kwargs, there is no session.info and nothing stored in session.new either.

Therefore, my question would be. Is there any way to retrieve some information regarding the commited transaction in the after_commit handler or do I have to use the after_process signal instead. In which case, I would also appreciate some guidance how to access the relevant request data (such as URL and body data). I was not able to find anything helpful on this matter in the existing plugin code. Although the signal is used in a couple of places (e.g., here, here or here), these plugins always only seem to access the flask application context which doesn’t do it for me.

Thank you in advance and enjoy your week :sparkles:

after_commit is meant for things like triggering background tasks that should of course only run after a successful commit.

DB change tracking (SQLALCHEMY_TRACK_MODIFICATIONS) which would give you signals related to changes to models are disabled, because Indico does not use them, and they have a performance impact.

If you just want to do stuff after RHEditNews, RHDeleteNews and RHCreateNews have been executed, I suggest using the signals.rh.process signal instead. The sender of the signal is the RH class, so in your plugin you can simply import it and pass it like this:

def init(self):
    self.connect(signals.rh.process, self._after_news_rh_process, sender=RHEditNews)
    self.connect(signals.rh.process, self._after_news_rh_process, sender=RHDeleteNews)
    self.connect(signals.rh.process, self._after_news_rh_process, sender=RHCreateNews)

def _after_news_rh_process(self, rh_cls, rh, **kwargs):
    ...

In case of edit and delete, there’s rh.item pointing to the news item (even though in case of deletion that may no longer be useful at that point), for creation you’d need to query the latest news item from the DB since it’s not stored anywhere on the RH instance.

It’d also be interesting to know what exactly you want to do in your plugin. Maybe there’s a better way to do it.

PS: Not sure why we have both after_process and process signals since the latter is more powerful and verbose, and they run pretty much one after the other. Probably simply historical reasons…

1 Like

Awesome! Thank you very much :hand_with_index_finger_and_thumb_crossed:

The plugin adds a Deadline field to the News form so that news are automatically deleted once this deadline has passed. The deadline is a DateTime variable which gets stored in the database. In order to not mess with the existing models, the plugin creates its own schema and table where these deadlines and the according news ids are stored.

A celery task then takes care of deleting the outdated news (and deadline) entries.

There probably is a better way and I’m a bit insecure about it :sweat_smile: but that’s alright (it’s my first plugin, so I tried a naive approach). Also, we don’t even know whether this is actually how we’re going to deal with the news going forward, but I wanted to try and see if that was an option and also it seems like a good project to get a familiar with Indico’s plugin engine :blush: