protolizer 1.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- protolizer/__init__.py +13 -0
- protolizer/exceptions.py +25 -0
- protolizer/fields.py +663 -0
- protolizer/helpers.py +158 -0
- protolizer/meta.py +42 -0
- protolizer/py.typed +0 -0
- protolizer/serializer.py +412 -0
- protolizer-1.4.0.dist-info/METADATA +148 -0
- protolizer-1.4.0.dist-info/RECORD +12 -0
- protolizer-1.4.0.dist-info/WHEEL +5 -0
- protolizer-1.4.0.dist-info/licenses/LICENSE +21 -0
- protolizer-1.4.0.dist-info/top_level.txt +1 -0
protolizer/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from . import fields
|
|
2
|
+
from .exceptions import InvalidDataError, ValidationError
|
|
3
|
+
from .serializer import ListSerializer, Serializer, proto_to_dict, to_protobuf
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"Serializer",
|
|
7
|
+
"ListSerializer",
|
|
8
|
+
"to_protobuf",
|
|
9
|
+
"proto_to_dict",
|
|
10
|
+
"ValidationError",
|
|
11
|
+
"InvalidDataError",
|
|
12
|
+
"fields",
|
|
13
|
+
] + fields.__all__
|
protolizer/exceptions.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class ValidationError(Exception):
|
|
2
|
+
"""
|
|
3
|
+
Raised when a validation error occurs.
|
|
4
|
+
Note that this exception is only raised when the serializer is in `is_valid()` mode.
|
|
5
|
+
and raise_exception is passed as `True` on the `is_valid()` call.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
def __init__(self, detail=None):
|
|
9
|
+
self.detail = detail
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class InvalidDataError(Exception):
|
|
13
|
+
def __init__(self, field, data=None, expected_type=None, extra=None):
|
|
14
|
+
self.field = field
|
|
15
|
+
self.data = data
|
|
16
|
+
self.expected_type = expected_type
|
|
17
|
+
self.extra = extra
|
|
18
|
+
|
|
19
|
+
def __str__(self):
|
|
20
|
+
text = f"Field {self.field} has invalid data: [{self.data}] => [{type(self.data)}]"
|
|
21
|
+
if self.expected_type:
|
|
22
|
+
text += f", expected type: {self.expected_type}"
|
|
23
|
+
if self.extra:
|
|
24
|
+
text += f", extra: {self.extra}"
|
|
25
|
+
return text
|
protolizer/fields.py
ADDED
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import binascii
|
|
3
|
+
import copy
|
|
4
|
+
import functools
|
|
5
|
+
import inspect
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
9
|
+
|
|
10
|
+
from protolizer.exceptions import InvalidDataError, ValidationError
|
|
11
|
+
from protolizer.helpers import DictMapper
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"Empty",
|
|
15
|
+
"BaseField",
|
|
16
|
+
"BooleanField",
|
|
17
|
+
"CharField",
|
|
18
|
+
"BytesField",
|
|
19
|
+
"IntField",
|
|
20
|
+
"CustomField",
|
|
21
|
+
"DateTimeField",
|
|
22
|
+
"TimestampField",
|
|
23
|
+
"FloatField",
|
|
24
|
+
"EnumField",
|
|
25
|
+
"DictField",
|
|
26
|
+
"ListField",
|
|
27
|
+
"set_value",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Empty:
|
|
32
|
+
"""
|
|
33
|
+
Empty class to be used as a placeholder for None
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def is_simple_callable(obj):
|
|
40
|
+
"""
|
|
41
|
+
True if the object is a callable that takes no arguments.
|
|
42
|
+
"""
|
|
43
|
+
# Bail early since we cannot inspect built-in function signatures.
|
|
44
|
+
if inspect.isbuiltin(obj):
|
|
45
|
+
raise ValueError(
|
|
46
|
+
"Built-in function signatures are not injectable. Wrap the function call in a simple, pure Python function."
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if not (inspect.isfunction(obj) or inspect.ismethod(obj) or isinstance(obj, functools.partial)):
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
sig = inspect.signature(obj)
|
|
53
|
+
params = sig.parameters.values()
|
|
54
|
+
return all(
|
|
55
|
+
param.kind == param.VAR_POSITIONAL or param.kind == param.VAR_KEYWORD or param.default != param.empty
|
|
56
|
+
for param in params
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_attribute(instance: Any, attrs: List[str]) -> Any:
|
|
61
|
+
"""
|
|
62
|
+
Similar to Python's built in `getattr(instance, attr)`,
|
|
63
|
+
but takes a list of nested attributes, instead of a single attribute.
|
|
64
|
+
|
|
65
|
+
Also accepts either attribute lookup on objects or dictionary lookups.
|
|
66
|
+
"""
|
|
67
|
+
for attr in attrs:
|
|
68
|
+
try:
|
|
69
|
+
if isinstance(instance, Mapping):
|
|
70
|
+
instance = instance[attr]
|
|
71
|
+
else:
|
|
72
|
+
instance = getattr(instance, attr)
|
|
73
|
+
except (IndexError, KeyError, AttributeError):
|
|
74
|
+
return None
|
|
75
|
+
if is_simple_callable(instance):
|
|
76
|
+
try:
|
|
77
|
+
instance = instance()
|
|
78
|
+
except (AttributeError, KeyError) as exc:
|
|
79
|
+
# If we raised an Attribute or KeyError here it'd get treated
|
|
80
|
+
# as an omitted field in `Field.get_attribute()`. Instead, we
|
|
81
|
+
# raise a ValueError to ensure the exception is not masked.
|
|
82
|
+
raise ValueError(
|
|
83
|
+
'Exception raised in callable attribute "{}"; original exception was: {}'.format(attr, exc)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
return instance
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def set_value(dictionary: Dict[str, Any], keys: List[str], value: Any) -> None:
|
|
90
|
+
"""
|
|
91
|
+
Similar to Python's built in `dictionary[key] = value`,
|
|
92
|
+
but takes a list of nested keys instead of a single key.
|
|
93
|
+
|
|
94
|
+
set_value({'a': 1}, [], {'b': 2}) -> {'a': 1, 'b': 2}
|
|
95
|
+
set_value({'a': 1}, ['x'], 2) -> {'a': 1, 'x': 2}
|
|
96
|
+
set_value({'a': 1}, ['x', 'y'], 2) -> {'a': 1, 'x': {'y': 2}}
|
|
97
|
+
"""
|
|
98
|
+
if not keys:
|
|
99
|
+
dictionary.update(value)
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
for key in keys[:-1]:
|
|
103
|
+
if key not in dictionary:
|
|
104
|
+
dictionary[key] = {}
|
|
105
|
+
dictionary = dictionary[key]
|
|
106
|
+
|
|
107
|
+
dictionary[keys[-1]] = value
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class BaseField(object):
|
|
111
|
+
_auto_creation_counter = 0
|
|
112
|
+
ALLOWED_TYPES = None
|
|
113
|
+
initial = None
|
|
114
|
+
|
|
115
|
+
def __init__(
|
|
116
|
+
self,
|
|
117
|
+
initial: Any = Empty,
|
|
118
|
+
proto_field: Optional[str] = None,
|
|
119
|
+
default: Any = None,
|
|
120
|
+
custom: bool = False,
|
|
121
|
+
context: Any = None,
|
|
122
|
+
read_only: bool = False,
|
|
123
|
+
write_only: bool = False,
|
|
124
|
+
required: bool = False,
|
|
125
|
+
allow_null: bool = True,
|
|
126
|
+
) -> None:
|
|
127
|
+
"""
|
|
128
|
+
Initializes the field.
|
|
129
|
+
:param initial: The initial value.
|
|
130
|
+
:param proto_field: proto field is protobuf message field
|
|
131
|
+
it's used when the input data key is different from the output data key.
|
|
132
|
+
:param default: default value for the field.
|
|
133
|
+
:param custom: whether the field is custom. If True, the field will be filled by the custom method.
|
|
134
|
+
custom method name is get_custom_{field_name}.
|
|
135
|
+
note that if the method is not defined, the field will be filled with None.
|
|
136
|
+
:param context: extra context for the field.
|
|
137
|
+
:param read_only: if True, the field is serialized but not deserialized.
|
|
138
|
+
:param write_only: if True, the field is deserialized but not serialized.
|
|
139
|
+
:param required: if True, the field must be present in input data.
|
|
140
|
+
:param allow_null: if False, None values raise a validation error.
|
|
141
|
+
"""
|
|
142
|
+
# Increase the auto creation counter
|
|
143
|
+
self._auto_creation_counter = BaseField._auto_creation_counter
|
|
144
|
+
BaseField._auto_creation_counter += 1
|
|
145
|
+
|
|
146
|
+
self.initial = self.initial if (initial is Empty) else initial
|
|
147
|
+
self.proto_field = proto_field
|
|
148
|
+
self.default = default
|
|
149
|
+
self.custom = custom
|
|
150
|
+
self.context = {} if context is None else context
|
|
151
|
+
self.read_only = read_only
|
|
152
|
+
self.write_only = write_only
|
|
153
|
+
self.required = required
|
|
154
|
+
self.allow_null = allow_null
|
|
155
|
+
|
|
156
|
+
# These are set up by `.bind()` when the field is added to a serializer.
|
|
157
|
+
self.field_name = None
|
|
158
|
+
self.parent = None
|
|
159
|
+
self.source = None
|
|
160
|
+
self.attributes = None
|
|
161
|
+
|
|
162
|
+
self.meta = getattr(self, "Meta", None)
|
|
163
|
+
self.pb = getattr(self.meta, "schema", None) if self.meta else None
|
|
164
|
+
|
|
165
|
+
def bind(self, field_name: str, parent: Any) -> None:
|
|
166
|
+
"""
|
|
167
|
+
Initializes the field name and parent for the field instance.
|
|
168
|
+
|
|
169
|
+
:param field_name: The field name.
|
|
170
|
+
:param parent: The parent field.
|
|
171
|
+
:return: None
|
|
172
|
+
"""
|
|
173
|
+
self.field_name = field_name
|
|
174
|
+
self.parent = parent
|
|
175
|
+
|
|
176
|
+
# self.source should default to being the same as the field name.
|
|
177
|
+
if self.source is None:
|
|
178
|
+
self.source = field_name
|
|
179
|
+
|
|
180
|
+
if self.source == "*":
|
|
181
|
+
self.attributes = []
|
|
182
|
+
else:
|
|
183
|
+
self.attributes = self.source.split(".")
|
|
184
|
+
|
|
185
|
+
def get_initial(self) -> Any:
|
|
186
|
+
return self.initial() if callable(self.initial) else self.initial
|
|
187
|
+
|
|
188
|
+
def get_value(self, dictionary: Any, instance: Any) -> Any:
|
|
189
|
+
"""
|
|
190
|
+
Given the *incoming* dictionary, return the value for this field
|
|
191
|
+
that should be validated and transformed to a native value.
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
# convert the dictionary to mapping to make it easier to work with
|
|
195
|
+
dictionary = DictMapper(dictionary) if isinstance(dictionary, dict) else dictionary
|
|
196
|
+
|
|
197
|
+
if self.custom:
|
|
198
|
+
fill_method = getattr(instance, f"get_custom_{self.field_name}", None)
|
|
199
|
+
return fill_method(dictionary) if fill_method else None
|
|
200
|
+
|
|
201
|
+
return dictionary[self.field_name] if self.field_name in dictionary and not self.custom else Empty
|
|
202
|
+
|
|
203
|
+
def get_attribute(self, instance: Any) -> Any:
|
|
204
|
+
"""
|
|
205
|
+
Given the *outgoing* instance, return the value for this field
|
|
206
|
+
that should be serialized.
|
|
207
|
+
"""
|
|
208
|
+
if self.custom and self.parent is not None:
|
|
209
|
+
fill_method = getattr(self.parent, f"get_custom_{self.field_name}", None)
|
|
210
|
+
if fill_method:
|
|
211
|
+
dictionary = DictMapper(instance) if isinstance(instance, dict) else instance
|
|
212
|
+
return fill_method(dictionary)
|
|
213
|
+
return None
|
|
214
|
+
return get_attribute(instance, self.attributes)
|
|
215
|
+
|
|
216
|
+
def get_default(self) -> Any:
|
|
217
|
+
"""
|
|
218
|
+
Return the default value for this field.
|
|
219
|
+
"""
|
|
220
|
+
return self.default
|
|
221
|
+
|
|
222
|
+
def validate_empty_values(self, data: Any) -> Tuple[bool, Any]:
|
|
223
|
+
"""
|
|
224
|
+
Validate empty values, and either:
|
|
225
|
+
"""
|
|
226
|
+
if data is Empty:
|
|
227
|
+
if self.required:
|
|
228
|
+
raise ValidationError("This field is required.")
|
|
229
|
+
return True, self.get_default()
|
|
230
|
+
|
|
231
|
+
if data is None:
|
|
232
|
+
if not self.allow_null:
|
|
233
|
+
raise ValidationError("This field may not be null.")
|
|
234
|
+
if self.source == "*":
|
|
235
|
+
return False, None
|
|
236
|
+
return True, None
|
|
237
|
+
|
|
238
|
+
return False, data
|
|
239
|
+
|
|
240
|
+
def run_validation(self, data: Any = Empty) -> Any:
|
|
241
|
+
"""
|
|
242
|
+
Run default validation on fields.
|
|
243
|
+
"""
|
|
244
|
+
(is_empty_value, data) = self.validate_empty_values(data)
|
|
245
|
+
if is_empty_value:
|
|
246
|
+
return data
|
|
247
|
+
|
|
248
|
+
return self.to_internal_value(data)
|
|
249
|
+
|
|
250
|
+
def to_internal_value(self, data: Any) -> Any:
|
|
251
|
+
"""
|
|
252
|
+
Given the *incoming* primitive data, return the native value.
|
|
253
|
+
"""
|
|
254
|
+
raise NotImplementedError("`to_internal_value()` must be implemented.")
|
|
255
|
+
|
|
256
|
+
def to_representation(self, value: Any) -> Any:
|
|
257
|
+
"""
|
|
258
|
+
Given the native value, return the representation of this field
|
|
259
|
+
that should be returned to the user.
|
|
260
|
+
"""
|
|
261
|
+
raise NotImplementedError("`to_representation()` must be implemented.")
|
|
262
|
+
|
|
263
|
+
def to_protobuf(self, data: Any) -> Any:
|
|
264
|
+
"""
|
|
265
|
+
Given the native value, return the protobuf value.
|
|
266
|
+
"""
|
|
267
|
+
raise NotImplementedError("`to_protobuf()` must be implemented.")
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
def root(self):
|
|
271
|
+
"""
|
|
272
|
+
Returns the top-level serializer for this field.
|
|
273
|
+
"""
|
|
274
|
+
root = self
|
|
275
|
+
while root.parent is not None:
|
|
276
|
+
root = root.parent
|
|
277
|
+
return root
|
|
278
|
+
|
|
279
|
+
def __new__(cls, *args, **kwargs):
|
|
280
|
+
instance = super().__new__(cls)
|
|
281
|
+
instance._args = args
|
|
282
|
+
instance._kwargs = kwargs
|
|
283
|
+
return instance
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class BooleanField(BaseField):
|
|
287
|
+
TRUE_VALUES = ["t", "T", "true", "True", "TRUE", "1", 1, True]
|
|
288
|
+
FALSE_VALUES = ["f", "F", "false", "False", "FALSE", "0", 0, False]
|
|
289
|
+
NULL_VALUES = ["n", "N", "null", "Null", "NULL", "", None]
|
|
290
|
+
|
|
291
|
+
def to_internal_value(self, data):
|
|
292
|
+
if data is not Empty:
|
|
293
|
+
if data in self.TRUE_VALUES:
|
|
294
|
+
return True
|
|
295
|
+
elif data in self.FALSE_VALUES:
|
|
296
|
+
return False
|
|
297
|
+
elif data in self.NULL_VALUES:
|
|
298
|
+
return None
|
|
299
|
+
raise InvalidDataError(field=self.field_name, data=data, expected_type="boolean/None")
|
|
300
|
+
return data
|
|
301
|
+
|
|
302
|
+
def to_representation(self, value):
|
|
303
|
+
if value in self.TRUE_VALUES:
|
|
304
|
+
return True
|
|
305
|
+
elif value in self.FALSE_VALUES:
|
|
306
|
+
return False
|
|
307
|
+
elif value in self.NULL_VALUES:
|
|
308
|
+
return None
|
|
309
|
+
return bool(value)
|
|
310
|
+
|
|
311
|
+
def to_protobuf(self, value):
|
|
312
|
+
_repr = self.to_representation(value)
|
|
313
|
+
if _repr is None:
|
|
314
|
+
return None
|
|
315
|
+
return _repr
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
class CharField(BaseField):
|
|
319
|
+
"""
|
|
320
|
+
A field that validates input as a string.
|
|
321
|
+
"""
|
|
322
|
+
|
|
323
|
+
def __init__(self, trim_whitespace: bool = False, **kwargs):
|
|
324
|
+
self.trim_whitespace = trim_whitespace
|
|
325
|
+
super().__init__(**kwargs)
|
|
326
|
+
|
|
327
|
+
def to_internal_value(self, data):
|
|
328
|
+
value = str(data)
|
|
329
|
+
return value.strip() if self.trim_whitespace else value
|
|
330
|
+
|
|
331
|
+
def to_representation(self, value):
|
|
332
|
+
return str(value) if value is not None else None
|
|
333
|
+
|
|
334
|
+
def to_protobuf(self, value):
|
|
335
|
+
_repr = self.to_representation(value)
|
|
336
|
+
if _repr is None:
|
|
337
|
+
return None
|
|
338
|
+
return _repr
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
class BytesField(BaseField):
|
|
342
|
+
"""
|
|
343
|
+
A field that validates input as bytes.
|
|
344
|
+
"""
|
|
345
|
+
|
|
346
|
+
def to_internal_value(self, data):
|
|
347
|
+
if isinstance(data, bytes):
|
|
348
|
+
return data
|
|
349
|
+
if isinstance(data, str):
|
|
350
|
+
try:
|
|
351
|
+
return base64.b64decode(data, validate=True)
|
|
352
|
+
except (ValueError, binascii.Error):
|
|
353
|
+
raise InvalidDataError(field=self.field_name, data=data, expected_type="bytes")
|
|
354
|
+
raise InvalidDataError(field=self.field_name, data=data, expected_type="bytes")
|
|
355
|
+
|
|
356
|
+
def to_representation(self, value):
|
|
357
|
+
if value is None:
|
|
358
|
+
return None
|
|
359
|
+
if isinstance(value, bytes):
|
|
360
|
+
return base64.b64encode(value).decode("ascii")
|
|
361
|
+
return value
|
|
362
|
+
|
|
363
|
+
def to_protobuf(self, value):
|
|
364
|
+
_repr = self.to_representation(value)
|
|
365
|
+
if _repr is None:
|
|
366
|
+
return None
|
|
367
|
+
return _repr
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
class IntField(BaseField):
|
|
371
|
+
"""
|
|
372
|
+
A field that validates input as an integer.
|
|
373
|
+
"""
|
|
374
|
+
|
|
375
|
+
def to_internal_value(self, data):
|
|
376
|
+
try:
|
|
377
|
+
return int(data)
|
|
378
|
+
except (TypeError, ValueError):
|
|
379
|
+
raise InvalidDataError(field=self.field_name, data=data, expected_type="integer")
|
|
380
|
+
|
|
381
|
+
def to_representation(self, value):
|
|
382
|
+
return int(value) if value is not None else None
|
|
383
|
+
|
|
384
|
+
def to_protobuf(self, value):
|
|
385
|
+
_repr = self.to_representation(value)
|
|
386
|
+
if _repr is None:
|
|
387
|
+
return None
|
|
388
|
+
return _repr
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
class FloatField(BaseField):
|
|
392
|
+
"""
|
|
393
|
+
A field that validates input as a float.
|
|
394
|
+
"""
|
|
395
|
+
|
|
396
|
+
def to_internal_value(self, data):
|
|
397
|
+
try:
|
|
398
|
+
return float(data)
|
|
399
|
+
except (TypeError, ValueError):
|
|
400
|
+
raise InvalidDataError(field=self.field_name, data=data, expected_type="float")
|
|
401
|
+
|
|
402
|
+
def to_representation(self, value):
|
|
403
|
+
return float(value) if value is not None else None
|
|
404
|
+
|
|
405
|
+
def to_protobuf(self, value):
|
|
406
|
+
_repr = self.to_representation(value)
|
|
407
|
+
if _repr is None:
|
|
408
|
+
return None
|
|
409
|
+
return _repr
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
class EnumField(BaseField):
|
|
413
|
+
"""
|
|
414
|
+
A field that validates input as a protobuf enum value.
|
|
415
|
+
"""
|
|
416
|
+
|
|
417
|
+
def __init__(self, enum_class, by_name: bool = False, **kwargs):
|
|
418
|
+
self.enum_class = enum_class
|
|
419
|
+
self.by_name = by_name
|
|
420
|
+
super().__init__(**kwargs)
|
|
421
|
+
|
|
422
|
+
def _coerce_enum_value(self, value):
|
|
423
|
+
if isinstance(value, int):
|
|
424
|
+
return value
|
|
425
|
+
if isinstance(value, str):
|
|
426
|
+
return self.enum_class.Value(value)
|
|
427
|
+
return int(value)
|
|
428
|
+
|
|
429
|
+
def to_internal_value(self, data):
|
|
430
|
+
if isinstance(data, int):
|
|
431
|
+
if data in self.enum_class.values():
|
|
432
|
+
return data
|
|
433
|
+
raise InvalidDataError(field=self.field_name, data=data, expected_type="enum")
|
|
434
|
+
if isinstance(data, str):
|
|
435
|
+
try:
|
|
436
|
+
return self.enum_class.Value(data)
|
|
437
|
+
except ValueError:
|
|
438
|
+
raise InvalidDataError(field=self.field_name, data=data, expected_type="enum")
|
|
439
|
+
raise InvalidDataError(field=self.field_name, data=data, expected_type="enum")
|
|
440
|
+
|
|
441
|
+
def to_representation(self, value):
|
|
442
|
+
if value is None:
|
|
443
|
+
return None
|
|
444
|
+
enum_value = self._coerce_enum_value(value)
|
|
445
|
+
if self.by_name:
|
|
446
|
+
return self.enum_class.Name(enum_value)
|
|
447
|
+
return enum_value
|
|
448
|
+
|
|
449
|
+
def to_protobuf(self, value):
|
|
450
|
+
_repr = self.to_representation(value)
|
|
451
|
+
if _repr is None:
|
|
452
|
+
return None
|
|
453
|
+
return _repr
|
|
454
|
+
|
|
455
|
+
def __deepcopy__(self, memo):
|
|
456
|
+
cls = self.__class__
|
|
457
|
+
result = cls.__new__(cls)
|
|
458
|
+
memo[id(self)] = result
|
|
459
|
+
for key, value in self.__dict__.items():
|
|
460
|
+
if key in ("enum_class", "_args"):
|
|
461
|
+
setattr(result, key, value)
|
|
462
|
+
else:
|
|
463
|
+
setattr(result, key, copy.deepcopy(value, memo))
|
|
464
|
+
return result
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
class CustomField(BaseField):
|
|
468
|
+
"""
|
|
469
|
+
A field that validates input as a custom field.
|
|
470
|
+
custom fields are defined in the model class
|
|
471
|
+
e.g:
|
|
472
|
+
class MyModel(Serializer):
|
|
473
|
+
username = CustomField()
|
|
474
|
+
|
|
475
|
+
def get_custom_username(obj):
|
|
476
|
+
return "John Doe"
|
|
477
|
+
"""
|
|
478
|
+
|
|
479
|
+
def __init__(self, child=None):
|
|
480
|
+
self.child = child
|
|
481
|
+
super().__init__(custom=True)
|
|
482
|
+
|
|
483
|
+
def to_internal_value(self, data):
|
|
484
|
+
if data is not Empty:
|
|
485
|
+
if not self.child:
|
|
486
|
+
return data
|
|
487
|
+
|
|
488
|
+
if isinstance(data, list):
|
|
489
|
+
return [self.child.to_internal_value(item) for item in data]
|
|
490
|
+
return self.child.to_internal_value(data)
|
|
491
|
+
return data
|
|
492
|
+
|
|
493
|
+
def to_representation(self, value):
|
|
494
|
+
if not self.child:
|
|
495
|
+
return value
|
|
496
|
+
if isinstance(value, list):
|
|
497
|
+
return [self.child.to_representation(item) for item in value]
|
|
498
|
+
return self.child.to_representation(value)
|
|
499
|
+
|
|
500
|
+
def to_protobuf(self, value):
|
|
501
|
+
_repr = self.to_representation(value)
|
|
502
|
+
if _repr is None:
|
|
503
|
+
return None
|
|
504
|
+
return _repr
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
class ListField(BaseField):
|
|
508
|
+
"""
|
|
509
|
+
A field that validates input as a list.
|
|
510
|
+
"""
|
|
511
|
+
|
|
512
|
+
ALLOWED_TYPES = [
|
|
513
|
+
"CharField",
|
|
514
|
+
"BytesField",
|
|
515
|
+
"DateTimeField",
|
|
516
|
+
"IntField",
|
|
517
|
+
"CustomField",
|
|
518
|
+
"BooleanField",
|
|
519
|
+
"FloatField",
|
|
520
|
+
"EnumField",
|
|
521
|
+
]
|
|
522
|
+
|
|
523
|
+
def __init__(self, child=None, **kwargs):
|
|
524
|
+
self.type = child
|
|
525
|
+
if child and child.__class__.__name__ not in self.ALLOWED_TYPES:
|
|
526
|
+
raise ValueError(
|
|
527
|
+
"type {!r} is not allowed for {!r}. Allowed types are: {}".format(
|
|
528
|
+
child.__class__.__name__, self.__class__.__name__, ", ".join(self.ALLOWED_TYPES)
|
|
529
|
+
)
|
|
530
|
+
)
|
|
531
|
+
super().__init__(**kwargs)
|
|
532
|
+
|
|
533
|
+
def to_internal_value(self, data):
|
|
534
|
+
if data is not Empty:
|
|
535
|
+
if not self.type:
|
|
536
|
+
return data
|
|
537
|
+
return [self.type.to_internal_value(item) for item in data]
|
|
538
|
+
return data
|
|
539
|
+
|
|
540
|
+
def to_representation(self, value):
|
|
541
|
+
if not self.type:
|
|
542
|
+
return value
|
|
543
|
+
return [self.type.to_representation(item) for item in value]
|
|
544
|
+
|
|
545
|
+
def to_protobuf(self, value):
|
|
546
|
+
_repr = self.to_representation(value)
|
|
547
|
+
if _repr is None:
|
|
548
|
+
return None
|
|
549
|
+
return _repr
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
class DictField(BaseField):
|
|
553
|
+
"""
|
|
554
|
+
A field that validates input as a dict.
|
|
555
|
+
"""
|
|
556
|
+
|
|
557
|
+
ALLOWED_TYPES = ["ListField"]
|
|
558
|
+
|
|
559
|
+
def __init__(self, child=None, **kwargs):
|
|
560
|
+
self.type = child
|
|
561
|
+
if child and child.__class__.__name__ not in self.ALLOWED_TYPES:
|
|
562
|
+
raise ValueError(
|
|
563
|
+
"type {!r} is not allowed for {!r}. Allowed types are: {}".format(
|
|
564
|
+
child.__class__.__name__, self.__class__.__name__, ", ".join(self.ALLOWED_TYPES)
|
|
565
|
+
)
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
super().__init__(**kwargs)
|
|
569
|
+
|
|
570
|
+
def to_internal_value(self, data):
|
|
571
|
+
if data is not Empty:
|
|
572
|
+
if not self.type:
|
|
573
|
+
return data
|
|
574
|
+
return {key: self.type.to_internal_value(value) for key, value in data.items()}
|
|
575
|
+
return data
|
|
576
|
+
|
|
577
|
+
def to_representation(self, value):
|
|
578
|
+
if not self.type:
|
|
579
|
+
return value
|
|
580
|
+
return {key: self.type.to_representation(item) for key, item in value.items()}
|
|
581
|
+
|
|
582
|
+
def to_protobuf(self, value):
|
|
583
|
+
_repr = self.to_representation(value)
|
|
584
|
+
if _repr is None:
|
|
585
|
+
return None
|
|
586
|
+
return _repr
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
class DateTimeField(BaseField):
|
|
590
|
+
"""
|
|
591
|
+
A field that validates input as a datetime.
|
|
592
|
+
Internal value is always datetime (or None). Representation is string (if format set) or timestamp float.
|
|
593
|
+
"""
|
|
594
|
+
|
|
595
|
+
def __init__(self, fmt: Optional[str] = "%Y-%m-%dT%H:%M:%S", **kwargs: Any) -> None:
|
|
596
|
+
self.format = fmt
|
|
597
|
+
super().__init__(**kwargs)
|
|
598
|
+
|
|
599
|
+
def to_internal_value(self, data: Any) -> Optional[datetime]:
|
|
600
|
+
if data is Empty:
|
|
601
|
+
return data
|
|
602
|
+
if isinstance(data, datetime):
|
|
603
|
+
return data
|
|
604
|
+
if self.format:
|
|
605
|
+
try:
|
|
606
|
+
return datetime.strptime(str(data), self.format)
|
|
607
|
+
except ValueError:
|
|
608
|
+
raise InvalidDataError(field=self.field_name, data=data, expected_type="datetime")
|
|
609
|
+
return datetime.fromtimestamp(float(data))
|
|
610
|
+
|
|
611
|
+
def to_representation(self, value: Any) -> Optional[Union[str, float]]:
|
|
612
|
+
if value is None:
|
|
613
|
+
return None
|
|
614
|
+
if isinstance(value, datetime):
|
|
615
|
+
if self.format:
|
|
616
|
+
return value.strftime(self.format)
|
|
617
|
+
return value.timestamp()
|
|
618
|
+
# Legacy: already a string (e.g. from older serialized data)
|
|
619
|
+
return value
|
|
620
|
+
|
|
621
|
+
def to_protobuf(self, value):
|
|
622
|
+
_repr = self.to_representation(value)
|
|
623
|
+
if _repr is None:
|
|
624
|
+
return None
|
|
625
|
+
return _repr
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
class TimestampField(BaseField):
|
|
629
|
+
"""
|
|
630
|
+
A field that validates input as a timestamp.
|
|
631
|
+
"""
|
|
632
|
+
|
|
633
|
+
ALLOWED_TYPES = ["IntField", "CharField", "FloatField"]
|
|
634
|
+
|
|
635
|
+
def __init__(self, child=None, **kwargs):
|
|
636
|
+
self.type = IntField() if child is None else child
|
|
637
|
+
if child and child.__class__.__name__ not in self.ALLOWED_TYPES:
|
|
638
|
+
raise ValueError(
|
|
639
|
+
"type {!r} is not allowed for {!r}. Allowed types are: {}".format(
|
|
640
|
+
child.__class__.__name__, self.__class__.__name__, ", ".join(self.ALLOWED_TYPES)
|
|
641
|
+
)
|
|
642
|
+
)
|
|
643
|
+
super().__init__(**kwargs)
|
|
644
|
+
|
|
645
|
+
def to_internal_value(self, data):
|
|
646
|
+
if data is not Empty:
|
|
647
|
+
if isinstance(data, datetime):
|
|
648
|
+
return data.timestamp()
|
|
649
|
+
return self.type.to_internal_value(data)
|
|
650
|
+
return data
|
|
651
|
+
|
|
652
|
+
def to_representation(self, value):
|
|
653
|
+
if value is not None:
|
|
654
|
+
if isinstance(value, datetime):
|
|
655
|
+
return value.timestamp()
|
|
656
|
+
return self.type.to_representation(value)
|
|
657
|
+
return value
|
|
658
|
+
|
|
659
|
+
def to_protobuf(self, value):
|
|
660
|
+
_repr = self.to_representation(value)
|
|
661
|
+
if _repr is None:
|
|
662
|
+
return None
|
|
663
|
+
return _repr
|