Toto 1.0.10 documentation

toto.invocation

Contents

Source code for toto.invocation

'''``toto.invocation`` contains many decorators that may be applied to the ``invoke(handler, parameters)`` functions in
  method modules in order to modify their behavior.
'''

from exceptions import *
from tornado.options import options
from traceback import format_exc
from tornado.gen import coroutine, Return, engine
from toto.tasks import TaskQueue
import logging
import json

"""
This is a list of all attributes that may be added by a decorator,
it is used to allow decorators to be order agnostic.
"""
invocation_attributes = ['asynchronous', '__doc__', '__module__', '__name__', '__repr__']

[docs]def _add_doc(fn, wrapper, doc): '''A convenience method for appending to a decorated method's docstring.''' wrapper.__name__ = fn.__name__ wrapper.__module__ = fn.__module__ wrapper.__doc__ = fn.__doc__ and '%s\n\n%s' % (fn.__doc__, doc) or doc
[docs]def _copy_attributes(fn, wrapper, doc=None, attributes=invocation_attributes): '''A convenience method for copying certain attributes to a decorated method. Optionally, ``doc`` can be passed to append to a the method's docstring. To specify which attributes to copy, pass an iterable as the ``attributes`` argument. ''' for a in attributes: if hasattr(fn, a): setattr(wrapper, a, getattr(fn, a)) if doc: _add_doc(fn, wrapper, doc)
[docs]def asynchronous(fn): '''Invoke functions with the ``@asynchronous`` decorator will not cause the request handler to finish when they return. Use this decorator to support long running operations like tasks sent to workers or long polling. ''' fn.asynchronous = True return fn
[docs]def anonymous_session(fn): '''Invoke functions marked with the ``@anonymous_session`` decorator will attempt to load the current session (either referenced by the x-toto-session-id request headers or cookie). If no session is found, an anonymous session will be created. Note: If the user was previously authenticated, the authenticated session will be loaded. ''' def wrapper(handler, parameters): handler.retrieve_session() if not handler.session: handler.create_session() return fn(handler, parameters) _copy_attributes(fn, wrapper, '*If not authenticated, this request will use an anonymous session for state persistence*.') return wrapper
[docs]def authenticated(fn): '''Invoke functions marked with the ``@authenticated`` decorator will attempt to load the current session (either referenced by the x-toto-session-id request header or cookie). If no session is found, or if the current session is anonymous, a "Not authorized" error will be returned to the client. ''' def wrapper(handler, parameters): handler.retrieve_session() if not handler.session or not handler.session.user_id: raise TotoException(ERROR_NOT_AUTHORIZED, "Not authorized") return fn(handler, parameters) _copy_attributes(fn, wrapper, '*Requires authentication*.') return wrapper
[docs]def optionally_authenticated(fn): '''Invoke functions marked with the ``@optionally_authenticated`` decorator will attempt to load the current session (either referenced by the x-toto-session-id request header or cookie). If no session is found, the request proceeds as usual. ''' def wrapper(handler, parameters): handler.retrieve_session() return fn(handler, parameters) _copy_attributes(fn, wrapper) return wrapper
[docs]def authenticated_with_parameter(fn): '''Invoke functions marked with the ``@authenticated_with_parameter`` decorator will behave like functions decorated with ``@authenticated`` but will use the session_id parameter to find the current session instead of the x-toto-session-id header or cookie. ''' def wrapper(handler, parameters): if 'session_id' in parameters: handler.retrieve_session(parameters['session_id']) del parameters['session_id'] if not handler.session: raise TotoException(ERROR_NOT_AUTHORIZED, "Not authorized") return fn(handler, parameters) _copy_attributes(fn, wrapper, '*Authenticated session. Requires the session to be passed as* ``session_id``.') return wrapper
[docs]def requires(*args): '''Invoke functions marked with the ``@requires`` decorator will error if any of the parameters passed to the decorator are missing. The following example will error if either "param1" or "param2" is not included in the request:: @requires('param1', 'param2') def invoke(handler, parameters): pass ''' required_parameters = set(args) def decorator(fn): def wrapper(handler, parameters): missing_parameters = required_parameters.difference(parameters) if missing_parameters: raise TotoException(ERROR_MISSING_PARAMS, "Missing parameters: " + ', '.join(missing_parameters)) return fn(handler, parameters) _copy_attributes(fn, wrapper, '*Required parameters:* %s.' % ', '.join('``%s``' % i for i in args)) return wrapper return decorator
[docs]def raw_response(fn): '''Invoke functions marked with the ``@raw_response`` decorator will not be serialized before response to the client. This can be used to send text, html or other binary data without the usual JSON (or other) processing. The ``handler.response_type`` property will be used to set the response "Content-Type" header. By default this will be "application/octet-stream". ''' def wrapper(handler, parameters): handler.response_type = 'application/octet-stream' handler.respond_raw(fn(handler, parameters), handler.response_type) return None _copy_attributes(fn, wrapper) return wrapper
[docs]def jsonp(callback_name='jsonp'): '''Invoke functions marked with the ``@jsonp`` decorator will return a wrapper response that will call a client-side javascript function. This decorator requires a "jsonp" parameter set to the name of the javascript callback function to be passed with the request. If no "jsonp" parameter is passed, the request will respond like any other Toto request. The decorator can be applied with an optional ``callback_name`` argument to specify a parameter to use instead of "jsonp", e.g.:: @jsonp('callback') def invoke(handler, parameters): #do stuff Will allow JSONP requests that call the function specified by "callback" in their response. Applying the decorator without the ``callback_name`` parameter will use the default "jsonp":: @jsonp def invoke(handler, parameters): #do stuff Note: JSONP requests will only be affected by decorators before ``@jsonp`` in the decorator chain. ''' def decorator(fn): def wrapper(handler, parameters): callback = parameters.get(callback_name, None) if callback: del parameters[callback_name] handler.respond_raw('%s(%s)' % (callback, json.dumps(fn(handler, parameters))), 'text/javascript') return None else: return fn(handler, parameters) _copy_attributes(fn, wrapper, '*Supports JSONP requests with the callback parameter* ``%s``.' % callback_name) return wrapper if isinstance(callback_name, basestring): return decorator fn = callback_name callback_name = 'jsonp' return decorator(fn)
[docs]def error_redirect(redirect_map, default=None): '''Invoke functions marked with the ``@error_redirect`` decorator will redirect according to the ``redirect_map`` dictionary. ``redirect_map`` should consist of ``status_code``, ``url`` pairs. This decorator will check the ``code`` then ``status_code`` properties of the raised error for matches in the redirect map before falling back to the usual error behavior. The optional ``default`` parameter can be used to specify a url to redirect to if there are no matches in ``redirect_map``. The following code will redirect to "not_found.html" on 404, and "error.html" otherwise:: @error_redirect({'404': 'not_found.html'}, 'error.html') def invoke(handler, parameters): pass ''' def decorator(fn): def wrapper(handler, parameters): try: return fn(handler, parameters) except Exception as e: if options.debug: logging.error(format_exc()) if hasattr(e, 'code') and str(e.code) in redirect_map: handler.redirect(redirect_map[str(e.code)]) elif hasattr(e, 'status_code') and str(e.status_code) in redirect_map: handler.redirect(redirect_map[str(e.status_code)]) elif default: handler.redirect(default) else: raise _copy_attributes(fn, wrapper) return wrapper return decorator
[docs]def default_parameters(defaults): '''Invoke functions marked with the ``@default_parameters`` decorator will set missing parameters according to the dictionary passed as the ``defaults`` argument. ''' def decorator(fn): def wrapper(handler, parameters): for p in defaults: if p not in parameters: parameters[p] = defaults[p] return fn(handler, parameters) _copy_attributes(fn, wrapper, '*Automatically adds default parameters:* %s.' % ', '.join(('``%s: %s``' % (k, v) for k, v in defaults.iteritems()))) return wrapper return decorator

Contents