diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3cf6f6663..925b075da 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,19 @@ Changelog ========= +spyne-2.15.0 +------------ +* Python 3.11, 3.12, 3.13 and 3.14 support. +* Fixed ``SyntaxWarning`` for invalid string escapes in ``protocol/http.py``, + ``protocol/json.py`` and ``util/invregexp.py``. +* Fixed ``SyntaxWarning`` from PEP 765 (Python 3.14): ``break`` statement + hoisted out of a ``finally`` block in ``protocol/cloth/to_cloth.py``. +* ``server/twisted/http.py``: ``cgi`` module was removed in Python 3.13; + the multipart code path now falls back to the ``legacy-cgi`` PyPI package + and raises a descriptive error if it is needed but missing. +* Replaced deprecated ``unittest`` aliases (``assertEquals`` etc.) in the + test suite for Python 3.12+ compatibility. + spyne-2.14.0 ------------ * Python 3.10 support. diff --git a/setup.py b/setup.py index 2f29fc2c9..1db47ed8d 100755 --- a/setup.py +++ b/setup.py @@ -275,6 +275,10 @@ def run_tests(self): 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.14', 'Programming Language :: Python :: Implementation :: CPython', #'Programming Language :: Python :: Implementation :: Jython', 'Programming Language :: Python :: Implementation :: PyPy', diff --git a/spyne/__init__.py b/spyne/__init__.py index f7e01853b..797d08157 100644 --- a/spyne/__init__.py +++ b/spyne/__init__.py @@ -21,7 +21,7 @@ class LogicError(Exception): pass -__version__ = '2.15.0-alpha' +__version__ = '2.15.0' from pytz import utc as LOCAL_TZ from decimal import Decimal as D diff --git a/spyne/protocol/cloth/to_cloth.py b/spyne/protocol/cloth/to_cloth.py index 6dd59cd77..1afaf6f20 100644 --- a/spyne/protocol/cloth/to_cloth.py +++ b/spyne/protocol/cloth/to_cloth.py @@ -764,10 +764,9 @@ def complex_to_cloth(self, ctx, cls, inst, cloth, parent, name=None, ret.throw(e) except StopIteration: pass - finally: - # cf below - if not (as_attr or as_data): - break + # cf below; hoisted out of finally for PEP 765 (Py 3.14) + if not (as_attr or as_data): + break else: # this is here so that attribute on complex model doesn't get # mixed with in-line attr inside complex model. if an element diff --git a/spyne/protocol/http.py b/spyne/protocol/http.py index b351d8169..79f5caf8b 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/server/twisted/http.py b/spyne/server/twisted/http.py index fda93d152..54fca1e05 100644 --- a/spyne/server/twisted/http.py +++ b/spyne/server/twisted/http.py @@ -43,7 +43,13 @@ logger = logging.getLogger(__name__) import re -import cgi +try: + import cgi # removed in Python 3.13; pip install legacy-cgi to restore +except ImportError: # pragma: no cover + try: + import legacy_cgi as cgi + except ImportError: + cgi = None import gzip import shutil import threading @@ -485,6 +491,11 @@ def _get_file_info(ctx): shutil.copyfileobj(ifstr, content) content.seek(0) + if cgi is None: + raise RuntimeError( + "Multipart parsing requires the 'cgi' module which was removed in " + "Python 3.13. Install 'legacy-cgi' (pip install legacy-cgi) to " + "enable this code path.") img = cgi.FieldStorage( fp=content, headers=ctx.in_header_doc, diff --git a/spyne/test/interop/test_django.py b/spyne/test/interop/test_django.py index b5df8b511..cae494b80 100755 --- a/spyne/test/interop/test_django.py +++ b/spyne/test/interop/test_django.py @@ -281,13 +281,13 @@ class DjangoServiceTestCase(TestCase): def test_handle_does_not_exist(self): """Test if Django service handles `ObjectDoesNotExist` exceptions.""" client = DjangoTestClient('/api/', app) - with self.assertRaisesRegexp(Fault, 'Client.FieldContainerNotFound'): + with self.assertRaisesRegex(Fault, 'Client.FieldContainerNotFound'): client.service.raise_does_not_exist() def test_handle_validation_error(self): """Test if Django service handles `ValidationError` exceptions.""" client = DjangoTestClient('/api/', app) - with self.assertRaisesRegexp(Fault, 'Client.ValidationError'): + with self.assertRaisesRegex(Fault, 'Client.ValidationError'): client.service.raise_validation_error() 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/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