-
Notifications
You must be signed in to change notification settings - Fork 62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for deserialization #2
base: master
Are you sure you want to change the base?
Changes from 5 commits
2273b24
3df6e87
ca17891
c2bb0fe
6d7fcaf
42d82b9
c8b4753
1a892ec
f12cdc2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,14 +3,14 @@ Custom Fields | |
************* | ||
|
||
The most common way to create a custom field with **serpy** is to override | ||
:meth:`serpy.Field.to_value`. This method is called on the value | ||
:meth:`serpy.Field.to_representation`. This method is called on the value | ||
retrieved from the object being serialized. For example, to create a field that | ||
adds 5 to every value it serializes, do: | ||
|
||
.. code-block:: python | ||
|
||
class Add5Field(serpy.Field): | ||
def to_value(self, value): | ||
def to_representation(self, value): | ||
return value + 5 | ||
|
||
Then to use it: | ||
|
@@ -25,7 +25,7 @@ Then to use it: | |
|
||
f = Obj() | ||
f.foo = 9 | ||
ObjSerializer(f).data | ||
ObjSerializer(f).representation | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. change to |
||
# {'foo': 14} | ||
|
||
Another use for custom fields is data validation. For example, to validate that | ||
|
@@ -34,7 +34,7 @@ every serialized value has a ``'.'`` in it: | |
.. code-block:: python | ||
|
||
class ValidateDotField(serpy.Field): | ||
def to_value(self, value): | ||
def to_representation(self, value): | ||
if '.' not in value: | ||
raise ValidationError('no dot!') | ||
return value | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,53 +1,99 @@ | ||
import six | ||
import types | ||
import warnings | ||
|
||
|
||
class Field(object): | ||
""":class:`Field` is used to define what attributes will be serialized. | ||
|
||
A :class:`Field` maps a property or function on an object to a value in the | ||
serialized result. Subclass this to make custom fields. For most simple | ||
cases, overriding :meth:`Field.to_value` should give enough flexibility. If | ||
more control is needed, override :meth:`Field.as_getter`. | ||
cases, overriding :meth:`Field.to_representation` should give enough | ||
flexibility. If more control is needed, override :meth:`Field.as_getter`. | ||
|
||
:param str attr: The attribute to get on the object, using the same format | ||
as ``operator.attrgetter``. If this is not supplied, the name this | ||
field was assigned to on the serializer will be used. | ||
:param bool call: Whether the value should be called after it is retrieved | ||
from the object. Useful if an object has a method to be serialized. | ||
:param bool required: Whether the field is required. If set to ``False``, | ||
:meth:`Field.to_value` will not be called if the value is ``None``. | ||
:meth:`Field.to_representation` will not be called if the value is | ||
``None``. | ||
:param bool read_only: Whether the field is read-only. If set to ``False``, | ||
the field won't be deserialized. If ``call`` is True, or if ``attr`` | ||
contains a '.', then this param is set to True. | ||
""" | ||
#: Set to ``True`` if the value function returned from | ||
#: :meth:`Field.as_getter` requires the serializer to be passed in as the | ||
#: first argument. Otherwise, the object will be the only parameter. | ||
getter_takes_serializer = False | ||
|
||
def __init__(self, attr=None, call=False, required=True): | ||
#: Set to ``True`` if the value function returned from | ||
#: :meth:`Field.as_setter` requires the serializer to be passed in as the | ||
#: first argument. Otherwise, the object will be the only parameter. | ||
setter_takes_serializer = False | ||
|
||
def __init__(self, attr=None, call=False, required=True, read_only=False): | ||
self.attr = attr | ||
self.call = call | ||
self.required = required | ||
self.read_only = read_only or call or \ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i prefer to use parens instead of self.read_only = (read_only or call or
(attr is not None and '.' in attr)) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure |
||
(attr is not None and '.' in attr) | ||
|
||
def to_value(self, value): | ||
def to_representation(self, value): | ||
"""Transform the serialized value. | ||
|
||
Override this method to clean and validate values serialized by this | ||
field. For example to implement an ``int`` field: :: | ||
|
||
def to_value(self, value): | ||
def to_representation(self, value): | ||
return int(value) | ||
|
||
:param value: The value fetched from the object being serialized. | ||
""" | ||
return value | ||
to_value._serpy_base_implementation = True | ||
to_representation._serpy_base_implementation = True | ||
|
||
def _is_to_representation_overridden(self): | ||
to_representation = self.to_representation | ||
# If to_representation isn't a method, it must have been overridden. | ||
if not isinstance(to_representation, types.MethodType): | ||
return True | ||
return not getattr(to_representation, | ||
'_serpy_base_implementation', | ||
False) | ||
|
||
def to_value(self, obj): | ||
warnings.warn( | ||
"`.to_value` method is deprecated, use `.to_representation` " | ||
"instead", | ||
DeprecationWarning, | ||
stacklevel=2 | ||
) | ||
return self.to_representation(obj) | ||
|
||
def to_internal_value(self, data): | ||
"""Transform the serialized value into Python object | ||
|
||
Override this method to clean and validate values deserialized by this | ||
field. For example to implement an ``int`` field: :: | ||
|
||
def _is_to_value_overriden(self): | ||
to_value = self.to_value | ||
# If to_value isn't a method, it must have been overriden. | ||
if not isinstance(to_value, types.MethodType): | ||
def to_internal_value(self, data): | ||
return data | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't you still want to cast it back to |
||
|
||
:param data: The data fetched from the object being deserialized. | ||
""" | ||
return data | ||
to_internal_value._serpy_base_implementation = True | ||
|
||
def _is_to_internal_value_overridden(self): | ||
to_internal_value = self.to_internal_value | ||
# If to_internal_value isn't a method, it must have been overridden. | ||
if not isinstance(to_internal_value, types.MethodType): | ||
return True | ||
return not getattr(to_value, '_serpy_base_implementation', False) | ||
return not getattr(to_internal_value, | ||
'_serpy_base_implementation', | ||
False) | ||
|
||
def as_getter(self, serializer_field_name, serializer_cls): | ||
"""Returns a function that fetches an attribute from an object. | ||
|
@@ -59,7 +105,7 @@ def as_getter(self, serializer_field_name, serializer_cls): | |
converted into a getter function using this method. During | ||
serialization, each getter will be called with the object being | ||
serialized, and the return value will be passed through | ||
:meth:`Field.to_value`. | ||
:meth:`Field.to_representation`. | ||
|
||
If a :class:`Field` has ``getter_takes_serializer = True``, then the | ||
getter returned from this method will be called with the | ||
|
@@ -72,25 +118,52 @@ def as_getter(self, serializer_field_name, serializer_cls): | |
""" | ||
return None | ||
|
||
def as_setter(self, serializer_field_name, serializer_cls): | ||
"""Returns a function that sets an attribute on an object | ||
|
||
Return ``None`` to use the default setter for the serializer defined in | ||
:attr:`Serializer.default_setter`. | ||
|
||
When a :class:`Serializer` is defined, each :class:`Field` will be | ||
converted into a setter function using this method. During | ||
deserialization, each setter will be called with the object being | ||
deserialized with the argument passed as the return value of | ||
:meth:`Field.to_internal_value`. | ||
|
||
If a :class:`Field` has ``setter_takes_serializer = True``, then the | ||
setter returned from this method will be called with the | ||
:class:`Serializer` instance as the first argument, and the object | ||
being serialized as the second. | ||
|
||
:param str serializer_field_name: The name this field was assigned to | ||
on the serializer. | ||
:param serializer_cls: The :class:`Serializer` this field is a part of. | ||
""" | ||
return None | ||
|
||
|
||
class StrField(Field): | ||
"""A :class:`Field` that converts the value to a string.""" | ||
to_value = staticmethod(six.text_type) | ||
to_representation = staticmethod(six.text_type) | ||
to_internal_value = staticmethod(six.text_type) | ||
|
||
|
||
class IntField(Field): | ||
"""A :class:`Field` that converts the value to an integer.""" | ||
to_value = staticmethod(int) | ||
to_representation = staticmethod(int) | ||
to_internal_value = staticmethod(int) | ||
|
||
|
||
class FloatField(Field): | ||
"""A :class:`Field` that converts the value to a float.""" | ||
to_value = staticmethod(float) | ||
to_representation = staticmethod(float) | ||
to_internal_value = staticmethod(float) | ||
|
||
|
||
class BoolField(Field): | ||
"""A :class:`Field` that converts the value to a boolean.""" | ||
to_value = staticmethod(bool) | ||
to_representation = staticmethod(bool) | ||
to_internal_value = staticmethod(bool) | ||
|
||
|
||
class MethodField(Field): | ||
|
@@ -110,20 +183,28 @@ def do_minus(self, foo_obj): | |
return foo_obj.bar - foo_obj.baz | ||
|
||
foo = Foo(bar=5, baz=10) | ||
FooSerializer(foo).data | ||
FooSerializer(foo).representation | ||
# {'plus': 15, 'minus': -5} | ||
|
||
:param str method: The method on the serializer to call. Defaults to | ||
``'get_<field name>'``. | ||
""" | ||
getter_takes_serializer = True | ||
setter_takes_serializer = True | ||
|
||
def __init__(self, method=None, **kwargs): | ||
def __init__(self, getter=None, setter=None, **kwargs): | ||
super(MethodField, self).__init__(**kwargs) | ||
self.method = method | ||
self.getter_method = getter | ||
self.setter_method = setter | ||
|
||
def as_getter(self, serializer_field_name, serializer_cls): | ||
method_name = self.method | ||
method_name = self.getter_method | ||
if method_name is None: | ||
method_name = 'get_{0}'.format(serializer_field_name) | ||
return getattr(serializer_cls, method_name) | ||
return getattr(serializer_cls, method_name, None) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i would rather not specify the default to getattr and have these methods fail if method_name doesn't exist There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 |
||
|
||
def as_setter(self, serializer_field_name, serializer_cls): | ||
method_name = self.setter_method | ||
if method_name is None: | ||
method_name = 'set_{0}'.format(serializer_field_name) | ||
return getattr(serializer_cls, method_name, None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
change these back to
.data