diff --git a/docs/api/codegen.rst b/docs/api/codegen.rst index a53d25ea8..b216332ad 100644 --- a/docs/api/codegen.rst +++ b/docs/api/codegen.rst @@ -18,6 +18,7 @@ like naming conventions and aliases. GeneratorConfig GeneratorOutput + OutputFormat GeneratorConventions GeneratorAliases StructureStyle diff --git a/docs/codegen.rst b/docs/codegen.rst index c9e29447a..ea3532493 100644 --- a/docs/codegen.rst +++ b/docs/codegen.rst @@ -23,6 +23,7 @@ Generate Code - :ref:`Compound fields ` - :ref:`Docstring styles` + - :ref:`Dataclasses Features` .. code-block:: console @@ -96,8 +97,9 @@ altogether. .. warning:: - Auto :ref:`locating types ` during parsing might not work since - all classes are bundled together under the same module namespace. + Auto :ref:`locating types ` during parsing + might not work since all classes are bundled together under the same module + namespace. Initialize Config diff --git a/docs/examples.rst b/docs/examples.rst index b01ab6595..7709e186c 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -12,6 +12,7 @@ Code Generation examples/xml-modeling examples/json-modeling examples/compound-fields + examples/dataclasses-features Advance Topics diff --git a/docs/examples/dataclasses-features.rst b/docs/examples/dataclasses-features.rst new file mode 100644 index 000000000..bf186d4ef --- /dev/null +++ b/docs/examples/dataclasses-features.rst @@ -0,0 +1,41 @@ +==================== +Dataclasses Features +==================== + +By default xsdata with generate +`dataclasses `_ with the default +features on but you can use a :ref:`generator config ` to toggle +almost all of them. + + +.. literalinclude:: /../tests/fixtures/stripe/.xsdata.xml + :language: xml + :lines: 2-6 + + +.. tab:: Frozen Model + + The code generator will use tuples instead of lists as well. + + .. literalinclude:: /../tests/fixtures/stripe/models/balance.py + :language: python + :lines: 93-128 + +.. tab:: Frozen Bindings + + .. testcode:: + + import pprint + from tests import fixtures_dir + from tests.fixtures.stripe.models import Balance + from xsdata.formats.dataclass.parsers import JsonParser + + xml_path = fixtures_dir.joinpath("stripe/samples/balance.json") + parser = JsonParser() + root = parser.from_path(xml_path, Balance) + pprint.pprint(root.pending) + + .. testoutput:: + + (Pending(amount=835408472, currency='usd', source_types=SourceTypes(bank_account=0, card=835408472)), + Pending(amount=-22251, currency='eur', source_types=SourceTypes(bank_account=0, card=-22251))) diff --git a/docs/examples/json-modeling.rst b/docs/examples/json-modeling.rst index bc7025480..8a802b5ea 100644 --- a/docs/examples/json-modeling.rst +++ b/docs/examples/json-modeling.rst @@ -9,17 +9,17 @@ duplicate classes and their fields and their field types. .. code-block:: console - $ xsdata --package tests.fixtures.series tests/fixtures/series + $ xsdata --package tests.fixtures.series tests/fixtures/series/samples .. tab:: Sample #1 - .. literalinclude:: /../tests/fixtures/series/show1.json + .. literalinclude:: /../tests/fixtures/series/samples/show1.json :language: json .. tab:: Sample #2 - .. literalinclude:: /../tests/fixtures/series/show2.json + .. literalinclude:: /../tests/fixtures/series/samples/show2.json :language: json diff --git a/docs/models.rst b/docs/models.rst index 286d8eb45..15ba02ba3 100644 --- a/docs/models.rst +++ b/docs/models.rst @@ -124,7 +124,7 @@ Simply follow the Python lib .. warning:: - Currently only List, Dict and Union annotations are supported. + Currently only List, Tuple, Dict and Union annotations are supported. Everything else will raise an exception as unsupported. diff --git a/tests/fixtures/series/show1.json b/tests/fixtures/series/samples/show1.json similarity index 100% rename from tests/fixtures/series/show1.json rename to tests/fixtures/series/samples/show1.json diff --git a/tests/fixtures/series/show2.json b/tests/fixtures/series/samples/show2.json similarity index 100% rename from tests/fixtures/series/show2.json rename to tests/fixtures/series/samples/show2.json diff --git a/tests/fixtures/stripe/.xsdata.xml b/tests/fixtures/stripe/.xsdata.xml new file mode 100644 index 000000000..b68df305b --- /dev/null +++ b/tests/fixtures/stripe/.xsdata.xml @@ -0,0 +1,20 @@ + + + + tests.fixtures.stripe.models.balance + dataclasses + single-package + reStructuredText + true + false + + + + + + + + + + + diff --git a/tests/fixtures/stripe/__init__.py b/tests/fixtures/stripe/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/stripe/models/__init__.py b/tests/fixtures/stripe/models/__init__.py new file mode 100644 index 000000000..6ef63e4a2 --- /dev/null +++ b/tests/fixtures/stripe/models/__init__.py @@ -0,0 +1,15 @@ +from .balance import ( + Available, + Balance, + ConnectReserved, + Pending, + SourceTypes, +) + +__all__ = [ + "Available", + "Balance", + "ConnectReserved", + "Pending", + "SourceTypes", +] diff --git a/tests/fixtures/stripe/models/balance.py b/tests/fixtures/stripe/models/balance.py new file mode 100644 index 000000000..6cb4e543b --- /dev/null +++ b/tests/fixtures/stripe/models/balance.py @@ -0,0 +1,128 @@ +from dataclasses import dataclass, field +from typing import Optional, Tuple + + +@dataclass(order=True, frozen=True) +class ConnectReserved: + class Meta: + name = "connect_reserved" + + amount: Optional[int] = field( + default=None, + metadata={ + "type": "Element", + } + ) + currency: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + } + ) + + +@dataclass(order=True, frozen=True) +class SourceTypes: + class Meta: + name = "source_types" + + bank_account: Optional[int] = field( + default=None, + metadata={ + "type": "Element", + } + ) + card: Optional[int] = field( + default=None, + metadata={ + "type": "Element", + } + ) + + +@dataclass(order=True, frozen=True) +class Available: + class Meta: + name = "available" + + amount: Optional[int] = field( + default=None, + metadata={ + "type": "Element", + } + ) + currency: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + } + ) + source_types: Optional[SourceTypes] = field( + default=None, + metadata={ + "type": "Element", + } + ) + + +@dataclass(order=True, frozen=True) +class Pending: + class Meta: + name = "pending" + + amount: Optional[int] = field( + default=None, + metadata={ + "type": "Element", + } + ) + currency: Optional[str] = field( + default=None, + metadata={ + "type": "Element", + } + ) + source_types: Optional[SourceTypes] = field( + default=None, + metadata={ + "type": "Element", + } + ) + + +@dataclass(order=True, frozen=True) +class Balance: + class Meta: + name = "balance" + + object_value: Optional[str] = field( + default=None, + metadata={ + "name": "object", + "type": "Element", + } + ) + available: Tuple[Available, ...] = field( + default_factory=tuple, + metadata={ + "type": "Element", + } + ) + connect_reserved: Tuple[ConnectReserved, ...] = field( + default_factory=tuple, + metadata={ + "type": "Element", + } + ) + livemode: Optional[bool] = field( + default=None, + metadata={ + "type": "Element", + } + ) + pending: Tuple[Pending, ...] = field( + default_factory=tuple, + metadata={ + "type": "Element", + } + ) diff --git a/tests/fixtures/stripe/samples/balance.json b/tests/fixtures/stripe/samples/balance.json new file mode 100644 index 000000000..dd7ef82a0 --- /dev/null +++ b/tests/fixtures/stripe/samples/balance.json @@ -0,0 +1,158 @@ +{ + "object": "balance", + "available": [ + { + "amount": 2685, + "currency": "nok", + "source_types": { + "bank_account": 0, + "card": 2685 + } + }, + { + "amount": 218420, + "currency": "nzd", + "source_types": { + "bank_account": 0, + "card": 218420 + } + }, + { + "amount": 779902, + "currency": "czk", + "source_types": { + "bank_account": 0, + "card": 779902 + } + }, + { + "amount": -1854, + "currency": "aud", + "source_types": { + "bank_account": 0, + "card": -1854 + } + }, + { + "amount": 278067892166, + "currency": "usd", + "source_types": { + "bank_account": 280201532, + "card": 277786138508 + } + }, + { + "amount": -54204, + "currency": "eur", + "source_types": { + "bank_account": 0, + "card": -54204 + } + }, + { + "amount": 2213741, + "currency": "cad", + "source_types": { + "bank_account": 0, + "card": 2213741 + } + }, + { + "amount": 7259805, + "currency": "gbp", + "source_types": { + "bank_account": 0, + "card": 7259805 + } + }, + { + "amount": -40320, + "currency": "jpy", + "source_types": { + "bank_account": 0, + "card": -40320 + } + }, + { + "amount": 12000, + "currency": "brl", + "source_types": { + "bank_account": 0, + "card": 12000 + } + }, + { + "amount": -412, + "currency": "sek", + "source_types": { + "bank_account": 0, + "card": -412 + } + } + ], + "connect_reserved": [ + { + "amount": 0, + "currency": "nok" + }, + { + "amount": 0, + "currency": "nzd" + }, + { + "amount": 0, + "currency": "czk" + }, + { + "amount": 0, + "currency": "aud" + }, + { + "amount": 55880, + "currency": "usd" + }, + { + "amount": 54584, + "currency": "eur" + }, + { + "amount": 0, + "currency": "cad" + }, + { + "amount": 0, + "currency": "gbp" + }, + { + "amount": 0, + "currency": "jpy" + }, + { + "amount": 0, + "currency": "brl" + }, + { + "amount": 0, + "currency": "sek" + } + ], + "livemode": false, + "pending": [ + { + "amount": 835408472, + "currency": "usd", + "source_types": { + "bank_account": 0, + "card": 835408472 + } + }, + { + "amount": -22251, + "currency": "eur", + "source_types": { + "bank_account": 0, + "card": -22251 + } + } + ] +} \ No newline at end of file diff --git a/tests/integration/test_series.py b/tests/integration/test_series.py index 1fe6f7f6b..69444e30a 100644 --- a/tests/integration/test_series.py +++ b/tests/integration/test_series.py @@ -17,7 +17,9 @@ def test_json_documents(): filepath = fixtures_dir.joinpath("series") package = "tests.fixtures.series" runner = CliRunner() - result = runner.invoke(cli, [str(filepath), "--package", package]) + result = runner.invoke( + cli, [str(filepath.joinpath("samples")), "--package", package] + ) if result.exception: raise result.exception @@ -28,7 +30,7 @@ def test_json_documents(): serializer = JsonSerializer(indent=4) for i in range(1, 3): - ori = filepath.joinpath(f"show{i}.json").read_text() + ori = filepath.joinpath(f"samples/show{i}.json").read_text() obj = parser.from_string(ori, clazz) actual = serializer.render(obj) diff --git a/tests/integration/test_stripe.py b/tests/integration/test_stripe.py new file mode 100644 index 000000000..26373a89c --- /dev/null +++ b/tests/integration/test_stripe.py @@ -0,0 +1,49 @@ +import json +import os + +from click.testing import CliRunner + +from tests import fixtures_dir +from tests import root +from xsdata.cli import cli +from xsdata.formats.dataclass.parsers import JsonParser +from xsdata.formats.dataclass.serializers import JsonSerializer +from xsdata.utils.testing import load_class + +os.chdir(root) + + +def test_json_documents(): + + filepath = fixtures_dir.joinpath("stripe") + package = "tests.fixtures.series" + runner = CliRunner() + result = runner.invoke( + cli, + [ + str(filepath.joinpath("samples")), + f"--config={str(filepath.joinpath('.xsdata.xml'))}", + ], + ) + + if result.exception: + raise result.exception + + clazz = load_class(result.output, "Balance") + + parser = JsonParser() + serializer = JsonSerializer(indent=4) + + for sample in filepath.joinpath("samples").glob("*.json"): + ori = sample.read_text() + obj = parser.from_string(ori, clazz) + actual = serializer.render(obj) + + assert filter_none(json.loads(ori)) == filter_none(json.loads(actual)) + + +def filter_none(d): + if isinstance(d, dict): + return {k: filter_none(v) for k, v in d.items() if v is not None} + else: + return d diff --git a/xsdata/formats/dataclass/utils.py b/xsdata/formats/dataclass/utils.py index 4d168859f..1f45b084b 100644 --- a/xsdata/formats/dataclass/utils.py +++ b/xsdata/formats/dataclass/utils.py @@ -15,6 +15,7 @@ "false", "none", "yield", + "object", "break", "for", "not", diff --git a/xsdata/models/config.py b/xsdata/models/config.py index 5a9200f7a..d23a3dd9b 100644 --- a/xsdata/models/config.py +++ b/xsdata/models/config.py @@ -133,7 +133,7 @@ class OutputFormat: :param repr: Generate repr methods :param eq: Generate equal method :param order: Generate rich comparison methods - :param unsafe_hash: Forcefully generate hash method without frozen + :param unsafe_hash: Generate hash method when frozen is false :param frozen: Enable read only properties with immutable containers """