sprockets.http

Version ReadTheDocs Travis Coverage

The goal of this library is to make it a little easier to develop great HTTP API services using the Tornado web framework. It concentrates on running applications in a reliable & resilient manner and handling errors in a clean manner.

  • SIGTERM is gracefully handled with respect to outstanding timeouts and callbacks

  • Listening port is configured by the PORT environment variable

  • “Debug mode” is enabled by the DEBUG environment variable

    • catches SIGINT (e.g., Ctrl+C)

    • application run in a single process

Running Your Application

Running a Tornado application intelligently should be very easy. Ideally your application wrapping code should look something like the following.

from tornado import web
import sprockets.http

def make_app(**settings):
    return web.Application([
       # insert your handlers
    ], **settings)

if __name__ == '__main__':
    sprockets.http.run(make_app)

That’s it. The sprockets.http.run function will set up signal handlers and make sure that your application terminates gracefully when it is sent either an interrupt or terminate signal.

It also takes care of configuring the standard logging module albeit in a opinionated way. The goal is to let you write your application without worrying about figuring out how to run and monitor it reliably.

If you are OO-minded, then you can also make use of a custom Application class instead of writing a make_app function:

import sprockets.http.app

class Application(sprockets.http.app.Application):
    def __init__(self, *args, **kwargs):
        handlers = [
            # insert your handlers
        ]
        super().__init__(handlers, *args, **kwargs)

if __name__ == '__main__':
    sprockets.http.run(Application)

This approach is handy if you have application level state and logic that needs to be bundled together.

From setup.py

If you want, you can even run your application directly from setup.py:

$ ./setup.py httprun -a mymodule:make_app

The httprun command is installed as a distutils.command when you install the sprockets.http package. This command accepts the following command line parameters:

application

The “callable” that returns your application. You want to specify whatever you are passing to sprockets.http.run() using a syntax similar to a setuptools console script. Basically, this is a string that contains the module name to import and the callable to invoke separated by a colon (e.g., mypackage.module.submodule:function). This is the only required parameter.

env-file

Optional name of a file containing environment variable definitions to parse and load into the environment before running the application. The file is a list of environment variables formatted as name=value with one setting on each line. If the line starts with export, then the export portion is removed (for the sake of convenience). If the value portion is omitted, then the environment variable named will be removed from the environment if it is present.

port

Optional port number to bind the application to. This will set the PORT environment variable before running the application and after the environment file is read.

Error Logging

Handling errors should be simple as well. Tornado already does a great job of isolating the error handling into two methods on the request handler:

  • send_error is called by a request handler to send a HTTP error code to the caller. This is what you should be calling in your code. It handles setting the status, reporting the error, and finishing the request out.

  • write_error is called by send_error when it needs to send an error document to the caller. This should be overridden when you need to provide customized error pages. The important thing to realize is that send_error calls write_error.

So your request handlers are already doing something like the following:

class MyHandler(web.RequestHandler):
    def get(self):
       try:
          do_something()
       except:
          self.send_error(500, reason='Uh oh!')
          return

In order for this to be really useful to you (the one that gets pinged when a failure happens), you need to have some information in your application logs that points to the problem. Cool… so do something like this then:

class MyHandler(web.RequestHandler):
    def get(self):
       try:
          do_something()
       except:
          LOGGER.exception('do_something exploded for %s - returning %s',
                           self.request.uri, '500 Uh oh!')
          self.send_error(500, reason='Uh oh!')
          return

Simple enough. This works in the small, but think about how this approach scales. After a while your error handling might end up looking like:

class MyHandler(web.RequestHandler):
    def get(self):
       try:
          do_something()

       except SomethingSerious:
          LOGGER.exception('do_something exploded for %s - returning %s',
                           self.request.uri, '500 Uh oh!')
          self.send_error(500, reason='Uh oh!')
          return

       except SomethingYouDid:
          LOGGER.exception('do_something exploded for %s - returning %s',
                           self.request.uri, '400 Stop That')
          self.send_error(400, reason='Stop That')
          return

Or maybe you are raising tornado.web.HTTPError instead of calling send_errorsend_error will be called for you in this case. The sprockets.http.mixins.ErrorLogger mix-in extends write_error to log the failure to the self.logger BEFORE calling the super implementation. This very simple piece of functionality ensures that when your application is calling send_error to signal errors you are writing the failure out somewhere so you will have it later.

It is also nice enough to log 4xx status codes as warnings, 5xx codes as errors, and include exception tracebacks if an exception is being handled. You can go back to writing self.send_error and let someone else keep track of what happened.

Error Response Documents

Now that we have useful information in our log files, we should be returning something useful as well. By default, the Tornado provided send_error implementation writes a simple HTML file as the response body. The sprockets.http.mixins.ErrorWriter mix-in provides an implementation of write_error that is more amenable to programmatic usage. By default it uses a JSON body since that is the defacto format these days. Let’s look at our example again:

class MyHandler(web.RequestHandler):
    def get(self):
       try:
          do_something()
       except:
          self.send_error(500, reason='Uh oh!')
          return

The implementation of tornado.web.RequestHandler.write_error will produce a response that looks something like:

HTTP/1.1 500 Uh oh!
Server: TornadoServer/4.2.1
Content-Type: text/html; charset=UTF-8
Date: Fri, 20 Nov 2015 08:10:25 GMT

<html><title>500: Uh oh!</title><body>500: Uh oh!</body></html>

That is a lot better than nothing but not very useful when your user is someone else’s code. By adding sprockets.http.mixins.ErrorWriter to the handler’s inheritance chain, we would get the following response instead:

HTTP/1.1 500 Uh oh!
Server: TornadoServer/4.2.1
Content-Type: application/json
Date: Fri, 20 Nov 2015 08:10:25 GMT

{"message": "Uh oh!", "type": null, "traceback": null}

The traceback and type properties hint at the fact that exceptions are handled in a manner similar to what Tornado would do – if the call to send_error includes exception information, then the exception’s type will be included in the response. The traceback is only included when the standard serve_traceback Tornado option is enabled.

If the sprockets.mixins.mediatype.ContentMixin is also extended by your base class, write-error will use the ContentMixin.send_response method for choosing the appropriate response format and sending the error response.

License

Copyright (c) 2015-2020 AWeber Communications All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  • Neither the name of Sprockets nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

API Documentation

Running your Application

This library exposes a utility function named sprockets.http.run() for running your application. You need to pass in a callable that accepts keyword parameters destined for tornado.web.Application and return the application instance.

sprockets.http.run(create_application, settings=None, log_config=<object object>)[source]

Run a Tornado create_application.

Parameters
  • create_application – function to call to create a new application instance

  • settings (dict|None) – optional configuration dictionary that will be passed through to create_application as kwargs.

  • log_config (dict|None) – optional logging configuration dictionary to use. By default, a reasonable logging configuration is generated based on settings. If you need to override the configuration, then use this parameter. It is passed as-is to logging.config.dictConfig().

settings[‘debug’]

If the settings parameter includes a value for the debug key, then the application will be run in Tornado debug mode.

If the settings parameter does not include a debug key, then debug mode will be enabled based on the DEBUG environment variable.

settings[‘port’]

If the settings parameter includes a value for the port key, then the application will be configured to listen on the specified port. If this key is not present, then the PORT environment variable determines which port to bind to. The default port is 8000 if nothing overrides it.

settings[‘number_of_procs’]

If the settings parameter includes a value for the number_of_procs key, then the application will be configured to run this many processes unless in debug mode. This is passed to HTTPServer.start.

settings[‘xheaders’]

If the settings parameter includes a value for the xheaders key, then the application will be configured to use headers, like X-Real-IP, to get the user’s IP address instead of attributing all traffic to the load balancer’s IP address. When running behind a load balancer like nginx, it is recommended to pass xheaders=True. The default value is True if nothing overrides it.

Using sprockets.http.run
def create_application(**settings):
   return web.Application(
      [
         # add your handlers here
      ], **settings)

if __name__ == '__main__':
   sprockets.http.run(create_application)

Since sprockets.http.run() accepts any callable, you can pass a class instance in as well. The sprockets.http.app.Application is a specialization of tornado.web.Application that includes state management callbacks that work together with the run function and provide hooks for performing initialization and shutdown tasks.

The following example uses sprockets.http.app.Application as a base class to implement asynchronously connecting to a mythical database when the application starts.

Using the Application class
from tornado import locks, web
from sprockets.http import app, run

class Application(app.Application):
   def __init__(self, *args, **kwargs):
      handlers = [
         # insert your handlers here
      ]
      super().__init__(handlers, *args, **kwargs)
      self.ready_to_serve = locks.Event()
      self.ready_to_serve.clear()
      self.on_start_callbacks.append(self._connect_to_database)

   def _connect_to_database(self, _self, iol):
      def on_connected(future):
         if future.exception():
            iol.call_later(0.5, self._connect_to_database, _self, iol)
         else:
            self.ready_to_serve.set()

      future = dbconnector.connect()
      iol.add_future(future, on_connected)

if __name__ == '__main__':
   run(Application)

Implementing a ready_to_serve event is a useful paradigm for applications that need to asynchronously initialize before they can service requests. We can continue the example and add a /status endpoint that makes use of the event:

Implementing health checks
class StatusHandler(web.RequestHandler):
   @gen.coroutine
   def prepare(self):
      maybe_future = super().prepare()
      if concurrent.is_future(maybe_future):
         yield maybe_future
      if not self._finished and not self.application.ready_to_serve.is_set():
         self.set_header('Retry-After', '5')
         self.set_status(503, 'Not Ready')
         self.finish()

   def get(self):
      self.set_status(200)
      self.write(json.dumps({'status': 'ok'})
Before Run Callbacks

This set of callbacks is invoked after Tornado forks sub-processes (based on the number_of_procs setting) and before start() is called. Callbacks can safely access the IOLoop without causing the start() method to explode.

If any callback raises an exception, then the application is terminated before the IOLoop is started.

On Start Callbacks

This set of callbacks is invoked after Tornado forks sub-processes (using tornado.ioloop.IOLoop.spawn_callback()) and after start() is called.

Shutdown Callbacks

When the application receives a stop signal, it will run each of the callbacks before terminating the application instance. Exceptions raised by the callbacks are simply logged.

Testing your Application

The SprocketsHttpTestCase class makes it simple to test sprockets.http based applications. It knows how to call the appropriate callbacks at the appropriate time. Use this as a base class in place of AsyncHTTPTestCase and modify your get_app method to set self.app.

class sprockets.http.testing.SprocketsHttpTestCase(methodName: str = 'runTest')[source]

Test case that correctly runs a sprockets.http.app.Application.

This test case correctly starts and stops a sprockets.http Application by calling the start() and stop() methods during setUp and tearDown.

app

You are required to set this attribute in your get_app() implementation.

get_app()[source]

Override this method to create your application.

Make sure to set self.app before returning.

setUp()[source]

Hook method for setting up the test fixture before exercising it.

The sprockets.http application is started by calling the start() method after the application is created.

shutdown_limit = 0.25

Maximum number of seconds to wait for the application to shut down.

tearDown()[source]

Hook method for deconstructing the test fixture after exercising it.

The sprockets.http application is fully stopped by calling the stop() and running the ioloop before stopping the ioloop. The shutdown timing is configured using the shutdown_limit and wait_timeout variables.

wait_timeout = 0.05

Number of seconds to wait between checking for pending callbacks.

Response Logging

Version 0.5.0 introduced the sprockets.http.mixins module with two simple classes - LoggingHandler and ErrorLogger. Together they ensure that errors emitted from your handlers will be logged in a consistent manner. All too often request handlers simply call write_error to report a failure to the caller with code that looks something like:

class MyHandler(web.RequestHandler):

   def get(self):
      try:
         do_something()
      except Failure:
         self.send_error(500, reason='Uh oh')
         return

This makes debugging an application fun since your caller generally has more information about the failure than you do :/

By adding ErrorLogger into the inheritance chain, your error will be emitted to the application log as if you had written the following instead:

class MyHandler(web.RequestHandler):
   def initialize(self):
      super().initialize()
      self.logger = logging.getLogger('MyHandler')

   def get(self):
      try:
         do_something()
      except Failure:
         self.logger.error('%s %s failed with %d: %s',
                           self.request.method, self.request.uri,
                           500, 'Uh oh')
         self.send_error(500, reason='Uh oh')
         return

It doesn’t look like much, but the error reporting is a little more interesting than that – 4XX errors are reported as a warning, exceptions will include the stack traces, etc.

class sprockets.http.mixins.LoggingHandler[source]

Add self.logger.

Mix this into your inheritance chain to add a logger attribute unless one already exists.

logger

Instance of logging.Logger with the same name as the class.

class sprockets.http.mixins.ErrorLogger[source]

Log a message in send_error.

Mix this class into your inheritance chain to ensure that errors sent via tornado.web.RequestHandler.send_error() and tornado.web.RequestHandler.write_error() are written to the log.

Standardized Error Response Documents

Version 0.5.0 also introduced the ErrorWriter class which implements write_error to provide a standard machine-readable document response instead of the default HTML response that Tornado implements. If ContentMixin is being used as well, write_error will use send_response() to send the document, otherwise it is sent as JSON.

class sprockets.http.mixins.ErrorWriter[source]

Write error bodies out consistently.

Mix this class in to your inheritance chain to include error bodies in a machine-readable document format.

If ContentMixin is also in use, it will send the error response with it, otherwise the response is sent as a JSON document.

The error document has three simple properties:

type

This is the type of exception that occurred or null. It is only set when write_error() is invoked with a non-empty exc_info parameter. In that case, it is set to the name of the first value in the tuple; IOW, exc_type.__name__.

message

This is a description of the error. If exception info is present, then the stringified exception value is used as the message (e.g., str(exc_value)); otherwise, the HTTP reason will be used. If a custom reason is not present, then the standard HTTP reason phrase is used. In the final case of a non-standard HTTP status code with neither an exception nor a custom reason, the string Unknown will be used.

traceback

If the application is configured to serve tracebacks and the error was caused by an exception (based on exc_info kwarg), then this is the formatted traceback as an array of strings returned from traceback.format_exception(). Otherwise, this property is set to null.

Internal Interfaces

Run a Tornado HTTP service.

  • Runner: encapsulates the running of the application

  • RunCommand: distutils command to runs an application

class sprockets.http.runner.RunCommand(dist)[source]

Simple distutils.Command that calls sprockets.http.run()

This is installed as the httprun distutils command when you install the sprockets.http module.

finalize_options()[source]

Set final values for all the options that this command supports. This is always called as late as possible, ie. after any option assignments from the command-line or from other commands have been done. Thus, this is the place to code option dependencies: if ‘foo’ depends on ‘bar’, then it is safe to set ‘foo’ from ‘bar’ as long as ‘foo’ still has the same value it was assigned in ‘initialize_options()’.

This method must be implemented by all command classes.

initialize_options()[source]

Set default values for all the options that this command supports. Note that these defaults may be overridden by other commands, by the setup script, by config files, or by the command-line. Thus, this is not the place to code dependencies between options; generally, ‘initialize_options()’ implementations are just a bunch of “self.foo = None” assignments.

This method must be implemented by all command classes.

run()[source]

A command’s raison d’etre: carry out the action it exists to perform, controlled by the options initialized in ‘initialize_options()’, customized by other commands, the setup script, the command-line, and config files, and finalized in ‘finalize_options()’. All terminal output and filesystem interaction should be done by ‘run()’.

This method must be implemented by all command classes.

class sprockets.http.runner.Runner(app, before_run=None, on_start=None, shutdown=None)[source]

HTTP service runner.

Parameters

app (tornado.web.Application) – the application to serve

This class implements the logic necessary to safely run a Tornado HTTP service inside of a docker container.

Usage Example

def make_app():
    return web.Application(...)

def run():
    server = runner.Runner(make_app())
    server.start_server()
    ioloop.IOLoop.instance().start()

The start_server() method sets up the necessary signal handling to ensure that we have a clean shutdown in the face of signals.

run(port_number, number_of_procs=0)[source]

Create the server and run the IOLoop.

Parameters
  • port_number (int) – the port number to bind the server to

  • number_of_procs (int) – number of processes to pass to Tornado’s httpserver.HTTPServer.start.

If the application’s debug setting is True, then we are going to run in a single-process mode; otherwise, we’ll let tornado decide how many sub-processes based on the value of the number_of_procs argument. In any case, the application’s before_run callbacks are invoked. If a callback raises an exception, then the application is terminated by calling sys.exit().

If any on_start callbacks are registered, they will be added to the Tornado IOLoop for execution after the IOLoop is started.

The following additional configuration parameters can be set on the httpserver.HTTPServer instance by setting them in the application settings: xheaders, max_body_size, max_buffer_size.

start_server(port_number, number_of_procs=0)[source]

Create a HTTP server and start it.

Parameters
  • port_number (int) – the port number to bind the server to

  • number_of_procs (int) – number of processes to pass to Tornado’s httpserver.HTTPServer.start.

If the application’s debug setting is True, then we are going to run in a single-process mode; otherwise, we’ll let tornado decide how many sub-processes to spawn.

The following additional configuration parameters can be set on the httpserver.HTTPServer instance by setting them in the application settings: xheaders, max_body_size, max_buffer_size.

stop_server()[source]

Stop the HTTP Server

class sprockets.http.app.Application(*args, **kwargs)[source]

Callback-aware version of tornado.web.Application.

Using this class instead of the vanilla Tornado Application class provides a clean way to customize application-level constructs such as connection pools.

Note that much of the functionality is implemented in CallbackManager.

log_request(handler)[source]

Customized access log function.

Parameters

handler (tornado.web.RequestHandler) –

class sprockets.http.app.CallbackManager(tornado_application, *args, **kwargs)[source]

Application state management.

This is where the core of the application wrapper actually lives. It is responsible for managing and calling the various application callbacks. Sub-classes are responsible for gluing in the actual tornado.web.Application object and the sprockets.http.runner module is responsible for starting up the HTTP stack and calling the start() and stop() methods.

runner_callbacks

dict of lists of callback functions to call at certain points in the application lifecycle. See before_run_callbacks, on_start_callbacks, and on_shutdown_callbacks.

Deprecated since version 1.4: Use the property callbacks instead of this dictionary. It will be going away in a future release.

property before_run_callbacks

List of synchronous functions called before the IOLoop is started.

The before_run callbacks are called after the IOLoop is created and before it is started. The callbacks are run synchronously and the application will exit if a callback raises an exception.

Signature: callback(application, io_loop)

property on_shutdown_callbacks

List of functions when the application is shutting down.

The on_shutdown callbacks are called after the HTTP server has been stopped. If a callback returns a tornado.concurrent.Future instance, then the future is added to the IOLoop.

Signature: callback(application)

property on_start_callbacks

List of asynchronous functions spawned before the IOLoop is started.

The on_start callbacks are spawned after the IOLoop is created and before it is started. The callbacks are run asynchronously via tornado.ioloop.IOLoop.spawn_callback() as soon as the IOLoop is started.

Signature: callback(application, io_loop)

start(io_loop)[source]

Run the before_run callbacks and queue to on_start callbacks.

Parameters

io_loop (tornado.ioloop.IOLoop) – loop to start the app on.

stop(io_loop, shutdown_limit=5.0, wait_timeout=1.0)[source]

Asynchronously stop the application.

Parameters
  • io_loop (tornado.ioloop.IOLoop) – loop to run until all callbacks, timeouts, and queued calls are complete

  • shutdown_limit (float) – maximum number of seconds to wait before terminating

  • wait_timeout (float) – number of seconds to wait between checks for pending callbacks & timers

Call this method to start the application shutdown process. The IOLoop will be stopped once the application is completely shut down or after shutdown_limit seconds.

property tornado_application

The underlying tornado.web.Application instance.

sprockets.http.app.wrap_application(application, before_run, on_start, shutdown)[source]

Wrap a tornado application in a callback-aware wrapper.

Parameters
  • application (tornado.web.Application) – application to wrap.

  • before_run (list|NoneType) – optional list of callbacks to invoke before the IOLoop is started.

  • on_start (list|NoneType) – optional list of callbacks to register with spawn_callback().

  • shutdown (list|NoneType) – optional list of callbacks to invoke before stopping the IOLoop

Returns

a wrapped application object

Return type

sprockets.http.app.Application

How to Contribute

Do you want to contribute fixes or improvements?

AWesome! Thank you very much, and let’s get started.

Quickstart Development Guide

Setup

code:

python3.10 -m venv env
pip install -r requires/development.txt
Testing

code:

coverage run
coverage report

Release History

2.4.0 (16 Mar 2022)

  • Add support for Python 3.10

  • Change the default access log format

2.3.0 (03 Feb 2022)

  • Added optional Sentry integration

2.2.0 (28 Sep 2020)

  • Change xheaders option to default to True

2.1.2 (15 Sep 2020)

  • Updated to support Python 3.9. asyncio.Task.all_tasks was removed so I switched to asyncio.all_tasks if it exists.

  • Deprecate calling sprockets.http.run with anything that isn’t a sprockets.app.Application instance.

2.1.1 (19 Feb 2020)

2.1.0 (9 Oct 2019)

2.0.1 (5 Mar 2019)

  • Include Tornado 6 in pin

2.0.0 (27 Nov 2018)

  • Add support for Tornado 5.0

  • Drop support for Tornado versions earlier than 5.0

  • Drop support for Python versions earlier than 3.5

  • Remove logging from the signal handler. Logger’s cannot safely be used from within signal handlers. See Thread Safety in the logging module documentation for details.

1.5.0 (29 Jan 2018)

  • Enable port reuse for Tornado versions newer than 4.3.

1.4.2 (25 Jan 2018)

  • Allow max_body_size and max_buffer_size to be specified on the http server.

1.4.1 (3 Jan 2018)

1.4.0 (29 Sep 2017)

  • Separate the concerns of running the application from the callback chains. The latter has been refactored into sprockets.http.app. This change is completely invisible to the outside world.

  • Officially deprecated the runner_callbacks application attribute.

1.3.3 (20 Sept 2016)

  • Include correlation-id in the structured log data when logging.

1.3.2 (19 Sept 2016)

  • Include the service and environment (if set) in the structured log data.

1.3.1 (16 Sept 2016)

  • Change the non-DEBUG log format to include structured data and a leading first byte for log level.

1.3.0 (11 Mar 2016)

  • Add httprun setup.py command.

  • Use declare_namespace to declare the sprockets namespace package.

  • Remove JSONRequestFormatter logging when not in debug mode

  • Remove sprockets.logging dependency

1.2.0 (11 Mar 2016)

  • Add support for the on_start callback.

  • Add support to wait for the completion of shutdown callbacks that return a future.

  • Adds new init params to runner.Runner for the three callback types

1.1.2 (23 Feb 2016)

  • Allow xheaders to be set in the application.settings.

1.1.1 (15 Feb 2016)

  • Delay grabbing the IOLoop instance until after fork.

1.1.0 (11 Feb 2016)

  • Add support for the before_run callback set.

1.0.2 (10 Dec 2015)

  • Add log_config parameter to sprockets.http.run

1.0.1 (20 Nov 2015)

  • Add support for sprockets.mixins.mediatype in sprockets.http.mixins.ErrorWriter

1.0.0 (20 Nov 2015)

  • Add sprockets.http.mixins.LoggingHandler

  • Add sprockets.http.mixins.ErrorLogger

  • Add sprockets.http.mixins.ErrorWriter

0.4.0 (24 Sep 2015)

  • Run callbacks from application.runner_callbacks['shutdown'] when the application is shutting down.

  • Add number_of_procs parameter to sprockets.http.

0.3.0 (28 Aug 2015)

  • Install sprockets.logging.tornado_log_function() as the logging function when we are running in release mode

0.2.2 (23 Jul 2015)

  • Fixed requirements management… why is packaging so hard?!

0.2.1 (23 Jul 2015)

  • Corrected packaging metadata

0.2.0 (22 Jul 2015)