Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
],
Expand Down
15 changes: 9 additions & 6 deletions src/zope/sqlalchemy/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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'))
<sqlalchemy.engine... object at ...>
>>> from zope.sqlalchemy import mark_changed
>>> mark_changed(session)
Expand All @@ -205,7 +208,7 @@ session in the 'changed' state initially.
<zope.sqlalchemy.datamanager.ZopeTransactionEvents object at ...>
>>> 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'))
<sqlalchemy.engine... object at ...>
>>> transaction.commit()
>>> session = Session()
Expand Down
82 changes: 52 additions & 30 deletions src/zope/sqlalchemy/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)


Expand All @@ -146,26 +146,39 @@ 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"]
),
)
},
)
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]


Expand Down Expand Up @@ -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
Expand All @@ -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()

Expand All @@ -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
Expand All @@ -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()

Expand All @@ -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(
Expand Down Expand Up @@ -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)

Expand All @@ -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)

Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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"
)
Expand Down Expand Up @@ -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()
Expand Down