==========
Data Types
==========

There is a global converter factory that is used during data binding
between python and xml/json documents.

The converter ships with multiple adapters and will attempt to convert values
according to the provided list of possible types but in case of an error it will
fall back to str, even if that means violating the field typing definition.

A warning will also be raised in that case.

.. code-block::

    ConverterWarning: Failed to convert value `a` to one of [<class 'float'>]


Schema Types Mapping
====================

Below you can find the mapping of data types between python and xml schema.

.. include:: data-types-table.rst


Working with custom types
=========================

If you are working with a type that's not supported or you want to override one
of the builtin adapter's behavior you can register you own.

.. doctest::

    >>> from dataclasses import dataclass, field
    >>> from xsdata.formats.converter import Converter, converter
    >>> from xsdata.formats.dataclass.parsers import XmlParser
    >>> from xsdata.formats.dataclass.serializers import XmlSerializer
    ...
    >>> class TheGoodFloat(float):
    ...     pass
    ...
    >>> @dataclass
    ... class Example:
    ...     good: TheGoodFloat = field(metadata=dict(format="{:.2f}"))
    ...     bad: float
    ...
    >>> class TheGoodFloatConverter(Converter):
    ...    def deserialize(self, value: str, **kwargs) -> TheGoodFloat:
    ...        return round(TheGoodFloat(value), 1)  # Even nicer
    ...
    ...    def serialize(self, value: TheGoodFloat, **kwargs) -> str:
    ...        if kwargs["format"]:
    ...            return kwargs["format"].format(value)
    ...        return str(value)
    ...
    >>> converter.register_converter(TheGoodFloat, TheGoodFloatConverter())
    >>> serializer = XmlSerializer()
    >>> output = serializer.render(Example(TheGoodFloat(10.983263748), -9.9827632))
    >>> print(output)
    <?xml version="1.0" encoding="UTF-8"?>
    <Example><good>10.98</good><bad>-9.9827632</bad></Example>
    >>> XmlParser().from_string(output)
    Example(good=11.0, bad=-9.9827632)


Working with date strings
=========================

The builtin converter includes adapters for :class:`datetime.time`,
:class:`datetime.date` and :class:`datetime.datetime`.

Use the field metadata to set the target `format` according to the
:ref:`python:strftime-strptime-behavior`

.. doctest::

    >>> import datetime as dt
    >>> from dataclasses import dataclass, field
    >>> from xsdata.formats.dataclass.serializers import XmlSerializer
    >>> from xsdata.formats.dataclass.serializers.config import SerializerConfig
    ...
    >>> @dataclass
    ... class Dates:
    ...     date: dt.date = field(metadata=dict(format="%d/%m/%Y"))
    ...     time: dt.time = field(metadata=dict(format="%H:%M"))
    ...     datetime: dt.datetime = field(metadata=dict(format="%d/%m/%Y %H:%M"))
    ...
    >>> obj = Dates(
    ...     date=dt.date(2020, 1, 6),
    ...     time=dt.time(18, 15, 30),
    ...     datetime=dt.datetime(2020, 1, 6, 18, 15, 30)
    ... )
    ...
    >>> config = SerializerConfig(pretty_print=True)
    >>> serializer = XmlSerializer(config=config)
    >>> print(serializer.render(obj))
    <?xml version="1.0" encoding="UTF-8"?>
    <Dates>
      <date>06/01/2020</date>
      <time>18:15</time>
      <datetime>06/01/2020 18:15</datetime>
    </Dates>
    <BLANKLINE>


Models generated by the cli from a schema definition use the builtin datatypes
:class:`~xsdata.models.datatype.XmlDate`, :class:`~xsdata.models.datatype.XmlTime`
and :class:`~xsdata.models.datatype.XmlDateTime` instead because the
datetime objects don't support all the xml features like years bce.

The builtin date types have util methods to convert from and to datetime objects
whenever possible.

.. doctest::

    >>> from dataclasses import dataclass
    >>> from xsdata.formats.dataclass.serializers import XmlSerializer
    >>> from xsdata.formats.dataclass.serializers.config import SerializerConfig
    >>> from xsdata.models.datatype import XmlDate, XmlTime, XmlDateTime
    ...
    >>> @dataclass
    ... class Dates:
    ...     date: XmlDate
    ...     time: XmlTime
    ...     datetime: XmlDateTime
    ...
    >>> obj = Dates(
    ...     date=XmlDate(2020, 1, 6, 120),
    ...     time=XmlTime(18, 15, 30, 123000000, 0),
    ...     datetime=XmlDateTime(2020, 1, 6, 18, 15, 30)
    ... )
    ...
    >>> config = SerializerConfig(pretty_print=True)
    >>> serializer = XmlSerializer(config=config)
    >>> print(serializer.render(obj))
    <?xml version="1.0" encoding="UTF-8"?>
    <Dates>
      <date>2020-01-06+02:00</date>
      <time>18:15:30.123Z</time>
      <datetime>2020-01-06T18:15:30</datetime>
    </Dates>
    <BLANKLINE>


Working with binary data
========================

xs:hexBinary and xs:base64Binary types are mapped to python :class:`bytes` and are
encoded/decoded with :mod:`python:binascii` and :mod:`python:base64` modules.


.. doctest::

    >>> from dataclasses import dataclass, field
    >>> from xsdata.formats.dataclass.serializers import XmlSerializer
    >>> from xsdata.formats.dataclass.serializers.config import SerializerConfig
    ...
    >>> @dataclass
    ... class Binary:
    ...     base_16: bytes = field(metadata=dict(format="base16"))
    ...     base_64: bytes = field(metadata=dict(format="base64"))
    ...
    >>> obj = Binary(
    ...     base_16="xml".encode(),
    ...     base_64="binding".encode(),
    ... )
    ...
    >>> config = SerializerConfig(pretty_print=True)
    >>> serializer = XmlSerializer(config=config)
    >>> print(serializer.render(obj))
    <?xml version="1.0" encoding="UTF-8"?>
    <Binary>
      <base_16>786D6C</base_16>
      <base_64>YmluZGluZw==</base_64>
    </Binary>
    <BLANKLINE>
