这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
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
18 changes: 3 additions & 15 deletions docs/source/pages/misc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,22 +49,10 @@ Since xml format doesn't support ``null`` type natively it is not obvious how to
(ignore it, encode it as an empty string or mark it as ``xsi:nil``) the library doesn't implement
``None`` type encoding by default.

You can define your own encoding format for the model:
You can define your own encoding format:

.. code-block:: python

from typing import Annotated
from pydantic import PlainSerializer

InnerType = TypeVar('InnerType')
XmlOptional = Annotated[Optional[InnerType], PlainSerializer(lambda val: val if val is not None else '')]

class Company(BaseXmlModel):
title: XmlOptional[str] = element(default=None)


company = Company()
assert company.to_xml() == b'<Company><title></title></Company>'
.. literalinclude:: ../../../examples/snippets/py3.9/serialization.py
:language: python


or drop ``None`` fields at all:
Expand Down
29 changes: 29 additions & 0 deletions examples/snippets/py3.9/serialization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from typing import Annotated, Optional, TypeVar
from xml.etree.ElementTree import canonicalize

from pydantic import BeforeValidator, PlainSerializer

from pydantic_xml import BaseXmlModel, element

InnerType = TypeVar('InnerType')
XmlOptional = Annotated[
Optional[InnerType],
PlainSerializer(lambda val: val if val is not None else 'null'),
BeforeValidator(lambda val: val if val != 'null' else None),
]


class Company(BaseXmlModel):
title: XmlOptional[str] = element(default=None)


xml_doc = '''
<Company>
<title>null</title>
</Company>
'''

company = Company.from_xml(xml_doc)

assert company.title is None
assert canonicalize(company.to_xml(), strip_text=True) == canonicalize(xml_doc, strip_text=True)
57 changes: 52 additions & 5 deletions tests/test_encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import sys
from decimal import Decimal
from enum import Enum
from typing import Any
from uuid import UUID

import pytest
from helpers import assert_xml_equal
from pydantic import field_serializer
from pydantic.functional_serializers import PlainSerializer
from pydantic.functional_serializers import PlainSerializer, WrapSerializer
from pydantic.functional_validators import AfterValidator, BeforeValidator, WrapValidator

from pydantic_xml import BaseXmlModel, element

Expand Down Expand Up @@ -123,12 +125,16 @@ def serialize_dt(self, value: dt.datetime) -> float:


@pytest.mark.skipif(sys.version_info < (3, 9), reason="requires python 3.9 and above")
def test_plain_serializer():
@pytest.mark.parametrize(
'Serializer', [
PlainSerializer(lambda val: val.timestamp(), return_type=float),
WrapSerializer(lambda val, nxt: val.timestamp(), return_type=float),
],
)
def test_serializer(Serializer: Any):
from typing import Annotated

Timestamp = Annotated[
dt.datetime, PlainSerializer(lambda val: val.timestamp(), return_type=float),
]
Timestamp = Annotated[dt.datetime, Serializer]

class TestSubModel(BaseXmlModel, tag='submodel'):
field1: Timestamp = element(tag='field1')
Expand All @@ -155,3 +161,44 @@ class TestModel(BaseXmlModel, tag='model'):

actual_xml = obj.to_xml()
assert_xml_equal(actual_xml, xml.encode())


@pytest.mark.skipif(sys.version_info < (3, 9), reason="requires python 3.9 and above")
@pytest.mark.parametrize(
'Validator', [
AfterValidator(lambda val: val),
BeforeValidator(lambda val: dt.datetime.fromtimestamp(float(val), tz=dt.timezone.utc)),
WrapValidator(lambda val, nxt: dt.datetime.fromtimestamp(float(val), tz=dt.timezone.utc)),
],
)
def test_validator(Validator: Any):
from typing import Annotated

Timestamp = Annotated[dt.datetime, Validator]

class TestSubModel(BaseXmlModel, tag='submodel'):
field1: Timestamp = element(tag='field1')

class TestModel(BaseXmlModel, tag='model'):
field1: Timestamp = element(tag='field1')
field2: TestSubModel

xml = '''
<model>
<field1>1675468800.0</field1>
<submodel>
<field1>1675468800.0</field1>
</submodel>
</model>
'''

actual_obj = TestModel.from_xml(xml)

expected_obj = TestModel.model_construct(
field1=dt.datetime(2023, 2, 4, tzinfo=dt.timezone.utc),
field2=TestSubModel.model_construct(
field1=dt.datetime(2023, 2, 4, tzinfo=dt.timezone.utc),
),
)

assert actual_obj == expected_obj
8 changes: 8 additions & 0 deletions tests/test_examples.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import importlib.machinery
import sys
from pathlib import Path

import pytest
Expand All @@ -14,6 +15,13 @@ def test_snippets(snippet: Path):
loader.load_module('snippet')


@pytest.mark.skipif(sys.version_info < (3, 9), reason="requires python 3.9 and above")
@pytest.mark.parametrize('snippet', list((EXAMPLES_PATH / 'snippets' / 'py3.9').glob('*.py')), ids=lambda p: p.name)
def test_snippets_py39(snippet: Path):
loader = importlib.machinery.SourceFileLoader('snippet', str(snippet))
loader.load_module('snippet')


@pytest.fixture(
params=[
'custom-encoder',
Expand Down