File: code.py

package info (click to toggle)
python-xsdata 24.1-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 2,936 kB
  • sloc: python: 29,257; xml: 404; makefile: 27; sh: 6
file content (146 lines) | stat: -rw-r--r-- 4,511 bytes parent folder | download
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})"