Skip to content

Commit

Permalink
Expose element/attribute name generators
Browse files Browse the repository at this point in the history
Closes #381
  • Loading branch information
tefra committed Jan 21, 2021
1 parent c860fbf commit 79dcbfe
Show file tree
Hide file tree
Showing 17 changed files with 267 additions and 62 deletions.
19 changes: 19 additions & 0 deletions docs/api/utils.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
=========
Utilities
=========


.. currentmodule:: xsdata.utils

.. autosummary::
:toctree: reference
:nosignatures:

text.capitalize
text.pascal_case
text.camel_case
text.mixed_case
text.mixed_pascal_case
text.mixed_snake_case
text.snake_case
text.kebab_case
155 changes: 153 additions & 2 deletions docs/models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ Basic Model
Class Meta
==========

Through the Meta class you can control the model's behaviour during data binding
procedures.

.. list-table::
:widths: 20 10 300
:header-rows: 1
Expand All @@ -36,13 +39,19 @@ Class Meta
- Description
* - name
- str
- The real name of the element this class represents.
- The real/local name of the element this class represents.
* - nillable
- bool
- Specifies whether an explicit empty value can be assigned, default: False
* - namespace
- str
- The element xml namespace.
* - element_name_generator
- Callable
- Element name generator
* - attribute_name_generator
- Callable
- Attribute name generator


Field Typing
Expand All @@ -55,6 +64,9 @@ Simply follow the Python lib
Field Metadata
==============

Through the metadata properties you can control the field's behaviour during data
binding procedures.

.. list-table::
:widths: 20 10 250
:header-rows: 1
Expand All @@ -64,7 +76,7 @@ Field Metadata
- Description
* - name
- str
- The real name of the element or attribute this field represents.
- The real/local name of the element or attribute this field represents.
* - type
- str
- The field xml type:
Expand Down Expand Up @@ -328,3 +340,142 @@ is directly assigned as text to elements.
.. code-block:: xml
<root>2020</root>
Advance Topics
==============

Customize element and attribute names
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Through the model and field metadata you can explicitly specify the serialized
names. You can also provide callables to set the real/local names per model or
for the entire binding context.


.. doctest::

>>> from dataclasses import dataclass, field
>>> from datetime import date
>>> from xsdata.formats.dataclass.context import XmlContext
>>> from xsdata.formats.dataclass.parsers import XmlParser
>>> from xsdata.formats.dataclass.serializers import XmlSerializer
>>> from xsdata.formats.dataclass.serializers.config import SerializerConfig
>>> from xsdata.utils import text
>>> config = SerializerConfig(pretty_print=True, xml_declaration=False)
>>> serializer = XmlSerializer(config=config)

**Ordered by priority**

.. tab:: Explicit names

Explicit model and field names is the most straight forward way to customize
the real/local names for elements and attributes. It can become tedious though
when you have to do this for models with a lot of fields.

.. doctest::

>>> @dataclass
... class Person:
...
... class Meta:
... name = "Person" # Explicit name
...
... first_name: str = field(metadata=dict(name="firstName"))
... last_name: str = field(metadata=dict(name="lastName"))
... birth_date: date = field(
... metadata=dict(
... type="Attribute",
... format="%Y-%m-%d",
... name="dob" # Explicit name
... )
... )
...
>>> obj = Person(
... first_name="Chris",
... last_name="T",
... birth_date=date(1986, 9, 25),
... )
>>> print(serializer.render(obj))
<Person dob="1986-09-25">
<firstName>Chris</firstName>
<lastName>T</lastName>
</Person>
<BLANKLINE>


.. tab:: Model name generators

Through the Meta class you can provide callables to apply a naming scheme for all
the model fields. The :mod:`xsdata.utils.text` has various helpers that you can
reuse.

.. doctest::

>>> @dataclass
... class person:
...
... class Meta:
... element_name_generator = text.pascal_case
... attribute_name_generator = text.camel_case
...
... first_name: str
... last_name: str
... birth_date: date = field(
... metadata=dict(
... type="Attribute",
... format="%Y-%m-%d"
... )
... )
...
>>> obj = person(
... first_name="Chris",
... last_name="T",
... birth_date=date(1986, 9, 25),
... )
>>> print(serializer.render(obj))
<Person birthDate="1986-09-25">
<FirstName>Chris</FirstName>
<LastName>T</LastName>
</Person>
<BLANKLINE>


.. tab:: Context name generators

Through the :class:`~xsdata.formats.dataclass.context.XmlContext` instance you can
provide callables to apply a naming scheme for all models and their fields. This way
you can avoid declaring them for every model but you have to use the same context
whenever you want to use a parser/serializer.

.. doctest::

>>> @dataclass
... class Person:
...
... first_name: str
... last_name: str
... birth_date: date = field(
... metadata=dict(
... type="Attribute",
... format="%Y-%m-%d"
... )
... )
...
>>> obj = Person(
... first_name="Chris",
... last_name="T",
... birth_date=date(1986, 9, 25),
... )
...
>>> context = XmlContext(
... element_name_generator=text.camel_case,
... attribute_name_generator=text.kebab_case
... )
>>> serializer = XmlSerializer(context=context, config=config)
>>> print(serializer.render(obj))
<person birth-date="1986-09-25">
<firstName>Chris</firstName>
<lastName>T</lastName>
</person>
<BLANKLINE>
4 changes: 0 additions & 4 deletions tests/codegen/handlers/test_attribute_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,8 @@
from xsdata.codegen.models import Restrictions
from xsdata.codegen.models import Status
from xsdata.codegen.utils import ClassUtils
from xsdata.exceptions import AnalyzerValueError
from xsdata.models.enums import DataType
from xsdata.models.enums import Tag
from xsdata.models.xsd import ComplexType
from xsdata.models.xsd import Element
from xsdata.models.xsd import SimpleType


class AttributeTypeHandlerTests(FactoryTestCase):
Expand Down
1 change: 0 additions & 1 deletion tests/codegen/mappers/test_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
from xsdata.models.wsdl import PortTypeOperation
from xsdata.models.wsdl import Service
from xsdata.models.wsdl import ServicePort
from xsdata.models.xsd import Element
from xsdata.utils.namespaces import build_qname


Expand Down
3 changes: 0 additions & 3 deletions tests/codegen/models/test_attr.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import sys
from unittest import mock

from tests.factories import AttrFactory
from tests.factories import FactoryTestCase
from xsdata.codegen.models import Attr
from xsdata.codegen.models import Restrictions
from xsdata.formats.dataclass.models.elements import XmlType
from xsdata.models.enums import Namespace
from xsdata.models.enums import Tag

Expand Down
6 changes: 3 additions & 3 deletions tests/codegen/test_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ def test_write(self, mock_designate, mock_render):
def test_print(self, mock_print, mock_designate, mock_render):
classes = ClassFactory.list(2)
mock_render.return_value = [
GeneratorResult(Path(f"foo/a.py"), "file", "aAa"),
GeneratorResult(Path(f"bar/b.py"), "file", "bBb"),
GeneratorResult(Path(f"c.py"), "file", ""),
GeneratorResult(Path("foo/a.py"), "file", "aAa"),
GeneratorResult(Path("bar/b.py"), "file", "bBb"),
GeneratorResult(Path("c.py"), "file", ""),
]
self.writer.print(classes)

Expand Down
1 change: 0 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import json
from pathlib import Path
from typing import Type

Expand Down
Loading

0 comments on commit 79dcbfe

Please sign in to comment.