Skip to content

Commit c1ae049

Browse files
authored
Merge pull request #139 from dapper91/dev
- attributes with default namespace bug fixed.
2 parents e432302 + eb8efc0 commit c1ae049

File tree

10 files changed

+66
-15
lines changed

10 files changed

+66
-15
lines changed

CHANGELOG.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
Changelog
22
=========
33

4+
5+
2.4.0 (2023-11-06)
6+
------------------
7+
8+
- attributes with default namespace bug fixed. See https://github.com/dapper91/pydantic-xml/issues/137.
9+
10+
411
2.3.0 (2023-10-22)
512
------------------
613

pydantic_xml/serializers/factories/mapping.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
from pydantic_xml.element import XmlElementReader, XmlElementWriter
77
from pydantic_xml.serializers.serializer import TYPE_FAMILY, SchemaTypeFamily, SearchMode, Serializer
88
from pydantic_xml.typedefs import EntityLocation, NsMap
9-
from pydantic_xml.utils import QName, merge_nsmaps
9+
from pydantic_xml.utils import QName, merge_nsmaps, select_ns
1010

1111

1212
class AttributesSerializer(Serializer):
1313
@classmethod
1414
def from_core_schema(cls, schema: pcs.CoreSchema, ctx: Serializer.Context) -> 'AttributesSerializer':
15-
ns = ctx.entity_ns or ctx.parent_ns
15+
ns = select_ns(ctx.entity_ns, ctx.parent_ns)
1616
nsmap = merge_nsmaps(ctx.entity_nsmap, ctx.parent_nsmap)
1717
namespaced_attrs = ctx.namespaced_attrs
1818
computed = ctx.field_computed
@@ -66,7 +66,7 @@ class ElementSerializer(AttributesSerializer):
6666
@classmethod
6767
def from_core_schema(cls, schema: pcs.CoreSchema, ctx: Serializer.Context) -> 'ElementSerializer':
6868
name = ctx.entity_path or ctx.field_alias or ctx.field_name
69-
ns = ctx.entity_ns or ctx.parent_ns
69+
ns = select_ns(ctx.entity_ns, ctx.parent_ns)
7070
nsmap = merge_nsmaps(ctx.entity_nsmap, ctx.parent_nsmap)
7171
namespaced_attrs = ctx.namespaced_attrs
7272
search_mode = ctx.search_mode

pydantic_xml/serializers/factories/model.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from pydantic_xml.element import XmlElementReader, XmlElementWriter
1212
from pydantic_xml.serializers.serializer import SearchMode, Serializer, XmlEntityInfoP
1313
from pydantic_xml.typedefs import EntityLocation, NsMap
14-
from pydantic_xml.utils import QName, merge_nsmaps
14+
from pydantic_xml.utils import QName, merge_nsmaps, select_ns
1515

1616

1717
class BaseModelSerializer(Serializer, abc.ABC):
@@ -283,7 +283,7 @@ def from_core_schema(cls, schema: pcs.ModelSchema, ctx: Serializer.Context) -> '
283283
assert issubclass(model_cls, pxml.BaseXmlModel), "unexpected model type"
284284

285285
name = ctx.entity_path or model_cls.__xml_tag__ or ctx.field_alias or ctx.field_name or model_cls.__name__
286-
ns = ctx.entity_ns or model_cls.__xml_ns__ or ctx.parent_ns
286+
ns = select_ns(ctx.entity_ns, model_cls.__xml_ns__, ctx.parent_ns)
287287
nsmap = merge_nsmaps(ctx.entity_nsmap, model_cls.__xml_nsmap__, ctx.parent_nsmap)
288288
search_mode = ctx.search_mode
289289
computed = ctx.field_computed

pydantic_xml/serializers/factories/primitive.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from pydantic_xml.element import XmlElementReader, XmlElementWriter
77
from pydantic_xml.serializers.serializer import SearchMode, Serializer, encode_primitive
88
from pydantic_xml.typedefs import EntityLocation, NsMap
9-
from pydantic_xml.utils import QName, merge_nsmaps
9+
from pydantic_xml.utils import QName, merge_nsmaps, select_ns
1010

1111
PrimitiveTypeSchema = Union[
1212
pcs.NoneSchema,
@@ -67,10 +67,16 @@ class AttributeSerializer(Serializer):
6767
def from_core_schema(cls, schema: PrimitiveTypeSchema, ctx: Serializer.Context) -> 'AttributeSerializer':
6868
namespaced_attrs = ctx.namespaced_attrs
6969
name = ctx.entity_path or ctx.field_alias or ctx.field_name
70-
ns = ctx.entity_ns or (ctx.parent_ns if namespaced_attrs else None)
70+
ns = select_ns(ctx.entity_ns, ctx.parent_ns if namespaced_attrs else None)
7171
nsmap = merge_nsmaps(ctx.entity_nsmap, ctx.parent_nsmap)
7272
computed = ctx.field_computed
7373

74+
if ns == '':
75+
raise errors.ModelFieldError(
76+
ctx.model_name,
77+
ctx.field_name,
78+
"attributes with default namespace are forbidden",
79+
)
7480
if name is None:
7581
raise errors.ModelFieldError(ctx.model_name, ctx.field_name, "entity name is not provided")
7682

@@ -113,7 +119,7 @@ class ElementSerializer(TextSerializer):
113119
@classmethod
114120
def from_core_schema(cls, schema: PrimitiveTypeSchema, ctx: Serializer.Context) -> 'ElementSerializer':
115121
name = ctx.entity_path or ctx.field_alias or ctx.field_name
116-
ns = ctx.entity_ns or ctx.parent_ns
122+
ns = select_ns(ctx.entity_ns, ctx.parent_ns)
117123
nsmap = merge_nsmaps(ctx.entity_nsmap, ctx.parent_nsmap)
118124
search_mode = ctx.search_mode
119125
computed = ctx.field_computed

pydantic_xml/serializers/factories/raw.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
from pydantic_xml.element import XmlElementReader, XmlElementWriter
77
from pydantic_xml.serializers.serializer import SearchMode, Serializer
88
from pydantic_xml.typedefs import EntityLocation, NsMap
9-
from pydantic_xml.utils import QName, merge_nsmaps
9+
from pydantic_xml.utils import QName, merge_nsmaps, select_ns
1010

1111

1212
class ElementSerializer(Serializer):
1313
@classmethod
1414
def from_core_schema(cls, schema: pcs.IsInstanceSchema, ctx: Serializer.Context) -> 'ElementSerializer':
1515
name = ctx.entity_path or ctx.field_alias or ctx.field_name
16-
ns = ctx.entity_ns or ctx.parent_ns
16+
ns = select_ns(ctx.entity_ns, ctx.parent_ns)
1717
nsmap = merge_nsmaps(ctx.entity_nsmap, ctx.parent_nsmap)
1818
search_mode = ctx.search_mode
1919
computed = ctx.field_computed

pydantic_xml/serializers/factories/wrapper.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
from pydantic_xml.element import XmlElementReader, XmlElementWriter
66
from pydantic_xml.serializers.serializer import SearchMode, Serializer
77
from pydantic_xml.typedefs import NsMap
8-
from pydantic_xml.utils import QName, merge_nsmaps
8+
from pydantic_xml.utils import QName, merge_nsmaps, select_ns
99

1010

1111
class ElementPathSerializer(Serializer):
1212
@classmethod
1313
def from_core_schema(cls, schema: pcs.CoreSchema, ctx: Serializer.Context) -> 'ElementPathSerializer':
1414
path = ctx.entity_path
15-
ns = ctx.entity_ns or ctx.parent_ns
15+
ns = select_ns(ctx.entity_ns, ctx.parent_ns)
1616
nsmap = merge_nsmaps(ctx.entity_nsmap, ctx.parent_nsmap)
1717
search_mode = ctx.search_mode
1818
computed = ctx.field_computed

pydantic_xml/serializers/serializer.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from pydantic_xml.element import SearchMode, XmlElementReader, XmlElementWriter
1212
from pydantic_xml.errors import ModelError
1313
from pydantic_xml.typedefs import EntityLocation, NsMap
14+
from pydantic_xml.utils import select_ns
1415

1516
from . import factories
1617

@@ -143,7 +144,8 @@ def entity_wrapped(self) -> Optional['XmlEntityInfoP']:
143144
@cached_property
144145
def parent_ns(self) -> Optional[str]:
145146
if parent_ctx := self.parent_ctx:
146-
return parent_ctx.entity_ns or parent_ctx.parent_ns
147+
ns = select_ns(parent_ctx.entity_ns, parent_ctx.parent_ns)
148+
return ns
147149

148150
return None
149151

pydantic_xml/utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,11 @@ def register_nsmap(nsmap: NsMap) -> None:
8484

8585
def get_slots(o: object) -> Iterable[str]:
8686
return it.chain.from_iterable(getattr(cls, '__slots__', []) for cls in o.__class__.__mro__)
87+
88+
89+
def select_ns(*nss: Optional[str]) -> Optional[str]:
90+
for ns in nss:
91+
if ns is not None:
92+
return ns
93+
94+
return None

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "pydantic-xml"
3-
version = "2.3.0"
3+
version = "2.4.0"
44
description = "pydantic xml extension"
55
authors = ["Dmitry Pershin <dapper1291@gmail.com>"]
66
license = "Unlicense"

tests/test_namespaces.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class TestModel(BaseXmlModel, tag='model'):
4242
@pytest.mark.skipif(not is_lxml_native(), reason='not lxml used')
4343
def test_lxml_default_namespace_serialisation():
4444
class TestSubModel(BaseXmlModel, tag='submodel', ns='', nsmap={'': 'http://test3.org', 'tst': 'http://test4.org'}):
45-
attr1: int = attr(ns='')
45+
attr1: int = attr()
4646
attr2: int = attr(ns='tst')
4747
element1: str = element(ns='')
4848

@@ -357,3 +357,31 @@ class TestModel(BaseTestModel, tag='model', ns='tst', nsmap={'tst': 'http://test
357357

358358
actual_xml = actual_obj.to_xml()
359359
assert_xml_equal(actual_xml, xml1)
360+
361+
362+
def test_submodel_namespaces_default_namespace_inheritance():
363+
class TestSubModel(BaseXmlModel, tag='submodel', ns='', nsmap={'': 'http://test2.org'}):
364+
attr1: int = attr()
365+
attr2: int = attr()
366+
element1: str = element()
367+
368+
class TestModel(BaseXmlModel, tag='model', ns='tst', nsmap={'tst': 'http://test1.org'}):
369+
submodel: TestSubModel
370+
371+
xml = '''
372+
<tst:model xmlns:tst="http://test1.org">
373+
<submodel xmlns="http://test2.org" attr1="1" attr2="2">
374+
<element1>value</element1>
375+
</submodel>
376+
</tst:model>
377+
'''
378+
379+
actual_obj = TestModel.from_xml(xml)
380+
expected_obj = TestModel(
381+
submodel=TestSubModel(element1='value', attr1=1, attr2=2),
382+
)
383+
384+
assert actual_obj == expected_obj
385+
386+
actual_xml = actual_obj.to_xml()
387+
assert_xml_equal(actual_xml, xml)

0 commit comments

Comments
 (0)