Package: pyruvate
Pyruvate WSGI serverPyruvate is a fast, multithreadedWSGIserver implemented inRust.FeaturesNon-blocking read/write usingmioRequest parsing usinghttparserust-cpythonbased Python interfaceWorker pool based onthreadpoolPasteDeployentry pointInstallationIf you are on Linux and use a recent Python version,$ pip install pyruvateis probably all you need to do.Binary Packagesmanylinux_2_17andmusllinux_1_1wheels are available for thex86_64architecture and active Python 3 versions (currently 3.7-3.11).Source InstallationOn macOS or if for any other reason you want to install the source tarball (e.g. usingpip install –no-binary) you will need toinstall Rustfirst.Development InstallationInstallRustInstall and activate a Python 3 (>= 3.7)virtualenvInstallsetuptools_rustusing pip:$ pip install setuptools_rustInstall Pyruvate, e.g. using pip:$ pip install -e git+https://gitlab.com/tschorr/pyruvate.git#egg=pyruvate[test]Using Pyruvate in your WSGI applicationFrom Python using a TCP portA hello world WSGI application using Pyruvate listening on 127.0.0.1:7878 and using 2 worker threads looks like this:importpyruvatedefapplication(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)From Python using a Unix socketA hello world WSGI application using Pyruvate listening on unix:/tmp/pyruvate.socket and using 2 worker threads looks like this:importpyruvatedefapplication(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,"/tmp/pyruvate.socket",2)Using PasteDeployAgain 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 = 2Configuration OptionssocketRequired: The TCP socket Pyruvate should bind to.Pyruvatealso supportssystemd socket activationIf you specifyNoneas the socket value,Pyruvatewill try to acquire a socket bound bysystemd.workersRequired: Number of worker threads to use.async_loggingOptional: Log asynchronously using a dedicated thread.
Defaults toTrue.chunked_transferOptional: Whether to use chunked transfer encoding if no Content-Length header is present.
Defaults toFalse.keepalive_timeoutOptional: Specify a timeout in integer seconds for keepalive connection.
The persistent connection will be closed after the timeout expires.
Defaults to 60 seconds.max_number_headersOptional: Maximum number of request headers that will be parsed.
If a request contains more headers than configured, request processing will stop with an error indicating an incomplete request.
The default is 32 headersmax_reuse_countOptional: Specify how often to reuse an existing connection.
Setting this parameter to 0 will effectively disable keep-alive connections.
This is the default.qmon_warn_thresholdOptional: Warning threshold for the number of requests in the request queue.
A warning will be logged if the number of queued requests reaches this value.
The value must be a positive integer.
The default isNonewhich disables the queue monitor.send_timeoutOptional: Time to wait for a client connection to become available for
writing after EAGAIN, in seconds. Connections that do not receive data
within this time are closed.
The value must be a positive integer.
The default is 60 seconds.LoggingPyruvate uses the standardPython logging facility.
The logger name ispyruvate.
See the Python documentation (logging,logging.config) for configuration options.Example ConfigurationsDjangoAfter installing Pyruvate in your Django virtualenv, create or modify yourwsgi.pyfile (one worker listening on 127.0.0.1:8000):importosimportpyruvatefromdjango.core.wsgiimportget_wsgi_applicationos.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.pyOverride settings by using theDJANGO_SETTINGS_MODULEenvironment variable when appropriate.
Tested withDjango 4.1.x, 3.2.x, 2.2.x.MapProxyFirst create a basic WSGI configuration following theMapProxy deployment documentation.
Then modifyconfig.pyso it is using Pyruvate (2 workers listening on 127.0.0.1:8005):importos.pathimportpyruvatefrommapproxy.wsgiappimportmake_wsgi_appapplication=make_wsgi_app(r'/path/to/mapproxy/mapproxy.yaml')pyruvate.serve(application,"127.0.0.1:8005",2)Start from your virtualenv:$ python config.pyTested withMapproxy 1.15.x, 1.13.x, 1.12.x.PloneUsingpipAfter installing Pyruvate in your Plone virtualenv, change theserversection in yourzope.inifile (located ininstance/etcif you are usingmkwsgiinstanceto create the instance):[server:main]
use = egg:pyruvate#main
socket = localhost:7878
workers = 2Usingzc.buildoutUsingzc.buildoutandplone.recipe.zope2instanceyou can define an instance part using Pyruvate’sPasteDeployentry point:[instance]
recipe = plone.recipe.zope2instance
http-address = 127.0.0.1:8080
eggs =
    Plone
    pyruvate
wsgi-ini-template = ${buildout:directory}/templates/pyruvate.ini.inTheserversection of the template provided with thewsgi-ini-templateoption should look like this (3 workers listening onhttp-addressas specified in the buildout[instance]part):[server:main]
use = egg:pyruvate#main
socket = %(http_address)s
workers = 3There is a minimal buildout example configuration for Plone 5.2 in theexamples directoryof the package.Tested withPlone 6.0.x, 5.2.x.PyramidInstall Pyruvate in your Pyramid virtualenv using pip:$ pip install pyruvateModify the server section in your.inifile to use Pyruvate’sPasteDeployentry point (listening on 127.0.0.1:7878 and using 5 workers):[server:main]
use = egg:pyruvate#main
socket = 127.0.0.1:7878
workers = 5Start your application as usual usingpserve:$ pserve path/to/your/configfile.iniTested withPyramid 2.0, 1.10.x.RadicaleYou can find an example configuration forRadicalein theexamples directoryof the package.
Tested withRadicale 3.1.8.Nginx settingsLike other WSGI servers Pyruvate should be used behind a reverse proxy, e.g. Nginx:....
location / {
    proxy_pass http://localhost:7878;
    ...
}
...Nginx doesn’t use keepalive connections by default so you will need tomodify your configurationif you want persistent connections.Changelog1.2.2 (2023-07-02)Document Unix Domain Socket usage (#27)Provide legacy manylinux wheel names (#26)1.2.1 (2022-12-22)Track and remove unfinished responses that did not otherwise error (#23)Build musllinux_1_1 wheels (#24)1.2.0 (2022-10-26)Support Python 3.11 and discontinue Python 3.6, switch to manylinux2014 for building wheels (#19)Add a request queue monitor (#17)Remove blocking worker (#18)Improve parsing of Content-Length header (#20)1.1.4 (2022-04-19)Fix handling of empty list responses (#14)Support hostnames in socket addresses (#15)1.1.3 (2022-04-11)Simplify response writing and improve performance (#12)Improve signal handling (#13)1.1.2 (2022-01-10)Migrate to Rust 2021Use codecov binary uploaderAdd CONTRIBUTING.rstFixed: The wrk benchmarking tool could make pyruvate hang when there is no Content-Length header (#11)1.1.1 (2021-10-12)Support Python 3.101.1.0 (2021-09-14)Refactor FileWrapper and improve its performanceIncrease the default maximum number of headersAddRadicaleexample configurationUpdate development status1.0.3 (2021-06-05)HEAD request: Do not complain about content length mismatch (#4)More appropriate log level for client side connection termination (#5)Simplify request parsing1.0.2 (2021-05-02)Close connection and log an error in the case where the actual content length is
less than the Content-Length header provided by the applicationFix readme1.0.1 (2021-04-28)Fix decoding of URLs that contain non-ascii charactersRaise Python exception when response contains objects other than bytestrings
instead of simply logging the error.1.0.0 (2021-03-24)Improve query string handling0.9.2 (2021-01-30)Better support for HTTP 1.1 Expect/ContinueImprove documentation0.9.1 (2021-01-13)Improve GIL handlingPropagate worker thread name to Python loggingDo not report broken pipe as errorPasteDeploy entry point: fix option handling0.9.0 (2021-01-06)Reusable connectionsChunked transfer-encodingSupport macOS0.8.4 (2020-12-12)Lower CPU usage0.8.3 (2020-11-26)Clean wheel build directoriesFix some test isolation problemsRemove a println0.8.2 (2020-11-17)Fix blocksize handling for sendfile caseFormat unix stream peer addressUse latest mio0.8.1 (2020-11-10)Receiver in non-blocking worker must not block when channel is empty0.8.0 (2020-11-07)Logging overhaulNew async_logging optionSome performance improvementsSupport Python 3.9Switch to manylinux2010 platform tag0.7.1 (2020-09-16)Raise Python exception when socket is unavailableAdd Pyramid configuration example in readme0.7.0 (2020-08-30)Use Python loggingDisplay server info on startupFix socket activation for unix domain sockets0.6.2 (2020-08-12)Improved loggingPasteDeploy entry point now also uses at most 24 headers by default0.6.1 (2020-08-10)Improve request parsingIncrease default maximum number of headers to 240.6.0 (2020-07-29)Support unix domain socketsImprove sendfile usage0.5.3 (2020-07-15)Fix testing for completed sendfile call in case of EAGAIN0.5.2 (2020-07-15)Fix testing for completed response in case of EAGAINCargo update0.5.1 (2020-07-07)Fix handling of read eventsFix changelogCargo update‘Interrupted’ error is not a todoRemove unused code0.5.0 (2020-06-07)Add support for systemd socket activation0.4.0 (2020-06-29)Add a new worker that does nonblocking writeAdd default argumentsAdd option to configure maximum number of request headersAdd Via header0.3.0 (2020-06-16)Switch to rust-cpythonFix passing of tcp connections to worker threads0.2.0 (2020-03-10)Added some Python tests (using py.test and tox)Improve handling of HTTP headersRespect content length header when using sendfile0.1.0 (2020-02-10)Initial release