Skip to main content

Tools for running pyramid using asyncio.

Project description

Introduction

A library for leveraging pyramid infrastructure asynchronously using the new asyncio.

See Approach for why I made this library.

Since this library is built on relatively new technology, it is not intended for production use.

Getting Started

Aiopyramid includes a scaffold that creates a “hello world” application, check it out. The scaffold is designed to work with either gunicorn via a custom worker or uWSGI via the uWSGI asyncio plugin.

For example:

pip install aiopyramid gunicorn
pcreate -s aio_starter <project>
cd <project>
python setup.py develop
pserve development.ini

There is also a websocket scaffold aio_websocket for those who basic tools for setting up a websocket server.

Features

Aiopyramid provides tools for making web applications with pyramid and aysncio. It will not necessarily make your application run faster Instead, it gives you some tools and patterns to build an application on asynchronous servers. Bear in mind that you will need to use asynchronous libraries for io where appropriate.

Asynchronous Views

Aiopyramid provides three view mappers for calling view callables:

  • CoroutineOrExecutorMapper maps views to coroutines or separate threads

  • CoroutineMapper maps views to coroutines

  • ExecutorMapper maps views to separate threads

When you include Aiopyramid, the default view mapper is replaced with the CoroutineOrExecutorMapper which detects whether your view_callable is a coroutine and does a yield from to call it asynchronously. If your view_callable is not a coroutine, it will run it in a separate thread to avoid blocking the thread with the main loop. Asyncio is not thread-safe, so you will need to guarantee that either in memory resources are not shared between view callables running in the executor or that such resources are synchronized.

This means that you should not need to change existing views. Also, it is possible to restore the default view mapper, but note that this will mean that coroutine views that do not specify CoroutineMapper as their view mapper will fail.

Asynchronous Tweens

Pyramid allows you to write tweens which wrap the request/response chain. Most existing tweens expect those tweens above and below them to run synchronously. Therefore, if you have a tween that needs to run asynchronously (e.g. it looks up some data from a database for each request), then you will need to write that tween so that it can wait without other tweens needing explicitly to yield from it. An example of this pattern is provided in aiopyramid.tweens.

Asynchronous Traversal

When using pyramid’s traversal view lookup, it is often the case that you will want to make some io calls to a database or storage when traversing via __getitem__. Aiopyramid provides a custom traverser that allows for __getitem__ to be an asyncio coroutine. To use the traverser simply follow the pyramid documentation like so.

from aiopyramid.traversal import AsyncioTraverser

...

config.registry.registerAdapter(AsyncioTraverser, (Interface,), ITraverser)

Server Support

Aiopyramid supports both asynchronous gunicorn and the uWSGI asyncio plugin.

Example gunicorn config:

[server:main]
use = egg:gunicorn#main
host = 0.0.0.0
port = 6543
worker_class = aiopyramid.gunicorn.worker.AsyncGunicornWorker

Example uWSGI config:

[uwsgi]
http-socket = 0.0.0.0:6543
workers = 1
plugins =
    asyncio = 50
    greenlet

Websockets

Aiopyramid provides additional view mappers for handling websocket connections with either gunicorn or uWSGI. Websockets with gunicorn use the websockets library whereas with uWSGI has native websocket support. In either case, the interface is the same.

A function view callable for a websocket connection follows this pattern:

@view_config(mapper=<WebsocketMapper>)
def websocket_callable(ws):
    # do stuff with ws

The ws argument passed to the callable has three methods for communicating with the websocket: recv, send, and close, which correspond to similar methods in the websockets library. A websocket connection that echoes all messages using gunicorn would be:

from pyramid.view import view_config
from aiopyramid.websocket.config import WebsocketMapper

@view_config(route_name="ws", mapper=WebsocketMapper)
def echo(ws):
    while True:
        message = yield from ws.recv()
        if message is None:
            break
        yield from ws.send(message)

Aiopyramid also provides a view callable class WebsocketConnectionView that has on_message, on_open, and on_close callbacks. Class-based websocket views also have a send convenience method, otherwise the underyling ws may be accessed as self.ws. Simply extend WebsocketConnectionView specifying the correct view mapper for your server either via the __view_mapper__ attribute or the view_config decorator. The above example could be rewritten in a larger project, this time using uWSGI, as follows:

from pyramid.view import view_config
from aiopyramid.websocket.view import WebsocketConnectionView
from aiopyramid.websocket.config import UWSGIWebsocketMapper

from myproject.resources import MyWebsocketContext

class MyWebsocket(WebsocketConnectionView):
    __view_mapper__ = UWSGIWebsocketMapper


@view_config(context=MyWebsocketContext)
class EchoWebsocket(MyWebsocket):

    def on_message(self, message):
        yield from self.send(message)

uWSGI Special Note

Aiopyramid uses a special WebsocketClosed exception to disconnect a greenlet after a websocket has been closed. This exception will be visible in log ouput when using uWSGI. In order to squelch this message, wrap the wsgi application in the ignore_websocket_closed middleware in your application’s constructor like so:

from aiopyramid.websocket.helpers import ignore_websocket_closed

...
app = config.make_wsgi_app()
return ignore_websocket_closed(app)

Tests

Core functionality is backed by tests. The recommended test runner is pytest. To run the tests, grab the code on github, install pytest, and run it like so:

git clone https://github.com/housleyjk/aiopyramid
cd aiopyramid
pip install pytest
py.test

Approach

TL;DR I chose to make a new asyncio extension because I wanted to support uWSGI and existing non-asynchronous extensions such as pyramid_debugtoolbar.

Aiopyramid was originally based on pyramid_asyncio, but I followed a different approach for the following reasons:

  • The pyramid_asyncio library depends on patches made to the pyramid router that prevent it from working with the uWSGI asyncio plugin.

  • The pyramid_asyncio rewrites various parts of pyramid, including tweens, to expect coroutines from pyramid internals.

On the other hand aiopyramid is designed to follow these principles:

  • Aiopyramid should extend pyramid through existing pyramid mechanisms where possible.

  • Asynchronous code that should be wrapped so that existing callers can treat it as synchronous code.

The first principle is one of the reasons why I used view mappers rather than patching the router. View mappers are a mechanism already in place to handle how views are called. We don’t need to rewrite vast parts of pyramid to run a view in the asyncio event loop. Yes, pyramid is that awesome.

The second principle is what allows aiopyramid to support existing extensions. The goal is to isolate asynchronous code from code that expects a synchronous response. Those methods that already exist in pyramid should not be rewritten as coroutines because we don’t know who will try to call them as regular methods.

Most of the pyramid framework does not run io blocking code. So, it is not actually necessary to change the framework itself. Instead we need tools for making application code asynchronous. It should be possible to run an existing url dispatch application asynchronously without modification. Blocking code will naturally end up being run in a separate thread via the asyncio run_in_executor method. This allows you to optimize only those highly concurrent views in your application or add in websocket support without needing to refactor all of the code.

It is easy to simulate a multithreaded server by increasing the number of threads available to the executor.

For example, include the following in your application’s constructor:

import
from concurrent.futures import ThreadPoolExecutor
...
asyncio.get_event_loop().set_default_executor(ThreadPoolExecutor(max_workers=150))

It should be noted that Aiopyramid is not thread-safe by nature. You will need to ensure that in memory resources are not modified by multiple non-coroutine view callables. For most existing applications, this should not be a problem.

Changes

0.2.4 (2014-10-06)

  • Fix issue with gunicorn websockets

  • Fix issue with class-based view mappers

0.2.3 (2014-10-01)

  • Fix issue with synchronize

0.2.2 (2014-09-30)

  • Update example tween to work with gunicorn

  • Add kwargs support to helpers

  • Add tox for testing

  • Add decorator synchronize for wrapping coroutines

  • Refactored mappers and tween example to use synchronize

  • Bug fixes

0.2.1 (2014-09-15)

  • Update scaffold example tests

  • Add test suite

  • Update README

0.2.0 (2014-09-01)

  • Update README

  • added websocket mappers for uwsgi and gunicorn

  • added websocket view class

0.1.2 (2014-08-02)

  • Update MANIFEST.in

0.1.0 (2014-08-01)

  • Update README ready for release

  • Added asyncio traverser (patched from ResourceTreeTraverser)

  • Added custom gunicorn worker

  • Fix issue with uwsgi and executor threads

  • Update starter scaffold

0.0.3 (2014-07-30)

  • Moving to an extension-based rather than patched-based approach

  • removed most code based on pyramid_asyncio except testing and scaffolds

  • added view mappers for running views in asyncio

  • added example tween that can come before or after synchronous tweens

0.0.2 (2014-07-22)

  • Removed Gunicorn specific code

  • disabled excview_tween_factory

  • made viewresult_to_response a coroutine

  • added dummy code for testing with uwsgi

0.0.1 (2014-07-22)

  • Migrated from pyramid_asyncio (Thank you Guillaume)

  • Removed worker.py and Gunicorn dependency

  • Added greenlet dependency

  • Changed contact information in setup.py

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aiopyramid-0.2.4.tar.gz (33.8 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page