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

Reply via email to