Origin: https://github.com/arskom/spyne/pull/707
From: Eric Atkin <eatkin@certusllc.us>
Date: Wed, 1 May 2024 16:12:51 -0600
Subject: Fix tests for python 3.12 and sqlalchemy >=2

---
 spyne/client/twisted/__init__.py             |   3 +-
 spyne/protocol/http.py                       |   2 +-
 spyne/protocol/json.py                       |   2 +-
 spyne/store/relational/_base.py              |  12 ++-
 spyne/test/interop/test_pyramid.py           |   2 +-
 spyne/test/multipython/model/test_complex.py |   6 +-
 spyne/test/protocol/_test_dictdoc.py         |   4 +-
 spyne/test/test_sqlalchemy.py                | 102 ++++++++++---------
 spyne/util/invregexp.py                      |   2 +-
 9 files changed, 73 insertions(+), 62 deletions(-)

diff --git a/spyne/client/twisted/__init__.py b/spyne/client/twisted/__init__.py
index 6609e82c8..aaf65e9c4 100644
--- a/spyne/client/twisted/__init__.py
+++ b/spyne/client/twisted/__init__.py
@@ -26,8 +26,6 @@
 from spyne.client import RemoteProcedureBase
 from spyne.client import ClientBase
 
-from zope.interface import implements
-
 from twisted.internet import reactor
 from twisted.internet.defer import Deferred
 from twisted.internet.protocol import Protocol
@@ -42,6 +40,7 @@
 
 class _Producer(object):
     if six.PY2:
+        from zope.interface import implements
         implements(IBodyProducer)
 
     _deferred = None
diff --git a/spyne/protocol/http.py b/spyne/protocol/http.py
index b351d8169..1db705e38 100644
--- a/spyne/protocol/http.py
+++ b/spyne/protocol/http.py
@@ -435,7 +435,7 @@ def _compile_host_pattern(cls, pattern):
         pattern = _full_pattern_re.sub(r'(?P<\1>.*)', pattern)
 
         pattern_b = pattern.encode(cls.HOST_ENCODING)
-        pattern_b = _fragment_pattern_b_re.sub(b'(?P<\\1>[^\.]*)', pattern_b)
+        pattern_b = _fragment_pattern_b_re.sub(rb'(?P<\\1>[^\.]*)', pattern_b)
         pattern_b = _full_pattern_b_re.sub(b'(?P<\\1>.*)', pattern_b)
 
         return re.compile(pattern), re.compile(pattern_b)
diff --git a/spyne/protocol/json.py b/spyne/protocol/json.py
index 16acb11f3..fa569a32c 100644
--- a/spyne/protocol/json.py
+++ b/spyne/protocol/json.py
@@ -17,7 +17,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
 #
 
-"""The ``spyne.protocol.json`` package contains the Json-related protocols.
+r"""The ``spyne.protocol.json`` package contains the Json-related protocols.
 Currently, only :class:`spyne.protocol.json.JsonDocument` is supported.
 
 Initially released in 2.8.0-rc.
diff --git a/spyne/store/relational/_base.py b/spyne/store/relational/_base.py
index 66fca3219..715162622 100644
--- a/spyne/store/relational/_base.py
+++ b/spyne/store/relational/_base.py
@@ -44,7 +44,6 @@
 from sqlalchemy.dialects.postgresql.base import PGUuid, PGInet
 
 from sqlalchemy.orm import relationship
-from sqlalchemy.orm import mapper
 from sqlalchemy.ext.associationproxy import association_proxy
 
 # TODO: find the latest way of checking whether a class is already mapped
@@ -87,6 +86,11 @@
 
 from spyne.util import sanitize_args
 
+if int(sqlalchemy.__version__.split('.')[0]) < 2:
+    from sqlalchemy.orm import mapper
+else:
+    from sqlalchemy.orm import registry
+    mapper = registry().map_imperatively
 
 # Inheritance type constants.
 class _SINGLE:
@@ -582,10 +586,11 @@ def _gen_array_m2m(cls, props, subname, arrser, storage):
     rel_kwargs = dict(
         lazy=storage.lazy,
         backref=storage.backref,
-        cascade=storage.cascade,
         order_by=storage.order_by,
         back_populates=storage.back_populates,
     )
+    if storage.cascade:  # sqla >=2 doesn't accept False
+        rel_kwargs['cascade'] = cascade
 
     if storage.explicit_join:
         # Specify primaryjoin and secondaryjoin when requested.
@@ -753,11 +758,12 @@ def _gen_array_o2m(cls, props, subname, arrser, arrser_cust, storage):
     rel_kwargs = dict(
         lazy=storage.lazy,
         backref=storage.backref,
-        cascade=storage.cascade,
         order_by=storage.order_by,
         foreign_keys=[col],
         back_populates=storage.back_populates,
     )
+    if storage.cascade:  # sqla >=2 doesn't accept False
+        rel_kwargs['cascade'] = cascade
 
     if storage.single_parent is not None:
         rel_kwargs['single_parent'] = storage.single_parent
diff --git a/spyne/test/interop/test_pyramid.py b/spyne/test/interop/test_pyramid.py
index 2dd3b044c..f0fd12be4 100755
--- a/spyne/test/interop/test_pyramid.py
+++ b/spyne/test/interop/test_pyramid.py
@@ -71,6 +71,6 @@ def testGetWsdl(self):
 
         request = Request(env)
         resp = request.get_response(wsgi_app)
-        self.assert_(resp.status.startswith("200 "))
+        self.assertTrue(resp.status.startswith("200 "))
         node = etree.XML(resp.body)  # will throw exception if non well formed
 
diff --git a/spyne/test/multipython/model/test_complex.py b/spyne/test/multipython/model/test_complex.py
index 9aa8d1cb2..0cd5b001f 100644
--- a/spyne/test/multipython/model/test_complex.py
+++ b/spyne/test/multipython/model/test_complex.py
@@ -140,7 +140,7 @@ class Attributes(ComplexModel.Attributes):
 
         Base2 = Base.customize(prop1=4)
 
-        self.assertNotEquals(Base.Attributes.prop1, Base2.Attributes.prop1)
+        self.assertNotEqual(Base.Attributes.prop1, Base2.Attributes.prop1)
         self.assertEqual(Base.Attributes.prop2, Base2.Attributes.prop2)
 
         class Derived(Base):
@@ -156,7 +156,7 @@ class Attributes(Base.Attributes):
         self.assertEqual(Derived.Attributes.prop1, 3)
         self.assertEqual(Derived2.Attributes.prop1, 5)
 
-        self.assertNotEquals(Derived.Attributes.prop3, Derived2.Attributes.prop3)
+        self.assertNotEqual(Derived.Attributes.prop3, Derived2.Attributes.prop3)
         self.assertEqual(Derived.Attributes.prop4, Derived2.Attributes.prop4)
 
         Derived3 = Derived.customize(prop3=12)
@@ -164,7 +164,7 @@ class Attributes(Base.Attributes):
 
         # changes made to bases propagate, unless overridden
         self.assertEqual(Derived.Attributes.prop1, Base.Attributes.prop1)
-        self.assertNotEquals(Derived2.Attributes.prop1, Base.Attributes.prop1)
+        self.assertNotEqual(Derived2.Attributes.prop1, Base.Attributes.prop1)
         self.assertEqual(Derived3.Attributes.prop1, Base.Attributes.prop1)
 
     def test_declare_order(self):
diff --git a/spyne/test/protocol/_test_dictdoc.py b/spyne/test/protocol/_test_dictdoc.py
index 7e4af3dfa..ba5e6b5cc 100644
--- a/spyne/test/protocol/_test_dictdoc.py
+++ b/spyne/test/protocol/_test_dictdoc.py
@@ -1234,7 +1234,7 @@ def some_call(p):
             print(ctx.out_document)
 
             d = convert_dict({"some_callResponse": {"some_callResult": inner}})
-            self.assertEquals(ctx.out_document[0], d)
+            self.assertEqual(ctx.out_document[0], d)
 
         def test_validation_freq_parent(self):
             class C(ComplexModel):
@@ -1308,7 +1308,7 @@ def some_call(sc):
             doc = [{"C": {"s1": "s1","s2": "s2"}}]
             ctx = _dry_me([SomeService], {"some_call": doc})
 
-            self.assertEquals(ctx.out_document[0], convert_dict(
+            self.assertEqual(ctx.out_document[0], convert_dict(
                 {'some_callResponse': {'some_callResult': {'C': {'s2': 's2'}}}})
             )
 
diff --git a/spyne/test/test_sqlalchemy.py b/spyne/test/test_sqlalchemy.py
index 9d7af0df3..6a137a9eb 100755
--- a/spyne/test/test_sqlalchemy.py
+++ b/spyne/test/test_sqlalchemy.py
@@ -30,9 +30,9 @@
 from sqlalchemy import MetaData
 from sqlalchemy import Column
 from sqlalchemy import Table
+from sqlalchemy import text
 from sqlalchemy.exc import IntegrityError
 
-from sqlalchemy.orm import mapper
 from sqlalchemy.orm import sessionmaker
 
 from spyne import M, Any, Double
@@ -142,7 +142,6 @@ def setUp(self):
         self.engine = create_engine('sqlite:///:memory:')
         self.session = sessionmaker(bind=self.engine)()
         self.metadata = TableModel.Attributes.sqla_metadata = MetaData()
-        self.metadata.bind = self.engine
         logging.info('Testing against sqlalchemy-%s', sqlalchemy.__version__)
 
     def test_obj_json_dirty(self):
@@ -159,7 +158,7 @@ class SomeClass1(TableModel):
                 ('a', SomeClass.store_as('jsonb')),
             ]
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
 
         sc1 = SomeClass1(i=5, a=SomeClass(s="s", d=42.0))
         self.session.add(sc1)
@@ -191,7 +190,7 @@ class SomeClass(TableModel):
             i = Integer32(64, index=True)
 
         t = SomeClass.__table__
-        self.metadata.create_all()  # not needed, just nice to see.
+        self.metadata.create_all(self.engine)  # not needed, just nice to see.
 
         assert t.c.id.primary_key == True
         assert t.c.id.autoincrement == False
@@ -211,7 +210,7 @@ class SomeClass(TableModel):
             s = Unicode(64, sqla_column_args=dict(name='ss'))
 
         t = SomeClass.__table__
-        self.metadata.create_all()  # not needed, just nice to see.
+        self.metadata.create_all(self.engine)  # not needed, just nice to see.
 
         assert 'ss' in t.c
 
@@ -234,7 +233,7 @@ class SomeClass(TableModel):
                                                sqla_column_args=dict(name='oo'))
 
         t = SomeClass.__table__
-        self.metadata.create_all()  # not needed, just nice to see.
+        self.metadata.create_all(self.engine)  # not needed, just nice to see.
 
         assert 'oo_id' in t.c
 
@@ -257,7 +256,7 @@ class SomeClass(TableModel):
                                                sqla_column_args=dict(name='oo'))
 
         t = SomeClass.__table__
-        self.metadata.create_all()  # not needed, just nice to see.
+        self.metadata.create_all(self.engine)  # not needed, just nice to see.
 
         assert 'oo' in t.c
 
@@ -278,7 +277,7 @@ class SomeClass(TableModel):
             id = Integer32(primary_key=True)
             o = SomeOtherClass.customize(store_as='table')
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
 
         soc = SomeOtherClass(s='ehe')
         sc = SomeClass(o=soc)
@@ -315,7 +314,7 @@ class SomeClass(TableModel):
             id = Integer32(primary_key=True)
             others = Array(SomeOtherClass, store_as='table')
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
 
         soc1 = SomeOtherClass(s='ehe1')
         soc2 = SomeOtherClass(s='ehe2')
@@ -347,7 +346,7 @@ class SomeClass(TableModel):
             id = Integer32(primary_key=True)
             others = Array(SomeOtherClass, store_as=table(multi=True))
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
 
         soc1 = SomeOtherClass(s='ehe1')
         soc2 = SomeOtherClass(s='ehe2')
@@ -380,7 +379,7 @@ class SomeClass(TableModel):
             others = Array(SomeOtherClass,
                              store_as=table(multi=True, backref='some_classes'))
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
 
         soc1 = SomeOtherClass(s='ehe1')
         soc2 = SomeOtherClass(s='ehe2')
@@ -409,7 +408,7 @@ class SomeClass(TableModel):
             id = Integer32(primary_key=True)
             others = Array(SomeOtherClass, store_as='xml')
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
 
         soc1 = SomeOtherClass(s='ehe1')
         soc2 = SomeOtherClass(s='ehe2')
@@ -438,7 +437,7 @@ class SomeClass(TableModel):
             id = Integer32(primary_key=True)
             others = Array(SomeOtherClass, store_as=xml(no_ns=True))
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
 
         soc1 = SomeOtherClass(s='ehe1')
         soc2 = SomeOtherClass(s='ehe2')
@@ -449,7 +448,8 @@ class SomeClass(TableModel):
         self.session.close()
 
         sc_xml = self.session.connection() \
-                     .execute("select others from some_class") .fetchall()[0][0]
+                     .execute(text("select others from some_class")) \
+                     .fetchall()[0][0]
 
         from lxml import etree
         assert etree.fromstring(sc_xml).tag == 'SomeOtherClassArray'
@@ -467,7 +467,7 @@ class SomeOtherClass(TableModel):
         class SomeClass(SomeOtherClass):
             numbers = Array(Integer32).store_as(xml(no_ns=True, root_tag='a'))
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
 
         sc = SomeClass(id=5, s='s', numbers=[1, 2, 3, 4])
 
@@ -614,26 +614,34 @@ def __repr__(self):
             Column('type', sqlalchemy.String(20), nullable=False),
         )
 
-        employee_mapper = mapper(Employee, employees_table,
-                                        polymorphic_on=employees_table.c.type,
-                                                polymorphic_identity='employee')
+        if int(sqlalchemy.__version__.split('.')[0]) < 2:
+            from sqlalchemy.orm import mapper
+        else:
+            from sqlalchemy.orm import registry
+            mapper = registry().map_imperatively
+
+        employee_mapper = mapper(Employee,
+                                 employees_table,
+                                 polymorphic_on=employees_table.c.type,
+                                 polymorphic_identity='employee')
 
-        manager_mapper = mapper(Manager, inherits=employee_mapper,
-                                                polymorphic_identity='manager')
+        manager_mapper = mapper(Manager,
+                                inherits=employee_mapper,
+                                polymorphic_identity='manager')
 
-        engineer_mapper = mapper(Engineer, inherits=employee_mapper,
-                                                polymorphic_identity='engineer')
+        engineer_mapper = mapper(Engineer,
+                                 inherits=employee_mapper,
+                                 polymorphic_identity='engineer')
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
 
         manager = Manager('name', 'data')
         self.session.add(manager)
         self.session.commit()
         self.session.close()
 
-        assert self.session.query(Employee).with_polymorphic('*') \
-                   .filter_by(employee_id=1) \
-                   .one().type == 'manager'
+        assert self.session.query(Employee).filter_by(employee_id=1) \
+                .one().type == 'manager'
 
     def test_inheritance_polymorphic_with_non_nullables_in_subclasses(self):
         class SomeOtherClass(TableModel):
@@ -653,7 +661,7 @@ class SomeClass(SomeOtherClass):
 
             i = Integer(nillable=False)
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
 
         assert SomeOtherClass.__table__.c.s.nullable == False
 
@@ -683,8 +691,8 @@ class SomeClass(SomeOtherClass):
 
         self.session.expunge_all()
 
-        assert self.session.query(SomeOtherClass).with_polymorphic('*') \
-                   .filter_by(id=soc_id).one().t == 1
+        assert self.session.query(SomeOtherClass).filter_by(id=soc_id) \
+                .one().t == 1
 
         self.session.close()
 
@@ -702,7 +710,7 @@ class SomeClass(SomeOtherClass):
             __mapper_args__ = {'polymorphic_identity': 2}
             numbers = Array(Integer32).store_as(xml(no_ns=True, root_tag='a'))
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
 
         sc = SomeClass(id=5, s='s', numbers=[1, 2, 3, 4])
 
@@ -710,8 +718,7 @@ class SomeClass(SomeOtherClass):
         self.session.commit()
         self.session.close()
 
-        assert self.session.query(SomeOtherClass).with_polymorphic('*') \
-            .filter_by(id=5).one().t == 2
+        assert self.session.query(SomeOtherClass).filter_by(id=5).one().t == 2
         self.session.close()
 
     def test_nested_sql_array_as_json(self):
@@ -726,7 +733,7 @@ class SomeClass(TableModel):
             id = Integer32(primary_key=True)
             others = Array(SomeOtherClass, store_as='json')
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
 
         soc1 = SomeOtherClass(s='ehe1')
         soc2 = SomeOtherClass(s='ehe2')
@@ -751,7 +758,7 @@ class SomeClass(TableModel):
             i = XmlAttribute(Integer32(pk=True))
             s = XmlData(Unicode(64))
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
         self.session.add(SomeClass(s='s'))
         self.session.commit()
         self.session.expunge_all()
@@ -773,7 +780,7 @@ class SomeClass(TableModel):
             others = Array(SomeOtherClass, store_as='json')
             f = Unicode(32, default='uuu')
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
         self.session.add(SomeClass())
         self.session.commit()
         self.session.expunge_all()
@@ -788,7 +795,7 @@ class SomeClass(TableModel):
             id = Integer32(primary_key=True)
             f = Unicode(32, db_default=u'uuu')
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
         val = SomeClass()
         assert val.f is None
 
@@ -814,7 +821,7 @@ class SomeClass(TableModel):
             id = Integer32(primary_key=True)
             o = SomeOtherClass.customize(store_as='table')
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
         self.session.add(SomeClass())
         self.session.commit()
 
@@ -833,7 +840,7 @@ class SomeClass(TableModel):
             id = Integer32(primary_key=True)
             o = SomeOtherClass.customize(store_as='table', index='btree')
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
         idx, = SomeClass.__table__.indexes
         assert 'o_id' in idx.columns
 
@@ -844,7 +851,7 @@ class SomeClass(TableModel):
             id = Integer32(primary_key=True)
             values = Array(Unicode).store_as('table')
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
 
         self.session.add(SomeClass(id=1, values=['a', 'b', 'c']))
         self.session.commit()
@@ -881,7 +888,7 @@ class SomeClass(TableModel):
             children = Array(SomeChildClass).store_as('table')
             mirror = SomeChildClass.store_as('table')
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
 
         children = [
             SomeChildClass(s='p', i=600),
@@ -973,14 +980,13 @@ class SomeClass(TableModel):
             id = Integer32(primary_key=True)
             s = Unicode(32)
 
-        TableModel.Attributes.sqla_metadata.create_all()
+        TableModel.Attributes.sqla_metadata.create_all(self.engine)
 
         # create a new table model with empty metadata
         TM2 = TTableModel()
-        TM2.Attributes.sqla_metadata.bind = self.engine
 
         # fill it with information from the db
-        TM2.Attributes.sqla_metadata.reflect()
+        TM2.Attributes.sqla_metadata.reflect(self.engine)
 
         # convert sqla info to spyne info
         class Reflected(TM2):
@@ -1037,7 +1043,7 @@ class C(TableModel):
             id = Integer32(pk=True)
             f = File(store_as=HybridFileStore('test_file_storage', 'json'))
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
         c = C(f=File.Value(name=u"name", type=u"type", data=[b"data"]))
         self.session.add(c)
         self.session.flush()
@@ -1100,7 +1106,7 @@ class D(TableModel):
             c = Array(C).customize(store_as=table(right='dd_id'))
 
         C.append_field('d', D.customize(store_as=table(left='dd_id')))
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
 
         c1, c2 = C(id=1), C(id=2)
         d = D(id=1, c=[c1, c2])
@@ -1179,7 +1185,7 @@ class C(TableModel):
 
         C()
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
 
         assert "s" in C2._type_info
         assert "s" in C2.Attributes.sqla_mapper.columns
@@ -1233,7 +1239,7 @@ class C(B):
 
         B.append_field('i', Integer32)
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
 
         self.session.add(C(s="foo", i=42))
         self.session.commit()
@@ -1268,7 +1274,7 @@ class D(TableModel):
 
         B.append_field('d', D.store_as('table'))
 
-        self.metadata.create_all()
+        self.metadata.create_all(self.engine)
 
         self.session.add(C(d=D(i=42)))
         self.session.commit()
diff --git a/spyne/util/invregexp.py b/spyne/util/invregexp.py
index 1e7f1e52b..8d35a2662 100644
--- a/spyne/util/invregexp.py
+++ b/spyne/util/invregexp.py
@@ -256,7 +256,7 @@ def count(gen):
 
 
 def invregexp(regex):
-    """Call this routine as a generator to return all the strings that
+    r"""Call this routine as a generator to return all the strings that
        match the input regular expression.
            for s in invregexp("[A-Z]{3}\d{3}"):
                print s
