Source code for invenio_records_rest.schemas.fields.marshmallow_contrib

# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2018-2019 CERN.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.

"""Contributed Marshmallow fields."""

import functools
from inspect import isfunction, ismethod

from marshmallow import fields, utils

# Recommended way of maintaining compatibility with python 2 for getargspec
try:
    from inspect import getfullargspec as getargspec
except ImportError:
    from inspect import getargspec


def _get_func_args(func):
    """Get a list of the arguments a function or method has."""
    if isinstance(func, functools.partial):
        return _get_func_args(func.func)
    if isfunction(func) or ismethod(func):
        return list(getargspec(func).args)
    if callable(func):
        return list(getargspec(func.__call__).args)


[docs]class Function(fields.Function): """Enhanced marshmallow Function field. The main difference between the original marshmallow.fields.Function is for the ``deserialize`` function, which can now also point to a three-argument function, with the third argument being the original data that was passed to ``Schema.load``. The following example better demonstrates how this works: .. code-block:: python def serialize_foo(obj, context): return {'serialize_args': {'obj': obj, 'context': context}} def deserialize_foo(value, context, data): return {'deserialize_args': { 'value': value, 'context': context, 'data': data}} class FooSchema(marshmallow.Schema): foo = Function(serialize_foo, deserialize_foo) FooSchema().dump({'foo': 42}).data {'foo': { 'serialize_args': { 'obj': {'foo': 42}, 'context': {} # no context was passed } }} FooSchema().load({'foo': 42}).data {'foo': { 'deserialize_args': { 'value': 42, 'context': {}, # no context was passed 'data': {'foo': 42}, } }} """ def _deserialize(self, value, attr, data, **kwargs): if self.deserialize_func: return self._call_or_raise(self.deserialize_func, value, attr, data) return value def _call_or_raise(self, func, value, attr, data=None): func_args_len = len(_get_func_args(func)) if func_args_len > 2: return func(value, self.parent.context, data) elif func_args_len > 1: return func(value, self.parent.context) else: return func(value)
[docs]class Method(fields.Method): """Enhanced marshmallow Method field. The main difference between the original marshmallow.fields.Method is for the ``deserialize`` method, which can now also point to a two-argument method, with the second argument being the original data that was passed to ``Schema.load``. The following example better demonstrates how this works: .. code-block:: python class BarSchema(marshmallow.Schema): bar = Method('serialize_bar', 'deserialize_bar') # Exactly the same behavior as in ``marshmallow.fields.Method`` def serialize_bar(self, obj): return {'serialize_args': {'obj': obj}} def deserialize_bar(self, value, data): return {'deserialize_args': {'value': value, 'data': data}} BarSchema().dump({'bar': 42}).data {'bar': { 'serialize_args': { 'obj': {'bar': 42} } }} BarSchema().load({'bar': 42}).data {'bar': { 'deserialize_args': { 'data': {'bar': 42}, 'value': 42 } }} """ def _deserialize(self, value, attr, data, **kwargs): if self.deserialize_method_name: try: method = utils.callable_or_raise( getattr(self.parent, self.deserialize_method_name, None) ) method_args_len = len(_get_func_args(method)) if method_args_len > 2: return method(value, data) return method(value) except AttributeError: pass return value