diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index f040cdb..a878334 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: ['3.8', '3.9', '3.10']
+ python-version: ['3.8', '3.9', '3.10', '3.11']
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index eab6a47..a5aa350 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,13 @@
Changelog
=========
+0.3.0 (2022-11-10)
+------------------
+
+- recursive (self-referencing) models support added.
+- inherit_ns flag dropped due to recursive models implementation details.
+
+
0.2.2 (2022-10-07)
------------------
diff --git a/README.md b/README.md
index 3f3922a..a47305c 100644
--- a/README.md
+++ b/README.md
@@ -192,8 +192,14 @@ from pydantic import conint, HttpUrl
from pydantic_xml import BaseXmlModel, attr, element, wrapped
+NSMAP = {
+ 'co': 'http://www.test.com/contact',
+ 'hq': 'http://www.test.com/hq',
+ 'pd': 'http://www.test.com/prod',
+}
+
-class Headquarters(BaseXmlModel, ns='hq', nsmap={'hq': 'http://www.test.com/hq'}):
+class Headquarters(BaseXmlModel, ns='hq', nsmap=NSMAP):
country: str = element()
state: str = element()
city: str = element()
@@ -209,12 +215,12 @@ class Industries(BaseXmlModel):
__root__: Set[str] = element(tag='Industry')
-class Social(BaseXmlModel, ns_attrs=True, inherit_ns=True):
+class Social(BaseXmlModel, ns_attrs=True, ns='co', nsmap=NSMAP):
type: str = attr()
url: str
-class Product(BaseXmlModel, ns_attrs=True, inherit_ns=True):
+class Product(BaseXmlModel, ns_attrs=True, ns='pd', nsmap=NSMAP):
status: Literal['running', 'development'] = attr()
launched: Optional[int] = attr()
title: str
@@ -236,7 +242,7 @@ class COO(Person):
position: Literal['COO'] = attr()
-class Company(BaseXmlModel, tag='Company', nsmap={'pd': 'http://www.test.com/prod'}):
+class Company(BaseXmlModel, tag='Company', nsmap=NSMAP):
class CompanyType(str, Enum):
PRIVATE = 'Private'
PUBLIC = 'Public'
@@ -254,9 +260,9 @@ class Company(BaseXmlModel, tag='Company', nsmap={'pd': 'http://www.test.com/pro
headquarters: Headquarters
socials: List[Social] = wrapped(
'contacts/socials',
- element(tag='social', default_factory=set),
+ element(tag='social', default_factory=list),
ns='co',
- nsmap={'co': 'http://www.test.com/contact'}
+ nsmap=NSMAP,
)
products: Tuple[Product, ...] = element(tag='product', ns='pd')
@@ -428,6 +434,108 @@ print(request.json(indent=4))
```
+### Self-referencing models:
+
+`pydantic` library supports [self-referencing models](https://pydantic-docs.helpmanual.io/usage/postponed_annotations/#self-referencing-models).
+`pydantic-xml` supports it either.
+
+*request.xml:*
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+```
+
+*main.py:*
+
+```python
+from typing import List, Optional
+
+import pydantic_xml as pxml
+
+
+class File(pxml.BaseXmlModel, tag="File"):
+ name: str = pxml.attr(name='Name')
+ mode: str = pxml.attr(name='Mode')
+
+
+class Directory(pxml.BaseXmlModel, tag="Directory"):
+ name: str = pxml.attr(name='Name')
+ mode: str = pxml.attr(name='Mode')
+ dirs: Optional[List['Directory']] = pxml.element(tag='Directory')
+ files: Optional[List[File]] = pxml.element(tag='File', default_factory=list)
+
+
+with open('request.xml') as file:
+ xml = file.read()
+
+root = Directory.from_xml(xml)
+print(root.json(indent=4))
+
+```
+
+*output:*
+
+```json
+{
+ "name": "root",
+ "mode": "rwxr-xr-x",
+ "dirs": [
+ {
+ "name": "etc",
+ "mode": "rwxr-xr-x",
+ "dirs": [
+ {
+ "name": "ssh",
+ "mode": "rwxr-xr-x",
+ "dirs": [],
+ "files": []
+ }
+ ],
+ "files": [
+ {
+ "name": "passwd",
+ "mode": "-rw-r--r--"
+ },
+ {
+ "name": "hosts",
+ "mode": "-rw-r--r--"
+ }
+ ]
+ },
+ {
+ "name": "bin",
+ "mode": "rwxr-xr-x",
+ "dirs": [],
+ "files": []
+ },
+ {
+ "name": "usr",
+ "mode": "rwxr-xr-x",
+ "dirs": [
+ {
+ "name": "bin",
+ "mode": "rwxr-xr-x",
+ "dirs": [],
+ "files": []
+ }
+ ],
+ "files": []
+ }
+ ],
+ "files": []
+}
+```
+
### JSON
Since `pydantic` supports json serialization, `pydantic-xml` could be used as xml-to-json transcoder:
diff --git a/examples/quickstart.py b/examples/quickstart.py
index 7e2352d..2c66823 100644
--- a/examples/quickstart.py
+++ b/examples/quickstart.py
@@ -45,8 +45,14 @@
'''
+NSMAP = {
+ 'co': 'http://www.test.com/contact',
+ 'hq': 'http://www.test.com/hq',
+ 'pd': 'http://www.test.com/prod',
+}
-class Headquarters(BaseXmlModel, ns='hq', nsmap={'hq': 'http://www.test.com/hq'}):
+
+class Headquarters(BaseXmlModel, ns='hq', nsmap=NSMAP):
country: str = element()
state: str = element()
city: str = element()
@@ -62,12 +68,12 @@ class Industries(BaseXmlModel):
__root__: Set[str] = element(tag='Industry')
-class Social(BaseXmlModel, ns_attrs=True, inherit_ns=True):
+class Social(BaseXmlModel, ns_attrs=True, ns='co', nsmap=NSMAP):
type: str = attr()
url: str
-class Product(BaseXmlModel, ns_attrs=True, inherit_ns=True):
+class Product(BaseXmlModel, ns_attrs=True, ns='pd', nsmap=NSMAP):
status: Literal['running', 'development'] = attr()
launched: Optional[int] = attr()
title: str
@@ -89,7 +95,7 @@ class COO(Person):
position: Literal['COO'] = attr()
-class Company(BaseXmlModel, tag='Company', nsmap={'pd': 'http://www.test.com/prod'}):
+class Company(BaseXmlModel, tag='Company', nsmap=NSMAP):
class CompanyType(str, Enum):
PRIVATE = 'Private'
PUBLIC = 'Public'
@@ -109,7 +115,7 @@ class CompanyType(str, Enum):
'contacts/socials',
element(tag='social', default_factory=list),
ns='co',
- nsmap={'co': 'http://www.test.com/contact'},
+ nsmap=NSMAP,
)
products: Tuple[Product, ...] = element(tag='product', ns='pd')
diff --git a/examples/recursive.py b/examples/recursive.py
new file mode 100644
index 0000000..d6f5a99
--- /dev/null
+++ b/examples/recursive.py
@@ -0,0 +1,33 @@
+from typing import List, Optional
+
+import pydantic_xml as pxml
+
+xml = '''
+
+
+
+
+
+
+
+
+
+
+
+'''
+
+
+class File(pxml.BaseXmlModel, tag="File"):
+ name: str = pxml.attr(name='Name')
+ mode: str = pxml.attr(name='Mode')
+
+
+class Directory(pxml.BaseXmlModel, tag="Directory"):
+ name: str = pxml.attr(name='Name')
+ mode: str = pxml.attr(name='Mode')
+ dirs: Optional[List['Directory']] = pxml.element(tag='Directory')
+ files: Optional[List[File]] = pxml.element(tag='File', default_factory=list)
+
+
+root = Directory.from_xml(xml)
+print(root.json(indent=4))
diff --git a/pydantic_xml/model.py b/pydantic_xml/model.py
index a63d2d6..b8c4253 100644
--- a/pydantic_xml/model.py
+++ b/pydantic_xml/model.py
@@ -172,7 +172,6 @@ class BaseXmlModel(pd.BaseModel, metaclass=XmlModelMeta):
__xml_tag__: ClassVar[Optional[str]]
__xml_ns__: ClassVar[Optional[str]]
__xml_nsmap__: ClassVar[Optional[NsMap]]
- __xml_inherit_ns__: ClassVar[bool]
__xml_ns_attrs__: ClassVar[bool]
__xml_serializer__: ClassVar[Optional[serializers.ModelSerializerFactory.RootSerializer]]
@@ -182,7 +181,6 @@ def __init_subclass__(
tag: Optional[str] = None,
ns: Optional[str] = None,
nsmap: Optional[NsMap] = None,
- inherit_ns: bool = False,
ns_attrs: bool = False,
**kwargs: Any,
):
@@ -192,7 +190,6 @@ def __init_subclass__(
:param tag: element tag
:param ns: element namespace
:param nsmap: element namespace map
- :param inherit_ns: if `True` and ns argument is not provided - inherits namespace from the outer model
:param ns_attrs: use namespaced attributes
"""
@@ -201,7 +198,6 @@ def __init_subclass__(
cls.__xml_tag__ = tag
cls.__xml_ns__ = ns
cls.__xml_nsmap__ = nsmap
- cls.__xml_inherit_ns__ = inherit_ns
cls.__xml_ns_attrs__ = ns_attrs
@classmethod
@@ -220,7 +216,7 @@ def from_xml_tree(cls, root: etree.Element) -> 'BaseXmlModel':
:return: deserialized object
"""
- assert cls.__xml_serializer__ is not None
+ assert cls.__xml_serializer__ is not None, "model is partially initialized"
obj = cls.__xml_serializer__.deserialize(root)
return cls.parse_obj(obj)
@@ -254,6 +250,7 @@ def to_xml_tree(
assert self.__xml_serializer__ is not None
root = self.__xml_serializer__.serialize(None, self, encoder=encoder, skip_empty=skip_empty)
+ assert root is not None
if self.__xml_nsmap__ and (default_ns := self.__xml_nsmap__.get('')):
root.set('xmlns', default_ns)
@@ -289,7 +286,6 @@ def __class_getitem__(cls, params: Union[Type[Any], Tuple[Type[Any], ...]]) -> T
model.__xml_tag__ = cls.__xml_tag__
model.__xml_ns__ = cls.__xml_ns__
model.__xml_nsmap__ = cls.__xml_nsmap__
- model.__xml_inherit_ns__ = cls.__xml_inherit_ns__
model.__xml_ns_attrs__ = cls.__xml_ns_attrs__
model.__init_serializer__()
diff --git a/pydantic_xml/serializers.py b/pydantic_xml/serializers.py
index 9b8316d..f873a8f 100644
--- a/pydantic_xml/serializers.py
+++ b/pydantic_xml/serializers.py
@@ -284,16 +284,14 @@ class ModelSerializerFactory:
Model serializer factory.
"""
- class BaseSerializer(Serializer, abc.ABC):
+ class RootSerializer(Serializer):
def __init__(
self,
- model_field: Optional[pd.fields.ModelField],
model: Type['pxml.BaseXmlModel'],
ctx: Serializer.Context,
):
- field_name = model_field.name if model_field else None
- name = ctx.entity_name or model.__xml_tag__ or field_name or model.__name__
- ns = ctx.entity_ns or model.__xml_ns__ or (ctx.parent_ns if model.__xml_inherit_ns__ else None)
+ name = ctx.entity_name or model.__xml_tag__ or model.__name__
+ ns = ctx.entity_ns or model.__xml_ns__
nsmap = merge_nsmaps(ctx.entity_nsmap, model.__xml_nsmap__, ctx.parent_nsmap)
is_root = model.__custom_root_type__
ctx = dc.replace(
@@ -310,10 +308,12 @@ def __init__(
for field_name, model_subfield in model.__fields__.items()
}
- class RootSerializer(BaseSerializer):
def serialize(
self, element: Optional[etree.Element], value: Any, *, encoder: XmlEncoder, skip_empty: bool = False,
- ) -> etree.Element:
+ ) -> Optional[etree.Element]:
+ if value is None:
+ return None
+
if element is None:
element = etree.Element(self.element_name)
@@ -332,16 +332,33 @@ def deserialize(self, element: etree.Element) -> Any:
else:
return result
- class ElementSerializer(BaseSerializer):
+ class ElementSerializer(Serializer):
+
+ def __init__(
+ self,
+ model_field: Optional[pd.fields.ModelField],
+ model: Type['pxml.BaseXmlModel'],
+ ctx: Serializer.Context,
+ ):
+ field_name = model_field.name if model_field else None
+ name = ctx.entity_name or model.__xml_tag__ or field_name or model.__name__
+ ns = ctx.entity_ns or model.__xml_ns__
+ nsmap = merge_nsmaps(ctx.entity_nsmap, model.__xml_nsmap__, ctx.parent_nsmap)
+
+ self.element_name = QName.from_alias(tag=name, ns=ns, nsmap=nsmap).uri
+ self.model = model
+
def serialize(
self, element: etree.Element, value: Any, *, encoder: XmlEncoder, skip_empty: bool = False,
) -> Optional[etree.Element]:
+ assert self.model.__xml_serializer__ is not None, "model is partially initialized"
+
+ if value is None:
+ return None
+
sub_element = etree.Element(self.element_name)
- for field_name, field_serializer in self.field_serializers.items():
- field_serializer.serialize(
- sub_element, getattr(value, field_name), encoder=encoder, skip_empty=skip_empty,
- )
+ self.model.__xml_serializer__.serialize(sub_element, value, encoder=encoder, skip_empty=skip_empty)
if not skip_empty or sub_element.text or sub_element.attrib or len(sub_element) != 0:
element.append(sub_element)
@@ -350,21 +367,33 @@ def serialize(
return None
def deserialize(self, element: etree.Element) -> Optional[Dict[str, Any]]:
+ assert self.model.__xml_serializer__ is not None, "model is partially initialized"
+
if (sub_element := element.find(self.element_name)) is not None:
- result = {
- field_name: field_serializer.deserialize(sub_element)
- for field_name, field_serializer in self.field_serializers.items()
- }
- if self.is_root:
- return result['__root__']
- else:
- return result
+ return self.model.__xml_serializer__.deserialize(sub_element)
else:
return None
+ class DeferredSerializer(Serializer):
+
+ def __init__(self, model: Type['pxml.BaseXmlModel']):
+ self.model = model
+
+ def serialize(
+ self, element: etree.Element, value: Any, *, encoder: XmlEncoder, skip_empty: bool = False,
+ ) -> Optional[etree.Element]:
+ assert self.model.__xml_serializer__ is not None, "model is partially initialized"
+
+ return self.model.__xml_serializer__.serialize(element, value, encoder=encoder, skip_empty=skip_empty)
+
+ def deserialize(self, element: etree.Element) -> Optional[Dict[str, Any]]:
+ assert self.model.__xml_serializer__ is not None, "model is partially initialized"
+
+ return self.model.__xml_serializer__.deserialize(element)
+
@classmethod
def from_model(cls, model: Type['pxml.BaseXmlModel']) -> 'RootSerializer':
- return cls.RootSerializer(None, model, Serializer.Context(parent_is_root=True))
+ return cls.RootSerializer(model, Serializer.Context(parent_is_root=True))
@classmethod
def build(
@@ -386,7 +415,7 @@ def build(
elif not ctx.parent_is_root and field_location is Location.MISSING:
return cls.ElementSerializer(model_field, sub_model, ctx)
elif ctx.parent_is_root and field_location is Location.MISSING:
- return cls.RootSerializer(model_field, sub_model, ctx)
+ return cls.DeferredSerializer(sub_model)
elif field_location is Location.ATTRIBUTE:
raise errors.ModelFieldError(
model.__name__, model_field.name, "attributes of model type are not supported",
diff --git a/pyproject.toml b/pyproject.toml
index f95e20c..e7d6188 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pydantic-xml"
-version = "0.2.2"
+version = "0.3.0"
description = "pydantic xml serialization/deserialization extension"
authors = ["Dmitry Pershin "]
license = "Unlicense"
@@ -21,6 +21,7 @@ classifiers = [
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
]
[tool.poetry.dependencies]
diff --git a/tests/test_misc.py b/tests/test_misc.py
index 91f82c1..82d0971 100644
--- a/tests/test_misc.py
+++ b/tests/test_misc.py
@@ -43,3 +43,50 @@ class TestModel(BaseXmlModel, tag='model'):
actual_xml = obj.to_xml(skip_empty=True)
assert_xml_equal(actual_xml, xml.encode())
+
+
+def test_recursive_models():
+ class TestModel(BaseXmlModel, tag='model'):
+ attr1: int = attr()
+ element1: float = element()
+
+ model1: Optional['TestModel'] = element(tag='model1')
+ models: Optional[List['TestModel']] = element(tag='item')
+
+ xml = '''
+
+ 1.1
+
+ 2.2
+
+
+ -
+ 3.3
+
+ -
+ 4.4
+
+
+ '''
+
+ obj = TestModel(
+ attr1=1,
+ element1=1.1,
+ model1=TestModel(
+ attr1=2,
+ element1=2.2,
+ ),
+ models=[
+ TestModel(
+ attr1=3,
+ element1=3.3,
+ ),
+ TestModel(
+ attr1=4,
+ element1=4.4,
+ ),
+ ],
+ )
+
+ actual_xml = obj.to_xml(skip_empty=True)
+ assert_xml_equal(actual_xml, xml.encode())
diff --git a/tests/test_namespaces.py b/tests/test_namespaces.py
index b444666..85a6384 100644
--- a/tests/test_namespaces.py
+++ b/tests/test_namespaces.py
@@ -7,7 +7,7 @@
def test_default_namespaces():
- class TestSubMode2(BaseXmlModel, nsmap={'tst': 'http://test3.org'}):
+ class TestSubMode2(BaseXmlModel, nsmap={'': 'http://test2.org', 'tst': 'http://test3.org'}):
attr1: int = attr()
attr2: int = attr(ns='tst')
element: str = element()
@@ -117,202 +117,6 @@ class TestModel(
assert_xml_equal(actual_xml, xml)
-@pytest.mark.parametrize(
- 'inherit_ns, model_ns, submodel_ns, element_ns, expected_model_ns, expected_submodel_ns',
- [
- (True, 'tst1', 'tst2', 'tst3', 'tst1', 'tst3'),
- ],
-)
-def test_namespace_inheritance(
- inherit_ns, model_ns, submodel_ns, element_ns, expected_model_ns, expected_submodel_ns,
-):
- class TestSubModel(BaseXmlModel, ns=submodel_ns, inherit_ns=inherit_ns):
- element: float = element()
-
- class TestModel(
- BaseXmlModel,
- tag='model1',
- ns=model_ns,
- nsmap={'tst1': 'http://test1.org', 'tst2': 'http://test2.org', 'tst3': 'http://test3.org'},
- ):
- model: TestSubModel = element(tag='model2', ns=element_ns)
-
- xml = '''
- <{ns_pref1}model1 xmlns:tst1="http://test1.org" xmlns:tst2="http://test2.org" xmlns:tst3="http://test3.org">
- <{ns_pref2}model2>
- <{ns_pref2}element>1.1{ns_pref2}element>
- {ns_pref2}model2>
- {ns_pref1}model1>
- '''.format(
- ns_pref1=f"{expected_model_ns}:" if expected_model_ns else "",
- ns_pref2=f"{expected_submodel_ns}:" if expected_submodel_ns else "",
- )
-
- actual_obj = TestModel.from_xml(xml)
- expected_obj = TestModel(
- model=TestSubModel(element=1.1),
- )
-
- assert actual_obj == expected_obj
-
- actual_xml = actual_obj.to_xml()
- assert_xml_equal(actual_xml, xml)
-
-
-def test_recursive_namespace_inheritance():
- class TestSubModel3(BaseXmlModel, tag='model4', inherit_ns=True):
- element: str = element()
-
- class TestSubModel2(BaseXmlModel, tag='model3', ns='tst2', nsmap={'tst2': 'http://test.org'}):
- element: TestSubModel3
-
- class TestSubModel1(BaseXmlModel, tag='model2'):
- element: TestSubModel2
-
- class TestModel(BaseXmlModel, tag='model1', ns='tst1', nsmap={'tst1': 'http://test.org'}):
- element: TestSubModel1
-
- xml = '''
-
-
-
-
- string1
-
-
-
-
- '''
-
- actual_obj = TestModel.from_xml(xml)
- expected_obj = TestModel(
- element=TestSubModel1(
- element=TestSubModel2(
- element=TestSubModel3(element="string1"),
- ),
- ),
- )
-
- assert actual_obj == expected_obj
-
- actual_xml = actual_obj.to_xml()
- assert_xml_equal(actual_xml, xml)
-
-
-@pytest.mark.parametrize(
- 'ns_attrs, expected_attr_ns',
- [
- (True, 'tst1'),
- (False, None),
- ],
-)
-def test_mapping_namespace_inheritance(ns_attrs, expected_attr_ns):
- class TestModel(BaseXmlModel, tag='model', ns='tst1', nsmap={'tst1': 'http://test.org'}, ns_attrs=ns_attrs):
- attrs: Dict[str, int]
-
- xml = '''
-
- '''.format(
- ns_pref1=f"{expected_attr_ns}:" if expected_attr_ns else '',
- )
-
- actual_obj = TestModel.from_xml(xml)
- expected_obj = TestModel(
- attrs={
- 'attr1': 1,
- 'attr2': 2,
- },
- )
-
- assert actual_obj == expected_obj
-
- actual_xml = actual_obj.to_xml()
- assert_xml_equal(actual_xml, xml)
-
-
-@pytest.mark.parametrize(
- 'inherit_ns, model_ns, submodel_ns, element_ns, expected_model_ns, expected_submodel_ns, expected_element_ns',
- [
- (True, 'tst1', 'tst2', 'tst3', 'tst1', 'tst3', 'tst2'),
- (True, 'tst1', None, 'tst3', 'tst1', 'tst3', 'tst3'),
- (True, 'tst1', None, None, 'tst1', 'tst1', 'tst1'),
- (False, 'tst1', None, 'tst3', 'tst1', 'tst3', None),
- (False, None, None, None, None, None, None),
- ],
-)
-def test_homogeneous_collections_namespace_inheritance(
- inherit_ns, model_ns, submodel_ns, element_ns, expected_model_ns, expected_submodel_ns, expected_element_ns,
-):
- class TestSubModel(BaseXmlModel, ns=submodel_ns, inherit_ns=inherit_ns):
- element: float = element()
-
- class RootModel(
- BaseXmlModel,
- tag='model1',
- ns=model_ns,
- nsmap={'tst1': 'http://test1.org', 'tst2': 'http://test2.org', 'tst3': 'http://test3.org'},
- ):
- elements: List[TestSubModel] = element(tag='model2', ns=element_ns)
-
- xml = '''
- <{ns_pref1}model1 xmlns:tst1="http://test1.org" xmlns:tst2="http://test2.org" xmlns:tst3="http://test3.org">
- <{ns_pref2}model2>
- <{ns_pref3}element>1.1{ns_pref3}element>
- {ns_pref2}model2>
- <{ns_pref2}model2>
- <{ns_pref3}element>2.2{ns_pref3}element>
- {ns_pref2}model2>
- {ns_pref1}model1>
- '''.format(
- ns_pref1=f"{expected_model_ns}:" if expected_model_ns else "",
- ns_pref2=f"{expected_submodel_ns}:" if expected_submodel_ns else "",
- ns_pref3=f"{expected_element_ns}:" if expected_element_ns else "",
- )
-
- actual_obj = RootModel.from_xml(xml)
- expected_obj = RootModel(
- elements=[
- TestSubModel(element=1.1),
- TestSubModel(element=2.2),
- ],
- )
-
- assert actual_obj == expected_obj
-
- actual_xml = actual_obj.to_xml()
- assert_xml_equal(actual_xml, xml)
-
-
-def test_heterogeneous_collections_namespace_inheritance():
- class TestSubModel1(BaseXmlModel, ns_attrs=True, inherit_ns=True):
- attr1: int = attr()
- element: float = element()
-
- class TestModel(BaseXmlModel, tag='model1', ns='tst1', nsmap={'tst1': 'http://test.org'}):
- elements: Tuple[TestSubModel1, TestSubModel1] = element(tag='model2', ns='tst1')
-
- xml = '''
-
-
- 1.1
-
-
- 2.2
-
-
- '''
-
- actual_obj = TestModel.from_xml(xml)
- expected_obj = TestModel(
- elements=(TestSubModel1(attr1=1, element=1.1), TestSubModel1(attr1=2, element=2.2)),
- )
-
- assert actual_obj == expected_obj
-
- actual_xml = actual_obj.to_xml()
- assert_xml_equal(actual_xml, xml)
-
-
def test_wrapper_namespaces():
class TestModel(BaseXmlModel, tag='model1', ns='tst1', nsmap={'tst1': 'http://test1.org'}):
data: int = wrapped('model2/model3', ns='tst2', nsmap={'tst2': 'http://test2.org'})
@@ -358,7 +162,7 @@ class TestModel(BaseXmlModel, tag='model1', ns='tst1', nsmap={'tst1': 'http://te
def test_homogeneous_collection_wrapper_namespace_inheritance():
- class TestSubModel1(BaseXmlModel, inherit_ns=True):
+ class TestSubModel1(BaseXmlModel):
data: int
class TestModel(BaseXmlModel, tag='model1', ns='tst1', nsmap={'tst1': 'http://test1.org'}):
@@ -388,7 +192,7 @@ class TestModel(BaseXmlModel, tag='model1', ns='tst1', nsmap={'tst1': 'http://te
def test_heterogeneous_collection_wrapper_namespace_inheritance():
- class TestSubModel1(BaseXmlModel, inherit_ns=True):
+ class TestSubModel1(BaseXmlModel):
data: int
class TestModel(BaseXmlModel, tag='model1', ns='tst1', nsmap={'tst1': 'http://test1.org'}):
diff --git a/tests/test_submodels.py b/tests/test_submodels.py
index a4b7a89..d83f647 100644
--- a/tests/test_submodels.py
+++ b/tests/test_submodels.py
@@ -1,3 +1,5 @@
+from typing import Optional
+
import pytest
from helpers import assert_xml_equal
@@ -11,6 +13,7 @@ class TestSubModel(BaseXmlModel, tag='model2'):
class TestModel(BaseXmlModel, tag='model1'):
model2: TestSubModel
+ model3: Optional[TestSubModel] = element(tag='model3')
xml = '''
diff --git a/tests/test_wrapped.py b/tests/test_wrapped.py
index e032d36..d3b56f4 100644
--- a/tests/test_wrapped.py
+++ b/tests/test_wrapped.py
@@ -1,4 +1,4 @@
-from typing import Dict, List
+from typing import Dict, List, Optional
from helpers import assert_xml_equal
@@ -30,6 +30,7 @@ class TestSubModel(BaseXmlModel, tag='model4'):
class TestModel(BaseXmlModel, tag='model1'):
data: TestSubModel = wrapped('model2/model3', element())
+ empty: Optional[TestSubModel] = wrapped('model2/model4', element())
xml = '''