Source code for supercell.api.decorators

# vim: set fileencoding=utf-8 :
#
# Copyright (c) 2013 Daniel Truemper <truemped at googlemail.com>
#
# 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.
#
#
'''Several decorators for using with :class:`supercell.api.RequestHandler`
implementations.
'''
from __future__ import absolute_import, division, print_function, with_statement

from collections import defaultdict
from functools import wraps

from tornado import gen
from tornado.web import asynchronous

from supercell.api import ContentType


[docs]def async(fn): '''Decorator that merges the :func:`tornado.web.asynchronous` as well as the :func:`tornado.gen.coroutine` decorators. Example:: class MyHandler(s.RequestHandler): @s.async def get(self, user_id): # ... raise s.Return(User()) ''' return asynchronous(gen.coroutine(fn))
[docs]def provides(content_type, vendor=None, version=None, default=False): '''Class decorator for mapping HTTP GET responses to content types and their representation. In order to allow the **application/json** content type, create the handler class like this:: @s.provides(sMediaType.ApplicationJson) class MyHandler(sRequestHandler): pass It is also possible to support more than one content type. The content type selection is then based on the client **Accept** header. If this is not present, ordering of the :func:`provides` decorators matter, i.e. the first content type is used:: @s.provides(s.MediaType.ApplicationJson, model=MyModel) class MyHandler(s.RequestHandler): ... :param str content_type: The base content type such as **application/json** :param model: The model that should be consumed. :type model: :class:`schematics.models.Model` :param str vendor: Any vendor information for the base content type :param float version: The vendor version :param bool default: If **True** and no **Accept** header is present, this content type is provided ''' def wrapper(cls): '''The real class decorator.''' assert isinstance(cls, type), 'This decorator may only be used as ' + \ 'class decorator' if not hasattr(cls, '_PROD_CONTENT_TYPES'): cls._PROD_CONTENT_TYPES = defaultdict(list) ctype = ContentType(content_type, vendor, version) cls._PROD_CONTENT_TYPES[content_type].append(ctype) if default: assert 'default' not in cls._PROD_CONTENT_TYPES, 'TODO: nice msg' cls._PROD_CONTENT_TYPES['default'] = ctype return cls return wrapper
[docs]def consumes(content_type, model, vendor=None, version=None): '''Class decorator for mapping HTTP POST and PUT bodies to Example:: @s.consumes(s.MediaType.ApplicationJson, model=Model) class MyHandler(s.RequestHandler): def post(self, *args, **kwargs): # ... raise s.OkCreated() :param str content_type: The base content type such as **application/json** :param model: The model that should be consumed. :type model: :class:`schematics.models.Model` :param str vendor: Any vendor information for the base content type :param float version: The vendor version ''' def wrapper(cls): '''The real decorator.''' assert isinstance(cls, type), 'This decorator may only be used as ' + \ 'class decorator' assert model, 'In order to consume content a schematics model ' + \ 'class has to be given via the model parameter' if not hasattr(cls, '_CONS_CONTENT_TYPES'): cls._CONS_CONTENT_TYPES = defaultdict(list) if not hasattr(cls, '_CONS_MODEL'): cls._CONS_MODEL = dict() ct = ContentType(content_type, vendor, version) cls._CONS_CONTENT_TYPES[content_type].append(ct) cls._CONS_MODEL[ct] = model return cls return wrapper
[docs]def cache(max_age, s_max_age=None, public=False, private=False, no_cache=False, no_store=False, must_revalidate=True, proxy_revalidate=False): '''Set the `Cache-Control` HTTP header to allow for fine grained caching controls. For detailed information read http://www.mnot.net/cache_docs/ and http://www.ietf.org/rfc/rfc2616.txt Usage:: @cache(timedelta(minutes=10)) def get(self): self.finish({'ok': True}) This would allow public caches to store the result for 10 minutes. :param max_age: Number of seconds the response can be cached :type max_age: datetime.timedelta :param s_max_age: Like `max_age` but only applies to shared caches :type s_max_age: datetime.timedelta :param public: Marks responses as cachable even if they contain authentication information :type public: bool :param private: Allows the browser to cache the result but not shared caches :type private: bool :param no_cache: If *True* caches will revalidate the request before delivering the cached copy :type no_cache: bool :param no_store: Caches should not store any cached copy. :type no_store: bool :param must_revalidate: Tells the cache to not serve stale copies of the response :type must_revalidate: bool :param proxy_revalidate: Like `must_revalidate` except it only applies to public caches :type proxy_revalidate: bool ''' def wrapper(fn): @wraps(fn) def _func(self, *args, **kwargs): '''Inner method setting the HTTP header according to the params.''' params = [] params.append('max-age=%s' % max_age.seconds) if s_max_age: params.append('s-max-age=%s' % s_max_age.seconds) if public: params.append('public') if private: params.append('private') if no_cache: params.append('no-cache') if no_store: params.append('no-store') if must_revalidate: params.append('must-revalidate') if proxy_revalidate: params.append('proxy-revalidate') self.set_header('Cache-Control', ', '.join(params)) fn(self, *args, **kwargs) return _func return wrapper

Project Versions

This Page