python 64式: 第18式、python專案通用rest api框架搭建與編寫
1 用PasteScript建立專案
1.1 安裝PasteScript
安裝命令:
pip install PasteScript
檢視可用的模板
[[email protected] 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 檢視生成的專案目錄結構
[[email protected] 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引數指定
總結:
此時的整體目錄結構如下:
[[email protected] 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