Custom Search

Thursday, March 27, 2014

Getting Started with Python WSGI and Paste Deployment

WSGI and Paste Deployment
#####
################
http://docs.repoze.org/moonshining/index.html

The Python web development world has adopted on WSGI as an interoperability standard for Python web servers, applications, and middleware components. This tutorial examines that specification in deatil, and shows practial examples of using WSGI to improve development productivity, integrate applications, and solve various deployment problems.

1)
Example Application
=================

http://docs.repoze.org/moonshining/pep333.html#example-application

* Simplest possible WSGI application

##SM:Our Application
def simple_app(environ, start_response):
    """
    ##SM:Simplest Possible Application Object.
    ##SM:Our Application.
    """
    ##SM:HTTP status for the response
    status = '200 OK'
    ##SM:Construct HTTP response headers
    response_headers = [('Content-Type', 'text/plain')]
    start_response(status, response_headers)
    return ['Hello world!\n']

##SM:Server to serve Our Application "simple_app"
##Run server and serve the Application
if __name__ == '__main__':
    ##SM:Import http webserver from paste tool
    from paste import httpserver
    ##SM:Run our application "simple_app" under the Paste webserver
    httpserver.serve(simple_app, host='127.0.0.1', port='8080')

##Flow
Http REQUEST --> WSGI Server creates "environ" and "start_response" -->
calls Application (Example:simple_app) or Application's "__call__" if Application is an instance of a class -->
calls ``start_response`` with status and headers --> returns iterable

2)
Architecture
=============

a)
Architecture of Application
----------------------------
http://docs.repoze.org/moonshining/pep333.html#architecture

Any callable with the following signature:

def __call__(environ, start_response):
    """
    Return an iterable.
    Here "environ" is the WSGI environment dictionary.
    Normally, call 'start_response' with a status and headers before returning.
    """
* OR one like in our simple example "def simple_app(environ, start_response):".

##Flow
Http REQUEST --> WSGI Server creates "environ" and "start_response" -->
calls Application (Example:simple_app) or Application's "__call__" if Application is an instance of a class -->
calls ``start_response`` with status and headers --> returns iterable

b)
Architecture of Server
------------------------------
Must populate the "environ" dictionary, and provide the "start_response" callback.


WSGI Environment ("environ" dictionary)
=======================================
http://docs.repoze.org/moonshining/pep333.html#wsgi-environment

The "environ" dictionary passed by the server is normally a copy of "os.environ" with the standard CGI keys:

* SCRIPT_NAME: the “base” of the URL, representing the root of the application.
* PATH_INFO: the remainder of the URL, to be interpreted by the application.

The environ also contains addtional, WSGI-specific keys, of which the most important are:
* "wsgi.input": represents the body / payload of the request.
* "wsgi.errors": represents a stream to which error logging may be done.
* "wsgi.url_scheme": is typically either “http” or “https”.

The "start_response" Callback
===============================
http://docs.repoze.org/moonshining/pep333.html#the-start-response-callback

The start_response callback takes two arguments, conventionally named “status” and “headers”:
The first argument is a string containing the contents of the HTTP response status line, e.g. '200 OK'.
The second argument is a list of two-tuples representing HTTP response headers, e.g. [('Content-Type', 'text/html'), ('Content-Length', '15')]

3)
Middleware
============

http://docs.repoze.org/moonshining/pep333.html#middleware

A WSGI middleware component is one which plays both the role of of the application and the role of the server: the “upstream” server (Eg:Paste httpserver or Apache webserver) calls it, passing the "environ" and "start_response" arguments, and expects it to return the iterable response body. The middleware component in turn calls “downstream” component (Eg:Our Application), perhaps mutating/change "environ" first, or replacing the "start_response" with another callable. The middleware component may also intercept the returned iterator and transform or replace it, and may add exception handling.

* In computer networking, upstream server refers to a server that provides service to another server. In other words, upstream server is a server that is located higher in the hierarchy of servers.

a)
Example Middleware
---------------------------
http://docs.repoze.org/moonshining/pep333.html#example-middleware

This example middleware component or class filters the output of the “downstream” application (Our Application), converting it all to lowercase:

class Caseless:
    """
        Creates a middleware as class.
        Class as a middleware component.
    """
    def __init__(self, app):
        """
        Save the “downstream” application (Out Application) as an attribute "self.app".
        This middelware should be created before the application starts serving requests.
        """
        self.app = app

    def __call__(self, environ, start_response):
        """
            WSGI Architecture for Application.
            WSGI application interface.
            So this will act as an application.
        """
        ##Call Out Application and convert result to lowercase and return
        for chunk in self.app(environ, start_response):
            yield chunk.lower()

b)
Add middleware to the simplest application "simple_app" created before
--------------------------------------------------------------------------------------------
http://docs.repoze.org/moonshining/pep333.html#exercise-add-middleware-to-the-simplest-application

##Run server and serve the Application "simple_app"
if __name__ == '__main__':
    from paste import httpserver
    from simplest import simple_app
    ##Calls the middleware "Caseless"
    httpserver.serve(Caseless(simple_app), host='127.0.0.1', port='8080')

c)
Pipelines or Chaining Middleware together
-----------------------------------------------------------
http://docs.repoze.org/moonshining/pep333.html#pipelines-chaining-middleware-together

Middleware components can be chained together to form a WSGI “pipeline.”

##Flow
Http REQUEST --> WSGI Server creates "environ" and "start_response" -->
Middleware A's ``__call__`` ---> Middleware B's ``__call__`` --> Middleware C's ``__call__`` -->
Application's ``__call__`` --> calls ``start_response`` with status and headers --> returns iterable

d)
Example of Pipelining or Chaining Middleware
--------------------------------------------------------------
http://docs.repoze.org/moonshining/pep333.html#exercise-chaining-middleware

##Our new Application
def green(environ, start_response):
    """Simplest possible application object"""
    status = '200 OK'
    response_headers = [('Content-Type', 'text/plain')]
    start_response(status, response_headers)
    return ['%s\n' % environ.get('GREETING', 'Hello world!')]

##New Middleware
def greetingSetter(app):
    """
        Creates a middleware as closure or nested function or decorator.
    """
    def _curried(environ, start_response):
        environ['GREETING'] = "Hi Saju.."
        return app(environ, start_response)
    return _curried

##Run server and serve the Application "green"
if __name__ == '__main__':
    from paste import httpserver
    from caseless import Caseless
    ##Calls the middlewares "Caseless" and "greetingSetter".
    httpserver.serve(Caseless(greetingSetter(green)),
                        host='127.0.0.1', port='8080')

4)
Making WSGI Configuration Declarative using .ini file: PasteDeploy
==================================================

http://docs.repoze.org/moonshining/tools/paste.html#making-wsgi-configuration-declarative-pastedeploy

In the previous examples, we created the various components (middlewares, our applications) and assembled them to form the WSGI pipeline in the if __name__ == "__main__" section of our applications. PasteDeploy allows us to make drive that process from an INI-style configuration file, rather than from Python code. The configuration file defines the WSGI server (paste.httpserver or Apache) to use, as well as one or more applications (our applications, Example:simple_app, green), as well as the middleware filters and pipelines which compose them with applications.


a)
Example of Configuring the Simplest Application in .ini file
------------------------------------------------------------------------------

* Each section of the configuration file defines a different component like middlware and our Apllications.

##Section to configure Our Applications in .ini file
[app:main]
paste.app_factory = my_module:app_factory
name = Phred
greeting = Wilkommen

* [app:main] --> the app: prefix, used to define a WSGI application endpoint. The name "main" makes this application the default for this file.
* Here the name ":main" is important since it make that section in the .ini file as the default/main application.
* paste.app_factory = my_module:app_factory --> tells PasteDeploy to look up the function app_factory in the module my_module, and call it to get the application.
* name = Phred and greeting = Wilkommen --> provide configuration values to be are passed to the factory method "my_module:app_factory".

b)
Loading the Configuration from .ini file
----------------------------------------------------
"PasteDeploy" provides two APIs for loading a configuration file:

* "paste.deploy.appconfig" loads the configuration for a given application from the file (.ini file) and returns it as a dictionary, merging in any supplied “global” configuration.
* "paste.deploy.loadapp" instantiates and return a given application from the file (.ini file), merging in any supplied “global” configuration.

c)
Example of Configuring the Our Application in .ini file
----------------------------------------------------------------------
http://docs.repoze.org/moonshining/tools/paste.html#exercise-configuring-an-application

##Our Application "OurApplication.py"
class OurApplication:
    def __init__(self, name, greeting):
        self.name = name
        self.greeting = greeting
    def __call__(self, environ, start_response):
        status = '200 OK'
        response_headers = [('Content-Type', 'text/plain')]
        start_response(status, response_headers)
        return ['%s, %s!\n' % (self.greeting, self.name)]

##factory method to get Our Application via .ini file
def app_factory(global_config, name='Johnny', greeting='Howdy'):
    return OurApplication(name, greeting)


* We define both the application (OurApplication) and the factory for the application (app_factory). The factory is necessary to permit passing in values from the configuration file.


##Add a configuration file, configured.ini
[app:main]
paste.app_factory = my_module:app_factory
name = Phred
greeting = Wilkommen

##Code which drives the application from the configuration file configured.ini
if __name__ == '__main__':
    from paste import httpserver
    from paste.deploy import loadapp
    httpserver.serve(loadapp('config:configured.ini', relative_to='.'),
                     host='127.0.0.1', port='8080')

* This file doesn’t import "OurApplication.py" directly, but instead allows "PasteDeploy" to handle that process as part of the configuration. We can therefore tweak/change the configuration (.ini file) to use a different module or factory function, as well as providing configuration values, without changing the software.

Example of Configuring Middleware in .ini file
-------------------------------------------------------------
http://docs.repoze.org/moonshining/tools/paste.html#example-configuring-middleware

* Under "PasteDeploy", middleware components are called “filters”, and can be configured via config file sections using the filter: prefix.

##Section to configure Our Middleware in .ini file
[filter:caseless]
filter_app_factory = my_module:filter_factory


* [filter:caseless] --> Defines the filter/middleware. by default, no filters are included: they must be explicitly configured.
* filter_app_factory = my_module:filter_factory --> Tells "PasteDeploy" to look up the function "filter_factory" in the module my_module, and call it to get the filter/middleware.
* Extra configuration values will be passed to the filter/middeware factory "my_module:filter_factory".

##Example of filter/middelware factory in "my_module.py"
from caseless import Caseless
def filter_factory(app, global_config):
    return Caseless(app)

Example of Configuring the WSGI Pipeline (Chaining Middleware) in .ini file
-----------------------------------------------------------------------------
http://docs.repoze.org/moonshining/tools/paste.html#example-configuring-the-wsgi-pipeline

* PasteDeploy provides another section type, using the "pipeline:" prefix, which it uses to construct a pipline (pipeline section) as a special kind of WSGI application (new application by invoking all middlewares and applications in the order of they specified in the pipeline section on .ini file).

##Section to configure Our Applications in .ini file
[app:greeting]
paste.app_factory = configured:app_factory
name = Bharney
greeting = Bienvenu

##Section to configure filter/middleware in .ini file
[filter:caseless]
paste.filter_app_factory = configured:filter_factory

##Section to specify order of components (middlewares and applications) in which they call.
[pipeline:main]
pipeline =
    caseless
    greeting

* Here the name ":main" is important since it make that section in the .ini file as the default/main application.
* The items in the pipeline, point to other applications (caseless) or filters/middleware (greeting) in the .ini file.
* The Pipeline is configured “bottom up”, with each layer wrapping the one below it,
Example: Caseless(greeting)
httpserver.serve(Caseless(greeting), host='127.0.0.1', port='8080')

Example of Configuring the WSGI Server in .ini file
------------------------------------------------------------------
http://docs.repoze.org/moonshining/tools/paste.html#exercise-configuring-the-wsgi-server

We can use "PasteScript" in conjuction with "PasteDeploy" to replace our hand-rolled run server script.
First, add the following server configuration from the above example to the "configured.ini" file:

##Section to configure Server in .ini file
[server:main]
use = egg:Paste#http
host = 127.0.0.1
port = 8080

* [server:main] --> The section with the "server:" prefix, used to define a WSGI server. As with the application, the name "main" marks this server as the default.
* use = egg:Paste#http --> Tells PasteDeploy to look up the http “entry point” in the Paste egg, and call it to as a "factory" to get the server instance (like factory return application in app section).
* host and port --> additional configuration parameters which are passed to the server factory.

5)
paste
======

http://pythonpaste.org/
http://docs.repoze.org/moonshining/tools/paste.html
* Paste is a WSGI Developers’ Toolkit

2 comments:

  1. http://stackoverflow.com/questions/18952315/what-is-api-paste-ini-file-in-openstack
    http://pythonpaste.org/deploy/

    ReplyDelete