IT技术

Paste.Deploy用法整理

http://pythonpaste.org/deploy/

Paste Deployment是用于发现和配置WSGI appliaction和server的系统。对于WSGI application用户提供一个单独的简单的函数(loadapp),用于从配置文件或者python egg中加载WSGI application。因为WSGI application提供了唯一的单独简单的访问入口,所以application 发布者并不需要暴露application的内部的实现细节。

那么系统管理员可在不了解任何python知识或者WSGI Application或者WSGI container的情况下进行安装和管理。Paste Deployment目前已经不需要Paste项目的其他部分支持,可以单独做一个package进行使用。Paste Deploy的发布在MIT license。

Paste Deploy目前已经通过了V1.5。

必须注意的地方:Paste Deploy最关键的地方是入口点entry points】(例如,paste.app_factory(入口点是python 项目发布时定义的,并且会指定发布的程序的入口点符合的protocol,关于protocol的种类,后面会有介绍))。Paste Deploy并不是这些入口点唯一的使用者,很多扩展可能直接去使用这些入口点,这样做可能比通过Paste Deploy使用更好。入口点是不能发生变化的,如果改变是必须的话,只能定义新的入口点。

1.1.1.    Paste.deploy 基本的使用

可以使用Paste Deployment用于加载WSGI的应用,大多数的Python框架都支持WSGI。主要的函数是paste.deploy.loadapp,基本使用方法如下:

1
2
from paste.deploy import loadapp
wsgi_app = loadapp('config:/path/to/config.ini')

目前支持两种URI模式config:和egg:

1.1.1.1. config:URIs
config:URIs——URI表示配置文件(不同于3.3.1节的配置文件,此处的配置文件是用于指示,所需要加载的application是哪些)的位置
配置文件的格式:

1
2
3
4
[section_name]
key = value
another key = a long value
 that extends over multiple lines

所有的value都是string类型,key和section name都是大小写敏感的,并且可以包含标点符号和空白符,但是头部和尾部的空白部分将会去掉。
如果出现需要断行的情况,只需要在下一行开始时是空白符即可
#和;表示注释。
配置文件中可能会定义多个application,定义方式如下:
application定义
在一个文件中可以定义多个application,每个application都应该有自己独立的section,即使仅有一个application存在,也需要写入到section中。
每个定义一个application的section都应该以app:前缀开头。main section进入[app:main]或[app]
指定运行application的Python code方式有两种:
第一种方法,指向其他的URI(前两个section)或name(后两个section)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[app:myapp]
use = config:another_config_file.ini#app_name
#调用其他的配置文件(可能定义了多个应用程序),并需要执行的应用程序的名称
 
# or any URI:
[app:myotherapp]
use = egg:MyApp
 
# or a callable from a module:
[app:mythirdapp]
use = call:my.project:myapplication
 
# or even another section:
[app:mylastapp]
use = myotherapp

Configuration
配置了use字段或者通过协议名称(上面提到的两种方法),就完成了配置文件最基本的配置,section中其他的key都会作为关键字参数传递给factory。下面看一个例子:

1
2
3
4
[app:blog]
use = egg:MyBlog
database = mysql://localhost/blogdb
blogname = This Is My Blog!

定义了一个application,名字为blog,运行的程序是MyBlog(他是一个egg),需要传递给MyBlog的参数是database和blogname。
这里有个比较有趣的例子,我们可以再定义另外一个section去覆盖上面的配置,如下:

1
2
3
[app:otherblog]
use = blog
blogname = The other face of my blog

“use = blog”表明使用的应用程序是section blog;
但是传递的参数是:blogname = The other face of my blog,覆盖掉了section blog的对应的配置信息。这种设置方式也可以定义在普通的配置文件中,即使用use = config:other_config_file 这种方式进行。
这种方式看起来好像是没什么意义的,只是从一个位置指向另一个位置,而实际上这对于增添和更新application是非常有效的。
Global Configuration
当多个application需要共享相同的配置时,我们可以采用上述的方法,使用其他的section,如果想要不同的value值,则重新定义覆盖掉它。但是这种方法,不能够添加其他的配置参数,即只能覆盖掉use的section中已经定义的部分。
所以可以采用[DEFUALT]方法定义全局的配置信息,[DEFUALT]的配置信息将会全部传入到所有的Application中,通常情况下,也可作为application的默认设置。application可以自主定义覆盖全局的默认值
为免去对所有application配置全局共享信息的麻烦可以使用[DEUALT]section,注意DEFUALT是大小写敏感的。

1
2
3
4
5
6
[DEFAULT]
admin_email = webmaster@example.com
 
[app:main]
use = ...
set admin_email = bob@example.com

set关键字使用,将会覆盖掉global的对应的配置。
第二种方法,精确定义application需要运行的Python code

1
2
[app:myapp]
paste.app_factory = myapp.modulename:app_factory

这种方式必须明确指定使用的protocol(此例中是paste.app_factory),value值表示需要import的内容。此例中是import myapp.modulename,然后检测app_factory执行
application的其他类型:composition application
composition application:类似与application,但是他是由多个application组成的,一个简单的例子就是URL mapper,例如,

1
2
3
4
5
6
7
8
9
10
11
[composite:main]
use = egg:Paste#urlmap
/ = mainapp
/files = staticapp
 
[app:mainapp]
use = egg:MyApp
 
[app:staticapp]
use = egg:Paste#static
document_root = /path/to/docroot

使用loadapp加载composition application, “main” 看起来就是和其他应用程序相同的,仅是一个application,但是根据配置文件的设置,“main”是可以访问其他的application的。
filter composition
可以采用不同的方式将filter应用到application中,通常情况下依赖于需要使用多少个filter和使用filter的顺序。
第一种方法:filter-with设置

1
2
3
4
5
6
7
[app:main]
use = egg:MyEgg
filter-with = printdebug
 
[filter:printdebug]
use = egg:Paste#printdebug
# and you could have another filter-with here, and so on...

另一种方法:filter-app pipeline
此外,还有两个特殊的section可以添加filter到application中去:[filter-app:…] [pipeline:…]
filter-app:定义一个filter,并且使用一个特殊的关键字next指向application,将application添加到filter中(这与[filter:…]很类似)
pipeline:当需要定义一系列的fiter时应用。在configuration中添加关键字pipeline,pipeline是一个filters的链表,以一个application结束,例如

1
2
3
4
5
[pipeline:main]
pipeline = filter1 egg:FilterEgg#filter2 filter3 app
 
[filter:filter1]
...

注:参考博文http://blog.csdn.net/sonicatnoc/article/details/6539716
里面描述filter调用的顺序,其实是一个嵌套的过程,调用的过程是不断的返回
filter1->filter2->filter3->……->end_app
执行的时候是
end_app->……->filter3->filter2->filter1

1.1.1.1. 配置文件简单例子
与Paste Deploy交互的主要方式是通过配置文件,一个非常简单配置文件的例子如下:

1
2
3
4
5
6
7
8
9
[app:main]
use = egg:PasteEnabledPackage
option1 = foo
option2 = bar
 
[server:main]
use = egg:PasteScript#wsgiutils
host = 127.0.0.1
port = 80

每个[]表示一个section,彼此间是独立的。
[type:name]
如[app:main],Paste Deploy认为是一个名字为main的application。
配置文件的内容都是以“name=value”的形式进行表示的,如果有多个value,那么就表示为“ name = value1#value2#value3”类似的模式。
[app:main]:appliction section
[server:main]:server section
[composite:…]用于调度多个application,compostion是复合的意思

use = egg:PasteEnabledPackage
表示名为PasteEnabledPackage的Package将被运行,这个Package的格式是egg的格式。而后面的字段,将作为关键字参数传递给PastEnablePackage
第2个section:[server:main],名为main的server
use = egg:PasteScript#wsgiutils
表示使用发布的egg文件PasteScript中的wsgiutils程序

官网上的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[composite:main]
use = egg:Paste#urlmap
/ = home
/blog = blog
/wiki = wiki
/cms = config:cms.ini
 
[app:home]
use = egg:Paste#static
document_root = %(here)s/htdocs
 
[filter-app:blog]
use = egg:Authentication#auth
next = blogapp
roles = admin
htpasswd = /home/me/users.htpasswd
 
[app:blogapp]
use = egg:BlogApp
database = sqlite:/home/me/blog.db
 
[app:wiki]
use = call:mywiki.main:application
database = sqlite:/home/me/wiki.db

先看[composite:main]section,这是一个composite类型的section,意味着将会调度请求到其他的application中去。use = egg:Paste#urlmap,表明使用的compostite Appliaciton是Paste.urlmap。urlmap是一个特殊的composite application,它可以根据请求的前缀,调度到其他的application中去。如配置文件中所示

1
2
3
4
5
6
[composite:main]
use = egg:Paste#urlmap
/ = home #请求前缀“/”转去home application
/blog = blog  #请求前缀“/blog”转去blog application
/wiki = wiki #请求前缀“/wiki”转去wiki application
/cms = config:cms.ini # just refers to another file cms.ini in the same directory.

[app:home]section

1
2
3
[app:home]
use = egg:Paste#static
document_root = %(here)s/htdocs

其他部分

1
2
3
4
5
6
7
8
[filter-app:blog]
use = egg:Authentication#auth
next = blogapp
roles = admin
htpasswd = /home/me/users.htpasswd
[app:blogapp]
use = egg:BlogApp
database = sqlite:/home/me/blog.db

[app:wiki]

1
2
3
[app:wiki]
use = call:mywiki.main:application
database = sqlite:/home/me/wiki.db

1.1.1.2. egg: URIs
Python Egg是使用setuptools发布和安装形式,并且将metadata添加到普通的Python package
对于egg有两个重要的地方:
第一,specificaition——name关键字指定egg 的名字,并且能够指定egg的版本号
第二,entry point —— 指向了你创建的packages的Python Object,并且指明Protocol的具体类型。
以swift的setup.py为例:protocol是paste.app_factory,包含的应用程序名称是proxy、object、container、account,你可以通过egg:egg名字#应用程序名字获取应用程序服务,以swift为例
name=’swift’
那么object应用程序的URI表示为egg:swift#object
value是导入objects的命令,主入口函数在swift.obj.server的app_factory函数

1
2
3
4
5
6
7
8
9
setup(
    name='MyApp',
    ...
    entry_points={
        'paste.app_factory': [
            'main=myapp.mymodule:app_factory',
            'ob2=myapp.mymodule:ob_factory'],
        },
    )

没有办法为egg添加进入的app增加配置文件
1.1.2. 定义Factories
遵循具体协议定义factories
新的协议:paste.app_factory、paste.composite_factory、paste.filter_factory
旧协议:paste.server_factory
Each of these expects a callable (like a function, method, or class)#等待被调用(??)
paste.app_factory:最常用的方式,定义类似如:

1
2
def app_factory(global_config, **local_conf):
    return wsgi_app

global_config是一个dictionary,local_conf是以关键字参数传入(即键值对)
函数返回WSGI application
paste.composite_factory: composite相对来说最复杂。

1
2
def composite_factory(loader, global_config, **local_conf):
    return wsgi_app

loader参数是一个对象,有一组方法:
get_app(name_or_uri, global_conf=None) return a WSGI application with the given name.
get_filter and get_server work the same way
还有一种用法,定义一个”pipeline” application

1
2
3
4
5
6
7
8
9
def pipeline_factory(loader, global_config, pipeline):
    # space-separated list of filter and app names:
    pipeline = pipeline.split()
    filters = [loader.get_filter(n) for n in pipeline[:-1]]
    app = loader.get_app(pipeline[-1])
    filters.reverse() # apply in reverse order!
    for filter in filters:
        app = filter(app)
return app

使用方法是

1
2
3
4
5
6
7
8
9
10
[composite:main]
use =
pipeline = egg:Paste#printdebug session myapp
 
[filter:session]
use = egg:Paste#session
store = memory
 
[app:myapp]
use = egg:MyApp

paste.filter_factory:功能和app factories类似,但是结果返回是fiter,Filters也都是可调用的,参数只有一个WSGI application,并返回具有filter版本的application

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def auth_filter_factory(global_conf, req_usernames):
    # space-separated list of usernames:
    req_usernames = req_usernames.split()
    def filter(app):
        return AuthFilter(app, req_usernames)
    return filter
 
class AuthFilter(object):
    def __init__(self, app, req_usernames):
        self.app = app
        self.req_usernames = req_usernames
 
    def __call__(self, environ, start_response):
        if environ.get('REMOTE_USER') in self.req_usernames:
            return self.app(environ, start_response)
        start_response(
            '403 Forbidden', [('Content-type', 'text/html')])
        return ['You are forbidden to view this resource']

paste.filter_app_factory:与paste.filter_factory类似,也是需要一个wsgi_app参数,返回WSGI application,讲述例子更改如下:

1
2
class AuthFilter(object):
    def __init__(self, app, global_conf, req_usernames):

Then AuthFilter would serve as a filter_app_factory (req_usernames is a required local configuration key in this case).
paste.server_factory:类似application和filters,只是返回server
server也是使用WSGI application作为参数是可调用的。例如,

1
2
3
4
5
6
def server_factory(global_conf, host, port):
    port = int(port)
    def serve(app):
        s = Server(app, host=host, port=port)
        s.serve_forever()
    return serve

Server是需要用户自己去实现的
paste.server_runner: 类似server_factory , wsgi_app作为第一个参数,server需要马上运行

1.1.3. 其他的对象
和加载application对象类似,可以使用loadserver和loadfilter加载server和filter的配置
定义filter和server的配置文件,通过在section中设定前缀server:和filter:,执行过程与application是一样的,不同的地方仅在于返回不同的对象。
[filter:name]section是filter类型,使用loadfilter加载
[server:name]section是server类型,使用loadserver加载

1.1.4. 获取配置文件
如果想要在没有创建application之前就过去配置信息,可以使用appconfig(uri)函数,以dict类型返回配置文件的信息。全局配置信息和local配置信息都合并起来生成单独的dictionary,但是可以通过使用属性分别查看.local_conf和.global_conf

1.1.5. 其他
swift只使用了paste.deploy两个功能:loadapp和configapp
python paste.deploy中filter,filter_factory,app,app_factory作用:

  • app是一个callable object,接受的参数(environ,start_response),这是paste系统交给application的,符合WSGI规范的参数. app需要完成的任务是响应envrion中的请求,准备好响应头和消息体,然后交给start_response处理,并返回响应消息体。
  • filter是一个callable object,其唯一参数是(app),这是WSGI的application对象,见(1),filter需要完成的工作是将application包装成另一个application(“过滤”),并返回这个包装后的application。
  • app_factory是一个callable object,其接受的参数是一些关于application的配置信息:(global_conf,**kwargs)【注:swift就是使用这部分】,global_conf是在ini文件中default section中定义的一系列key-value对,而**kwargs,即一些本地配置,是在ini文件中,app:xxx section中定义的一系列key-value对。app_factory返回值是一个application对象

filter_factory是一个callable object,其接受的参数是一系列关于filter的配置信息:(global_conf,**kwargs),global_conf是在ini文件中default section中定义的一系列key-value对,而**kwargs,即一些本地配置,是在ini文件中,filter:xxx section中定义的一系列key-value对。filter_factory返回一个filter对象

发表评论