From 9eb3d0c4a0b7388c8c793751add82b11bde64537 Mon Sep 17 00:00:00 2001 From: Sebastian Wozniak Date: Fri, 19 Dec 2025 12:55:33 +0100 Subject: [PATCH 1/2] When a complexType with only attributes was extended by another complexType, the attributes were being rendered as NotSet instead of their actual values. This occurred because a placeholder element was unnecessarily created during type extension, causing the base type to be rendered twice - once with correct values and once with empty values that overwrote the correct ones. --- src/zeep/xsd/types/complex.py | 2 + tests/test_xsd_complex_types.py | 104 ++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/src/zeep/xsd/types/complex.py b/src/zeep/xsd/types/complex.py index 4c69a9cde..65c39b953 100644 --- a/src/zeep/xsd/types/complex.py +++ b/src/zeep/xsd/types/complex.py @@ -447,6 +447,8 @@ def extend(self, base): elif self._element or base_element: element = self._element or base_element + elif isinstance(base, ComplexType): + element = None else: element = Element("_value_1", base) diff --git a/tests/test_xsd_complex_types.py b/tests/test_xsd_complex_types.py index 55430c91f..6efb8945b 100644 --- a/tests/test_xsd_complex_types.py +++ b/tests/test_xsd_complex_types.py @@ -422,3 +422,107 @@ def test_ignore_sequence_order(): response = elm.parse(node[0], schema) assert response.Baz.id == 3 + + +def test_extension_with_attributes_preserves_values(): + schema = xsd.Schema( + load_xml( + """ + + + + + + + + + + + + + + """ + ) + ) + schema.set_ns_prefix("tns", "http://tests.python-zeep.org/") + + item_elm = schema.get_element("tns:item") + obj = item_elm(attr1=10, attr2="test") + + assert obj.attr1 == 10 + assert obj.attr2 == "test" + + result = render_node(item_elm, obj) + expected = """ + + + + """ + assert_nodes_equal(result, expected) + + parsed = item_elm.parse(result[0], schema) + assert parsed.attr1 == 10 + assert parsed.attr2 == "test" + + +def test_extension_with_attributes_and_elements(): + schema = xsd.Schema( + load_xml( + """ + + + + + + + + + + + + + + + + + + + + + + + + """ + ) + ) + schema.set_ns_prefix("tns", "http://tests.python-zeep.org/") + + container_elm = schema.get_element("tns:container") + extended_type = schema.get_type("tns:ExtendedType") + + obj = container_elm(item=extended_type(attr1=10, attr2="test")) + + assert obj.item.attr1 == 10 + assert obj.item.attr2 == "test" + + result = render_node(container_elm, obj) + expected = """ + + + + + + """ + assert_nodes_equal(result, expected) + + parsed = container_elm.parse(result[0], schema) + assert parsed.item.attr1 == 10 + assert parsed.item.attr2 == "test" From 3367bcf375830e02a2f822d4f2c498ca642859fd Mon Sep 17 00:00:00 2001 From: Sebastian Wozniak Date: Wed, 25 Feb 2026 14:23:25 +0100 Subject: [PATCH 2/2] Add Signature class reading key/cert from files (wrapper around MemorySignature) --- src/zeep/wsse/signature.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/zeep/wsse/signature.py b/src/zeep/wsse/signature.py index 47ab7fd4e..3ff489290 100644 --- a/src/zeep/wsse/signature.py +++ b/src/zeep/wsse/signature.py @@ -53,6 +53,8 @@ def __init__( password=None, signature_method=None, digest_method=None, + verify_response_signature=True, + response_cert_data=None, ): check_xmlsec_import() @@ -61,6 +63,8 @@ def __init__( self.password = password self.digest_method = digest_method self.signature_method = signature_method + self.verify_response_signature = verify_response_signature + self.response_cert_data = response_cert_data def apply(self, envelope, headers): key = _make_sign_key(self.key_data, self.cert_data, self.password) @@ -70,7 +74,11 @@ def apply(self, envelope, headers): return envelope, headers def verify(self, envelope): - key = _make_verify_key(self.cert_data) + if not self.verify_response_signature: + return envelope + + cert_data = self.response_cert_data if self.response_cert_data else self.cert_data + key = _make_verify_key(cert_data) _verify_envelope_with_key(envelope, key) return envelope @@ -85,6 +93,8 @@ def __init__( password=None, signature_method=None, digest_method=None, + verify_response_signature=True, + response_certfile=None, ): super().__init__( _read_file(key_file), @@ -92,6 +102,8 @@ def __init__( password, signature_method, digest_method, + verify_response_signature, + _read_file(response_certfile) if response_certfile else None )