diff --git a/setup.py b/setup.py index b36d7d1..b55a405 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ python_requires='>=3.7', install_requires=[ 'setuptools', - 'SQLAlchemy>=1.1,!=1.4.0,!=1.4.1,!=1.4.2,!=1.4.3,!=1.4.4,!=1.4.5,!=1.4.6,<2', # noqa: E501 line too long + 'SQLAlchemy>=1.1,!=1.4.0,!=1.4.1,!=1.4.2,!=1.4.3,!=1.4.4,!=1.4.5,!=1.4.6', # noqa: E501 line too long 'transaction>=1.6.0', 'zope.interface>=3.6.0', ], diff --git a/src/zope/sqlalchemy/README.rst b/src/zope/sqlalchemy/README.rst index 1fa8c8e..732f161 100644 --- a/src/zope/sqlalchemy/README.rst +++ b/src/zope/sqlalchemy/README.rst @@ -96,10 +96,13 @@ This example is lifted directly from the SQLAlchemy declarative documentation. First the necessary imports. >>> from sqlalchemy import * - >>> from sqlalchemy.ext.declarative import declarative_base - >>> from sqlalchemy.orm import scoped_session, sessionmaker, relation + >>> from sqlalchemy.orm import scoped_session, sessionmaker, relationship >>> from zope.sqlalchemy import register >>> import transaction + >>> try: + ... from sqlalchemy.orm import declarative_base + ... except ImportError: + ... from sqlalchemy.ext.declarative import declarative_base Now to define the mapper classes. @@ -108,7 +111,7 @@ Now to define the mapper classes. ... __tablename__ = 'test_users' ... id = Column('id', Integer, primary_key=True) ... name = Column('name', String(50)) - ... addresses = relation("Address", backref="user") + ... addresses = relationship("Address", backref="user") >>> class Address(Base): ... __tablename__ = 'test_addresses' ... id = Column('id', Integer, primary_key=True) @@ -146,7 +149,7 @@ machinery, just as Zope's publisher would. Engine level connections are outside the scope of the transaction integration. - >>> engine.connect().execute('SELECT * FROM test_users').fetchall() + >>> engine.connect().execute(text('SELECT * FROM test_users')).fetchall() [(1, ...'bob')] A new transaction requires a new session. Let's add an address. @@ -187,7 +190,7 @@ to the DB. >>> session = Session() >>> conn = session.connection() >>> users = Base.metadata.tables['test_users'] - >>> conn.execute(users.update(users.c.name=='bob'), name='ben') + >>> conn.execute(users.update().where(users.c.name=='bob').values(name='ben')) >>> from zope.sqlalchemy import mark_changed >>> mark_changed(session) @@ -205,7 +208,7 @@ session in the 'changed' state initially. >>> session = Session() >>> conn = session.connection() - >>> conn.execute(users.update(users.c.name=='ben'), name='bob') + >>> conn.execute(users.update().where(users.c.name=='ben').values(name='bob')) >>> transaction.commit() >>> session = Session() diff --git a/src/zope/sqlalchemy/tests.py b/src/zope/sqlalchemy/tests.py index f0eab37..5ab2222 100644 --- a/src/zope/sqlalchemy/tests.py +++ b/src/zope/sqlalchemy/tests.py @@ -127,14 +127,14 @@ def connect(dbapi_connection, connection_record): sa.ForeignKeyConstraint(("user_id",), ("test_users.id",)), ) -bound_metadata1 = sa.MetaData(engine) -bound_metadata2 = sa.MetaData(engine2) +metadata1 = sa.MetaData() +metadata2 = sa.MetaData() test_one = sa.Table( - "test_one", bound_metadata1, sa.Column("id", sa.Integer, primary_key=True) + "test_one", metadata1, sa.Column("id", sa.Integer, primary_key=True) ) test_two = sa.Table( - "test_two", bound_metadata2, sa.Column("id", sa.Integer, primary_key=True) + "test_two", metadata2, sa.Column("id", sa.Integer, primary_key=True) ) @@ -146,15 +146,28 @@ class TestTwo(SimpleModel): pass +try: + mapper_registry = orm.registry() +except AttributeError: + class MockMapperRegistry: + def map_imperatively(self, *args, **kwargs): + return orm.mapper(*args, **kwargs) + + def dispose(self, *args, **kwargs): + return orm.clear_mappers(*args, **kwargs) + + mapper_registry = MockMapperRegistry() + + def setup_mappers(): - orm.clear_mappers() - # Other tests can clear mappers by calling clear_mappers(), + mapper_registry.dispose() + # Other tests can clear mappers by calling mapper_registry.dispose(), # be more robust by setting up mappers in the test setup. - m1 = orm.mapper( + m1 = mapper_registry.map_imperatively( User, test_users, properties={ - "skills": orm.relation( + "skills": orm.relationship( Skill, primaryjoin=( test_users.columns["id"] == test_skills.columns["user_id"] @@ -162,10 +175,10 @@ def setup_mappers(): ) }, ) - m2 = orm.mapper(Skill, test_skills) + m2 = mapper_registry.map_imperatively(Skill, test_skills) - m3 = orm.mapper(TestOne, test_one) - m4 = orm.mapper(TestTwo, test_two) + m3 = mapper_registry.map_imperatively(TestOne, test_one) + m4 = mapper_registry.map_imperatively(TestTwo, test_two) return [m1, m2, m3, m4] @@ -226,7 +239,7 @@ def setUp(self): def tearDown(self): transaction.abort() metadata.drop_all(engine) - orm.clear_mappers() + mapper_registry.dispose() def testMarkUnknownSession(self): import zope.sqlalchemy.datamanager @@ -253,7 +266,7 @@ def testAbortBeforeCommit(self): transaction.begin() session = Session() conn = session.connection() - conn.execute("SELECT 1 FROM test_users") + conn.execute(sa.text("SELECT 1 FROM test_users")) mark_changed(session) transaction.commit() @@ -267,7 +280,7 @@ def testAbortAfterCommit(self): conn = session.connection() # At least PostgresSQL requires a rollback after invalid SQL is # executed - self.assertRaises(Exception, conn.execute, "BAD SQL SYNTAX") + self.assertRaises(Exception, conn.execute, sa.text("BAD SQL SYNTAX")) mark_changed(session) try: # Thus we could fail in commit @@ -280,7 +293,7 @@ def testAbortAfterCommit(self): transaction.begin() session = Session() conn = session.connection() - conn.execute("SELECT 1 FROM test_users") + conn.execute(sa.text("SELECT 1 FROM test_users")) mark_changed(session) transaction.commit() @@ -302,7 +315,10 @@ def testSimplePopulation(self): d, {"firstname": "udo", "lastname": "juergens", "id": 1}) # bypass the session machinery - stmt = sql.select(test_users.columns).order_by("id") + try: + stmt = sql.select(*test_users.columns).order_by("id") + except exc.ArgumentError: + stmt = sql.select(test_users.columns).order_by("id") conn = session.connection() results = conn.execute(stmt) self.assertEqual( @@ -593,7 +609,7 @@ def testBulkUpdate(self): session.query(User).update(dict(lastname="smith")) transaction.commit() results = engine.connect().execute( - test_users.select(test_users.c.lastname == "smith") + test_users.select().where(test_users.c.lastname == "smith") ) self.assertEqual(len(results.fetchall()), 2) @@ -617,7 +633,7 @@ def testBulkUpdateUsingRegister(self): session.query(User).update(dict(lastname="smith")) transaction.commit() results = engine.connect().execute( - test_users.select(test_users.c.lastname == "smith") + test_users.select().where(test_users.c.lastname == "smith") ) self.assertEqual(len(results.fetchall()), 2) @@ -659,7 +675,7 @@ def testKeepSession(self): with transaction.manager: session.add(User(id=1, firstname="foo", lastname="bar")) - user = session.query(User).get(1) + user = session.query(User).filter(User.id == 1).one() # if the keep_session works correctly, this transaction will not # close the session after commit @@ -680,7 +696,7 @@ def testExpireAll(self): transaction.commit() session = Session() - instance = session.query(User).get(1) + instance = session.query(User).filter(User.id == 1).one() transaction.commit() # No work, session.close() self.assertEqual(sa.inspect(instance).expired, True) @@ -709,7 +725,7 @@ def tearDown(self): self.tm1.abort() self.tm2.abort() metadata.drop_all(engine) - orm.clear_mappers() + mapper_registry.dispose() def testRetry(self): # sqlite is unable to run this test as the databse is locked @@ -724,7 +740,7 @@ def testRetry(self): len(s2.query(User).all()) == 1, "Users table should have one row" ) s1.query(User).delete() - user = s2.query(User).get(1) + user = s2.query(User).filter(User.id == 1).one() user.lastname = "smith" tm1.commit() raised = False @@ -746,7 +762,9 @@ def testRetryThread(self): len(s1.query(User).all()) == 1, "Users table should have one row" ) tm2.begin() - s2.connection().execute("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE") + s2.connection().execute( + sa.text("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE") + ) self.assertTrue( len(s2.query(User).all()) == 1, "Users table should have one row" ) @@ -774,24 +792,28 @@ def target(): class MultipleEngineTests(unittest.TestCase): def setUp(self): self.mappers = setup_mappers() - bound_metadata1.drop_all() - bound_metadata1.create_all() - bound_metadata2.drop_all() - bound_metadata2.create_all() + metadata1.drop_all(engine) + metadata1.create_all(engine) + metadata2.drop_all(engine2) + metadata2.create_all(engine2) def tearDown(self): transaction.abort() - bound_metadata1.drop_all() - bound_metadata2.drop_all() - orm.clear_mappers() + metadata1.drop_all(engine) + metadata2.drop_all(engine2) + mapper_registry.dispose() def testTwoEngines(self): session = UnboundSession() + session.bind_table(TestOne, bind=engine) + session.bind_table(TestTwo, bind=engine2) session.add(TestOne(id=1)) session.add(TestTwo(id=2)) session.flush() transaction.commit() session = UnboundSession() + session.bind_table(TestOne, bind=engine) + session.bind_table(TestTwo, bind=engine2) rows = session.query(TestOne).all() self.assertEqual(len(rows), 1) rows = session.query(TestTwo).all()