摘要:到這里,我們的服務的框架已經搭建完成,并且測試服務器也跑起來了。上面的代碼也就可以修改為再次運行我們的測試服務器,就可以返現返回值為格式了。我們先來完成利用來檢查返回值的代碼方法的第一個參數表示返回值的類型這樣就完成了的返回值檢查了。
上一篇文章說到,我們將以實例的形式來繼續講述這個API服務的開發知識,這里會使用Pecan和WSME兩個庫。
設計REST API要開發REST API服務,我們首先需要設計一下這個服務。設計包括要實現的功能,以及接口的具體規范。我們這里要實現的是一個簡單的用戶管理接口,包括增刪改查等功能。如果讀者對REST API不熟悉,可以先從Wiki頁面了解一下。
另外,為了方便大家閱讀和理解,本系列的代碼會放在github上,diabloneo/webdemo。
Version of REST API在OpenStack的項目中,都是在URL中表明這個API的版本號的,比如Keystone的API會有/v2.0和/v3的前綴,表明兩個不同版本的API;Magnum項目目前的API則為v1版本。因為我們的webdemo項目才剛剛開始,所以我們也把我們的API版本設置為v1,下文會說明怎么實現這個version號的設置。
REST API of Users我們將要設計一個管理用戶的API,這個和Keystone的用戶管理的API差不多,這里先列出每個API的形式,以及簡要的內容說明。這里我們會把上面提到的version號也加入到URL path中,讓讀者能更容易聯系起來。
GET /v1/users 獲取所有用戶的列表。
POST /v1/users 創建一個用戶
GET /v1/users/
獲取一個特定用戶的詳細信息。 PUT /v1/users/
修改一個用戶的詳細信息。 DELETE /v1/users/
刪除一個用戶。
這些就是我們要實現的用戶管理的API了。其中,
In [5]: import uuid In [6]: print uuid.uuid4() adb92482-baab-4832-84bc-f842f3eabd66 In [7]: print uuid.uuid4().hex 29520c88de6b4c76ae8deb48db0a71e7
因為是個demo,所以我們設置一個用戶包含的信息會比較簡單,只包含name和age。
使用Pecan搭建API服務的框架接下來就要開始編碼工作了。首先要把整個服務的框架搭建起來。我們會在軟件包管理這篇文件中的代碼基礎上繼續我們的demo(所有這些代碼在github的倉庫里都能看到)。
代碼目錄結構一般來說,OpenStack項目中,使用Pecan來開發API服務時,都會在代碼目錄下有一個專門的API目錄,用來保存API相關的代碼。比如Magnum項目的magnum/api,或者Ceilometer項目的ceilometer/api等。我們的代碼也遵守這個規范,讓我們直接來看下我們的代碼目錄結構(#后面的表示注釋):
? ~/programming/python/webdemo/webdemo/api git:(master) ? $ tree . . ├── app.py # 這個文件存放WSGI application的入口 ├── config.py # 這個文件存放Pecan的配置 ├── controllers/ # 這個目錄用來存放Pecan控制器的代碼 ├── hooks.py # 這個文件存放Pecan的hooks代碼(本文中用不到) └── __init__.py
這個在API服務(3)這篇文章中已經說明過了。
先讓我們的服務跑起來為了后面更好的開發,我們需要先讓我們的服務在本地跑起來,這樣可以方便自己做測試,看到代碼的效果。不過要做到這點,還是有些復雜的。
必要的代碼首先,先創建config.py文件的內容:
app = { "root": "webdemo.api.controllers.root.RootController", "modules": ["webdemo.api"], "debug": False, }
就是包含了Pecan的最基本配置,其中指定了root controller的位置。然后看下app.py文件的內容,主要就是讀取config.py中的配置,然后創建一個WSGI application:
import pecan from webdemo.api import config as api_config def get_pecan_config(): filename = api_config.__file__.replace(".pyc", ".py") return pecan.configuration.conf_from_file(filename) def setup_app(): config = get_pecan_config() app_conf = dict(config.app) app = pecan.make_app( app_conf.pop("root"), logging=getattr(config, "logging", {}), **app_conf ) return app
然后,我們至少還需要實現一下root controller,也就是webdemo/api/controllers/root.py這個文件中的RootController類:
from pecan import rest from wsme import types as wtypes import wsmeext.pecan as wsme_pecan class RootController(rest.RestController): @wsme_pecan.wsexpose(wtypes.text) def get(self): return "webdemo"本地測試服務器
為了繼續開放的方便,我們要先創建一個Python腳本,可以啟動一個單進程的API服務。這個腳本會放在webdemo/cmd/目錄下,名稱是api.py(這目錄和腳本名稱也是慣例),來看看我們的api.py吧:
from wsgiref import simple_server from webdemo.api import app def main(): host = "0.0.0.0" port = 8080 application = app.setup_app() srv = simple_server.make_server(host, port, application) srv.serve_forever() if __name__ == "__main__": main()運行測試服務器的環境
要運行這個測試服務器,首先需要安裝必要的包,并且設置正確的路徑。在后面的文章中,我們將會知道,這個可以通過tox這個工具來實現。現在,我們先做個簡單版本的,就是手動創建這個運行環境。
首先,完善一下requirements.txt這個文件,包含我們需要的包:
pbr<2.0,>=0.11 pecan WSME
然后,我們手動創建一個virtualenv環境,并且安裝requirements.txt中要求的包:
? ~/programming/python/webdemo git:(master) ? $ virtualenv .venv New python executable in .venv/bin/python Installing setuptools, pip, wheel...done. ? ~/programming/python/webdemo git:(master) ? $ source .venv/bin/activate (.venv)? ~/programming/python/webdemo git:(master) ? $ pip install -r requirement.txt ... Successfully installed Mako-1.0.3 MarkupSafe-0.23 WSME-0.8.0 WebOb-1.5.1 WebTest-2.0.20 beautifulsoup4-4.4.1 logutils-0.3.3 netaddr-0.7.18 pbr-1.8.1 pecan-1.0.3 pytz-2015.7 simplegeneric-0.8.1 singledispatch-3.4.0.3 six-1.10.0 waitress-0.8.10啟動我們的服務
啟動服務需要技巧,因為我們的webdemo還沒有安裝到系統的Python路徑中,也不在上面創建virtualenv環境中,所以我們需要通過指定PYTHONPATH這個環境變量來為Python程序增加庫的查找路徑:
(.venv)? ~/programming/python/webdemo git:(master) ? $ PYTHONPATH=. python webdemo/cmd/api.py
現在測試服務器已經起來了,可以通過瀏覽器訪問http://localhost:8080/ 這個地址來查看結果。(你可能會發現,返回的是XML格式的結果,而我們想要的是JSON格式的。這個是WSME的問題,我們后面再來處理)。
到這里,我們的REST API服務的框架已經搭建完成,并且測試服務器也跑起來了。
用戶管理API的實現現在我們來實現我們在第一章設計的API。這里先說明一下:我們會直接使用Pecan的RestController來實現REST API,這樣可以不用為每個接口指定接受的method。
讓API返回JSON格式的數據現在,所有的OpenStack項目的REST API的返回格式都是使用JSON標準,所以我們也要這么做。那么有什么辦法能夠讓WSME框架返回JSON數據呢?可以通過設置wsmeext.pecan.wsexpose()的rest_content_types參數來是先。這里,我們借鑒一段Magnum項目中的代碼,把這段代碼存放在文件webdemo/api/expose.py中:
import wsmeext.pecan as wsme_pecan def expose(*args, **kwargs): """Ensure that only JSON, and not XML, is supported.""" if "rest_content_types" not in kwargs: kwargs["rest_content_types"] = ("json",) return wsme_pecan.wsexpose(*args, **kwargs)
這樣我們就封裝了自己的expose裝飾器,每次都會設置響應的content-type為JSON。上面的root controller代碼也就可以修改為:
from pecan import rest from wsme import types as wtypes from webdemo.api import expose class RootController(rest.RestController): @expose.expose(wtypes.text) def get(self): return "webdemo"
再次運行我們的測試服務器,就可以返現返回值為JSON格式了。
實現 GET /v1這個其實就是實現v1這個版本的API的路徑前綴。在Pecan的幫助下,我們很容易實現這個,只要按照如下兩步做即可:
先實現v1這個controller
把v1 controller加入到root controller中
按照OpenStack項目的規范,我們會先建立一個webdemo/api/controllers/v1/目錄,然后將v1 controller放在這個目錄下的一個文件中,假設我們就放在v1/controller.py文件中,效果如下:
from pecan import rest from wsme import types as wtypes from webdemo.api import expose class V1Controller(rest.RestController): @expose.expose(wtypes.text) def get(self): return "webdemo v1controller"
然后把這個controller加入到root controller中:
... from webdemo.api.controllers.v1 import controller as v1_controller from webdemo.api import expose class RootController(rest.RestController): v1 = v1_controller.V1Controller() @expose.expose(wtypes.text) def get(self): return "webdemo"
此時,你訪問http://localhost:8080/v1就可以看到結果了。
實現 GET /v1/users 添加users controller這個API就是返回所有的用戶信息,功能很簡單。首先要添加users controller到上面的v1 controller中。為了不影響閱讀體驗,這里就不貼代碼了,請看github上的示例代碼。
使用WSME來規范API的響應值上篇文章中,我們已經提到了WSME可以用來規范API的請求和響應的值,這里我們就要用上它。首先,我們要參考OpenStack的慣例來設計這個API的返回值:
{ "users": [ { "name": "Alice", "age": 30 }, { "name": "Bob", "age": 40 } ] }
其中users是一個列表,列表中的每個元素都是一個user。那么,我們要如何使用WSME來規范我們的響應值呢?答案就是使用WSME的自定義類型。我們可以利用WSME的類型功能定義出一個user類型,然后再定義一個user的列表類型。最后,我們就可以使用上面的expose方法來規定這個API返回的是一個user的列表類型。
定義user類型和user列表類型這里我們需要用到WSME的Complex types的功能,請先看一下文檔Types。簡單說,就是我們可以把WSME的基本類型組合成一個復雜的類型。我們的類型需要繼承自wsme.types.Base這個類。因為我們在本文只會實現一個user相關的API,所以這里我們把所有的代碼都放在webdemo/api/controllers/v1/users.py文件中。來看下和user類型定義相關的部分:
from wsme import types as wtypes class User(wtypes.Base): name = wtypes.text age = int class Users(wtypes.Base): users = [User]
這里我們定義了class User,表示一個用戶信息,包含兩個字段,name是一個文本,age是一個整型。class Users表示一組用戶信息,包含一個字段users,是一個列表,列表的元素是上面定義的class User。完成這些定義后,我們就使用WSME來檢查我們的API是否返回了合格的值;另一方面,只要我們的API返回了這些類型,那么就能通過WSME的檢查。我們先來完成利用WSME來檢查API返回值的代碼:
class UsersController(rest.RestController): # expose方法的第一個參數表示返回值的類型 @expose.expose(Users) def get(self): pass
這樣就完成了API的返回值檢查了。
實現API邏輯我們現在來完成API的邏輯部分。不過為了方便大家理解,我們直接返回一個寫好的數據,就是上面貼出來的那個。
class UsersController(rest.RestController): @expose.expose(Users) def get(self): user_info_list = [ { "name": "Alice", "age": 30, }, { "name": "Bob", "age": 40, } ] users_list = [User(**user_info) for user_info in user_info_list] return Users(users=users_list)
代碼中,會先根據user信息生成User實例的列表users_list,然后再生成Users實例。此時,重啟測試服務器后,你就可以從瀏覽器訪問http://localhost:8080/v1/users,就能看到結果了。
實現 POST /v1/users這個API會接收用戶上傳的一個JSON格式的數據,然后打印出來(實際中一般是存到數據庫之類的),要求用戶上傳的數據符合User類型的規范,并且返回的狀態碼為201。代碼如下:
class UsersController(rest.RestController): @expose.expose(None, body=User, status_code=201) def post(self, user): print user
可以使用curl程序來測試:
~/programming/python/webdemo git:(master) ? $ curl -X POST http://localhost:8080/v1/users -H "Content-Type: application/json" -d "{"name": "Cook", "age": 50}" -v * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 8080 (#0) > POST /v1/users HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.43.0 > Accept: */* > Content-Type: application/json > Content-Length: 27 > * upload completely sent off: 27 out of 27 bytes * HTTP 1.0, assume close after body < HTTP/1.0 201 Created < Date: Mon, 16 Nov 2015 15:18:24 GMT < Server: WSGIServer/0.1 Python/2.7.10 < Content-Length: 0 < * Closing connection 0
同時,服務器上也會打印出:
127.0.0.1 - - [16/Nov/2015 23:16:28] "POST /v1/users HTTP/1.1" 201 0
我們用3行代碼就實現了這個POST的邏輯。現在來說明一下這里的秘密。expose裝飾器的第一個參數表示這個方法沒有返回值;第三個參數表示這個API的響應狀態碼是201,如果不加這個參數,在沒有返回值的情況下,默認會返回204。第二個參數要說明一下,這里用的是body=User,你也可以直接寫User。使用body=User這種形式,你可以直接發送符合User規范的JSON字符串;如果是用expose(None, User, status_code=201)那么你需要發送下面這樣的數據:
{ "user": {"name": "Cook", "age": 50} }
你可以自己測試一下區別。要更多的了解本節提到的expose參數,請參考WSM文檔Functions。
最后,你接收到一個創建用戶請求時,一般會為這個用戶分配一個id。本文前面已經提到了OpenStack項目中一般使用UUID。你可以修改一下上面的邏輯,為每個用戶分配一個UUID。
實現 GET /v1/users/要實現這個API,需要兩個步驟:
在UsersController中解析出
在UserController中實現get()方法。
使用_lookup()方法Pecan的_lookup()方法是controller中的一個特殊方法,Pecan會在特定的時候調用這個方法來實現更靈活的URL路由。Pecan還支持用戶實現_default()和_route()方法。這些方法的具體說明,請閱讀Pecan的文檔:routing。
我們這里只用到_lookup()方法,這個方法會在controller中沒有其他方法可以執行且沒有_default()方法的時候執行。比如上面的UsersController中,沒有定義/v1/users/
_lookup()方法需要返回一個元組,元組的第一個元素是下一個controller的實例,第二個元素是URL path中剩余的部分。
在這里,我們就需要在_lookup()方法中解析出UUID的部分并傳遞給新的controller作為新的參數,并且返回剩余的URL path。來看下代碼:
class UserController(rest.RestController): def __init__(self, user_id): self.user_id = user_id class UsersController(rest.RestController): @pecan.expose() def _lookup(self, user_id, *remainder): return UserController(user_id), remainder
_lookup()方法的形式為_lookup(self, user_id, *remainder),意思就是會把/v1/users/
實現前,我們要先修改一下我們返回的數據,里面需要增加一個id字段。對應的User定義如下:
class User(wtypes.Base): id = wtypes.text name = wtypes.text age = int
現在,完整的UserController代碼如下:
class UserController(rest.RestController): def __init__(self, user_id): self.user_id = user_id @expose.expose(User) def get(self): user_info = { "id": self.user_id, "name": "Alice", "age": 30, } return User(**user_info)
使用curl來檢查一下效果:
? ~/programming/python/webdemo git:(master) ? $ curl http://localhost:8080/v1/users/29520c88de6b4c76ae8deb48db0a71e7 {"age": 30, "id": "29520c88de6b4c76ae8deb48db0a71e7", "name": "Alice"}定義WSME類型的技巧
你可能會有疑問:這里我們修改了User類型,增加了一個id字段,那么前面實現的POST /v1/users會不會失效呢?你可以自己測試一下。(答案是不會,因為這個類型里的字段都是可選的)。這里順便講兩個技巧。
如何設置一個字段為強制字段
像下面這樣做就可以了(你可以測試一下,改成這樣后,不傳遞id的POST /v1/users會失敗):
class User(wtypes.Base): id = wtypes.wsattr(wtypes.text, mandatory=True) name = wtypes.text age = int
如何檢查一個可選字段的值是否存在
檢查這個值是否為None是肯定不行的,需要檢查這個值是否為wsme.Unset。
實現 PUT /v1/users/這個和上一個API一樣,不過_lookup()方法已經實現過了,直接添加方法到UserController中即可:
class UserController(rest.RestController): @expose.expose(User, body=User) def put(self, user): user_info = { "id": self.user_id, "name": user.name, "age": user.age + 1, } return User(**user_info)
通過curl來測試:
? ~/programming/python/webdemo git:(master) ? $ curl -X PUT http://localhost:8080/v1/users/29520c88de6b4c76ae8deb48db0a71e7 -H "Content-Type: application/json" -d "{"name": "Cook", "age": 50}" {"age": 51, "id": "29520c88de6b4c76ae8deb48db0a71e7", "name": "Cook"}%實現 DELETE /v1/users/
同上,沒有什么新的內容:
class UserController(rest.RestController): @expose.expose() def delete(self): print "Delete user_id: %s" % self.user_id總結
到此為止,我們已經完成了我們的API服務了,雖然沒有實際的邏輯,但是本文搭建起來的框架也是OpenStack中API服務的一個常用框架,很多大項目的API服務代碼都和我們的webdemo長得差不多。最后再說一下,本文的代碼在github上托管著:diabloneo/webdemo。
現在我們已經了解了包管理和API服務了,那么接下來就要開始數據庫相關的操作了。大部分OpenStack的項目都是使用非常著名的sqlalchemy庫來實現數據庫操作的,本系列接下來的文章就是要來說明數據庫的相關知識和應用。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/44182.html
摘要:通過,也就是通過各個項目提供的來使用各個服務的功能。通過使用的方式是由各個服務自己實現的,比如負責計算的項目實現了計算相關的,負責認證的項目實現了認證和授權相關的。的服務都是使用的方式來部署的。 使用OpenStack服務的方式 OpenStack項目作為一個IaaS平臺,提供了三種使用方式: 通過Web界面,也就是通過Dashboard(面板)來使用平臺上的功能。 通過命令行,也就...
摘要:在實際項目中,這么做肯定是不行的實際項目中不會使用內存數據庫,這種數據庫一般只是在單元測試中使用。接下來,我們將會了解中單元測試的相關知識。 在上一篇文章,我們介紹了SQLAlchemy的基本概念,也介紹了基本的使用流程。本文我們結合webdemo這個項目來介紹如何在項目中使用SQLAlchemy。另外,我們還會介紹數據庫版本管理的概念和實踐,這也是OpenStack每個項目都需要做的...
摘要:本文將進入單元測試的部分,這也是基礎知識中最后一個大塊。本文將重點講述和中的單元測試的生態環境。另外,在中指定要運行的單元測試用例的完整語法是。中使用模塊管理單元測試用例。每個項目的單元測試代碼結構可 本文將進入單元測試的部分,這也是基礎知識中最后一個大塊。本文將重點講述Python和OpenStack中的單元測試的生態環境。 單元測試的重要性 github上有個人畫了一些不同語言的學...
摘要:不幸的是,在軟件包管理十分混亂,至少歷史上十分混亂。的最大改進是將函數的參數單獨放到一個的文件中這些成為包的元數據。基于的版本號管理。的版本推導這里重點說明一下基于的版本號管理這個功能。開發版本號的形式如下。 為什么寫這個系列 OpenStack是目前我所知的最大最復雜的基于Python項目。整個OpenStack項目包含了數十個主要的子項目,每個子項目所用到的庫也不盡相同。因此,對于...
摘要:從上面的例子可以看出,決定響應類型的主要是傳遞給函數的參數,我們看下函數的完整聲明參數用來指定返回值的模板,如果是就會返回內容,這里可以指定一個文件,或者指定一個模板。用來做什么上面兩節已經說明了可以比較好的處理請求中的參數以及控制返回值。 上一篇文章我們了解了一個巨啰嗦的框架:Paste + PasteDeploy + Routes + WebOb。后來OpenStack社區的人受不...
閱讀 1679·2021-11-12 10:35
閱讀 1621·2021-08-03 14:02
閱讀 2691·2019-08-30 15:55
閱讀 2034·2019-08-30 15:54
閱讀 770·2019-08-30 14:01
閱讀 2433·2019-08-29 17:07
閱讀 2260·2019-08-26 18:37
閱讀 3039·2019-08-26 16:51