Skip to content

Commit

Permalink
Add priority param to processors
Browse files Browse the repository at this point in the history
  • Loading branch information
lgo committed May 25, 2017
1 parent e9b2a91 commit 8b440c8
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 13 deletions.
27 changes: 14 additions & 13 deletions marshmallow/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,16 @@ def validate_age(self, data):
VALIDATES_SCHEMA = 'validates_schema'


def validates(field_name):
def validates(field_name, priority=0):
"""Register a field validator.
:param str field_name: Name of the field that the method validates.
"""
return tag_processor(VALIDATES, None, False, field_name=field_name)
return tag_processor(VALIDATES, None, False, priority=priority, field_name=field_name)


def validates_schema(fn=None, pass_many=False, pass_original=False, skip_on_field_errors=True):
def validates_schema(fn=None, pass_many=False, pass_original=False, skip_on_field_errors=True,
priority=0):
"""Register a schema-level validator.
By default, receives a single object at a time, regardless of whether ``many=True``
Expand All @@ -85,52 +86,52 @@ def validates_schema(fn=None, pass_many=False, pass_original=False, skip_on_fiel
.. versionchanged:: 3.0.0b1
``skip_on_field_errors`` defaults to `True`.
"""
return tag_processor(VALIDATES_SCHEMA, fn, pass_many, pass_original=pass_original,
skip_on_field_errors=skip_on_field_errors)
return tag_processor(VALIDATES_SCHEMA, fn, pass_many, priority=priority,
pass_original=pass_original, skip_on_field_errors=skip_on_field_errors)


def pre_dump(fn=None, pass_many=False):
def pre_dump(fn=None, pass_many=False, priority=0):
"""Register a method to invoke before serializing an object. The method
receives the object to be serialized and returns the processed object.
By default, receives a single object at a time, regardless of whether ``many=True``
is passed to the `Schema`. If ``pass_many=True``, the raw data (which may be a collection)
and the value for ``many`` is passed.
"""
return tag_processor(PRE_DUMP, fn, pass_many)
return tag_processor(PRE_DUMP, fn, pass_many, priority=priority)


def post_dump(fn=None, pass_many=False, pass_original=False):
def post_dump(fn=None, pass_many=False, pass_original=False, priority=0):
"""Register a method to invoke after serializing an object. The method
receives the serialized object and returns the processed object.
By default, receives a single object at a time, transparently handling the ``many``
argument passed to the Schema. If ``pass_many=True``, the raw data
(which may be a collection) and the value for ``many`` is passed.
"""
return tag_processor(POST_DUMP, fn, pass_many, pass_original=pass_original)
return tag_processor(POST_DUMP, fn, pass_many, priority=priority, pass_original=pass_original)


def pre_load(fn=None, pass_many=False):
def pre_load(fn=None, pass_many=False, priority=0):
"""Register a method to invoke before deserializing an object. The method
receives the data to be deserialized and returns the processed data.
By default, receives a single datum at a time, transparently handling the ``many``
argument passed to the Schema. If ``pass_many=True``, the raw data
(which may be a collection) and the value for ``many`` is passed.
"""
return tag_processor(PRE_LOAD, fn, pass_many)
return tag_processor(PRE_LOAD, fn, pass_many, priority=priority)


def post_load(fn=None, pass_many=False, pass_original=False):
def post_load(fn=None, pass_many=False, pass_original=False, priority=0):
"""Register a method to invoke after deserializing an object. The method
receives the deserialized data and returns the processed data.
By default, receives a single datum at a time, transparently handling the ``many``
argument passed to the Schema. If ``pass_many=True``, the raw data
(which may be a collection) and the value for ``many`` is passed.
"""
return tag_processor(POST_LOAD, fn, pass_many, pass_original=pass_original)
return tag_processor(POST_LOAD, fn, pass_many, priority=priority, pass_original=pass_original)


def tag_processor(tag_name, fn, pass_many, **kwargs):
Expand Down
10 changes: 10 additions & 0 deletions marshmallow/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,16 @@ def _resolve_processors(self):
# the processor was a descriptor or something.
self.__processors__[tag].append(attr_name)

# Sort all processor tags by priority, descending
for tag, processors in self.__processors__.items():
sorted_processors = sorted(processors,
key=lambda proc: self._get_priority(proc, tag))
self.__processors__[tag] = sorted_processors

def _get_priority(self, attr_name, tag):
processor = getattr(self, attr_name)
return processor.__marshmallow_kwargs__[tag].get('priority', 0)


class SchemaOpts(object):
"""class Meta options for the :class:`Schema`. Defines defaults."""
Expand Down
26 changes: 26 additions & 0 deletions tests/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -706,3 +706,29 @@ def raise_value_error(self, item):
schema.dump(object())
assert exc.value.messages == {'foo': 'error'}
schema.load({})

class TestProcessorPriorities:

def test_pass_ordered_processors(self):
class MySchema(Schema):
data = fields.Field()

@post_load(priority=1)
def first_post_load(self, ret):
ret['data'].append(1)
return ret

@post_load(priority=10)
def third_post_load(self, ret):
ret['data'].append(10)
return ret

@post_load(priority=5)
def second_post_load(self, ret):
ret['data'].append(5)
return ret

schema = MySchema()
datum = {'data': []}
item_loaded = schema.load(datum).data
assert item_loaded['data'] == [1, 5, 10]

0 comments on commit 8b440c8

Please sign in to comment.