Source code for supercell.api.requesthandler

# 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.
#
#
from __future__ import absolute_import, division, print_function, with_statement

from datetime import datetime
from functools import partial
import json
import logging
import time

from schematics.models import Model
from tornado.escape import to_unicode
from tornado.util import bytes_type, unicode_type
from tornado.web import RequestHandler as rq, HTTPError

from supercell.api import MediaType
from supercell.api.cache import compute_cache_header
from supercell.api.metatypes import ReturnInformationT
from supercell.api.consumer import ConsumerBase, NoConsumerFound
from supercell.api.provider import ProviderBase, NoProviderFound


__all__ = ['RequestHandler']


_DEFAULT_CONTENT_TYPE = 'DEFAULT'


def _decode_utf8_and_latin1(value):
    '''Convert an string argument to a unicode string.

    Instead of decoding it as utf-8 we assume it is encoded as latin1.
    '''
    try:
        return to_unicode(value)
    except UnicodeDecodeError:
        if isinstance(value, (unicode_type, type(None))):
            return value
        assert isinstance(value, bytes_type), \
                'Expected bytes, unicode or None; got %s' % type(value)
        return value.decode('latin1')


[docs]class RequestHandler(rq): ''' **supercell** request handler. The only difference to the :class:`tornado.web.RequestHandler` is an adopted :func:`RequestHandler._execute_method()` method that will handle the consuming and providing of request inputs and results. ''' @property
[docs] def environment(self): '''Convinience method for accessing the environment.''' return self.application.environment
@property
[docs] def config(self): '''Convinience method for accessing the environment.''' return self.application.config
@property
[docs] def logger(self): '''Use this property to write to the log files. In a request handler you would simply log messages like this:: def get(self): self.logger.info('A test') ''' if not hasattr(self, '_logger'): name = '%s:%s' % (self.__class__.__name__, self.request_id) self._logger = logging.getLogger(name) return self._logger
[docs] def decode_argument(self, value, name=None): '''Overwrite the default :func:`RequestHandler.decode_argument()` method in order to allow *latin1* encoded URLs. ''' return _decode_utf8_and_latin1(value)
@property
[docs] def request_id(self): '''Return a unique id per request. Collisions are allowed but should should not occur within a 10 minutes time window. The current implementation is based on a timestamp in milliseconds substracted by a large number to make the id smaller. ''' if not hasattr(self, '_request_id'): self._request_id = int(time.time() * 1000) - 1374400000000 return self._request_id
def _request_summary(self): return self.request.method + " " + self.request.uri + \ " (ip:" + self.request.remote_ip + ", r_id:" + \ str(self.request_id) + ")" def _execute_method(self): '''Execute the request. The method to be executed is determined by the request method. ''' if not self._finished: verb = self.request.method.lower() headers = self.request.headers method = getattr(self, verb) kwargs = self.path_kwargs if verb in ['post', 'put'] and 'Content-Type' in headers: # try to find a matching consumer try: (model, consumer_class) = ConsumerBase.map_consumer( headers['Content-Type'], self) consumer = consumer_class() kwargs['model'] = consumer.consume(self, model) except NoConsumerFound: # TODO return available consumer types?! raise HTTPError(406) except ValueError: raise HTTPError(400) if verb in ['get', 'head']: # check if there is caching information stored with the handler cache_config = self.environment.get_cache_info(self.__class__) if cache_config: self.set_header('Cache-Control', compute_cache_header(cache_config)) expires = self.environment.get_expires_info(self.__class__) if expires: self.set_header('Expires', datetime.now() + expires) future_model = method(*self.path_args, **kwargs) if future_model: callback = partial(self._provide_result, verb, headers) future_model.add_done_callback(callback) def _provide_result(self, verb, headers, future_model): '''Find the correct provider for the result and call it with the final result.''' result = future_model.result() if isinstance(result, ReturnInformationT): self.set_header('Content-Type', MediaType.ApplicationJson) self.set_status(result.code) if 'additional' in result.message: self.logger.info(result.message['additional']) self.write(json.dumps(result.message)) elif not isinstance(result, Model): # raise an error when something else than a model has been returned self.logger.error('Returning a non-model is not supported') raise HTTPError(500) else: try: provider_class = ProviderBase.map_provider( headers.get('Accept', ''), self, allow_default=True) except NoProviderFound: raise HTTPError(406) provider = provider_class() if isinstance(result, Model): provider.provide(future_model.result(), self) if not self._finished: self.finish()
Read the Docs v: v0.4.0
Versions
latest
v0.4.0
v0.3.0
Downloads
On Read the Docs
Project Home
Builds

Free document hosting provided by Read the Docs.