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 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
|
from collections.abc import Iterator, Mapping
from dataclasses import dataclass, field
from enum import Enum
from io import StringIO
from typing import Any, TextIO
from xsdata.formats.dataclass.context import XmlContext
from xsdata.utils import collections
from xsdata.utils.objects import literal_value
spaces = " "
unset = object()
@dataclass
class PycodeSerializer:
"""Pycode serializer for data class instances.
Generate python pretty representation code from a model instance.
Args:
context: The models context instance
"""
context: XmlContext = field(default_factory=XmlContext)
def render(self, obj: object, var_name: str = "obj") -> str:
"""Serialize the input model instance to python representation string.
Args:
obj: The input model instance to serialize
var_name: The var name to assign the model instance
Returns:
The serialized representation string.
"""
output = StringIO()
self.write(output, obj, var_name)
return output.getvalue()
def write(self, out: TextIO, obj: Any, var_name: str) -> None:
"""Write the given object to the output text stream.
Args:
out: The output text stream
obj: The input model instance to serialize
var_name: The var name to assign the model instance
"""
types: set[type] = set()
tmp = StringIO()
for chunk in self.repr_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:
"""Build a list of imports from the given types.
Args:
types: A set of types
Returns:
The `from x import y` statements as string.
"""
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(imports))
def repr_object(self, obj: Any, level: int, types: set[type]) -> Iterator[str]:
"""Write the given object as repr code.
Args:
obj: The input object to serialize
level: The current object level
types: The parent object types
Yields:
An iterator of the representation strings.
"""
types.add(type(obj))
if collections.is_array(obj):
yield from self.repr_array(obj, level, types)
elif isinstance(obj, dict):
yield from self.repr_mapping(obj, level, types)
elif self.context.class_type.is_model(obj):
yield from self.repr_model(obj, level, types)
elif isinstance(obj, Enum):
yield str(obj)
else:
yield literal_value(obj)
def repr_array(
self,
obj: list | set | tuple,
level: int,
types: set[type],
) -> Iterator[str]:
"""Convert an iterable object to repr code.
Args:
obj: A list, set, tuple instance
level: The current object level
types: The parent object types
Yields:
An iterator of the representation strings.
"""
if not obj:
yield str(obj)
return
next_level = level + 1
yield "[\n"
for val in obj:
yield spaces * next_level
yield from self.repr_object(val, next_level, types)
yield ",\n"
yield f"{spaces * level}]"
def repr_mapping(self, obj: Mapping, level: int, types: set[type]) -> Iterator[str]:
"""Convert a map object to repr code.
Args:
obj: A map instance
level: The current object level
types: The parent object types
Yields:
An iterator of the representation strings.
"""
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.repr_object(key, next_level, types)
yield ": "
yield from self.repr_object(value, next_level, types)
yield ",\n"
yield f"{spaces * level}}}"
def repr_model(self, obj: Any, level: int, types: set[type]) -> Iterator[str]:
"""Convert a data model instance to repr code.
Args:
obj: A map instance
level: The current object level
types: The parent object types
Yields:
An iterator of the representation strings.
"""
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.repr_object(value, next_level, types)
index += 1
yield f"\n{spaces * level})"
|