File: python.py

package info (click to toggle)
python-django 3%3A5.2.5-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 61,236 kB
  • sloc: python: 361,585; javascript: 19,250; xml: 211; makefile: 182; sh: 28
file content (222 lines) | stat: -rw-r--r-- 8,094 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
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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
"""
A Python "serializer". Doesn't do much serializing per se -- just converts to
and from basic Python data types (lists, dicts, strings, etc.). Useful as a basis for
other serializers.
"""

from django.apps import apps
from django.core.serializers import base
from django.db import DEFAULT_DB_ALIAS, models
from django.db.models import CompositePrimaryKey
from django.utils.encoding import is_protected_type


class Serializer(base.Serializer):
    """
    Serialize a QuerySet to basic Python objects.
    """

    internal_use_only = True

    def start_serialization(self):
        self._current = None
        self.objects = []

    def end_serialization(self):
        pass

    def start_object(self, obj):
        self._current = {}

    def end_object(self, obj):
        self.objects.append(self.get_dump_object(obj))
        self._current = None

    def get_dump_object(self, obj):
        data = {"model": str(obj._meta)}
        if not self.use_natural_primary_keys or not hasattr(obj, "natural_key"):
            data["pk"] = self._value_from_field(obj, obj._meta.pk)
        data["fields"] = self._current
        return data

    def _value_from_field(self, obj, field):
        if isinstance(field, CompositePrimaryKey):
            return [self._value_from_field(obj, f) for f in field]
        value = field.value_from_object(obj)
        # Protected types (i.e., primitives like None, numbers, dates,
        # and Decimals) are passed through as is. All other values are
        # converted to string first.
        return value if is_protected_type(value) else field.value_to_string(obj)

    def handle_field(self, obj, field):
        self._current[field.name] = self._value_from_field(obj, field)

    def handle_fk_field(self, obj, field):
        if self.use_natural_foreign_keys and hasattr(
            field.remote_field.model, "natural_key"
        ):
            related = getattr(obj, field.name)
            if related:
                value = related.natural_key()
            else:
                value = None
        else:
            value = self._value_from_field(obj, field)
        self._current[field.name] = value

    def handle_m2m_field(self, obj, field):
        if field.remote_field.through._meta.auto_created:
            if self.use_natural_foreign_keys and hasattr(
                field.remote_field.model, "natural_key"
            ):

                def m2m_value(value):
                    return value.natural_key()

                def queryset_iterator(obj, field):
                    attr = getattr(obj, field.name)
                    chunk_size = (
                        2000 if getattr(attr, "prefetch_cache_name", None) else None
                    )
                    return attr.iterator(chunk_size)

            else:

                def m2m_value(value):
                    return self._value_from_field(value, value._meta.pk)

                def queryset_iterator(obj, field):
                    query_set = getattr(obj, field.name).select_related(None).only("pk")
                    chunk_size = 2000 if query_set._prefetch_related_lookups else None
                    return query_set.iterator(chunk_size=chunk_size)

            m2m_iter = getattr(obj, "_prefetched_objects_cache", {}).get(
                field.name,
                queryset_iterator(obj, field),
            )
            self._current[field.name] = [m2m_value(related) for related in m2m_iter]

    def getvalue(self):
        return self.objects


class Deserializer(base.Deserializer):
    """
    Deserialize simple Python objects back into Django ORM instances.

    It's expected that you pass the Python objects themselves (instead of a
    stream or a string) to the constructor
    """

    def __init__(
        self, object_list, *, using=DEFAULT_DB_ALIAS, ignorenonexistent=False, **options
    ):
        super().__init__(object_list, **options)
        self.handle_forward_references = options.pop("handle_forward_references", False)
        self.using = using
        self.ignorenonexistent = ignorenonexistent
        self.field_names_cache = {}  # Model: <list of field_names>
        self._iterator = None

    def __iter__(self):
        for obj in self.stream:
            yield from self._handle_object(obj)

    def __next__(self):
        if self._iterator is None:
            self._iterator = iter(self)
        return next(self._iterator)

    def _handle_object(self, obj):
        data = {}
        m2m_data = {}
        deferred_fields = {}

        # Look up the model and starting build a dict of data for it.
        try:
            Model = self._get_model_from_node(obj["model"])
        except base.DeserializationError:
            if self.ignorenonexistent:
                return
            raise
        if "pk" in obj:
            try:
                data[Model._meta.pk.attname] = Model._meta.pk.to_python(obj.get("pk"))
            except Exception as e:
                raise base.DeserializationError.WithData(
                    e, obj["model"], obj.get("pk"), None
                )

        if Model not in self.field_names_cache:
            self.field_names_cache[Model] = {f.name for f in Model._meta.get_fields()}
        field_names = self.field_names_cache[Model]

        # Handle each field
        for field_name, field_value in obj["fields"].items():
            if self.ignorenonexistent and field_name not in field_names:
                # skip fields no longer on model
                continue

            field = Model._meta.get_field(field_name)

            # Handle M2M relations
            if field.remote_field and isinstance(
                field.remote_field, models.ManyToManyRel
            ):
                try:
                    values = self._handle_m2m_field_node(field, field_value)
                    if values == base.DEFER_FIELD:
                        deferred_fields[field] = field_value
                    else:
                        m2m_data[field.name] = values
                except base.M2MDeserializationError as e:
                    raise base.DeserializationError.WithData(
                        e.original_exc, obj["model"], obj.get("pk"), e.pk
                    )

            # Handle FK fields
            elif field.remote_field and isinstance(
                field.remote_field, models.ManyToOneRel
            ):
                try:
                    value = self._handle_fk_field_node(field, field_value)
                    if value == base.DEFER_FIELD:
                        deferred_fields[field] = field_value
                    else:
                        data[field.attname] = value
                except Exception as e:
                    raise base.DeserializationError.WithData(
                        e, obj["model"], obj.get("pk"), field_value
                    )

            # Handle all other fields
            else:
                try:
                    data[field.name] = field.to_python(field_value)
                except Exception as e:
                    raise base.DeserializationError.WithData(
                        e, obj["model"], obj.get("pk"), field_value
                    )

        model_instance = base.build_instance(Model, data, self.using)
        yield base.DeserializedObject(model_instance, m2m_data, deferred_fields)

    def _handle_m2m_field_node(self, field, field_value):
        return base.deserialize_m2m_values(
            field, field_value, self.using, self.handle_forward_references
        )

    def _handle_fk_field_node(self, field, field_value):
        return base.deserialize_fk_value(
            field, field_value, self.using, self.handle_forward_references
        )

    @staticmethod
    def _get_model_from_node(model_identifier):
        """Look up a model from an "app_label.model_name" string."""
        try:
            return apps.get_model(model_identifier)
        except (LookupError, TypeError):
            raise base.DeserializationError(
                f"Invalid model identifier: {model_identifier}"
            )