1 用PasteScript创建项目
1.1 安装PasteScript
安装命令:
pip install PasteScript
查看可用的模板
[root@localhost test_project]# paster create --list-templates
Available templates:
basic_package: A basic setuptools-enabled package
paste_deploy: A web application deployed through paste.deploy
1.2 创建项目
paster create -t basic_package myproject
回答一系列问题后,项目生成
1.3 查看生成的项目目录结构
[root@localhost test_project]# tree myproject
myproject
|-- myproject
| `-- __init__.py
|-- myproject.egg-info
| |-- dependency_links.txt
| |-- entry_points.txt
| |-- not-zip-safe
| |-- PKG-INFO
| |-- SOURCES.txt
| `-- top_level.txt
|-- setup.cfg
`-- setup.py
2 修改setup.py,采用pbr部署
2.1 将setup.py中内容直接替换为如下内容
import setuptools
setuptools.setup(
setup_requires=['pbr'],
pbr=True)
2.2 修改setup.cfg内容为如下内容
[metadata]
name = myproject
version = 1.0
summary = myproject
description-file =
README.rst
author = me
author-email =
classifier =
Intended Audience :: Developers
Programming Language :: Python :: 2.7
[global]
setup-hooks =
pbr.hooks.setup_hook
[files]
packages =
myproject
data_files =
/etc/myproject = etc/myproject/*
/var/www/myproject = etc/apache2/app.wsgi
/etc/httpd/conf.d = etc/apache2/myproject.conf
[entry_points]
wsgi_scripts =
myproject-api = myproject.api.app:build_wsgi_app
oslo.config.opts =
myproject = myproject.opts:list_opts
分析:
一 pbr和setup.cfg
1) pbr是setuptools的插件,所以你必须使用setuptools,然后调用setuptools.setup()函数
2) pbr只需要最小化的setup.py 文件,跟普通的使用setuptools的项目相比。这是因为设置都在setup.cfg里面
3) setuptools的setup函数来进行设置时,如果与位于setup.cfg中的信息产生冲突,则setup.cfg则优先。
4) setup.cfg文档。这个文档像ini 文档。它基于项目distutils2的setup.cfg文件设置
5) setup.cfg文件中有几个段落:
metadata
files
entry_points
pbr
二 files段落定义了包中的文件位置,有三个基本的设置键:packages,namespace_packages,以及data_files.
1) packages: 指定需要安装的包的列表,若packages没有指定,默认为metadata段落中的name的值
2) data_files: 列出了需要被安装的文件。格式是缩进后跟上键值对
例如aodh的setup.cfg中配置
[files]
packages =
aodh
data_files =
etc/aodh = etc/aodh/*
上述例子表明: etc/aodh/*中的文件最终会被拷贝待/etc/aodh文件夹中
三 entry_points
作用:定义当前项目做为第三方python的库可以被其他python程序调用的lib进入点
1) console_scripts控制脚本
格式:
console_scripts =
脚本名称 = 模块:可导入对象
例子:
console_scripts =
aodh-evaluator = aodh.cmd.alarm:evaluator
解释:
这里会产生一个aodh-evaluator的脚本,执行aodh.cmd.alarm中的evaluator
函数。
2) 插件
格式:
命名空间 =
插件名称 = 模块:可导入对象
例子:
ceilometer.compute.virt =
libvirt = ceilometer.compute.virt.libvirt.inspector:LibvirtInspector
pbr参考
https://www.cnblogs.com/yasmi/p/5183423.html
https://docs.openstack.org/pbr/latest/
https://docs.openstack.org/pbr/latest/user/using.html
3 添加requirements.txt文件
该文件和setup.py在同一个目录
添加内容如下:
pbr<2.0,>=0.11
pecan
WSME
解释:
添加requirements.txt是因为给项目添加所依赖的第三方库,
使得项目在安装过程中会安装requirements.txt中的库,从而
确保项目运行正常。
4 添加paste部署所需要的内容
具体在myproject目录下新建一个目录api
4.1 api目录下新增一个文件: __init__.py
里面不要填写任何内容
4.2 api目录下新增一个文件: api-paste.ini
向该文件中写入如下内容:
[composite:main]
use = egg:Paste#urlmap
/ = api
[app:api]
paste.app_factory = myproject.api.app:app_factory
4.3 api目录下新增一个文件: app.py
在该文件中写入如下内容:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from paste import deploy
import pecan
PECAN_CONFIG = {
'app': {
'root': 'myproject.api.controllers.root.RootController',
'modules': ['myproject.api'],
},
}
def app_factory(global_config, **local_config):
print "######### enter app_factory"
# NOTE, it needs add the line below
pecan.configuration.set_config(dict(PECAN_CONFIG), overwrite=True)
app = pecan.make_app(
PECAN_CONFIG['app']['root']
)
return app
def getUri():
# TODO(), it needs to get real path of api-paste.ini
# the path is setted by the data_files under [files] in setup.config
configPath = "/etc/myproject/api-paste.ini"
result = "config:" + configPath
return result
def getAppName():
return "main"
def build_wsgi_app():
print "######### enter build_wsgi_app"
uri = getUri()
appName = getAppName()
app = deploy.loadapp(uri, name=appName)
return app
解释:
上述通过pecan来构建一个app并返回,需要指定处理总入口,这里通过root参数指定
总结:
此时的整体目录结构如下:
[root@localhost test_project]# tree myproject
myproject
|-- myproject
| |-- api
| | |-- api-paste.ini
| | |-- app.py
| | `-- __init__.py
| `-- __init__.py
|-- myproject.egg-info
| |-- dependency_links.txt
| |-- entry_points.txt
| |-- not-zip-safe
| |-- PKG-INFO
| |-- SOURCES.txt
| `-- top_level.txt
|-- requirements.txt
|-- setup.cfg
`-- setup.py
5 添加pecan路由分发所需的内容
因为在4.3中指定了处理入口是:
"myproject.api.controllers.root.RootController"
因此,需要在api目录下新建一个controllers目录,
5.1 在controllers目录下新增一个文件: __init__.py, 文件内容如下:
from oslo_config import cfg
# Register options for the service
OPTS = [
cfg.StrOpt('paste_config',
default="api-paste.ini",
help="Configuration file for WSGI definition of API."),
cfg.IntOpt('port',
default=8043,
help="The port for the myproject API server. (port value)."),
]
之所以要添加这个文件,是因为:
__init__.py可以做为package的标识,如果没有该文件,该目录不会被仍为是package
上述添加了一些配置,用于后续配置文件的生成
参考:
https://www.cnblogs.com/AlwinXu/p/5598543.html
5.2 在controllers目录下新增一个文件: root.py,
该文件内容如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pecan import rest
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from myproject.api.controllers.v1 import root as v1
class RootController(rest.RestController):
v1 = v1.V1Controller()
@wsme_pecan.wsexpose(wtypes.text)
def get(self):
return "####### RootController"
5.3 在controllers目录下新增v1目录
1) 在v1目录下新增 __init__.py文件
该文件中不要填写任何内容
2) 在v1目录下新增root.py文件
向该文件中填入如下内容:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pecan import rest
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from myproject.api.controllers.v1 import students as v1Students
class V1Controller(rest.RestController):
"""Version 1 API controller root."""
students = v1Students.StudentsController()
@wsme_pecan.wsexpose(wtypes.text)
def get(self):
return '####### V1Controller'
3) 在v1目录下新增students.py文件
向该文件中填入如下内容:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pecan
from pecan import rest
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
import wsmeext.pecan
class Student(wtypes.Base):
id = wtypes.wsattr(wtypes.text, mandatory=True)
name = wtypes.text
age = int
class Students(wtypes.Base):
students = [Student]
class StudentController(rest.RestController):
def __init__(self, id):
self.id = id
@wsmeext.pecan.wsexpose(Student)
def get(self):
# TODO(), get data from table of some database
# and repliace those code below
stud1 = {'id': '0001', 'name': 'chen', 'age': 20}
student = Student(**stud1)
return student
@wsmeext.pecan.wsexpose(Student, body=Student)
def put(self, data):
# TODO(), update the corresponding column which belongs to
# the table of some database and replace code below
studDict = {
'id': self.id,
'name': data.name,
'age': data.age
}
student = Student(**studDict)
return student
@wsmeext.pecan.wsexpose(None, status_code=204)
def delete(self):
# TODO(), delete the corresponding column which belongs to
# the table of some database and replace code below
print "delete id: {id}".format(id=self.id)
class StudentsController(rest.RestController):
@pecan.expose()
def _lookup(self, id, *remainder):
return StudentController(id), remainder
# @pecan.expose(Students)
@wsmeext.pecan.wsexpose(Students)
def get(self):
# TODO(), get datas from table of some database
# and replace those code below
stud1 = {'id': '0001', 'name': 'chen', 'age': 20}
stud2 = {'id': '0002', 'name': 'xiang', 'age': 30}
students = []
students.append(stud1)
students.append(stud2)
studList = [Student(**stud) for stud in students]
result = Students(students=studList)
return result
@wsmeext.pecan.wsexpose(Student, body=Student, status_code=201)
def post(self, data):
# TODO(), insert it into table of some database
# and return it
return data
分析:
1) pecan.rest.RestController
含义: RestController实现了一系列路由方法,
作用: 之所以StudentsController类继承pecan.rest.RestController类,
是因为这个类定义每个请求类型对应的处理方法(例如:指定请求如果是get类型,调用对应RestController
类的get方法进行处理)。它实现了默认的GET, POST, PUT, DELETE方法。
2) @pecan.expose()
作用: 之所以添加这个装饰器是因为,如果方法没有被expose()所装饰,Pecan不会将一个请求分发给这个方法
参数: 默认被装饰后返回的结果是html类型的,如果需要返回json格式的结果,需要用这种方式使用
@pecan.expose('json')
3) pecan.rest.RestController._lookup(currentContent, **remainder)
含义: 提供了一种方式来处理一个url中的一部分,返回一个新的controller对象来路由到剩余部分。
作用: 这个方法是用于根据当前url解析发现当前controller对象中没有任何方法可以处理后,就会调用
_lookup方法继续向下进行路由分发。
参数:
currentContent: 当前url处理的路径中的部分,举例如下: 假设请求的url是:
/v1/students/001/addr
先根据: /v1,进入V1Controller;
然后根据: 剩余url路径中的/students,进入StudentsController
然后根据: 剩余url路径中/001不对应任何StudentsController方法,那么
currrentContent就是001这个内容,remainder部分就是剩余的url路径按照/分隔为一个数组
remainder: 剩余url路径按照/分隔为的一个数组。
4) 这里使用了wsme库
含义: Web Services Made Easy,实际就是检查api中输入参数和返回值类型的校验
组成:
wsme.types: 对参数进行类型校验
wsme.wsattr(datatype, mandatory=False, name=None, default=Unset, readonly=False):
作用:允许你添加更多属性,例如: mandatory=True 表示该属性不能为空
参数:
datatype:数据类型,例如wtypes.text
mandatory: 是否不能为空,False表示可以为空
wsmeext.pecan: pecan使用wsme的适配器
wsmeext.pecan.wsexpose(return_type, *arg_type, **options):
作用: 对pecan路由分发得请求得返回值和输入参数进行类型检查
参数:
return_type:方法返回值的类型
arg_type: 输入参数的类型,可以有多个
options: 字典参数,例如用得较多得是status_code=204
参考:
https://blog.csdn.net/dingdingwolf/article/details/44486721
https://pecan.readthedocs.io/en/latest/routing.html
https://wsme.readthedocs.io/en/latest/types.html
6 安装项目
进入myproject项目,然后执行:
python setup.py install
安装项目
7 服务启动以及curl命令验证
7.1启动服务
普通启动方式,请执行如下命令:
/usr/bin/myproject-api --port=8043
或者如果想以httpd启动(这里在centos环境下),请执行如下命令:
systemctl restart httpd
7.2 curl命令验证rest api框架搭建成功
获取所有:
curl http://127.0.0.1:8043/v1/students
创建单个:
curl -X POST http://127.0.0.1:8043/v1/students -H "Content-Type: application/json" -d '{"id": "0003", "name": "ping", "age": 50}' -v
注意:
1 students后面不要添加/,否则会继续向下路由分发进入下一个controller,而不是用当前controller的post。
pecan对每个'/'都很细致
2 POST和PUT请求需要添加: -H "Content-Type: application/json"
否则解析传入的数据出错,因为数据格式是json的,这里要指定传入的数据类型也是json
获取单个:
curl http://127.0.0.1:8043/v1/students/0001
更新单个:
curl -X PUT http://127.0.0.1:8043/v1/students/0001 -H "Content-Type: application/json" -d '{"id": "0001","name": "dong", "age": 22}' -v
删除单个:
curl -X DELETE http://127.0.0.1:8043/v1/students/0001 -H "Content-Type: application/json"
注意: -d中 {}最外面的是单引号,括号里面的都是双引号,否则会导致解析错误
解释:
-X 指定请求方法
-H 增加头部信息
-d POST请求方法时发送的数据
-v 详细信息
curl参考:
https://blog.csdn.net/danchu/article/details/72290092
8 生成配置文件
8.1 配置文件的生成
配置部分的设置在setup.cfg中,下面是aodh.setup.cfg中关于配置生成的部分
oslo.config.opts =
aodh = aodh.opts:list_opts
aodh-auth = aodh.opts:list_keystoneauth_opts
oslo.config.opts.defaults =
aodh = aodh.conf.defaults:set_cors_middleware_defaults
分析:
1 oslo.config通用库用于解析命令行和配置文件中的配置选项
2 在aodh/opts.py文件中定义如下内容
def list_opts():
return [
('DEFAULT',
itertools.chain(
aodh.evaluator.OPTS,
aodh.evaluator.event.OPTS,
aodh.evaluator.threshold.OPTS,
aodh.notifier.rest.OPTS,
aodh.queue.OPTS,
aodh.service.OPTS)),
('api',
itertools.chain(
aodh.api.OPTS,
aodh.api.controllers.v2.alarm_rules.gnocchi.GNOCCHI_OPTS,
aodh.api.controllers.v2.alarms.ALARM_API_OPTS)),
('coordination', aodh.coordination.OPTS),
('database', aodh.storage.OPTS),
('evaluator', aodh.service.EVALUATOR_OPTS),
('listener', itertools.chain(aodh.service.LISTENER_OPTS,
aodh.event.OPTS)),
('notifier', aodh.service.NOTIFIER_OPTS),
('service_credentials', aodh.keystone_client.OPTS),
('service_types', aodh.notifier.zaqar.SERVICE_OPTS),
('notifier', aodh.notifier.OPTS),
('sms', aodh.notifier.sms.OPTS),
('email', aodh.notifier.email.OPTS),
('action_resources', aodh.action_resources.handler.OPTS),
('service_type', itertools.chain(aodh.chakra_client.OPTS,
aodh.billing_client.OPTS))
]
分析: 上述应该是收集各个组别下的配置项。返回一个数组,数组中的每个元素是
二元组(oslo.config中的组名,该组名下的所有配置项)
3 itertools.chain(*iterables)
作用:生成一个迭代器对象,用于不能将输入一下子放入内存进行处理的情况
参数: *iterables:一个可迭代对象,例如多个数组
返回: 迭代器,本质是用yield将每次运行结果返回
4 生成配文件
官网命令:
To generate a sample config file for an application myapp that has its own options and uses oslo.messaging, you would list both namespaces:
$ oslo-config-generator --namespace myapp \
--namespace oslo.messaging > myapp.conf
示例命令:
oslo-config-generator --config-file=myproject-config-generator.conf
最终根据示例命令生成的结果如下:
生成myproject.con文件,具体内容如下所示
[DEFAULT]
[api]
#
# From myproject
#
# Configuration file for WSGI definition of API. (string value)
#paste_config = api-paste.ini
# The port for the myproject API server. (port value). (integer value)
#port = 8043
参考:
配置文件生成参考
https://blog.csdn.net/karamos/article/details/80121792
https://docs.openstack.org/oslo.config/latest/cli/generator.html
itertools参考:
https://blog.csdn.net/weixin_38104872/article/details/78826948