Skip to content
This repository has been archived by the owner on Dec 19, 2017. It is now read-only.

Commit

Permalink
Update to Tornado 4.1
Browse files Browse the repository at this point in the history
This mostly only affected the RequestHandler._execute method that is now
decorated with the `gen.coroutine` decorator.
  • Loading branch information
Daniel Truemper committed Feb 20, 2015
1 parent 208794c commit 8b49a4a
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 71 deletions.
29 changes: 15 additions & 14 deletions buildout.cfg
Expand Up @@ -41,23 +41,24 @@ build = ${buildout:directory}/docs
eggs = ${buildout:eggs}

[versions]
Jinja2 = 2.7.2
MarkupSafe = 0.19
Pygments = 1.6
Sphinx = 1.2.2
collective.recipe.sphinxbuilder = 0.8.2
cov-core = 1.7
cov-core = 1.13.0
coverage = 3.7.1
docutils = 0.11
ipython = 1.2.1
mock = 1.0.1
py = 1.4.20
py = 1.4.25
pytest = 2.5.2
pytest-cov = 1.6
tornado = 3.2.0
zc.buildout = 2.2.1
pytest-cov = 1.7.0
scales = 1.0.8
schematics = 1.0.2
setuptools = 8.2.1
tornado = 4.1
zc.buildout = 2.3.1
zc.recipe.egg = 2.0.1
setuptools = 3.3
scales = 1.0.3
schematics = 0.9-4

# Required by:
# tornado==4.1
backports.ssl-match-hostname = 3.4.0.2

# Required by:
# tornado==4.1
certifi = 0.0.8
6 changes: 3 additions & 3 deletions requirements-test.txt
@@ -1,4 +1,4 @@
mock == 1.0.1
pytest == 2.5.2
pytest-cov == 1.6
futures == 2.1.6
pytest == 2.6.4
pytest-cov == 1.8.1
futures == 2.2.0
6 changes: 3 additions & 3 deletions requirements.txt
@@ -1,3 +1,3 @@
tornado == 3.2.0
schematics == 0.9-4
scales == 1.0.3
tornado == 4.1
schematics == 1.0.2
scales == 1.0.8
10 changes: 6 additions & 4 deletions setup.py
Expand Up @@ -51,13 +51,14 @@
url='http://supercell.readthedocs.org/',
license="http://www.apache.org/licenses/LICENSE-2.0",

description='Supercell is a framework for creating RESTful APIs that loosely follow the idea of domain driven design.',
description='Supercell is a framework for creating RESTful APIs that ' +
'loosely follow the idea of domain driven design.',
packages=['supercell'],

install_requires=[
'tornado >= 3.1.0, <= 3.2.0',
'schematics >= 0.9-4',
'scales >= 1.0.3'
'tornado >= 4.1',
'schematics >= 1.0.2',
'scales >= 1.0.8'
],

tests_require=tests_require,
Expand All @@ -66,6 +67,7 @@
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: Implementation :: PyPy',
Expand Down
2 changes: 1 addition & 1 deletion supercell/api.py
Expand Up @@ -25,9 +25,9 @@
from supercell.mediatypes import (ContentType, MediaType, Return, Ok, Error,
OkCreated, NoContent)
from supercell.decorators import provides, consumes
from supercell.environment import Environment
from supercell.health import (HealthCheckOk, HealthCheckWarning,
HealthCheckError)
from supercell.environment import Environment
from supercell.consumer import ConsumerBase, JsonConsumer
from supercell.provider import ProviderBase, JsonProvider
from supercell.requesthandler import RequestHandler
Expand Down
2 changes: 1 addition & 1 deletion supercell/environment.py
Expand Up @@ -36,9 +36,9 @@
from greplin import scales
from greplin.scales import util

from tornado.gen import coroutine as async
from tornado.web import Application as _TAPP

from supercell.api import async
from supercell.cache import CacheConfigT
from supercell.health import SystemHealthCheck
from supercell.requesthandler import RequestHandler
Expand Down
3 changes: 2 additions & 1 deletion supercell/health.py
Expand Up @@ -66,7 +66,8 @@ def run(self):
from __future__ import (absolute_import, division, print_function,
with_statement)

from supercell.api import async
from tornado.gen import coroutine as async

from supercell.decorators import provides
from supercell.mediatypes import Ok, Error, MediaType
from supercell.requesthandler import RequestHandler
Expand Down
2 changes: 1 addition & 1 deletion supercell/middleware.py
Expand Up @@ -22,8 +22,8 @@
from functools import wraps

from schematics.models import Model
from tornado.gen import coroutine, Return

from supercell.api import coroutine, Return
from supercell._compat import with_metaclass
from supercell.mediatypes import ReturnInformationT

Expand Down
152 changes: 109 additions & 43 deletions supercell/requesthandler.py
Expand Up @@ -19,15 +19,17 @@
with_statement)

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

from schematics.models import Model
from tornado import gen, iostream
from tornado.concurrent import is_future
from tornado.escape import to_unicode
from tornado.util import bytes_type, unicode_type
from tornado.web import RequestHandler as rq, HTTPError
from tornado.web import (RequestHandler as rq, HTTPError,
_has_stream_request_body)

from supercell.cache import compute_cache_header
from supercell.mediatypes import MediaType, ReturnInformationT
Expand Down Expand Up @@ -60,7 +62,7 @@ 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
adopted :func:`RequestHandler._execute()` method that will handle
the consuming and providing of request inputs and results.
"""

Expand Down Expand Up @@ -116,50 +118,114 @@ def get_template(self, model):
"""
raise NotImplementedError

def _execute_method(self):
"""Execute the request.
def _check_consumer(self):
"""For a POST or PUT request check if we can find a matching
consumer for the incoming data."""
verb = self.request.method.lower()
headers = self.request.headers
kwargs = self.path_kwargs

The method to be executed is determined by the request method.
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 StandardError as e:
raise HTTPError(400, reason=unicode(e))

def _add_cache_headers(self):
"""Maybe add cache headers on GET and HEAD requests."""
verb = self.request.method.lower()
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)

@gen.coroutine
def prepare(self):
"""Check for a consumer and optionally add the cache headers.
note:: when overriding the `prepare()` method, don't forget to call
the super 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
self._check_consumer()
self._add_cache_headers()

@gen.coroutine
def _execute(self, transforms, *args, **kwargs):
"""Executes this request with the given output transforms.
This is basically a copy of tornado's `_execute()` method. The only
difference is the expected result. Tornado expects the result to be
`None`, where we want this to be a :py:class:Model."""
verb = self.request.method.lower()
headers = self.request.headers
self._transforms = transforms
try:
if self.request.method not in self.SUPPORTED_METHODS:
raise HTTPError(405)
self.path_args = [self.decode_argument(arg) for arg in args]
self.path_kwargs = dict((k, self.decode_argument(v, name=k))
for (k, v) in kwargs.items())
# If XSRF cookies are turned on, reject form submissions without
# the proper cookie
if self.request.method not in ("GET", "HEAD", "OPTIONS") and \
self.application.settings.get("xsrf_cookies"):
self.check_xsrf_cookie()

result = self.prepare()
if is_future(result):
result = yield result
if result is not None:
raise TypeError("Expected None, got %r" % result)
if self._prepared_future is not None:
# Tell the Application we've finished with prepare()
# and are ready for the body to arrive.
self._prepared_future.set_result(None)
if self._finished:
return

if _has_stream_request_body(self.__class__):
# In streaming mode request.body is a Future that signals
# the body has been completely received. The Future has no
# result; the data has been passed to self.data_received
# instead.
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 StandardError as e:
raise HTTPError(400, reason=unicode(e))

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):
yield self.request.body
except iostream.StreamClosedError:
return

method = getattr(self, self.request.method.lower())
result = method(*self.path_args, **self.path_kwargs)
if is_future(result):
result = yield result
if result is not None:
self._provide_result(verb, headers, result)
if self._auto_finish and not self._finished:
self.finish()
except Exception as e:
self._handle_request_exception(e)
if (self._prepared_future is not None and
not self._prepared_future.done()):
# In case we failed before setting _prepared_future, do it
# now (to unblock the HTTP server). Note that this is not
# in a finally block to avoid GC issues prior to Python 3.4.
self._prepared_future.set_result(None)

def _provide_result(self, verb, headers, result):
"""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)
Expand All @@ -183,7 +249,7 @@ def _provide_result(self, verb, headers, future_model):

provider = provider_class()
if isinstance(result, Model):
provider.provide(future_model.result(), self)
provider.provide(result, self)

if not self._finished:
self.finish()

0 comments on commit 8b49a4a

Please sign in to comment.