# 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.
import functools
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)
setattr(resource, responder_name, do_before_all)
let()
return resource
else:
responder = responder_or_resource
do_before_one = _wrap_with_before(action, responder)
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)
setattr(resource, responder_name, do_after_all)
let()
return resource
else:
responder = responder_or_resource
do_after_one = _wrap_with_after(action, responder)
return do_after_one
return _after
# -----------------------------------------------------------------------------
# Helpers
# -----------------------------------------------------------------------------
def _has_resource_arg(action):
"""Check if the given action function accepts a resource arg."""
if isinstance(action, functools.partial):
# NOTE(kgriffs): We special-case this, since versions of
# Python prior to 3.4 raise an error when trying to get the
# spec for a partial.
spec = inspect.getargspec(action.func)
elif inspect.isroutine(action):
# NOTE(kgriffs): We have to distinguish between instances of a
# callable class vs. a routine, since Python versions prior to
# 3.4 raise an error when trying to get the spec from
# a callable class instance.
spec = inspect.getargspec(action)
else:
spec = inspect.getargspec(action.__call__)
return 'resource' in spec.args
def _wrap_with_after(action, responder):
"""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.
"""
# NOTE(swistakm): create shim before checking what will be actually
# decorated. This helps to avoid excessive nesting
if _has_resource_arg(action):
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)
@wraps(responder)
def do_after(self, req, resp, **kwargs):
responder(self, req, resp, **kwargs)
shim(req, resp, self)
return do_after
def _wrap_with_before(action, responder):
"""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
"""
# NOTE(swistakm): create shim before checking what will be actually
# decorated. This allows to avoid excessive nesting
if _has_resource_arg(action):
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)
@wraps(responder)
def do_before(self, req, resp, **kwargs):
shim(req, resp, self, kwargs)
responder(self, req, resp, **kwargs)
return do_before