-
Notifications
You must be signed in to change notification settings - Fork 60
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
Open
maroux
wants to merge
9
commits into
clarkduvall:master
Choose a base branch
from
maroux:deserialization
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 8 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
2273b24
Add support for deserialization
3df6e87
Fixing Flake8 errors
ca17891
Fixing tests on Python 2.6
c2bb0fe
Ugh, tripped Flake8 again
6d7fcaf
Yet another fix for Python 2.6
42d82b9
Some changes -
c8b4753
Adding inner-class Meta
1a892ec
Meta inner-class doesn't have to inherit from parents - it should be …
f12cdc2
Updating documentation
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
(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) | ||
|
||
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) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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