Source code for decorators

"""
Contains utilities to decorate endpoints with additional functionality

Decorators
~~~~~~~~~~

A decorator_ is a function that takes in a function as an argument, and
returns a function. In Python, a typical decorator can be used as follows

.. code-block:: python

    class UserContainer(Resource):
        @staticmethod
        def parse_search_params(params):
            pass

        @restful_pagination()
        def get(self, pag_args):
            pass

The use of the ``@`` symbol is syntactic sugar for

 .. code-block:: python

    parse_search_params = staticmethod(parse_search_params)

In order to provide arguments to the decorator, as in the case of
``@restful_pagination``, another level of indirection is needed.
``restful_pagination`` is not a decorator in and of itself, but it returns a
decorator that then decorates a particular function.

.. note::
    Due to `Sphinx Autodoc <http://sphinx-doc.org/ext/autodoc.html>`'s
    documentation generator, the :attr:`__name__` and :attr:`__doc__` property
    of the function to be decorated must be assigned to the :attr:`__name__`
    and :attr:`__doc__` of the decorator. See
    :class:`tests.unit.test_decorators.TestRestfulPagination` for an example
    of a unit test that tests this behaviour

.. _decorator: http://simeonfranklin.com/blog/2012/jul/1/python-decorators-in-12-steps/
"""
from collections import namedtuple
from flask import request

__author__ = 'Michal Kononenko'

PaginationArgs = namedtuple(
    'PaginationArgs', ['page', 'items_per_page', 'offset']
)


[docs]def restful_pagination(default_items_per_page=1000): """ Wraps a RESTful getter, extracting the arguments ``page`` and ``items_per_page`` from the URL query parameter. These arguments are then parsed into integers, and returned as a `named tuple <https://docs.python.org/2/library/collections.html#collections.namedtuple>`_. consisting of the following variables - :attr:`page`: The number of the page to be displayed - :attr:`items_per_page`: The number of items to be displayed on each page - :attr:`offset`: The offset (item number of the first item on this page) The `PaginationArgs` tuple is then injected into the decorated function as a keyword argument (kwarg_.), in a similar way to the ``@mock.patch`` decorator. A usage pattern for this decorator could be .. code-block:: python @restful_pagination() def paginated_input(pagination_args): with sessionmaker() as session: session.query(Something).limit( pagination_args.limit ).offset( pagination_args.offset ).all() :param default_items_per_page: The number of items that should be displayed by default. This is to prevent a query with, for example, 300,000 results loading a large amount of data into memory. :return: A decorator with the default arguments per page configured :rtype: function .. _kwarg: https://stackoverflow.com/questions/1769403/understanding-kwargs-in-python """ def _wraps(f): _wraps.__name__ = f.__name__ _wraps.__doc__ = f.__doc__ def _wrapped_function(*args, **kwargs): page = request.args.get('page') if page is None: page = 1 else: try: page = int(page) except ValueError: page = 1 items_per_page = request.args.get('items_per_page') if items_per_page is None: items_per_page = default_items_per_page else: try: items_per_page = int(items_per_page) except ValueError: items_per_page = default_items_per_page offset = (page - 1) * items_per_page - 1 pag_args = PaginationArgs( page, items_per_page, offset ) new_args = args + (pag_args,) response = f(*new_args, **kwargs) response.headers['page'] = page response.headers['items_per_page'] = items_per_page return response _wrapped_function.__name__ = f.__name__ _wrapped_function.__doc__ = f.__doc__ return _wrapped_function return _wraps