# Copyright 2013 by Rackspace Hosting, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from functools import wraps
import inspect
import six
from falcon import HTTP_METHODS
[docs]def before(action):
"""Decorator to execute the given action function *before* the responder.
Args:
action (callable): A function of the form
``func(req, resp, resource, params)``, where `resource` is a
reference to the resource class instance associated with the
request, and `params` is a dict of URI Template field names,
if any, that will be passed into the resource responder as
kwargs.
Note:
Hooks may inject extra params as needed. For example::
def do_something(req, resp, resource, params):
try:
params['id'] = int(params['id'])
except ValueError:
raise falcon.HTTPBadRequest('Invalid ID',
'ID was not valid.')
params['answer'] = 42
"""
def _before(responder_or_resource):
if isinstance(responder_or_resource, six.class_types):
resource = responder_or_resource
for method in HTTP_METHODS:
responder_name = 'on_' + method.lower()
try:
responder = getattr(resource, responder_name)
except AttributeError:
# resource does not implement this method
pass
else:
# Usually expect a method, but any callable will do
if callable(responder):
# This pattern is necessary to capture the current
# value of responder in the do_before_all closure;
# otherwise, they will capture the same responder
# variable that is shared between iterations of the
# for loop, above.
def let(responder=responder):
do_before_all = _wrap_with_before(
action, responder, resource, True)
setattr(resource, responder_name, do_before_all)
let()
return resource
else:
responder = responder_or_resource
do_before_one = _wrap_with_before(action, responder, None, True)
return do_before_one
return _before
[docs]def after(action):
"""Decorator to execute the given action function *after* the responder.
Args:
action (callable): A function of the form
``func(req, resp, resource)``, where `resource` is a
reference to the resource class instance associated with the
request
"""
def _after(responder_or_resource):
if isinstance(responder_or_resource, six.class_types):
resource = responder_or_resource
for method in HTTP_METHODS:
responder_name = 'on_' + method.lower()
try:
responder = getattr(resource, responder_name)
except AttributeError:
# resource does not implement this method
pass
else:
# Usually expect a method, but any callable will do
if callable(responder):
def let(responder=responder):
do_after_all = _wrap_with_after(
action, responder, resource, True)
setattr(resource, responder_name, do_after_all)
let()
return resource
else:
responder = responder_or_resource
do_after_one = _wrap_with_after(action, responder, None, True)
return do_after_one
return _after
# -----------------------------------------------------------------------------
# Helpers
# -----------------------------------------------------------------------------
# NOTE(kgriffs): Coverage disabled because under Python 3.4, the exception
# is never raised. Coverage has been verified when running under other
# versions of Python.
def _get_argspec(func): # pragma: no cover
"""Wrapper around inspect.getargspec to handle Py2/Py3 differences."""
try:
# NOTE(kgriffs): This will fail for callable classes, which
# explicitly define __call__, except under Python 3.4.
spec = inspect.getargspec(func)
except TypeError:
# NOTE(kgriffs): If this is a class that defines __call__ as a
# method, we need to get the argspec of __call__ directly. This
# does not work for regular functions and methods, because in
# that case, __call__ isn't actually a Python function under
# Python 2.6-3.3 (fixed in 3.4).
spec = inspect.getargspec(func.__call__)
return spec
def _has_self(spec):
"""Checks whether the given argspec includes a self param.
Warning:
If a method's spec lists "self", that doesn't necessarily mean
that it should be called with a `self` param; if the method
instance is bound, the caller must omit `self` on invocation.
"""
return len(spec.args) > 0 and spec.args[0] == 'self'
def _wrap_with_after(action, responder, resource=None, is_method=False):
"""Execute the given action function after a responder method.
Args:
action: A function with a signature similar to a resource responder
method, taking the form ``func(req, resp, resource)``.
responder: The responder method to wrap.
resource: The resource affected by `action` (default ``None``). If
``None``, `is_method` MUST BE True, so that the resource can be
derived from the `self` param that is passed into the wrapper.
is_method: Whether or not `responder` is an unbound method
(default ``False``).
"""
# NOTE(swistakm): introspect action function to guess if it can handle
# additional resource argument without breaking backwards compatibility
spec = _get_argspec(action)
# NOTE(swistakm): create shim before checking what will be actually
# decorated. This helps to avoid excessive nesting
if len(spec.args) == (4 if _has_self(spec) else 3):
shim = action
else:
# TODO(kgriffs): This decorator does not work on callable
# classes in Python vesions prior to 3.4.
#
# @wraps(action)
def shim(req, resp, resource):
action(req, resp)
# NOTE(swistakm): method must be decorated differently than
# normal function
if is_method:
@wraps(responder)
def do_after(self, req, resp, **kwargs):
responder(self, req, resp, **kwargs)
shim(req, resp, self)
else:
assert resource is not None
@wraps(responder)
def do_after(req, resp, **kwargs):
responder(req, resp, **kwargs)
shim(req, resp, resource)
return do_after
def _wrap_with_before(action, responder, resource=None, is_method=False):
"""Execute the given action function before a responder method.
Args:
action: A function with a similar signature to a resource responder
method, taking the form ``func(req, resp, resource, params)``.
responder: The responder method to wrap
resource: The resource affected by `action` (default ``None``). If
``None``, `is_method` MUST BE True, so that the resource can be
derived from the `self` param that is passed into the wrapper
is_method: Whether or not `responder` is an unbound method
(default ``False``)
"""
# NOTE(swistakm): introspect action function to guess if it can handle
# additional resource argument without breaking backwards compatibility
action_spec = _get_argspec(action)
# NOTE(swistakm): create shim before checking what will be actually
# decorated. This allows to avoid excessive nesting
if len(action_spec.args) == (5 if _has_self(action_spec) else 4):
shim = action
else:
# TODO(kgriffs): This decorator does not work on callable
# classes in Python vesions prior to 3.4.
#
# @wraps(action)
def shim(req, resp, resource, kwargs):
# NOTE(kgriffs): Don't have to pass "self" even if has_self,
# since method is assumed to be bound.
action(req, resp, kwargs)
# NOTE(swistakm): method must be decorated differently than
# normal function
if is_method:
@wraps(responder)
def do_before(self, req, resp, **kwargs):
shim(req, resp, self, kwargs)
responder(self, req, resp, **kwargs)
else:
assert resource is not None
@wraps(responder)
def do_before(req, resp, **kwargs):
shim(req, resp, resource, kwargs)
responder(req, resp, **kwargs)
return do_before
def _wrap_with_hooks(before, after, responder, resource):
"""Wrap responder on the given resource with "before" and "after" hooks.
Args:
before: An iterable of one or more "before" hooks
after: An iterable of one or more "after" hooks
responder: A method of a resource to wrap
resource: A reference to the resource instance providing the responder
"""
if after is not None:
for action in after:
responder = _wrap_with_after(action, responder, resource)
if before is not None:
# Wrap in reversed order to achieve natural (first...last)
# execution order.
for action in reversed(before):
responder = _wrap_with_before(action, responder, resource)
return responder