摘要:從上面的例子可以看出,決定響應(yīng)類型的主要是傳遞給函數(shù)的參數(shù),我們看下函數(shù)的完整聲明參數(shù)用來指定返回值的模板,如果是就會返回內(nèi)容,這里可以指定一個文件,或者指定一個模板。用來做什么上面兩節(jié)已經(jīng)說明了可以比較好的處理請求中的參數(shù)以及控制返回值。
上一篇文章我們了解了一個巨啰嗦的框架:Paste + PasteDeploy + Routes + WebOb。后來OpenStack社區(qū)的人受不了這么啰嗦的代碼了,決定換一個框架,他們最終選中了Pecan。Pecan框架相比上一篇文章的啰嗦框架有如下好處:
不用自己寫WSGI application了
請求路由很容易就可以實現(xiàn)了
總的來說,用上Pecan框架以后,很多重復(fù)的代碼不用寫了,開發(fā)人員可以專注于業(yè)務(wù),也就是實現(xiàn)每個API的功能。
PecanPecan框架的目標(biāo)是實現(xiàn)一個采用對象分發(fā)方式進行URL路由的輕量級Web框架。它非常專注于自己的目標(biāo),它的大部分功能都和URL路由以及請求和響應(yīng)的處理相關(guān),而不去實現(xiàn)模板、安全以及數(shù)據(jù)庫層,這些東西都可以通過其他的庫來實現(xiàn)。關(guān)于Pecan的更多信息,可以查看文檔:https://pecan.readthedocs.org/en/latest/index.html。本文以O(shè)penStack的magnum項目為例來說明Pecan項目在實際中的應(yīng)用,但是本文不會詳細(xì)講解Pecan的各個方面,一些細(xì)節(jié)請讀者閱讀Pecan的文檔。
項目中的代碼結(jié)構(gòu)使用Pecan框架時,OpenStack項目一般會把API服務(wù)的實現(xiàn)都放在一個api目錄下,比如magnum項目是這樣的:
? ~/openstack/env/p/magnum git:(master) $ tree magnum/api magnum/api ├── app.py ├── auth.py ├── config.py ├── controllers │?? ├── base.py │?? ├── __init__.py │?? ├── link.py │?? ├── root.py │?? └── v1 │?? ├── base.py │?? ├── baymodel.py │?? ├── bay.py │?? ├── certificate.py │?? ├── collection.py │?? ├── container.py │?? ├── __init__.py │?? ├── magnum_services.py │?? ├── node.py │?? ├── pod.py │?? ├── replicationcontroller.py │?? ├── service.py │?? ├── types.py │?? ├── utils.py │?? └── x509keypair.py ├── expose.py ├── hooks.py ├── __init__.py ├── middleware │?? ├── auth_token.py │?? ├── __init__.py │?? └── parsable_error.py ├── servicegroup.py └── validation.py
你也可以在Ceilometer項目中看到類似的結(jié)構(gòu)。介紹一下幾個主要的文件,這樣你以后看到一個使用Pecan的OpenStack項目時就會比較容易找到入口。
app.py 一般包含了Pecan應(yīng)用的入口,包含應(yīng)用初始化代碼
config.py 包含Pecan的應(yīng)用配置,會被app.py使用
controllers/ 這個目錄會包含所有的控制器,也就是API具體邏輯的地方
controllers/root.py 這個包含根路徑對應(yīng)的控制器
controllers/v1/ 這個目錄對應(yīng)v1版本的API的控制器。如果有多個版本的API,你一般能看到v2等目錄。
代碼變少了:application的配置Pecan的配置很容易,通過一個Python源碼式的配置文件就可以完成基本的配置。這個配置的主要目的是指定應(yīng)用程序的root,然后用于生成WSGI application。我們來看Magnum項目的例子。Magnum項目有個API服務(wù)是用Pecan實現(xiàn)的,在magnum/api/config.py文件中可以找到這個文件,主要內(nèi)容如下:
app = { "root": "magnum.api.controllers.root.RootController", "modules": ["magnum.api"], "debug": False, "hooks": [ hooks.ContextHook(), hooks.RPCHook(), hooks.NoExceptionTracebackHook(), ], "acl_public_routes": [ "/" ], }
上面這個app對象就是Pecan的配置,每個Pecan應(yīng)用都需要有這么一個名為app的配置。app配置中最主要的就是root的值,這個值表示了應(yīng)用程序的入口,也就是從哪個地方開始解析HTTP的根path:/。hooks對應(yīng)的配置是一些Pecan的hook,作用類似于WSGI Middleware。
有了app配置后,就可以讓Pecan生成一個WSGI application。在Magnum項目中,magnum/api/app.py文件就是生成WSGI application的地方,我們來看一下這個的主要內(nèi)容:
def get_pecan_config(): # Set up the pecan configuration filename = api_config.__file__.replace(".pyc", ".py") return pecan.configuration.conf_from_file(filename) def setup_app(config=None): if not config: config = get_pecan_config() app_conf = dict(config.app) app = pecan.make_app( app_conf.pop("root"), logging=getattr(config, "logging", {}), wrap_app=middleware.ParsableErrorMiddleware, **app_conf ) return auth.install(app, CONF, config.app.acl_public_routes)
get_pecan_config()方法讀取我們上面提到的config.py文件,然后返回一個pecan.configuration.Config對象。setup_app()函數(shù)首先調(diào)用get_pecan_config()函數(shù)獲取application的配置,然后調(diào)用pecan.make_app()函數(shù)創(chuàng)建了一個WSGI application,最后調(diào)用了 auth.install()函數(shù)(也就是magnum.api.auth.install()函數(shù))為剛剛生成的WSGI application加上Keystone的認(rèn)證中間件(確保所有的請求都會通過Keystone認(rèn)證)。
到這邊為止,一個Pecan的WSGI application就已經(jīng)準(zhǔn)備好了,只要調(diào)用這個setup_app()函數(shù)就能獲得。至于如何部署這個WSGI application,請參考WSGI簡介這篇文章。
從Magnum這個實際的例子可以看出,使用了Pecan之后,我們不再需要自己寫那些冗余的WSGI application代碼了,直接調(diào)用Pecan的make_app()函數(shù)就能完成這些工作。另外,對于之前使用PasteDeploy時用到的很多WSGI中間件,可以選擇使用Pecan的hooks機制來實現(xiàn),也選擇使用WSGI中間件的方式來實現(xiàn)。在Magnum的API服務(wù)就同時使用了這兩種方式。其實,Pecan還可以和PasteDeploy一起使用,Ceilometer項目就是這么做的,大家可以看看。
確定路由變得容易了:對象分發(fā)式的路由Pecan不僅縮減了生成WSGI application的代碼,而且也讓開發(fā)人員更容易的指定一個application的路由。Pecan采用了一種對象分發(fā)風(fēng)格(object-dispatch style)的路由模式。我們直接通過例子來解釋這種路由模式,還是以Magnum項目為例。
上面提到了,Magnum的API服務(wù)的root是magnum.api.controllers.root.RootController。這里的RootController的是一個類,我們來看它的代碼:
class RootController(rest.RestController): _versions = ["v1"] """All supported API versions""" _default_version = "v1" """The default API version""" v1 = v1.Controller() @expose.expose(Root) def get(self): # NOTE: The reason why convert() it"s being called for every # request is because we need to get the host url from # the request object to make the links. return Root.convert() @pecan.expose() def _route(self, args): """Overrides the default routing behavior. It redirects the request to the default version of the magnum API if the version number is not specified in the url. """ if args[0] and args[0] not in self._versions: args = [self._default_version] + args return super(RootController, self)._route(args)
別看這個類這么長,我來解釋一下你就懂了。首先,你可以先忽略掉_route()函數(shù),這個函數(shù)是用來覆蓋Pecan的默認(rèn)路由實現(xiàn)的,在這里去掉它不妨礙我們理解Pecan(這里的_route()函數(shù)的作用把所有請求重定向到默認(rèn)的API版本去)。去掉_route()和其他的東西后,整個類就變成這么短:
class RootController(rest.RestController): v1 = v1.Controller() @expose.expose(Root) def get(self): return Root.convert()
首先,你要記住,這個RootController對應(yīng)的是URL中根路徑,也就是path中最左邊的/。
RootController繼承自rest.RestController,是Pecan實現(xiàn)的RESTful控制器。這里的get()函數(shù)表示,當(dāng)訪問的是GET /時,由該函數(shù)處理。get()函數(shù)會返回一個WSME對象,表示一個形式化的HTTP Response,這個下面再講。get()函數(shù)上面的expose裝飾器是Pecan實現(xiàn)路由控制的一個方式,被expose的函數(shù)才會被路由處理。
這里的v1 = v1.Controller()表示,當(dāng)訪問的是GET /v1或者GET /v1/...時,請求由一個v1.Controller實例來處理。
為了加深大家的理解,我們再來看下v1.Controller的實現(xiàn):
class Controller(rest.RestController): """Version 1 API controller root.""" bays = bay.BaysController() baymodels = baymodel.BayModelsController() containers = container.ContainersController() nodes = node.NodesController() pods = pod.PodsController() rcs = rc.ReplicationControllersController() services = service.ServicesController() x509keypairs = x509keypair.X509KeyPairController() certificates = certificate.CertificateController() @expose.expose(V1) def get(self): return V1.convert() ...
上面這個Controller也是繼承自rest.RestController。所以它的get函數(shù)表示,當(dāng)訪問的是GET /v1的時候,要做的處理。然后,它還有很多類屬性,這些屬性分別表示不同URL路徑的控制器:
/v1/bays 由bays處理
/v1/baymodels 由baymodels處理
/v1/containers 由containers處理
其他的都是類似的。我們再繼續(xù)看bay.BaysController的代碼:
class BaysController(rest.RestController): """REST controller for Bays.""" def __init__(self): super(BaysController, self).__init__() _custom_actions = { "detail": ["GET"], } def get_all(...): def detail(...): def get_one(...): def post(...): def patch(...): def delete(...):
這個controller中只有函數(shù),沒有任何類屬性,而且沒有實現(xiàn)任何特殊方法,所以/v1/bays開頭的URL處理都在這個controller中終結(jié)。這個類會處理如下請求:
GET /v1/bays
GET /v1/bays/{UUID}
POST /v1/bays
PATCH /v1/bays/{UUID}
DELETE /v1/bays/{UUID}
GET /v1/bays/detail/{UUID}
看了上面的3個controller之后,你應(yīng)該能大概明白Pecan是如何對URL進行路由的。這種路由方式就是對象分發(fā):根據(jù)類屬性,包括數(shù)據(jù)屬性和方法屬性來決定如何路由一個HTTP請求。Pecan的文檔中對請求的路由有專門的描述,要想掌握Pecan的路由還是要完整的看一下官方文檔。
內(nèi)置RESTful支持我們上面舉例的controller都是繼承自pecan.rest.RestController,這種controller稱為RESTful controller,專門用于實現(xiàn)RESTful API的,因此在OpenStack中使用特別多。Pecan還支持普通的controller,稱為Generic controller。Generic controller繼承自object對象,默認(rèn)沒有實現(xiàn)對RESTful請求的方法。簡單的說,RESTful controller幫我們規(guī)定好了get_one(), get_all(), get(), post()等方法對應(yīng)的HTTP請求,而Generic controller則沒有。關(guān)于這兩種controller的區(qū)別,可以看官方文檔Writing RESTful Web Services with Generic Controllers,有很清楚的示例。
對于RestController中沒有預(yù)先定義好的方法,我們可以通過控制器的_custom_actions屬性來指定其能處理的方法。
class RootController(rest.RestController): _custom_actions = { "test": ["GET"], } @expose() def test(self): return "hello"
上面這個控制器是一個根控制器,指定了/test路徑支持GET方法,效果如下:
$ curl http://localhost:8080/test hello%那么HTTP請求和HTTP響應(yīng)呢?
上面講了這么多,我們都沒有說明在Pecan中如何處理請求和如何返回響應(yīng)。這個將在下一章中說明,同時我們會引入一個新的庫WSME。
WSME Pecan對請求和響應(yīng)的處理在開始提到WSME之前,我們先來看下Pecan自己對HTTP請求和響應(yīng)的處理。這樣你能更好的理解為什么會再引入一個WSME庫。
Pecan框架為每個線程維護了多帶帶的請求和響應(yīng)對象,你可以直接在請求處理函數(shù)中訪問。pecan.request和pecan.response分別代表當(dāng)前需要處理的請求和響應(yīng)對象。你可以直接操作這兩個對象,比如指定響應(yīng)的狀態(tài)碼,就像下面這個例子一樣(例子來自官方文檔):
@pecan.expose() def login(self): assert pecan.request.path == "/login" username = pecan.request.POST.get("username") password = pecan.request.POST.get("password") pecan.response.status = 403 pecan.response.text = "Bad Login!"
這個例子演示了訪問POST請求的參數(shù)以及返回403。你也可以重新構(gòu)造一個pecan.Response對象作為返回值(例子來自官方文檔):
from pecan import expose, Response class RootController(object): @expose() def hello(self): return Response("Hello, World!", 202)
另外,HTTP請求的參數(shù)也會可以作為控制器方法的參數(shù),還是來看幾個官方文檔的例子:
class RootController(object): @expose() def index(self, arg): return arg @expose() def kwargs(self, **kwargs): return str(kwargs)
這個控制器中的方法直接返回了參數(shù),演示了對GET請求參數(shù)的處理,效果是這樣的:
$ curl http://localhost:8080/?arg=foo foo $ curl http://localhost:8080/kwargs?a=1&b=2&c=3 {u"a": u"1", u"c": u"3", u"b": u"2"}
有時候,參數(shù)也可能是URL的一部分,比如最后的一段path作為參數(shù),就像下面這樣:
class RootController(object): @expose() def args(self, *args): return ",".join(args)
效果是這樣的:
$ curl http://localhost:8080/args/one/two/three one,two,three
另外,我們還要看一下POST方法的參數(shù)如何處理(例子來自官方文檔):
class RootController(object): @expose() def index(self, arg): return arg
效果如下,就是把HTTP body解析成了控制器方法的參數(shù):
$ curl -X POST "http://localhost:8080/" -H "Content-Type: application/x-www-form-urlencoded" -d "arg=foo" foo返回JSON還是HTML?
如果你不是明確的返回一個Response對象,那么Pecan中方法的返回內(nèi)容類型就是由expose()裝飾器決定的。默認(rèn)情況下,控制器的方法返回的content-type是HTML。
class RootController(rest.RestController): _custom_actions = { "test": ["GET"], } @expose() def test(self): return "hello"
效果如下:
$ curl -v http://localhost:8080/test * Hostname was NOT found in DNS cache * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 8080 (#0) > GET /test HTTP/1.1 > User-Agent: curl/7.38.0 > Host: localhost:8080 > Accept: */* > * HTTP 1.0, assume close after body < HTTP/1.0 200 OK < Date: Tue, 15 Sep 2015 14:31:28 GMT < Server: WSGIServer/0.1 Python/2.7.9 < Content-Length: 5 < Content-Type: text/html; charset=UTF-8 < * Closing connection 0 hello%
也可以讓它返回JSON:
class RootController(rest.RestController): _custom_actions = { "test": ["GET"], } @expose("json") def test(self): return "hello"
效果如下:
curl -v http://localhost:8080/test * Hostname was NOT found in DNS cache * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 8080 (#0) > GET /test HTTP/1.1 > User-Agent: curl/7.38.0 > Host: localhost:8080 > Accept: */* > * HTTP 1.0, assume close after body < HTTP/1.0 200 OK < Date: Tue, 15 Sep 2015 14:33:27 GMT < Server: WSGIServer/0.1 Python/2.7.9 < Content-Length: 18 < Content-Type: application/json; charset=UTF-8 < * Closing connection 0 {"hello": "world"}%
甚至,你還可以讓一個控制器方法根據(jù)URL path的來決定是返回HTML還是JSON:
class RootController(rest.RestController): _custom_actions = { "test": ["GET"], } @expose() @expose("json") def test(self): return json.dumps({"hello": "world"})
返回JSON:
$ curl -v http://localhost:8080/test.json * Hostname was NOT found in DNS cache * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 8080 (#0) > GET /test.json HTTP/1.1 > User-Agent: curl/7.38.0 > Host: localhost:8080 > Accept: */* > * HTTP 1.0, assume close after body < HTTP/1.0 200 OK < Date: Wed, 16 Sep 2015 14:26:27 GMT < Server: WSGIServer/0.1 Python/2.7.9 < Content-Length: 24 < Content-Type: application/json; charset=UTF-8 < * Closing connection 0 "{"hello": "world"}"%
返回HTML:
$ curl -v http://localhost:8080/test.html * Hostname was NOT found in DNS cache * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 8080 (#0) > GET /test.html HTTP/1.1 > User-Agent: curl/7.38.0 > Host: localhost:8080 > Accept: */* > * HTTP 1.0, assume close after body < HTTP/1.0 200 OK < Date: Wed, 16 Sep 2015 14:26:24 GMT < Server: WSGIServer/0.1 Python/2.7.9 < Content-Length: 18 < Content-Type: text/html; charset=UTF-8 < * Closing connection 0 {"hello": "world"}%
這里要注意一下:
同一個字符串作為JSON返回和作為HTML返回是不一樣的,仔細(xì)看一下HTTP響應(yīng)的內(nèi)容。
我們的例子中在URL的最后加上了.html后綴或者.json后綴,請嘗試一下不加后綴的化是返回什么?然后,調(diào)換一下兩個expose()的順序再試一下。
從上面的例子可以看出,決定響應(yīng)類型的主要是傳遞給expose()函數(shù)的參數(shù),我們看下expose()函數(shù)的完整聲明:
pecan.decorators.expose(template=None, content_type="text/html", generic=False)
template參數(shù)用來指定返回值的模板,如果是"json"就會返回JSON內(nèi)容,這里可以指定一個HTML文件,或者指定一個mako模板。
content_type指定響應(yīng)的content-type,默認(rèn)值是"text/html"。
generic參數(shù)表明該方法是一個“泛型”方法,可以指定多個不同的函數(shù)對應(yīng)同一個路徑的不同的HTTP方法。
看過參數(shù)的解釋后,你應(yīng)該能大概了解expose()函數(shù)是如何控制HTTP響應(yīng)的內(nèi)容和類型的。
用WSME來做什么?上面兩節(jié)已經(jīng)說明了Pecan可以比較好的處理HTTP請求中的參數(shù)以及控制HTTP返回值。那么為什么我們還需要WSME呢?因為Pecan在做下面這個事情的時候比較麻煩:請求參數(shù)和響應(yīng)內(nèi)容的類型檢查(英文簡稱就是typing)。當(dāng)然,做是可以做的,不過你需要自己訪問pecan.request和pecan.response,然后檢查指定的值的類型。WSME就是為解決這個問題而生的,而且適用場景就是RESTful API。
WSME簡介WSME的全稱是Web Service Made Easy,是專門用于實現(xiàn)REST服務(wù)的typing庫,讓你不需要直接操作請求和響應(yīng),而且剛好和Pecan結(jié)合得非常好,所以O(shè)penStack的很多項目都使用了Pecan + WSME的組合來實現(xiàn)API(好吧,我看過的項目,用了Pecan的都用了WSME)。WSME的理念是:在大部分情況下,Web服務(wù)的輸入和輸出對數(shù)據(jù)類型的要求都是嚴(yán)格的。所以它就專門解決了這個事情,然后把其他事情都交給其他框架去實現(xiàn)。因此,一般WSME都是和其他框架配合使用的,支持Pecan、Flask等。WSME的文檔地址是http://wsme.readthedocs.org/en/latest/index.html。
WSME的使用用了WSME后的好處是什么呢?WSME會自動幫你檢查HTTP請求和響應(yīng)中的數(shù)據(jù)是否符合預(yù)先設(shè)定好的要求。WSME的主要方式是通過裝飾器來控制controller方法的輸入和輸出。WSME中主要使用兩個控制器:
@signature: 這個裝飾器用來描述一個函數(shù)的輸入和輸出。
@wsexpose: 這個裝飾器包含@signature的功能,同時會把函數(shù)的路由信息暴露給Web框架,效果就像Pecan的expose裝飾器。
這里我們結(jié)合Pecan來講解WSME的使用。先來看一個原始類型的例子:
from wsmeext.pecan import wsexpose class RootController(rest.RestController): _custom_actions = { "test": ["GET"], } @wsexpose(int, int) def test(self, number): return number
如果不提供參數(shù),訪問會失?。?/p>
$ curl http://localhost:8080/test {"debuginfo": null, "faultcode": "Client", "faultstring": "Missing argument: "number""}%
如果提供的參數(shù)不是整型,訪問也會失?。?/p>
$ curl http://localhost:8080/test?number=a {"debuginfo": null, "faultcode": "Client", "faultstring": "Invalid input for field/attribute number. Value: "a". unable to convert to int"}%
上面這些錯誤信息都是由WSME框架直接返回的,還沒有執(zhí)行到你寫的方法。如果請求正確,那么會是這樣的:
$ curl -v http://localhost:8080/test?number=1 * Hostname was NOT found in DNS cache * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 8080 (#0) > GET /test?number=1 HTTP/1.1 > User-Agent: curl/7.38.0 > Host: localhost:8080 > Accept: */* > * HTTP 1.0, assume close after body < HTTP/1.0 200 OK < Date: Wed, 16 Sep 2015 15:06:35 GMT < Server: WSGIServer/0.1 Python/2.7.9 < Content-Length: 1 < Content-Type: application/json; charset=UTF-8 < * Closing connection 0 1%
請注意返回的content-type,這里返回JSON是因為我們使用的wsexpose設(shè)置的返回類型是XML和JSON,并且JSON是默認(rèn)值。上面這個例子就是WSME最簡單的應(yīng)用了。
那么現(xiàn)在有下面這些問題需要思考一下:
如果想用POST的方式來傳遞參數(shù),要怎么做呢?提示:要閱讀WSME中@signature裝飾器的文檔。
如果我希望使用/test/1這種方式來傳遞參數(shù)要怎么做呢?提示:要閱讀Pecan文檔中關(guān)于路由的部分。
WSME中支持對哪些類型的檢查呢?WSME支持整型、浮點型、字符串、布爾型、日期時間等,甚至還支持用戶自定義類型。提示:要閱讀WSME文檔中關(guān)于類型的部分。
WSME支持?jǐn)?shù)組類型么?支持。
上面的問題其實也是很多人使用WSME的時候經(jīng)常問的問題。我們將在下一篇文章中使用Pecan + WSME來繼續(xù)開發(fā)我們的demo,并且用代碼來回答上面所有的問題。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/37614.html
摘要:通過,也就是通過各個項目提供的來使用各個服務(wù)的功能。通過使用的方式是由各個服務(wù)自己實現(xiàn)的,比如負(fù)責(zé)計算的項目實現(xiàn)了計算相關(guān)的,負(fù)責(zé)認(rèn)證的項目實現(xiàn)了認(rèn)證和授權(quán)相關(guān)的。的服務(wù)都是使用的方式來部署的。 使用OpenStack服務(wù)的方式 OpenStack項目作為一個IaaS平臺,提供了三種使用方式: 通過Web界面,也就是通過Dashboard(面板)來使用平臺上的功能。 通過命令行,也就...
摘要:在實際項目中,這么做肯定是不行的實際項目中不會使用內(nèi)存數(shù)據(jù)庫,這種數(shù)據(jù)庫一般只是在單元測試中使用。接下來,我們將會了解中單元測試的相關(guān)知識。 在上一篇文章,我們介紹了SQLAlchemy的基本概念,也介紹了基本的使用流程。本文我們結(jié)合webdemo這個項目來介紹如何在項目中使用SQLAlchemy。另外,我們還會介紹數(shù)據(jù)庫版本管理的概念和實踐,這也是OpenStack每個項目都需要做的...
摘要:到這里,我們的服務(wù)的框架已經(jīng)搭建完成,并且測試服務(wù)器也跑起來了。上面的代碼也就可以修改為再次運行我們的測試服務(wù)器,就可以返現(xiàn)返回值為格式了。我們先來完成利用來檢查返回值的代碼方法的第一個參數(shù)表示返回值的類型這樣就完成了的返回值檢查了。 上一篇文章說到,我們將以實例的形式來繼續(xù)講述這個API服務(wù)的開發(fā)知識,這里會使用Pecan和WSME兩個庫。 設(shè)計REST API 要開發(fā)REST AP...
摘要:本文將進入單元測試的部分,這也是基礎(chǔ)知識中最后一個大塊。本文將重點講述和中的單元測試的生態(tài)環(huán)境。另外,在中指定要運行的單元測試用例的完整語法是。中使用模塊管理單元測試用例。每個項目的單元測試代碼結(jié)構(gòu)可 本文將進入單元測試的部分,這也是基礎(chǔ)知識中最后一個大塊。本文將重點講述Python和OpenStack中的單元測試的生態(tài)環(huán)境。 單元測試的重要性 github上有個人畫了一些不同語言的學(xué)...
摘要:不幸的是,在軟件包管理十分混亂,至少歷史上十分混亂。的最大改進是將函數(shù)的參數(shù)單獨放到一個的文件中這些成為包的元數(shù)據(jù)?;诘陌姹咎柟芾?。的版本推導(dǎo)這里重點說明一下基于的版本號管理這個功能。開發(fā)版本號的形式如下。 為什么寫這個系列 OpenStack是目前我所知的最大最復(fù)雜的基于Python項目。整個OpenStack項目包含了數(shù)十個主要的子項目,每個子項目所用到的庫也不盡相同。因此,對于...
閱讀 2015·2021-09-13 10:23
閱讀 2345·2021-09-02 09:47
閱讀 3805·2021-08-16 11:01
閱讀 1227·2021-07-25 21:37
閱讀 1608·2019-08-30 15:56
閱讀 542·2019-08-30 13:52
閱讀 3136·2019-08-26 10:17
閱讀 2453·2019-08-23 18:17