Skip to content
Merged

Dev #139

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Changelog
=========


2.4.0 (2023-11-06)
------------------

- attributes with default namespace bug fixed. See https://github.com/dapper91/pydantic-xml/issues/137.


2.3.0 (2023-10-22)
------------------

Expand Down
6 changes: 3 additions & 3 deletions pydantic_xml/serializers/factories/mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
from pydantic_xml.element import XmlElementReader, XmlElementWriter
from pydantic_xml.serializers.serializer import TYPE_FAMILY, SchemaTypeFamily, SearchMode, Serializer
from pydantic_xml.typedefs import EntityLocation, NsMap
from pydantic_xml.utils import QName, merge_nsmaps
from pydantic_xml.utils import QName, merge_nsmaps, select_ns


class AttributesSerializer(Serializer):
@classmethod
def from_core_schema(cls, schema: pcs.CoreSchema, ctx: Serializer.Context) -> 'AttributesSerializer':
ns = ctx.entity_ns or ctx.parent_ns
ns = select_ns(ctx.entity_ns, ctx.parent_ns)
nsmap = merge_nsmaps(ctx.entity_nsmap, ctx.parent_nsmap)
namespaced_attrs = ctx.namespaced_attrs
computed = ctx.field_computed
Expand Down Expand Up @@ -66,7 +66,7 @@ class ElementSerializer(AttributesSerializer):
@classmethod
def from_core_schema(cls, schema: pcs.CoreSchema, ctx: Serializer.Context) -> 'ElementSerializer':
name = ctx.entity_path or ctx.field_alias or ctx.field_name
ns = ctx.entity_ns or ctx.parent_ns
ns = select_ns(ctx.entity_ns, ctx.parent_ns)
nsmap = merge_nsmaps(ctx.entity_nsmap, ctx.parent_nsmap)
namespaced_attrs = ctx.namespaced_attrs
search_mode = ctx.search_mode
Expand Down
4 changes: 2 additions & 2 deletions pydantic_xml/serializers/factories/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from pydantic_xml.element import XmlElementReader, XmlElementWriter
from pydantic_xml.serializers.serializer import SearchMode, Serializer, XmlEntityInfoP
from pydantic_xml.typedefs import EntityLocation, NsMap
from pydantic_xml.utils import QName, merge_nsmaps
from pydantic_xml.utils import QName, merge_nsmaps, select_ns


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

name = ctx.entity_path or model_cls.__xml_tag__ or ctx.field_alias or ctx.field_name or model_cls.__name__
ns = ctx.entity_ns or model_cls.__xml_ns__ or ctx.parent_ns
ns = select_ns(ctx.entity_ns, model_cls.__xml_ns__, ctx.parent_ns)
nsmap = merge_nsmaps(ctx.entity_nsmap, model_cls.__xml_nsmap__, ctx.parent_nsmap)
search_mode = ctx.search_mode
computed = ctx.field_computed
Expand Down
12 changes: 9 additions & 3 deletions pydantic_xml/serializers/factories/primitive.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from pydantic_xml.element import XmlElementReader, XmlElementWriter
from pydantic_xml.serializers.serializer import SearchMode, Serializer, encode_primitive
from pydantic_xml.typedefs import EntityLocation, NsMap
from pydantic_xml.utils import QName, merge_nsmaps
from pydantic_xml.utils import QName, merge_nsmaps, select_ns

PrimitiveTypeSchema = Union[
pcs.NoneSchema,
Expand Down Expand Up @@ -67,10 +67,16 @@ class AttributeSerializer(Serializer):
def from_core_schema(cls, schema: PrimitiveTypeSchema, ctx: Serializer.Context) -> 'AttributeSerializer':
namespaced_attrs = ctx.namespaced_attrs
name = ctx.entity_path or ctx.field_alias or ctx.field_name
ns = ctx.entity_ns or (ctx.parent_ns if namespaced_attrs else None)
ns = select_ns(ctx.entity_ns, ctx.parent_ns if namespaced_attrs else None)
nsmap = merge_nsmaps(ctx.entity_nsmap, ctx.parent_nsmap)
computed = ctx.field_computed

if ns == '':
raise errors.ModelFieldError(
ctx.model_name,
ctx.field_name,
"attributes with default namespace are forbidden",
)
if name is None:
raise errors.ModelFieldError(ctx.model_name, ctx.field_name, "entity name is not provided")

Expand Down Expand Up @@ -113,7 +119,7 @@ class ElementSerializer(TextSerializer):
@classmethod
def from_core_schema(cls, schema: PrimitiveTypeSchema, ctx: Serializer.Context) -> 'ElementSerializer':
name = ctx.entity_path or ctx.field_alias or ctx.field_name
ns = ctx.entity_ns or ctx.parent_ns
ns = select_ns(ctx.entity_ns, ctx.parent_ns)
nsmap = merge_nsmaps(ctx.entity_nsmap, ctx.parent_nsmap)
search_mode = ctx.search_mode
computed = ctx.field_computed
Expand Down
4 changes: 2 additions & 2 deletions pydantic_xml/serializers/factories/raw.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
from pydantic_xml.element import XmlElementReader, XmlElementWriter
from pydantic_xml.serializers.serializer import SearchMode, Serializer
from pydantic_xml.typedefs import EntityLocation, NsMap
from pydantic_xml.utils import QName, merge_nsmaps
from pydantic_xml.utils import QName, merge_nsmaps, select_ns


class ElementSerializer(Serializer):
@classmethod
def from_core_schema(cls, schema: pcs.IsInstanceSchema, ctx: Serializer.Context) -> 'ElementSerializer':
name = ctx.entity_path or ctx.field_alias or ctx.field_name
ns = ctx.entity_ns or ctx.parent_ns
ns = select_ns(ctx.entity_ns, ctx.parent_ns)
nsmap = merge_nsmaps(ctx.entity_nsmap, ctx.parent_nsmap)
search_mode = ctx.search_mode
computed = ctx.field_computed
Expand Down
4 changes: 2 additions & 2 deletions pydantic_xml/serializers/factories/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
from pydantic_xml.element import XmlElementReader, XmlElementWriter
from pydantic_xml.serializers.serializer import SearchMode, Serializer
from pydantic_xml.typedefs import NsMap
from pydantic_xml.utils import QName, merge_nsmaps
from pydantic_xml.utils import QName, merge_nsmaps, select_ns


class ElementPathSerializer(Serializer):
@classmethod
def from_core_schema(cls, schema: pcs.CoreSchema, ctx: Serializer.Context) -> 'ElementPathSerializer':
path = ctx.entity_path
ns = ctx.entity_ns or ctx.parent_ns
ns = select_ns(ctx.entity_ns, ctx.parent_ns)
nsmap = merge_nsmaps(ctx.entity_nsmap, ctx.parent_nsmap)
search_mode = ctx.search_mode
computed = ctx.field_computed
Expand Down
4 changes: 3 additions & 1 deletion pydantic_xml/serializers/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from pydantic_xml.element import SearchMode, XmlElementReader, XmlElementWriter
from pydantic_xml.errors import ModelError
from pydantic_xml.typedefs import EntityLocation, NsMap
from pydantic_xml.utils import select_ns

from . import factories

Expand Down Expand Up @@ -143,7 +144,8 @@ def entity_wrapped(self) -> Optional['XmlEntityInfoP']:
@cached_property
def parent_ns(self) -> Optional[str]:
if parent_ctx := self.parent_ctx:
return parent_ctx.entity_ns or parent_ctx.parent_ns
ns = select_ns(parent_ctx.entity_ns, parent_ctx.parent_ns)
return ns

return None

Expand Down
8 changes: 8 additions & 0 deletions pydantic_xml/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,11 @@ def register_nsmap(nsmap: NsMap) -> None:

def get_slots(o: object) -> Iterable[str]:
return it.chain.from_iterable(getattr(cls, '__slots__', []) for cls in o.__class__.__mro__)


def select_ns(*nss: Optional[str]) -> Optional[str]:
for ns in nss:
if ns is not None:
return ns

return None
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pydantic-xml"
version = "2.3.0"
version = "2.4.0"
description = "pydantic xml extension"
authors = ["Dmitry Pershin <dapper1291@gmail.com>"]
license = "Unlicense"
Expand Down
30 changes: 29 additions & 1 deletion tests/test_namespaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class TestModel(BaseXmlModel, tag='model'):
@pytest.mark.skipif(not is_lxml_native(), reason='not lxml used')
def test_lxml_default_namespace_serialisation():
class TestSubModel(BaseXmlModel, tag='submodel', ns='', nsmap={'': 'http://test3.org', 'tst': 'http://test4.org'}):
attr1: int = attr(ns='')
attr1: int = attr()
attr2: int = attr(ns='tst')
element1: str = element(ns='')

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

actual_xml = actual_obj.to_xml()
assert_xml_equal(actual_xml, xml1)


def test_submodel_namespaces_default_namespace_inheritance():
class TestSubModel(BaseXmlModel, tag='submodel', ns='', nsmap={'': 'http://test2.org'}):
attr1: int = attr()
attr2: int = attr()
element1: str = element()

class TestModel(BaseXmlModel, tag='model', ns='tst', nsmap={'tst': 'http://test1.org'}):
submodel: TestSubModel

xml = '''
<tst:model xmlns:tst="http://test1.org">
<submodel xmlns="http://test2.org" attr1="1" attr2="2">
<element1>value</element1>
</submodel>
</tst:model>
'''

actual_obj = TestModel.from_xml(xml)
expected_obj = TestModel(
submodel=TestSubModel(element1='value', attr1=1, attr2=2),
)

assert actual_obj == expected_obj

actual_xml = actual_obj.to_xml()
assert_xml_equal(actual_xml, xml)