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
10 changes: 10 additions & 0 deletions docs/source/pages/misc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ The following example illustrate how to serialize ``xs:list`` element:
:language: python


JSON only field
~~~~~~~~~~~~~~~

To prevent a field from serialization/deserialization only for xml format
mark it with :py:class:`pydantic_xml.NoXml` annotation:

.. literalinclude:: ../../../examples/snippets/py3.9/noxml.py
:language: python


Optional type encoding
~~~~~~~~~~~~~~~~~~~~~~

Expand Down
29 changes: 29 additions & 0 deletions examples/snippets/py3.9/noxml.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from typing import Annotated, Optional
from xml.etree.ElementTree import canonicalize

from pydantic_xml import BaseXmlModel, NoXml, element


class Company(BaseXmlModel):
title: str = element()
website: Annotated[Optional[str], NoXml] = element(default=None)


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

company = Company.from_xml(xml_doc)

assert canonicalize(company.to_xml(), strip_text=True) == canonicalize(xml_doc, strip_text=True)

json_doc = '''
{
"title": "SpaceX",
"website": "https://spacex.com/"
}
'''
company = Company.model_validate_json(json_doc)
assert company.model_dump_json(indent=4) == json_doc.strip()
5 changes: 3 additions & 2 deletions pydantic_xml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

from . import config, errors, model
from .errors import ModelError, ParsingError
from .fields import XmlFieldSerializer, XmlFieldValidator, attr, computed_attr, computed_element, element, wrapped
from .fields import xml_field_serializer, xml_field_validator
from .fields import NoXml, XmlFieldSerializer, XmlFieldValidator, attr, computed_attr, computed_element, element
from .fields import wrapped, xml_field_serializer, xml_field_validator
from .model import BaseXmlModel, RootXmlModel, create_model

__all__ = (
Expand All @@ -23,6 +23,7 @@
'model',
'xml_field_serializer',
'xml_field_validator',
'NoXml',
'XmlFieldValidator',
'XmlFieldSerializer',
)
7 changes: 7 additions & 0 deletions pydantic_xml/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,20 @@
'xml_field_serializer',
'xml_field_validator',
'ComputedXmlEntityInfo',
'NoXml',
'XmlEntityInfo',
'XmlEntityInfoP',
'XmlFieldSerializer',
'XmlFieldValidator',
)


class NoXml:
"""
Annotation to prevent field encoding or decoding for xml format only.
"""


class XmlEntityInfoP(typing.Protocol):
location: Optional[EntityLocation]
path: Optional[str]
Expand Down
7 changes: 5 additions & 2 deletions pydantic_xml/serializers/factories/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import pydantic_xml as pxml
from pydantic_xml import errors, utils
from pydantic_xml.element import XmlElementReader, XmlElementWriter, is_element_nill, make_element_nill
from pydantic_xml.fields import ComputedXmlEntityInfo, XmlEntityInfoP, extract_field_xml_entity_info
from pydantic_xml.fields import ComputedXmlEntityInfo, NoXml, XmlEntityInfoP, extract_field_xml_entity_info
from pydantic_xml.serializers.serializer import SearchMode, Serializer
from pydantic_xml.typedefs import EntityLocation, Location, NsMap
from pydantic_xml.utils import QName, merge_nsmaps, select_ns
Expand Down Expand Up @@ -76,6 +76,10 @@ def from_core_schema(cls, schema: pcs.ModelSchema, ctx: Serializer.Context) -> '
fields_validation_aliases: Dict[str, str] = {}
fields_serializers: Dict[str, Serializer] = {}
for field_name, model_field in fields_schema['fields'].items():
field_info = model_cls.model_fields[field_name]
if NoXml in field_info.metadata:
continue

if model_field.get('serialization_exclude', False):
fields_serialization_exclude.add(field_name)

Expand All @@ -84,7 +88,6 @@ def from_core_schema(cls, schema: pcs.ModelSchema, ctx: Serializer.Context) -> '
if isinstance(validation_alias, str):
fields_validation_aliases[field_name] = validation_alias

field_info = model_cls.model_fields[field_name]
field_ctx = ctx.child(
field_name=field_name,
field_alias=field_alias,
Expand Down
26 changes: 25 additions & 1 deletion tests/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import pytest
from helpers import assert_xml_equal

from pydantic_xml import BaseXmlModel, RootXmlModel, attr, element, errors, wrapped
from pydantic_xml import BaseXmlModel, NoXml, RootXmlModel, attr, element, errors, wrapped


def test_xml_declaration():
Expand Down Expand Up @@ -441,3 +441,27 @@ class TestModel(BaseXmlModel, tag="model"):
assert not error_value_input, error_str
else:
assert error_value_input, error_str


@pytest.mark.skipif(sys.version_info < (3, 9), reason="requires python 3.9 and above")
def test_no_xml_annotation():
from typing import Annotated

class TestModel(BaseXmlModel, tag="model"):
field1: str = element()
field2: Annotated[Optional[str], NoXml] = element(default=None)

expected_xml = '<model><field1>data1</field1></model>'
actual_obj = TestModel.from_xml(expected_xml)
expected_obj = TestModel(field1='data1')

assert actual_obj == expected_obj
actual_xml = actual_obj.to_xml(skip_empty=True)
assert_xml_equal(actual_xml, expected_xml.encode())

expected_json = {'field1': 'data1', 'field2': 'data2'}
actual_obj = TestModel.model_validate(expected_json)
expected_obj = TestModel(field1='data1', field2='data2')
assert actual_obj == expected_obj

assert actual_obj.model_dump() == expected_json
Loading