pythonpython-3.xsqlalchemypyramidpylons

What is this ZopeTransactionEvents error with SQLAlchemy while updating a Pyramid application?


I'm updating Pyramid/SQLAlchemy legacy code to Python 3.8 from an app working fine under Python 2.7, and running it locally. All the necessary requirements are pip installed and setup.py runs without error.

On running initialise with my local .ini file, All goes well, the database tables (MariaDB) are all written.

in models.py

from sqlalchemy.orm import (
    scoped_session,
    sessionmaker,
    relationship,
    backref,
    synonym,
    )
from zope.sqlalchemy import ZopeTransactionEvents
#[...]
DBSession = scoped_session(sessionmaker(extension=ZopeTransactionEvents()))

in the main app it fails with 'ZopeTransactionEvents' object has no attribute 'after_commit' at this function, after getting the final input and attempting to add it to the DB at DBSession.add(user):

def do_create_admin_user(self):
    from ..models import User
    from getpass import getpass
    print("Create an administrative user")
    fullname = input("full name: ")
    username = input("username: ")
    if not username:
        self.log.info("missing username - aborted")
        return
    if len(username) > 50:
        self.log.info("username too long - aborted")
        return
    password = getpass("password for {}: ".format(username))

    with transaction.manager:
        user = User(
            username=username,
            fullname=fullname,
            administrator=True,
            password=password
        )
        DBSession.add(user)
    self.log.info("{} created".format(username))

Here are the two key parts of the stack trace:

Traceback (most recent call last):
"[...]sqlalchemy/util/_collections.py", line 1055, in __call__
    return self.registry.value
AttributeError: '_thread._local' object has no attribute 'value'

During handling of the above exception, another exception occurred:

[cruft omitted]

"[...]sqlalchemy/orm/deprecated_interfaces.py", line 367, in _adapt_listener
    ls_meth = getattr(listener, meth)
AttributeError: 'ZopeTransactionEvents' object has no attribute 'after_commit'

This specific issue halted the process, and despite days of research (and some unproductive hacking) I'm no closer to a solution. This is a legacy project and I'm not previously familiar with Pyramid or SQAlchemy, so finding my way as I go along.


Fixed

In the end, this is what worked i.e. no arguments to sessionmaker()

from zope.sqlalchemy import register
# ...
DBSession = scoped_session(sessionmaker())
register(DBSession)

Now on to the next error.


Solution

  • This is due to a breaking change introduced in zope.sqlalchemy v1.2. See details in the zope.sqlalchemy pypi page

    To make things clearer we renamed the ZopeTransactionExtension class to ZopeTransactionEvents. Existing code using the ‘register’ version stays compatible.

    To upgrade from 1.1

    Your old code like this:

    from zope.sqlalchemy import ZopeTransactionExtension
    
    DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension(), **options))
    

    becomes:

    from zope.sqlalchemy import register
    
    DBSession = scoped_session(sessionmaker(**options))
    register(DBSession)
    

    edit

    Essentially its a minor breaking change to the zope.sqlalchemy API that was introduced in version 1.2, this is now the way. Rather than registering the extension via keyword argument, you now register the session explicitly with the register callable.

    The above is a direct quote from the documentation. It may not be obvious but the use of **options refers to optional keyword arguments. If you are not using any keyword arguments then this would be omitted (i.e. just call sessionmaker() with no arguments).