Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-zeep for openSUSE:Factory checked in at 2026-06-23 17:41:44 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-zeep (Old) and /work/SRC/openSUSE:Factory/.python-zeep.new.1956 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-zeep" Tue Jun 23 17:41:44 2026 rev:13 rq:1361285 version:4.3.3 Changes: -------- --- /work/SRC/openSUSE:Factory/python-zeep/python-zeep.changes 2025-10-13 15:37:07.873743128 +0200 +++ /work/SRC/openSUSE:Factory/.python-zeep.new.1956/python-zeep.changes 2026-06-23 17:44:43.886383778 +0200 @@ -1,0 +2,13 @@ +Mon Jun 22 11:53:54 UTC 2026 - Nico Krapp <[email protected]> + +- Update to 4.3.3 (fixes bsc#1268679) + * Wire up the forbid_external setting (previously defined but unused since + the move off defusedxml in 4.0). When enabled, zeep refuses to transitively + fetch http/https resources via xsd:import, xsd:include, wsdl:import or lxml + entity resolution, raising zeep.exceptions.ExternalReferenceForbidden. The + user-supplied entry-point WSDL/schema URL is still loaded. The default + remains False to preserve existing behaviour; enable it when loading WSDLs + from untrusted sources to mitigate SSRF via attacker-controlled import + targets. + +------------------------------------------------------------------- Old: ---- zeep-4.3.2.tar.gz New: ---- zeep-4.3.3.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-zeep.spec ++++++ --- /var/tmp/diff_new_pack.nynr6K/_old 2026-06-23 17:44:45.162428248 +0200 +++ /var/tmp/diff_new_pack.nynr6K/_new 2026-06-23 17:44:45.170428527 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-zeep # -# Copyright (c) 2024 SUSE LLC +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,7 +17,7 @@ Name: python-zeep -Version: 4.3.2 +Version: 4.3.3 Release: 0 Summary: A Python SOAP client based on lxml/requests License: MIT ++++++ zeep-4.3.2.tar.gz -> zeep-4.3.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/CHANGES new/zeep-4.3.3/CHANGES --- old/zeep-4.3.2/CHANGES 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/CHANGES 2026-06-18 19:17:39.000000000 +0200 @@ -1,3 +1,17 @@ +4.3.3 (2026-06-18) +------------------ + - Wire up the ``forbid_external`` setting (previously defined but unused + since the move off ``defusedxml`` in 4.0). When enabled it refuses to + transitively fetch ``http``/``https`` resources via ``xsd:import``, + ``xsd:include``, ``wsdl:import`` or lxml entity resolution, raising + ``zeep.exceptions.ExternalReferenceForbidden``. The user-supplied + entry-point WSDL/schema URL is still loaded. The default remains + ``False`` to preserve existing behaviour; enable when loading WSDLs from + untrusted sources to mitigate SSRF via attacker-controlled import + targets. + - Internal tooling only: migrate dependency/build management to uv and + replace isort/flake8/black with ruff. No runtime changes. + 4.3.2 (2025-09-15) ----------------- - Support newer versions of httpx (#1447) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/PKG-INFO new/zeep-4.3.3/PKG-INFO --- old/zeep-4.3.2/PKG-INFO 2025-09-15 12:25:44.089512000 +0200 +++ new/zeep-4.3.3/PKG-INFO 2026-06-18 19:17:42.379905200 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: zeep -Version: 4.3.2 +Version: 4.3.3 Summary: A Python SOAP client Author-email: Michael van Tellingen <[email protected]> License: MIT @@ -9,14 +9,14 @@ Project-URL: Changelog, https://github.com/mvantellingen/python-zeep/blob/main/CHANGES Classifier: Development Status :: 5 - Production/Stable Classifier: License :: OSI Approved :: MIT License -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy -Requires-Python: >=3.8 +Requires-Python: >=3.10 Description-Content-Type: text/markdown License-File: LICENSE Requires-Dist: attrs>=17.2.0 @@ -26,23 +26,8 @@ Requires-Dist: requests>=2.7.0 Requires-Dist: requests-toolbelt>=0.7.1 Requires-Dist: requests-file>=1.5.1 -Requires-Dist: pytz Provides-Extra: docs Requires-Dist: sphinx>=1.4.0; extra == "docs" -Provides-Extra: test -Requires-Dist: coverage[toml]==7.6.2; extra == "test" -Requires-Dist: freezegun==1.5.1; extra == "test" -Requires-Dist: pretend==1.0.9; extra == "test" -Requires-Dist: pytest-cov==5.0.0; extra == "test" -Requires-Dist: pytest-httpx; extra == "test" -Requires-Dist: pytest-asyncio; extra == "test" -Requires-Dist: pytest==8.3.3; extra == "test" -Requires-Dist: requests_mock==1.12.1; extra == "test" -Requires-Dist: isort==5.13.2; extra == "test" -Requires-Dist: flake8==7.1.1; extra == "test" -Requires-Dist: flake8-blind-except==0.2.1; extra == "test" -Requires-Dist: flake8-debugger==4.1.2; extra == "test" -Requires-Dist: flake8-imports==0.1.1; extra == "test" Provides-Extra: async Requires-Dist: httpx>=0.15.0; extra == "async" Requires-Dist: packaging; extra == "async" @@ -59,7 +44,7 @@ A Python SOAP client ## Highlights: -- Compatible with Python 3.9, 3.10, 3.11, 3.12, 3.13 and PyPy3 +- Compatible with Python 3.10, 3.11, 3.12, 3.13, 3.14 and PyPy3 - Built on top of lxml, requests, and httpx - Support for Soap 1.1, Soap 1.2, and HTTP bindings - Support for WS-Addressing headers diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/README.md new/zeep-4.3.3/README.md --- old/zeep-4.3.2/README.md 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/README.md 2026-06-18 19:17:39.000000000 +0200 @@ -7,7 +7,7 @@ A Python SOAP client ## Highlights: -- Compatible with Python 3.9, 3.10, 3.11, 3.12, 3.13 and PyPy3 +- Compatible with Python 3.10, 3.11, 3.12, 3.13, 3.14 and PyPy3 - Built on top of lxml, requests, and httpx - Support for Soap 1.1, Soap 1.2, and HTTP bindings - Support for WS-Addressing headers diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/pyproject.toml new/zeep-4.3.3/pyproject.toml --- old/zeep-4.3.2/pyproject.toml 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/pyproject.toml 2026-06-18 19:17:39.000000000 +0200 @@ -1,21 +1,21 @@ [project] name = "zeep" -version = "4.3.2" +version = "4.3.3" description = "A Python SOAP client" readme = "README.md" license = { text = "MIT" } authors = [ { name = "Michael van Tellingen", email = "[email protected]" } ] -requires-python = ">=3.8" +requires-python = ">=3.10" classifiers = [ "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: MIT License", - "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 :: PyPy", ] @@ -27,7 +27,6 @@ "requests>=2.7.0", "requests-toolbelt>=0.7.1", "requests-file>=1.5.1", - "pytz", ] [project.urls] @@ -37,27 +36,24 @@ [project.optional-dependencies] docs = ["sphinx>=1.4.0"] -test = [ - "coverage[toml]==7.6.2", +async = [ + "httpx>=0.15.0", + "packaging", +] +xmlsec = ["xmlsec>=0.6.1"] + +[dependency-groups] +dev = [ + "coverage[toml]==7.14.1", "freezegun==1.5.1", "pretend==1.0.9", - "pytest-cov==5.0.0", + "pytest-cov==7.1.0", "pytest-httpx", "pytest-asyncio", - "pytest==8.3.3", + "pytest==9.1.0", "requests_mock==1.12.1", - # Linting - "isort==5.13.2", - "flake8==7.1.1", - "flake8-blind-except==0.2.1", - "flake8-debugger==4.1.2", - "flake8-imports==0.1.1", -] -async = [ - "httpx>=0.15.0", - "packaging", + "ruff==0.15.17", ] -xmlsec = ["xmlsec>=0.6.1"] [build-system] requires = ["setuptools>=40.6.0", "wheel"] @@ -66,21 +62,28 @@ [tool.coverage.run] branch = true source = ["zeep"] +# Store paths relative to the repo root so coverage data collected on +# different OSes (uv installs the project editable -> src/zeep/...) can be +# combined without absolute-path mismatches. +relative_files = true [tool.coverage.paths] -source = ["src", "*/site-packages/"] +source = ["src", "*/src", "*/site-packages"] [tool.coverage.report] show_missing = true -[tool.isort] -line_length = 88 -multi_line_output = 3 -include_trailing_comma = true -balanced_wrapping = true -default_section = "THIRDPARTY" -known_first_party = ["zeep", "tests"] -use_parentheses = true +[tool.ruff] +line-length = 88 + +[tool.ruff.lint] +# E/W: pycodestyle, F: pyflakes, I: isort, +# BLE: flake8-blind-except, T10: flake8-debugger +select = ["E", "W", "F", "I", "BLE", "T10"] +ignore = ["E501"] + +[tool.ruff.lint.isort] +known-first-party = ["zeep", "tests"] [tool.pytest.ini_options] minversion = "6.0" @@ -92,6 +95,3 @@ "requests", "network: test case requires network connection", ] - -[tool.flake8] -max-line-length = 99 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/src/zeep/__init__.py new/zeep-4.3.3/src/zeep/__init__.py --- old/zeep-4.3.2/src/zeep/__init__.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/src/zeep/__init__.py 2026-06-18 19:17:39.000000000 +0200 @@ -1,10 +1,15 @@ +from importlib.metadata import PackageNotFoundError, version + from zeep.client import AsyncClient, CachingClient, Client from zeep.plugins import Plugin from zeep.settings import Settings from zeep.transports import Transport from zeep.xsd.valueobjects import AnyObject -__version__ = "4.3.2" +try: + __version__ = version("zeep") +except PackageNotFoundError: # pragma: no cover + __version__ = "unknown" __all__ = [ "AsyncClient", "CachingClient", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/src/zeep/cache.py new/zeep-4.3.3/src/zeep/cache.py --- old/zeep-4.3.2/src/zeep/cache.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/src/zeep/cache.py 2026-06-18 19:17:39.000000000 +0200 @@ -8,7 +8,6 @@ from typing import Dict, Tuple, Union import platformdirs -import pytz # The sqlite3 is not available on Google App Engine so we handle the # ImportError here and set the sqlite3 var to None. @@ -57,9 +56,9 @@ """Expose the version prefix to be used in content serialization. :rtype: bytes """ - assert ( - getattr(self, "_version", None) is not None - ), "A version must be provided in order to use the VersionedCacheBase backend." + assert getattr(self, "_version", None) is not None, ( + "A version must be provided in order to use the VersionedCacheBase backend." + ) prefix = "$ZEEP:%s$" % self._version return bytes(prefix.encode("ascii")) @@ -169,8 +168,8 @@ if timeout is None: return False - now = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=pytz.utc) - max_age = value.replace(tzinfo=pytz.utc) + now = datetime.datetime.now(datetime.timezone.utc) + max_age = value.replace(tzinfo=datetime.timezone.utc) max_age += datetime.timedelta(seconds=timeout) return now > max_age diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/src/zeep/exceptions.py new/zeep-4.3.3/src/zeep/exceptions.py --- old/zeep-4.3.2/src/zeep/exceptions.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/src/zeep/exceptions.py 2026-06-18 19:17:39.000000000 +0200 @@ -114,3 +114,12 @@ def __str__(self): tpl = "EntitiesForbidden(name='{}', content={!r})" return tpl.format(self.name, self.content) + + +class ExternalReferenceForbidden(Error): + def __init__(self, url): + super().__init__("External reference to %r is forbidden" % (url,)) + self.url = url + + def __str__(self): + return "ExternalReferenceForbidden(url=%r)" % (self.url,) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/src/zeep/loader.py new/zeep-4.3.3/src/zeep/loader.py --- old/zeep-4.3.2/src/zeep/loader.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/src/zeep/loader.py 2026-06-18 19:17:39.000000000 +0200 @@ -3,20 +3,28 @@ from urllib.parse import urljoin, urlparse, urlunparse from lxml import etree -from lxml.etree import Resolver, XMLParser, XMLSyntaxError, fromstring +from lxml.etree import Resolver, XMLParser, fromstring -from zeep.exceptions import DTDForbidden, EntitiesForbidden, XMLSyntaxError +from zeep.exceptions import ( + DTDForbidden, + EntitiesForbidden, + ExternalReferenceForbidden, + XMLSyntaxError, +) from zeep.settings import Settings class ImportResolver(Resolver): """Custom lxml resolve to use the transport object""" - def __init__(self, transport): + def __init__(self, transport, settings=None): self.transport = transport + self.settings = settings or Settings() def resolve(self, url, pubid, context): if urlparse(url).scheme in ("http", "https"): + if self.settings.forbid_external: + raise ExternalReferenceForbidden(url) content = self.transport.load(url) return self.resolve_string(content, context) @@ -45,7 +53,7 @@ recover=recover, huge_tree=settings.xml_huge_tree, ) - parser.resolvers.add(ImportResolver(transport)) + parser.resolvers.add(ImportResolver(transport, settings)) try: elementtree = fromstring(content, parser=parser, base_url=base_url) docinfo = elementtree.getroottree().docinfo @@ -69,7 +77,12 @@ def load_external( - url: typing.Union[typing.IO, str], transport, base_url=None, settings=None + url: typing.Union[typing.IO, str], + transport, + base_url=None, + settings=None, + *, + _initial: bool = False, ): """Load an external XML document. @@ -78,6 +91,9 @@ :param base_url: :param settings: A zeep.settings.Settings object containing parse settings. :type settings: zeep.settings.Settings + :param _initial: Internal flag set by zeep when loading the user-supplied + entry-point document; transitive imports leave it False so that + ``settings.forbid_external`` can block them. """ settings = settings or Settings() @@ -86,11 +102,21 @@ else: if base_url: url = absolute_location(url, base_url) + if not _initial and settings.forbid_external: + if urlparse(str(url)).scheme in ("http", "https"): + raise ExternalReferenceForbidden(url) content = transport.load(url) return parse_xml(content, transport, base_url, settings=settings) -async def load_external_async(url: typing.IO, transport, base_url=None, settings=None): +async def load_external_async( + url: typing.IO, + transport, + base_url=None, + settings=None, + *, + _initial: bool = False, +): """Load an external XML document. :param url: @@ -98,6 +124,9 @@ :param base_url: :param settings: A zeep.settings.Settings object containing parse settings. :type settings: zeep.settings.Settings + :param _initial: Internal flag set by zeep when loading the user-supplied + entry-point document; transitive imports leave it False so that + ``settings.forbid_external`` can block them. """ settings = settings or Settings() @@ -106,6 +135,9 @@ else: if base_url: url = absolute_location(url, base_url) + if not _initial and settings.forbid_external: + if urlparse(str(url)).scheme in ("http", "https"): + raise ExternalReferenceForbidden(url) content = await transport.load(url) return parse_xml(content, transport, base_url, settings=settings) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/src/zeep/proxy.py new/zeep-4.3.3/src/zeep/proxy.py --- old/zeep-4.3.2/src/zeep/proxy.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/src/zeep/proxy.py 2026-06-18 19:17:39.000000000 +0200 @@ -20,7 +20,7 @@ # Merge the default _soapheaders with the passed _soapheaders if default_headers and operation_soap_headers: merged = copy.deepcopy(default_headers) - if type(merged) != type(operation_soap_headers): + if type(merged) is not type(operation_soap_headers): raise ValueError("Incompatible soapheaders definition") if isinstance(operation_soap_headers, list): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/src/zeep/settings.py new/zeep-4.3.3/src/zeep/settings.py --- old/zeep-4.3.2/src/zeep/settings.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/src/zeep/settings.py 2026-06-18 19:17:39.000000000 +0200 @@ -19,9 +19,15 @@ :type forbid_dtd: bool :param forbid_entities: disallow XML with <!ENTITY> declarations inside the DTD :type forbid_entities: bool - :param forbid_external: disallow any access to remote or local resources - in external entities or DTD and raising an ExternalReferenceForbidden - exception when a DTD or entity references an external resource. + :param forbid_external: disallow transitive fetches of external resources + (``http``/``https`` URLs reached via ``xsd:import``, ``xsd:include``, + ``wsdl:import`` or lxml entity/DTD resolution) while parsing the + user-supplied entry-point document. The entry-point WSDL or schema + itself is always loaded. Defaults to ``False`` for backwards + compatibility; enable when loading WSDLs from untrusted sources to + mitigate SSRF via attacker-controlled import targets. + An :class:`zeep.exceptions.ExternalReferenceForbidden` is raised when a + blocked fetch is attempted. :type forbid_external: bool :param xml_huge_tree: disable lxml/libxml2 security restrictions and support very deep trees and very long text content @@ -51,7 +57,7 @@ xml_huge_tree = attr.ib(default=False) forbid_dtd = attr.ib(default=False) forbid_entities = attr.ib(default=True) - forbid_external = attr.ib(default=True) + forbid_external = attr.ib(default=False) # xsd workarounds xsd_ignore_sequence_order = attr.ib(default=False) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/src/zeep/wsdl/wsdl.py new/zeep-4.3.3/src/zeep/wsdl/wsdl.py --- old/zeep-4.3.2/src/zeep/wsdl/wsdl.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/src/zeep/wsdl/wsdl.py 2026-06-18 19:17:39.000000000 +0200 @@ -74,9 +74,7 @@ self.transport = transport # Dict with all definition objects within this WSDL - self._definitions = ( - {} - ) # type: typing.Dict[typing.Tuple[str, str], "Definition"] + self._definitions = {} # type: typing.Dict[typing.Tuple[str, str], "Definition"] self.types = Schema( node=None, transport=self.transport, @@ -86,7 +84,7 @@ self.load(location) def load(self, location): - document = self._get_xml_document(location) + document = self._get_xml_document(location, _initial=True) root_definitions = Definition(self, document, self.location) root_definitions.resolve_imports() @@ -138,16 +136,25 @@ print("%s%s" % (" " * 12, str(operation))) print("") - def _get_xml_document(self, location: typing.IO) -> etree._Element: + def _get_xml_document( + self, location: typing.IO, *, _initial: bool = False + ) -> etree._Element: """Load the XML content from the given location and return an lxml.Element object. :param location: The URL of the document to load :type location: string + :param _initial: True when loading the user-supplied entry-point WSDL; + False for transitive ``wsdl:import`` documents (which are gated by + ``settings.forbid_external``). """ return load_external( - location, self.transport, self.location, settings=self.settings + location, + self.transport, + self.location, + settings=self.settings, + _initial=_initial, ) def _add_definition(self, definition: "Definition"): @@ -427,7 +434,6 @@ binding = None for binding_class in binding_classes: if binding_class.match(binding_node): - try: binding = binding_class.parse(self, binding_node) except NotImplementedError as exc: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/src/zeep/wsse/signature.py new/zeep-4.3.3/src/zeep/wsse/signature.py --- old/zeep-4.3.2/src/zeep/wsse/signature.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/src/zeep/wsse/signature.py 2026-06-18 19:17:39.000000000 +0200 @@ -244,7 +244,7 @@ ctx.key = key _sign_node(ctx, signature, envelope.find(QName(soap_env, "Body")), digest_method) timestamp = security.find(QName(ns.WSU, "Timestamp")) - if timestamp != None: + if timestamp is not None: _sign_node(ctx, signature, timestamp, digest_method) ctx.sign(signature) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/src/zeep/wsse/utils.py new/zeep-4.3.3/src/zeep/wsse/utils.py --- old/zeep-4.3.2/src/zeep/wsse/utils.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/src/zeep/wsse/utils.py 2026-06-18 19:17:39.000000000 +0200 @@ -1,7 +1,6 @@ import datetime from uuid import uuid4 -import pytz from lxml import etree from lxml.builder import ElementMaker @@ -29,7 +28,7 @@ def get_timestamp(timestamp=None, zulu_timestamp=None): timestamp = timestamp or datetime.datetime.now(datetime.timezone.utc) - timestamp = timestamp.replace(tzinfo=pytz.utc, microsecond=0) + timestamp = timestamp.replace(tzinfo=datetime.timezone.utc, microsecond=0) if zulu_timestamp: return timestamp.isoformat().replace("+00:00", "Z") else: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/src/zeep/xsd/__init__.py new/zeep-4.3.3/src/zeep/xsd/__init__.py --- old/zeep-4.3.2/src/zeep/xsd/__init__.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/src/zeep/xsd/__init__.py 2026-06-18 19:17:39.000000000 +0200 @@ -4,7 +4,8 @@ """ -from zeep.xsd.const import Nil, SkipValue +from zeep.xsd.const import Nil as Nil +from zeep.xsd.const import SkipValue as SkipValue from zeep.xsd.elements import * # noqa from zeep.xsd.schema import Schema as Schema from zeep.xsd.types import * # noqa diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/src/zeep/xsd/elements/any.py new/zeep-4.3.3/src/zeep/xsd/elements/any.py --- old/zeep-4.3.2/src/zeep/xsd/elements/any.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/src/zeep/xsd/elements/any.py 2026-06-18 19:17:39.000000000 +0200 @@ -166,7 +166,6 @@ def validate(self, value, render_path): if self.accepts_multiple and isinstance(value, list): - # Validate bounds if len(value) < self.min_occurs: raise exceptions.ValidationError( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/src/zeep/xsd/elements/element.py new/zeep-4.3.3/src/zeep/xsd/elements/element.py --- old/zeep-4.3.2/src/zeep/xsd/elements/element.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/src/zeep/xsd/elements/element.py 2026-06-18 19:17:39.000000000 +0200 @@ -181,8 +181,9 @@ and schema.settings.xsd_ignore_sequence_order and list( filter( - lambda elem: etree.QName(elem.tag).localname - == self.qname.localname, + lambda elem: ( + etree.QName(elem.tag).localname == self.qname.localname + ), xmlelements, ) ) @@ -190,8 +191,9 @@ # Search for the field in remaining elements, not only the leftmost xmlelement = list( filter( - lambda elem: etree.QName(elem.tag).localname - == self.qname.localname, + lambda elem: ( + etree.QName(elem.tag).localname == self.qname.localname + ), xmlelements, ) )[0] @@ -258,7 +260,6 @@ def validate(self, value, render_path=None): """Validate that the value is valid""" if self.accepts_multiple and isinstance(value, list): - # Validate bounds if len(value) < self.min_occurs: raise exceptions.ValidationError( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/src/zeep/xsd/elements/indicators.py new/zeep-4.3.3/src/zeep/xsd/elements/indicators.py --- old/zeep-4.3.2/src/zeep/xsd/elements/indicators.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/src/zeep/xsd/elements/indicators.py 2026-06-18 19:17:39.000000000 +0200 @@ -358,7 +358,6 @@ # Choose out of multiple options = [] for element_name, element in self.elements_nested: - local_xmlelements = copy.copy(xmlelements) try: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/src/zeep/xsd/schema.py new/zeep-4.3.3/src/zeep/xsd/schema.py --- old/zeep-4.3.2/src/zeep/xsd/schema.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/src/zeep/xsd/schema.py 2026-06-18 19:17:39.000000000 +0200 @@ -117,7 +117,9 @@ self._prefix_map_auto = self._create_prefix_map() def add_document_by_url(self, url: str) -> None: - schema_node = load_external(url, self._transport, settings=self.settings) + schema_node = load_external( + url, self._transport, settings=self.settings, _initial=True + ) document = self.create_new_document(schema_node, url=url) document.resolve() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/src/zeep/xsd/types/builtins.py new/zeep-4.3.3/src/zeep/xsd/types/builtins.py --- old/zeep-4.3.2/src/zeep/xsd/types/builtins.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/src/zeep/xsd/types/builtins.py 2026-06-18 19:17:39.000000000 +0200 @@ -1,11 +1,9 @@ import base64 import datetime -import math import re from decimal import Decimal as _Decimal import isodate -import pytz from zeep.xsd.const import xsd_ns from zeep.xsd.types.any import AnyType @@ -151,22 +149,7 @@ if isinstance(value, str): return value - # Bit of a hack, since datetime is a subclass of date we can't just - # test it with an isinstance(). And actually, we should not really - # care about the type, as long as it has the required attributes - if not all(hasattr(value, attr) for attr in ("hour", "minute", "second")): - value = datetime.datetime.combine( - value, - datetime.time( - getattr(value, "hour", 0), - getattr(value, "minute", 0), - getattr(value, "second", 0), - ), - ) - - if getattr(value, "microsecond", 0): - return isodate.isostrf.strftime(value, "%Y-%m-%dT%H:%M:%S.%f%Z") - return isodate.isostrf.strftime(value, "%Y-%m-%dT%H:%M:%S%Z") + return value.isoformat().replace("+00:00", "Z") @treat_whitespace("collapse") def pythonvalue(self, value): @@ -189,9 +172,7 @@ if isinstance(value, str): return value - if value.microsecond: - return isodate.isostrf.strftime(value, "%H:%M:%S.%f%Z") - return isodate.isostrf.strftime(value, "%H:%M:%S%Z") + return value.isoformat().replace("+00:00", "Z") @treat_whitespace("collapse") def pythonvalue(self, value): @@ -207,7 +188,7 @@ def xmlvalue(self, value): if isinstance(value, str): return value - return isodate.isostrf.strftime(value, "%Y-%m-%d") + return value.strftime("%Y-%m-%d") @treat_whitespace("collapse") def pythonvalue(self, value): @@ -548,12 +529,12 @@ ## # Other def _parse_timezone(val): - """Return a pytz.tzinfo object""" + """Return a timezone object""" if not val: return if val == "Z" or val == "+00:00": - return pytz.utc + return datetime.timezone.utc negative = val.startswith("-") minutes = int(val[-2:]) @@ -561,22 +542,17 @@ if negative: minutes = 0 - minutes - return pytz.FixedOffset(minutes) + return datetime.timezone(offset=datetime.timedelta(minutes=minutes)) -def _unparse_timezone(tzinfo): +def _unparse_timezone(tzinfo: datetime.timezone): if not tzinfo: return "" - if tzinfo == pytz.utc: + if tzinfo == datetime.timezone.utc: return "Z" - hours = math.floor(tzinfo._minutes / 60) - minutes = tzinfo._minutes % 60 - - if hours > 0: - return "+%02d:%02d" % (hours, minutes) - return "-%02d:%02d" % (abs(hours), minutes) + return datetime.datetime.now(tz=tzinfo).isoformat()[-6:] _types = [ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/src/zeep/xsd/types/complex.py new/zeep-4.3.3/src/zeep/xsd/types/complex.py --- old/zeep-4.3.2/src/zeep/xsd/types/complex.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/src/zeep/xsd/types/complex.py 2026-06-18 19:17:39.000000000 +0200 @@ -52,7 +52,7 @@ qname=None, is_global: bool = False, ): - if element and type(element) == list: + if element and type(element) is list: element = Sequence(element) self.name = self.__class__.__name__ if qname else None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/src/zeep.egg-info/PKG-INFO new/zeep-4.3.3/src/zeep.egg-info/PKG-INFO --- old/zeep-4.3.2/src/zeep.egg-info/PKG-INFO 2025-09-15 12:25:44.000000000 +0200 +++ new/zeep-4.3.3/src/zeep.egg-info/PKG-INFO 2026-06-18 19:17:42.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: zeep -Version: 4.3.2 +Version: 4.3.3 Summary: A Python SOAP client Author-email: Michael van Tellingen <[email protected]> License: MIT @@ -9,14 +9,14 @@ Project-URL: Changelog, https://github.com/mvantellingen/python-zeep/blob/main/CHANGES Classifier: Development Status :: 5 - Production/Stable Classifier: License :: OSI Approved :: MIT License -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy -Requires-Python: >=3.8 +Requires-Python: >=3.10 Description-Content-Type: text/markdown License-File: LICENSE Requires-Dist: attrs>=17.2.0 @@ -26,23 +26,8 @@ Requires-Dist: requests>=2.7.0 Requires-Dist: requests-toolbelt>=0.7.1 Requires-Dist: requests-file>=1.5.1 -Requires-Dist: pytz Provides-Extra: docs Requires-Dist: sphinx>=1.4.0; extra == "docs" -Provides-Extra: test -Requires-Dist: coverage[toml]==7.6.2; extra == "test" -Requires-Dist: freezegun==1.5.1; extra == "test" -Requires-Dist: pretend==1.0.9; extra == "test" -Requires-Dist: pytest-cov==5.0.0; extra == "test" -Requires-Dist: pytest-httpx; extra == "test" -Requires-Dist: pytest-asyncio; extra == "test" -Requires-Dist: pytest==8.3.3; extra == "test" -Requires-Dist: requests_mock==1.12.1; extra == "test" -Requires-Dist: isort==5.13.2; extra == "test" -Requires-Dist: flake8==7.1.1; extra == "test" -Requires-Dist: flake8-blind-except==0.2.1; extra == "test" -Requires-Dist: flake8-debugger==4.1.2; extra == "test" -Requires-Dist: flake8-imports==0.1.1; extra == "test" Provides-Extra: async Requires-Dist: httpx>=0.15.0; extra == "async" Requires-Dist: packaging; extra == "async" @@ -59,7 +44,7 @@ A Python SOAP client ## Highlights: -- Compatible with Python 3.9, 3.10, 3.11, 3.12, 3.13 and PyPy3 +- Compatible with Python 3.10, 3.11, 3.12, 3.13, 3.14 and PyPy3 - Built on top of lxml, requests, and httpx - Support for Soap 1.1, Soap 1.2, and HTTP bindings - Support for WS-Addressing headers diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/src/zeep.egg-info/requires.txt new/zeep-4.3.3/src/zeep.egg-info/requires.txt --- old/zeep-4.3.2/src/zeep.egg-info/requires.txt 2025-09-15 12:25:44.000000000 +0200 +++ new/zeep-4.3.3/src/zeep.egg-info/requires.txt 2026-06-18 19:17:42.000000000 +0200 @@ -5,7 +5,6 @@ requests>=2.7.0 requests-toolbelt>=0.7.1 requests-file>=1.5.1 -pytz [async] httpx>=0.15.0 @@ -14,20 +13,5 @@ [docs] sphinx>=1.4.0 -[test] -coverage[toml]==7.6.2 -freezegun==1.5.1 -pretend==1.0.9 -pytest-cov==5.0.0 -pytest-httpx -pytest-asyncio -pytest==8.3.3 -requests_mock==1.12.1 -isort==5.13.2 -flake8==7.1.1 -flake8-blind-except==0.2.1 -flake8-debugger==4.1.2 -flake8-imports==0.1.1 - [xmlsec] xmlsec>=0.6.1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/tests/test_loader.py new/zeep-4.3.3/tests/test_loader.py --- old/zeep-4.3.2/tests/test_loader.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/tests/test_loader.py 2026-06-18 19:17:39.000000000 +0200 @@ -1,8 +1,8 @@ import pytest from tests.utils import DummyTransport -from zeep.exceptions import DTDForbidden, EntitiesForbidden -from zeep.loader import parse_xml +from zeep.exceptions import DTDForbidden, EntitiesForbidden, ExternalReferenceForbidden +from zeep.loader import load_external, parse_xml from zeep.settings import Settings @@ -46,3 +46,36 @@ tree = parse_xml(xml, DummyTransport(), settings=Settings(forbid_entities=False)) assert tree[0][0].tag == "Author" + + +def test_forbid_external_blocks_transitive_http_load(): + transport = DummyTransport() + transport.bind("http://example.com/a.xsd", b"<root/>") + + with pytest.raises(ExternalReferenceForbidden): + load_external( + "http://example.com/a.xsd", + transport, + settings=Settings(forbid_external=True), + ) + + +def test_forbid_external_allows_initial_load(): + transport = DummyTransport() + transport.bind("http://example.com/a.xsd", b"<root/>") + + tree = load_external( + "http://example.com/a.xsd", + transport, + settings=Settings(forbid_external=True), + _initial=True, + ) + assert tree.tag == "root" + + +def test_forbid_external_default_allows_load(): + transport = DummyTransport() + transport.bind("http://example.com/a.xsd", b"<root/>") + + tree = load_external("http://example.com/a.xsd", transport) + assert tree.tag == "root" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/tests/test_wsdl.py new/zeep-4.3.3/tests/test_wsdl.py --- old/zeep-4.3.2/tests/test_wsdl.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/tests/test_wsdl.py 2026-06-18 19:17:39.000000000 +0200 @@ -1263,9 +1263,7 @@ transport.bind("https://tests.python-zeep.org/a.xsd", node_a) transport.bind("https://tests.python-zeep.org/b.xsd", node_b) - document = wsdl.Document( - wsdl_content, transport, "https://tests.python-zeep.org/content.wsdl" - ) + wsdl.Document(wsdl_content, transport, "https://tests.python-zeep.org/content.wsdl") def test_import_no_location(): @@ -1309,6 +1307,4 @@ transport = DummyTransport() transport.bind("https://tests.python-zeep.org/a.xsd", node_a) - document = wsdl.Document( - wsdl_content, transport, "https://tests.python-zeep.org/content.wsdl" - ) + wsdl.Document(wsdl_content, transport, "https://tests.python-zeep.org/content.wsdl") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/tests/test_wsdl_messages_document.py new/zeep-4.3.3/tests/test_wsdl_messages_document.py --- old/zeep-4.3.2/tests/test_wsdl_messages_document.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/tests/test_wsdl_messages_document.py 2026-06-18 19:17:39.000000000 +0200 @@ -1329,9 +1329,9 @@ def test_deserialize_part_no_element(): wsdl_content = StringIO( """ - <wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" - xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" - xmlns:xsd="http://www.w3.org/2001/XMLSchema" + <wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" + xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" + xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://tests.python-zeep.org/tns" xmlns:tns="http://tests.python-zeep.org/tns"> <wsdl:types> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/tests/test_xsd_builtins.py new/zeep-4.3.3/tests/test_xsd_builtins.py --- old/zeep-4.3.2/tests/test_xsd_builtins.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/tests/test_xsd_builtins.py 2026-06-18 19:17:39.000000000 +0200 @@ -3,7 +3,6 @@ import isodate import pytest -import pytz from zeep.xsd.types import builtins @@ -161,14 +160,16 @@ value = datetime.datetime(2016, 3, 4, 21, 14, 42) assert instance.xmlvalue(value) == "2016-03-04T21:14:42" - value = datetime.datetime(2016, 3, 4, 21, 14, 42, tzinfo=pytz.utc) + value = datetime.datetime(2016, 3, 4, 21, 14, 42, tzinfo=datetime.timezone.utc) assert instance.xmlvalue(value) == "2016-03-04T21:14:42Z" - value = datetime.datetime(2016, 3, 4, 21, 14, 42, 123456, tzinfo=pytz.utc) + value = datetime.datetime( + 2016, 3, 4, 21, 14, 42, 123456, tzinfo=datetime.timezone.utc + ) assert instance.xmlvalue(value) == "2016-03-04T21:14:42.123456Z" - value = datetime.datetime(2016, 3, 4, 21, 14, 42, tzinfo=pytz.utc) - value = value.astimezone(pytz.timezone("Europe/Amsterdam")) + value = datetime.datetime(2016, 3, 4, 21, 14, 42, tzinfo=datetime.timezone.utc) + value = value.astimezone(datetime.timezone(datetime.timedelta(hours=1))) assert instance.xmlvalue(value) == "2016-03-04T22:14:42+01:00" assert ( @@ -262,7 +263,7 @@ def test_xmlvalue(self): instance = builtins.gYearMonth() assert instance.xmlvalue((2012, 10, None)) == "2012-10" - assert instance.xmlvalue((2012, 10, pytz.utc)) == "2012-10Z" + assert instance.xmlvalue((2012, 10, datetime.timezone.utc)) == "2012-10Z" def test_pythonvalue(self): instance = builtins.gYearMonth() @@ -270,10 +271,14 @@ assert instance.pythonvalue("2001-10+02:00") == ( 2001, 10, - pytz.FixedOffset(120), + datetime.timezone(datetime.timedelta(minutes=120)), + ) + assert instance.pythonvalue("2001-10Z") == (2001, 10, datetime.timezone.utc) + assert instance.pythonvalue("2001-10+00:00") == ( + 2001, + 10, + datetime.timezone.utc, ) - assert instance.pythonvalue("2001-10Z") == (2001, 10, pytz.utc) - assert instance.pythonvalue("2001-10+00:00") == (2001, 10, pytz.utc) assert instance.pythonvalue("-2001-10") == (-2001, 10, None) assert instance.pythonvalue("-20001-10") == (-20001, 10, None) @@ -285,19 +290,22 @@ def test_xmlvalue(self): instance = builtins.gYear() assert instance.xmlvalue((2001, None)) == "2001" - assert instance.xmlvalue((2001, pytz.utc)) == "2001Z" + assert instance.xmlvalue((2001, datetime.timezone.utc)) == "2001Z" def test_pythonvalue(self): instance = builtins.gYear() assert instance.pythonvalue("2001") == (2001, None) - assert instance.pythonvalue("2001+02:00") == (2001, pytz.FixedOffset(120)) - assert instance.pythonvalue("2001Z") == (2001, pytz.utc) - assert instance.pythonvalue("2001+00:00") == (2001, pytz.utc) + assert instance.pythonvalue("2001+02:00") == ( + 2001, + datetime.timezone(datetime.timedelta(minutes=120)), + ) + assert instance.pythonvalue("2001Z") == (2001, datetime.timezone.utc) + assert instance.pythonvalue("2001+00:00") == (2001, datetime.timezone.utc) assert instance.pythonvalue("-2001") == (-2001, None) assert instance.pythonvalue("-20000") == (-20000, None) assert instance.pythonvalue(" \t2001+02:00\r\n ") == ( 2001, - pytz.FixedOffset(120), + datetime.timezone(datetime.timedelta(minutes=120)), ) with pytest.raises(builtins.ParseError): @@ -312,9 +320,17 @@ def test_pythonvalue(self): instance = builtins.gMonthDay() assert instance.pythonvalue("--05-01") == (5, 1, None) - assert instance.pythonvalue("--11-01Z") == (11, 1, pytz.utc) - assert instance.pythonvalue("--11-01+02:00") == (11, 1, pytz.FixedOffset(120)) - assert instance.pythonvalue("--11-01-04:00") == (11, 1, pytz.FixedOffset(-240)) + assert instance.pythonvalue("--11-01Z") == (11, 1, datetime.timezone.utc) + assert instance.pythonvalue("--11-01+02:00") == ( + 11, + 1, + datetime.timezone(datetime.timedelta(minutes=120)), + ) + assert instance.pythonvalue("--11-01-04:00") == ( + 11, + 1, + datetime.timezone(datetime.timedelta(minutes=-240)), + ) assert instance.pythonvalue("--11-15") == (11, 15, None) assert instance.pythonvalue("--02-29") == (2, 29, None) assert instance.pythonvalue("\t\r\n --05-01 ") == (5, 1, None) @@ -331,12 +347,18 @@ def test_pythonvalue(self): instance = builtins.gMonth() assert instance.pythonvalue("--05") == (5, None) - assert instance.pythonvalue("--11Z") == (11, pytz.utc) - assert instance.pythonvalue("--11+02:00") == (11, pytz.FixedOffset(120)) - assert instance.pythonvalue("--11-04:00") == (11, pytz.FixedOffset(-240)) + assert instance.pythonvalue("--11Z") == (11, datetime.timezone.utc) + assert instance.pythonvalue("--11+02:00") == ( + 11, + datetime.timezone(datetime.timedelta(minutes=120)), + ) + assert instance.pythonvalue("--11-04:00") == ( + 11, + datetime.timezone(datetime.timedelta(minutes=-240)), + ) assert instance.pythonvalue("--11") == (11, None) assert instance.pythonvalue("--02") == (2, None) - assert instance.pythonvalue("\n\t --11Z \r") == (11, pytz.utc) + assert instance.pythonvalue("\n\t --11Z \r") == (11, datetime.timezone.utc) with pytest.raises(builtins.ParseError): assert instance.pythonvalue("99") @@ -349,18 +371,24 @@ value = (1, None) assert instance.xmlvalue(value) == "---01" - value = (1, pytz.FixedOffset(120)) + value = (1, datetime.timezone(datetime.timedelta(minutes=120))) assert instance.xmlvalue(value) == "---01+02:00" - value = (1, pytz.FixedOffset(-240)) + value = (1, datetime.timezone(datetime.timedelta(minutes=-240))) assert instance.xmlvalue(value) == "---01-04:00" def test_pythonvalue(self): instance = builtins.gDay() assert instance.pythonvalue("---01") == (1, None) - assert instance.pythonvalue("---01Z") == (1, pytz.utc) - assert instance.pythonvalue("---01+02:00") == (1, pytz.FixedOffset(120)) - assert instance.pythonvalue("---01-04:00") == (1, pytz.FixedOffset(-240)) + assert instance.pythonvalue("---01Z") == (1, datetime.timezone.utc) + assert instance.pythonvalue("---01+02:00") == ( + 1, + datetime.timezone(datetime.timedelta(minutes=120)), + ) + assert instance.pythonvalue("---01-04:00") == ( + 1, + datetime.timezone(datetime.timedelta(minutes=-240)), + ) assert instance.pythonvalue("---15") == (15, None) assert instance.pythonvalue("---31") == (31, None) assert instance.pythonvalue("\r\n \t---31 ") == (31, None) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/tests/test_xsd_parse.py new/zeep-4.3.3/tests/test_xsd_parse.py --- old/zeep-4.3.2/tests/test_xsd_parse.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/tests/test_xsd_parse.py 2026-06-18 19:17:39.000000000 +0200 @@ -558,15 +558,15 @@ <xsd:element name="el2" type="xsd:string"/> <xsd:element name="container"> - <xsd:complexType> - <xsd:sequence> - <xsd:choice> - <xsd:element ref="zeep:el1"/> - <xsd:element ref="zeep:el2"/> - <xsd:element name="el3" type="xsd:string"/> - </xsd:choice> - </xsd:sequence> - </xsd:complexType> + <xsd:complexType> + <xsd:sequence> + <xsd:choice> + <xsd:element ref="zeep:el1"/> + <xsd:element ref="zeep:el2"/> + <xsd:element name="el3" type="xsd:string"/> + </xsd:choice> + </xsd:sequence> + </xsd:complexType> </xsd:element> </xsd:schema> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/tests/test_xsd_schemas.py new/zeep-4.3.3/tests/test_xsd_schemas.py --- old/zeep-4.3.2/tests/test_xsd_schemas.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/tests/test_xsd_schemas.py 2026-06-18 19:17:39.000000000 +0200 @@ -229,6 +229,39 @@ type_a(wat="x") +def test_forbid_external_blocks_transitive_xsd_import(): + from zeep.exceptions import ExternalReferenceForbidden + from zeep.settings import Settings + + node_a = etree.fromstring( + """ + <?xml version="1.0"?> + <xs:schema + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:tns="http://tests.python-zeep.org/a" + targetNamespace="http://tests.python-zeep.org/a" + xmlns:b="http://tests.python-zeep.org/b" + elementFormDefault="qualified"> + + <xs:import + schemaLocation="http://127.0.0.1:9/internal.xsd" + namespace="http://tests.python-zeep.org/b"/> + + <xs:element name="root" type="xs:string"/> + </xs:schema> + """.strip() + ) + + transport = DummyTransport() + + with pytest.raises(ExternalReferenceForbidden): + xsd.Schema( + node_a, + transport=transport, + settings=Settings(forbid_external=True), + ) + + def test_global_element_and_type(): node_a = etree.fromstring( """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/zeep-4.3.2/tests/test_xsd_visitor.py new/zeep-4.3.3/tests/test_xsd_visitor.py --- old/zeep-4.3.2/tests/test_xsd_visitor.py 2025-09-15 12:25:38.000000000 +0200 +++ new/zeep-4.3.3/tests/test_xsd_visitor.py 2026-06-18 19:17:39.000000000 +0200 @@ -655,15 +655,15 @@ <xsd:element name="el2" type="xsd:string"/> <xsd:element name="container"> - <xsd:complexType> - <xsd:sequence> - <xsd:choice> - <xsd:element ref="zeep:el1"/> - <xsd:element ref="zeep:el2"/> - <xsd:element name="el3" type="xsd:string"/> - </xsd:choice> - </xsd:sequence> - </xsd:complexType> + <xsd:complexType> + <xsd:sequence> + <xsd:choice> + <xsd:element ref="zeep:el1"/> + <xsd:element ref="zeep:el2"/> + <xsd:element name="el3" type="xsd:string"/> + </xsd:choice> + </xsd:sequence> + </xsd:complexType> </xsd:element> </xsd:schema> @@ -674,4 +674,4 @@ for el_name, sub_element in container_element.type.elements: assert el_name in ("el1", "el2", "el3") assert sub_element.min_occurs == 0 - assert sub_element.is_optional == True + assert sub_element.is_optional
