Pyruvate WSGI server
====================

.. image:: https://gitlab.com/tschorr/pyruvate/badges/master/pipeline.svg
   :target: https://gitlab.com/tschorr/pyruvate

.. image:: https://codecov.io/gl/tschorr/pyruvate/branch/master/graph/badge.svg
   :target: https://codecov.io/gl/tschorr/pyruvate

.. image:: http://img.shields.io/pypi/v/pyruvate.svg
   :target: https://pypi.org/project/pyruvate

Pyruvate is a `WSGI <https://www.python.org/dev/peps/pep-3333>`_ server written in `Rust <https://www.rust-lang.org/>`__.

Features
--------

* Asynchronous event loop using `mio <https://github.com/tokio-rs/mio>`_
* Request parsing using `httparse <https://github.com/seanmonstar/httparse>`_
* `rust-cpython <https://github.com/dgrunwald/rust-cpython>`_ based Python interface
* Worker pool based on `threadpool <https://github.com/rust-threadpool/rust-threadpool>`_
* `PasteDeploy <https://pastedeploy.readthedocs.io/en/latest/>`_ entry point

Development Installation
------------------------

* Install `Rust <https://doc.rust-lang.org/book/ch01-01-installation.html>`__
* Install and activate a Python 3 (> 3.5) `virtualenv <https://docs.python.org/3/tutorial/venv.html>`_
* Install `setuptools_rust <https://github.com/PyO3/setuptools-rust>`_ using pip:

    $ pip install setuptools_rust

* Install pyruvate, e.g. using pip:

    $ pip install -e git+https://gitlab.com/tschorr/pyruvate.git#egg=pyruvate[test]

Using Pyruvate in your WSGI application
---------------------------------------

From Python
+++++++++++

A hello world WSGI application using pyruvate listening on 127.0.0.1:7878 and using 2 worker threads looks like this::

    import pyruvate

    def application(environ, start_response):
        """Simplest possible application object"""
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        start_response(status, response_headers, None)
        return [b"Hello world!\n"]

    pyruvate.serve(application, "127.0.0.1:7878", 2)

Using PasteDeploy
+++++++++++++++++

Again listening on 127.0.0.1:7878 and using 2 worker threads::

    [server:main]
    use = egg:pyruvate#main
    socket = 127.0.0.1:7878
    workers = 2

Example Configurations
----------------------

Django 2
++++++++

After installing Pyruvate in your Django virtualenv, create or modify your `wsgi.py` file (1 worker listening on 127.0.0.1:8000)::

    import os
    import pyruvate

    from django.core.wsgi import get_wsgi_application

    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "your_django_application.settings")

    application = get_wsgi_application()

    pyruvate.serve(application, "127.0.0.1:8000", 1)

You can now start Django + Pyruvate with::

    $ python wsgi.py

Override settings by using the `DJANGO_SETTINGS_MODULE` environment variable when appropriate.
Tested with `Django 2.2.x <https://www.djangoproject.com/>`_.

Mapproxy
++++++++

Create or modify `config.py` (2 workers listening on 127.0.0.1:8005)::

    from logging.config import fileConfig
    import os.path
    import pyruvate
    fileConfig(r'/path/to/mapproxy/log.ini', {'here': os.path.dirname(__file__)})

    from mapproxy.wsgiapp import make_wsgi_app
    application = make_wsgi_app(r'/path/to/mapproxy/mapproxy.yml')

    pyruvate.serve(application, "127.0.0.1:8005", 2)

Start from your virtualenv::

    $ python config.py

Tested with `Mapproxy 1.12.x <https://mapproxy.org/>`_.

Plone 5.2
+++++++++

Using `zc.buildout <https://pypi.org/project/zc.buildout/>`_ and `plone.recipe.zope2instance <https://pypi.org/project/plone.recipe.zope2instance>`_ you can define an instance part using Pyruvate's `PasteDeploy <https://pastedeploy.readthedocs.io/en/latest/>` _entry point::

    [instance]
    recipe = plone.recipe.zope2instance
    http-address = 127.0.0.1:8080
    eggs =
        Plone
        pyruvate
    wsgi-ini-template = ${buildout:directory}/templates/pyruvate.ini.in

The `server` section of the template provided with the `wsgi-ini-template <https://pypi.org/project/plone.recipe.zope2instance/#advanced-options>`_ option should look like this (3 workers listening on `http-address` as specified in the buildout `[instance]` part)::

    [server:main]
    use = egg:pyruvate#main
    socket = %(http_address)s
    workers = 3

Tested with `Plone 5.2.x <https://plone.org/>`_.

Nginx settings
++++++++++++++

Like other WSGI servers pyruvate should be used behind a reverse proxy, e.g. Nginx::

    ....
    location / {
        proxy_pass http://localhost:7878;
        ...
    }
    ...
