1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
|
from dataclasses import dataclass, field
from enum import Enum
from io import StringIO
from typing import Any, List, Mapping, Set, TextIO, Type
from xsdata.formats.bindings import AbstractSerializer
from xsdata.formats.dataclass.context import XmlContext
from xsdata.formats.dataclass.serializers.config import SerializerConfig
from xsdata.utils import collections
from xsdata.utils.objects import literal_value
spaces = " "
unset = object()
@dataclass
class PycodeSerializer(AbstractSerializer):
"""
Pycode serializer for dataclasses.
Return a python representation code of a model instance.
:param config: Serializer configuration
:param context: Model context provider
"""
config: SerializerConfig = field(default_factory=SerializerConfig)
context: XmlContext = field(default_factory=XmlContext)
def render(self, obj: object, var_name: str = "obj") -> str:
"""
Convert and return the given object tree as python representation code.
:param obj: The input dataclass instance
:param var_name: The var name to assign the model instance
"""
output = StringIO()
self.write(output, obj, var_name)
return output.getvalue()
def write(self, out: TextIO, obj: Any, var_name: str):
"""
Write the given object tree to the output text stream.
:param out: The output stream
:param obj: The input dataclass instance
:param var_name: The var name to assign the model instance
"""
types: Set[Type] = set()
tmp = StringIO()
for chunk in self.write_object(obj, 0, types):
tmp.write(chunk)
imports = self.build_imports(types)
out.write(imports)
out.write("\n\n")
out.write(f"{var_name} = ")
out.write(tmp.getvalue())
out.write("\n")
@classmethod
def build_imports(cls, types: Set[Type]) -> str:
imports = set()
for tp in types:
module = tp.__module__
name = tp.__qualname__
if module != "builtins":
if "." in name:
name = name.split(".")[0]
imports.add(f"from {module} import {name}\n")
return "".join(sorted(set(imports)))
def write_object(self, obj: Any, level: int, types: Set[Type]):
types.add(type(obj))
if collections.is_array(obj):
yield from self.write_array(obj, level, types)
elif isinstance(obj, dict):
yield from self.write_mapping(obj, level, types)
elif self.context.class_type.is_model(obj):
yield from self.write_class(obj, level, types)
elif isinstance(obj, Enum):
yield str(obj)
else:
yield literal_value(obj)
def write_array(self, obj: List, level: int, types: Set[Type]):
if not obj:
yield str(obj)
return
next_level = level + 1
yield "[\n"
for val in obj:
yield spaces * next_level
yield from self.write_object(val, next_level, types)
yield ",\n"
yield f"{spaces * level}]"
def write_mapping(self, obj: Mapping, level: int, types: Set[Type]):
if not obj:
yield str(obj)
return
next_level = level + 1
yield "{\n"
for key, value in obj.items():
yield spaces * next_level
yield from self.write_object(key, next_level, types)
yield ": "
yield from self.write_object(value, next_level, types)
yield ",\n"
yield f"{spaces * level}}}"
def write_class(self, obj: Any, level: int, types: Set[Type]):
yield f"{obj.__class__.__qualname__}(\n"
next_level = level + 1
index = 0
for f in self.context.class_type.get_fields(obj):
if not f.init:
continue
value = getattr(obj, f.name, types)
default = self.context.class_type.default_value(f, default=unset)
if default is not unset and (
(callable(default) and default() == value) or default == value
):
continue
if index:
yield f",\n{spaces * next_level}{f.name}="
else:
yield f"{spaces * next_level}{f.name}="
yield from self.write_object(value, next_level, types)
index += 1
yield f"\n{spaces * level})"
|