Using "configure" on scoped_session does not bind engine properly

Issue #3977 resolved
Adam Olsen created an issue

Using SQLAlchemy 1.1.9 (though it happened with 1.1.8 too) with Python 2.7.13 on OS X

Using scoped_session.configure to change the engine doesn't bind the engine properly. For instance, if you do something like this:

session = orm.scoped_session(orm.sessionmaker())
engine = create_engine('sqlite://')
session.configure(bind=engine)
session.close_all()

engine = create_engine('postgresql://someuser:somepass@localhost:5432/db',
    json_serializer=serialize, json_deserializer=deserialize)
session.configure(bind=engine)

print session.bind  # will output `Engine(sqlite://)`

It results in errors such as the one you see below (if you start with sqlite, but then switch to postgres and try to query tables).

If you're wondering WHY we would want to do this, it is for testing. Our test suite tries to make sure that everything works ok with both sqlite and postgresql. We can't just create a new instance of the scoped_session, as it will break the factories and various other places that call for a session at load time (we could probably find some way to make it work with two different sessions, but I don't want to do that unless I have to).

Traceback (most recent call last):
  File "/vagrant/apps/skedup/tests/base.py", line 28, in setUp
    db.session.commit()
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/orm/scoping.py", line 157, in do
    return getattr(self.registry(), name)(*args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 874, in commit
    self.transaction.commit()
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 459, in commit
    self._assert_active(prepared_ok=True)
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 276, in _assert_active
    % self._rollback_exception
InvalidRequestError: This Session's transaction has been rolled back due to a previous exception during flush. To begin a new transaction with this Session, first issue Session.rollback(). Original exception was: (exceptions.AttributeError) 'SQLiteDialect_pysqlite' object has no attribute '_json_serializer'

Doing something like this totally fixes it (manually assigning the engine to session.bind), but it seems like it shouldn't be required:

session = orm.scoped_session(orm.sessionmaker())
engine = create_engine('sqlite://')
session.configure(bind=engine)
session.close_all()

engine = create_engine('postgresql://someuser:somepass@localhost:5432/db',
    json_serializer=serialize, json_deserializer=deserialize)
session.configure(bind=engine)
session.bind = engine  # <---- this is the key to making it work

print session.bind  # will output `Engine(postgresql://someuser:somepass@localhost:5432/db)`

Comments (5)

  1. Mike Bayer repo owner

    Your test as written does produce the correct output, because you are not actually instantiating the session:

    from sqlalchemy import orm, create_engine
    
    session = orm.scoped_session(orm.sessionmaker())
    engine = create_engine('sqlite://')
    session.configure(bind=engine)
    session.close_all()
    
    engine = create_engine('postgresql://someuser:somepass@localhost:5432/db')
    session.configure(bind=engine)
    
    print session.bind
    

    output:

    Engine(postgresql://someuser:***@localhost:5432/db)
    

    the issue you're having is that you are instantiating a session first, then changing the "bind" on the sessionmaker that produces a new session:

    from sqlalchemy import orm, create_engine
    
    session = orm.scoped_session(orm.sessionmaker())
    engine = create_engine('sqlite://')
    session.configure(bind=engine)
    session.connection()
    session.close_all()
    
    engine = create_engine('postgresql://someuser:somepass@localhost:5432/db')
    session.configure(bind=engine)
    
    print session.bind
    

    in the output, there is in fact a handy warning as well telling you as much:

    #!
    
    
    python test.py 
    /home/classic/dev/sqlalchemy/lib/sqlalchemy/orm/scoping.py:106: SAWarning: At least one scoped session is already present.  configure() can not affect sessions that have already been created.
      warn('At least one scoped session is already present. '
    Engine(sqlite://)
    

    the solution is to remove the session that's instantiated:

    from sqlalchemy import orm, create_engine
    
    session = orm.scoped_session(orm.sessionmaker())
    engine = create_engine('sqlite://')
    session.configure(bind=engine)
    session.connection()
    session.remove()
    
    engine = create_engine('postgresql://someuser:somepass@localhost:5432/db')
    session.configure(bind=engine)
    
    print session.bind
    

    assuming things are the same on your end, there's no bug here, please confirm thanks!

  2. Log in to comment