From 0e7bbdd0102358f1970b3cd589f8f99824268ec5 Mon Sep 17 00:00:00 2001 From: Ben Falk Date: Thu, 23 Apr 2026 10:42:14 -0400 Subject: [PATCH] Refactor XML namespace handling in BaseXmlModel and add test, resolves #315 --- pydantic_xml/model.py | 3 +-- tests/test_namespaces.py | 41 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/pydantic_xml/model.py b/pydantic_xml/model.py index 1ee18ef..af28031 100644 --- a/pydantic_xml/model.py +++ b/pydantic_xml/model.py @@ -193,8 +193,7 @@ def __init_subclass__( else getattr(cls, '__xml_search_mode__', SearchMode.STRICT) if parent_nsmap := getattr(cls, '__xml_nsmap__', None): - parent_nsmap.update(nsmap or {}) - cls.__xml_nsmap__ = parent_nsmap + cls.__xml_nsmap__ = parent_nsmap | (nsmap or {}) else: cls.__xml_nsmap__ = nsmap diff --git a/tests/test_namespaces.py b/tests/test_namespaces.py index 264a327..1b57351 100644 --- a/tests/test_namespaces.py +++ b/tests/test_namespaces.py @@ -359,6 +359,47 @@ class TestModel(BaseTestModel, tag='model', ns='tst', nsmap={'tst': 'http://test assert_xml_equal(actual_xml, xml1) +def test_subclass_nsmap_does_not_mutate_parent(): + # defining a subclass with a different nsmap must not modify the parent's + # (or any shared module-level) namespace map. + nsmap_v1 = {"hq": "http://www.company.com/hq/v1", "pd": "http://www.company.com/prod"} + nsmap = {"hq": "http://www.company.com/hq", "pd": "http://www.company.com/prod"} + original_nsmap = dict(nsmap) + + class Headquarters(BaseXmlModel, ns="hq", nsmap=nsmap): + country: str = element() + state: str = element() + city: str = element() + + class HeadquartersV1(Headquarters, ns="hq", nsmap=nsmap_v1): + pass + + class Company(BaseXmlModel, tag="Company", nsmap=nsmap): + trade_name: str = attr(name="trade-name") + headquarters: Headquarters + + assert nsmap == original_nsmap, "parent nsmap must not be mutated by subclass definition" + assert Headquarters.__xml_nsmap__ == original_nsmap + assert HeadquartersV1.__xml_nsmap__ == {**original_nsmap, **nsmap_v1} + + xml = """ + + + US + West Virginia + Almost Heaven + + + """ + + actual_obj = Company.from_xml(xml) + expected_obj = Company( + trade_name="Beboop", + headquarters=Headquarters(country="US", state="West Virginia", city="Almost Heaven"), + ) + assert actual_obj == expected_obj + + def test_submodel_namespaces_default_namespace_inheritance(): class TestSubModel(BaseXmlModel, tag='submodel', ns='', nsmap={'': 'http://test2.org'}): attr1: int = attr()