OpenAPI
1.介绍
Pait
除了参数类型转换与校验功能外,还支持自动生成路由函数的OpenAPI数据.你只需要编写路由函数的代码,Pait
就可以生成路由函数的对应OpenAPI文档,如文档首页的示例代码:
docs_source_code/introduction/flask_demo.py |
---|
| from typing import Type
from flask import Flask, Response, jsonify
from pydantic import BaseModel, Field
from pait.app.flask import pait
from pait.field import Json
from pait.model.response import JsonResponseModel
from pait.openapi.doc_route import AddDocRoute
class DemoResponseModel(JsonResponseModel):
class ResponseModel(BaseModel):
uid: int = Field()
user_name: str = Field()
description: str = "demo response"
response_data: Type[BaseModel] = ResponseModel
@pait(response_model_list=[DemoResponseModel])
def demo_post(
uid: int = Json.t(description="user id", gt=10, lt=1000),
username: str = Json.t(description="user name", min_length=2, max_length=4),
) -> Response:
return jsonify({"uid": uid, "user_name": username})
app = Flask("demo")
app.add_url_rule("/api", "demo", demo_post, methods=["POST"])
AddDocRoute(app)
if __name__ == "__main__":
app.run(port=8000)
|
docs_source_code/introduction/starlette_demo.py |
---|
| from typing import Type
from pydantic import BaseModel, Field
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route
from pait.app.starlette import pait
from pait.field import Json
from pait.model.response import JsonResponseModel
from pait.openapi.doc_route import AddDocRoute
class DemoResponseModel(JsonResponseModel):
class ResponseModel(BaseModel):
uid: int = Field()
user_name: str = Field()
description: str = "demo response"
response_data: Type[BaseModel] = ResponseModel
@pait(response_model_list=[DemoResponseModel])
async def demo_post(
uid: int = Json.t(description="user id", gt=10, lt=1000),
username: str = Json.t(description="user name", min_length=2, max_length=4),
) -> JSONResponse:
return JSONResponse({"uid": uid, "user_name": username})
app = Starlette(routes=[Route("/api", demo_post, methods=["POST"])])
AddDocRoute(app)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app)
|
docs_source_code/introduction/sanic_demo.py |
---|
| from typing import Type
from pydantic import BaseModel, Field
from sanic.app import Sanic
from sanic.response import HTTPResponse, json
from pait.app.sanic import pait
from pait.field import Json
from pait.model.response import JsonResponseModel
from pait.openapi.doc_route import AddDocRoute
class DemoResponseModel(JsonResponseModel):
class ResponseModel(BaseModel):
uid: int = Field()
user_name: str = Field()
description: str = "demo response"
response_data: Type[BaseModel] = ResponseModel
@pait(response_model_list=[DemoResponseModel])
async def demo_post(
uid: int = Json.t(description="user id", gt=10, lt=1000),
username: str = Json.t(description="user name", min_length=2, max_length=4),
) -> HTTPResponse:
return json({"uid": uid, "user_name": username})
app = Sanic(name="demo")
app.add_route(demo_post, "/api", methods=["POST"])
AddDocRoute(app)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app)
|
docs_source_code/introduction/tornado_demo.py |
---|
| from typing import Type
from pydantic import BaseModel, Field
from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler
from pait.app.tornado import pait
from pait.field import Json
from pait.model.response import JsonResponseModel
from pait.openapi.doc_route import AddDocRoute
class DemoResponseModel(JsonResponseModel):
class ResponseModel(BaseModel):
uid: int = Field()
user_name: str = Field()
description: str = "demo response"
response_data: Type[BaseModel] = ResponseModel
class DemoHandler(RequestHandler):
@pait(response_model_list=[DemoResponseModel])
def post(
self,
uid: int = Json.t(description="user id", gt=10, lt=1000),
username: str = Json.t(description="user name", min_length=2, max_length=4),
) -> None:
self.write({"uid": uid, "user_name": username})
app: Application = Application([(r"/api", DemoHandler)])
AddDocRoute(app)
if __name__ == "__main__":
app.listen(8000)
IOLoop.instance().start()
|
在运行代码并在浏览器访问: http://127.0.0.1:8000/swagger 就可以看到SwaggerUI的页面:
通过页面可以发现,Swagger页面展示的数据包括了响应对象的数据,通过Pait
装饰器标注的数据,以及路由函数的参数据。
2.路由函数的OpenAPI属性
为路由函数绑定OpenAPI属性非常简单,只需要在Pait
装饰器填写对应的属性即可,常见的路由函数属性绑定如下代码:
from pait.app.any import pait
from pait.model.tag import Tag
demo_tag = Tag("demo tag", desc="demo tag desc")
@pait(
desc="demo func desc",
name="demo",
summary="demo func summary",
tag=(demo_tag,)
)
def demo() -> None:
pass
该代码通过Pait
的属性来指定路由函数的OpenAPI信息,它们的具体作用如下:
属性 |
OpenAPI属性 |
描述 |
desc |
description |
接口的文档详细描述 |
name |
operation_id |
接口的名称 |
summary |
summary |
接口的简介 |
tag |
tag |
接口的OpenAPI标签 |
Note
- 1.在大多数情况下,
name
属性只是operation_id
属性的一部分,Pait
并不保证name
与operation_id
完全相等。
- 2.Tag的声明应该保证全局唯一
不过name
和desc
属性分别可以通过路由函数名和路由函数的__doc__
获取,比如下面代码中路由函数的name
和desc
属性与上面代码一致。
from pait.app.any import pait
from pait.model.tag import Tag
demo_tag = Tag("demo tag", desc="demo tag desc")
@pait(
summary="demo func summary",
tag=(demo_tag,)
)
def demo() -> None:
"""demo func desc"""
pass
除了上面几个属性外,OpenAPI还有一个名为deprecated
的属性,该属性主要是用来标记接口是否已经被遗弃。
Pait
没有直支持deprecated
属性的标记,而是通过PaitStatus
来判断路由函数的deprecated
是否为True
, PaitStatus
的使用方法非常简单,如下代码:
from pait.app.any import pait
from pait.model.status import PaitStatus
@pait(status=PaitStatus.test)
def demo() -> None:
pass
该代码表示路由函数为测试中且deprecated
为False
,更多的状态以及是否为deprecated
见下表:
状态值 |
阶段 |
deprecated |
描述 |
undefined |
默认 |
False |
未定义,默认的状态 |
design |
开发中 |
False |
设计中 |
dev |
开发中 |
False |
开发测试中 |
integration_testing |
开发中 |
False |
联合调试中 |
complete |
开发完成 |
False |
开发完成 |
test |
开发完成 |
False |
测试中 |
pre_release |
上线 |
False |
预发布 |
release |
上线 |
False |
发布 |
abnormal |
下线 |
True |
出现异常,临时下线 |
maintenance |
下线 |
False |
维护中 |
archive |
下线 |
True |
归档 |
abandoned |
下线 |
True |
被遗弃的,后续不会再使用 |
## 3.路由函数的响应对象 |
|
|
|
在前文的介绍中,通过response_model_list
定义了路由函数的响应对象的列表,其中包含了一个或多个响应对象。
Note
建议只填写一个响应对象,如果有多个响应对象,大多数非OpenAPI功能(如插件)会默认读取第一个响应对象。
Pait
提供多种响应对象,如下列表:
响应对象名称 |
描述 |
JsonResponseModel |
响应格式为Json的对象 |
XmlResponseModel |
响应格式为Xml的对象 |
TextResponseModel |
响应格式为文本的对象 |
HtmlResponseModel |
响应格式为Html的对象 |
FileResponseModel |
响应格式为File的对象 |
Pait
只提供常见的响应类型的响应对象,如果没有适用的响应对象,可以通过pait.model.response.BaseResponseModel
定义一个符合需求的响应对象。
pait.model.response.BaseResponseModel
是一个包含OpenAPI响应对象不同属性的容器,如下表:
属性名 |
描述 |
response_data |
定义响应数据,如果是带有结构的响应数据,那么应该是一个描述结构体的pydantic.BaseModel |
media_type |
响应对象的Media Type |
name |
响应对象的名称 |
description |
响应对象的描述 |
header |
响应对象的Header,填入的值应该是pydantic.BaseModel 而不是Dict |
status_code |
响应对象的Http状态码,默认为(200, ) |
openapi_schema |
响应对象的openapi.schema |
通过这些属性可以定义大多数响应对象,示例代码如下:
docs_source_code/openapi/how_to_use_openapi/flask_demo.py |
---|
| from typing import List
from flask import Flask, Response, jsonify
from pydantic import BaseModel, Field
from pait.app.flask import pait
from pait.field import Query
from pait.model.response import BaseResponseModel
from pait.openapi.doc_route import AddDocRoute
class MyJsonResponseModel(BaseResponseModel):
class ResponseModel(BaseModel):
class UserModel(BaseModel):
name: str = Field(..., example="so1n")
uid: int = Query.t(description="user id", gt=10, lt=1000)
age: int = Field(..., gt=0)
code: int = Field(..., ge=0)
msg: str = Field(...)
data: List[UserModel]
class HeaderModel(BaseModel):
x_token: str = Field(..., alias="X-Token")
content_type: str = Field(..., alias="Content-Type")
response_data = ResponseModel
description = "demo json response"
media_type = "application/json; charset=utf-8"
header = HeaderModel
status_code = (200, 201, 404)
@pait(response_model_list=[MyJsonResponseModel])
def demo(
uid: int = Query.t(description="user id", gt=10, lt=1000),
age: int = Query.t(description="age", gt=0),
username: str = Query.t(description="user name", min_length=2, max_length=4),
) -> Response:
resp = jsonify({"code": 0, "msg": "", "data": [{"name": username, "uid": uid, "age": age}]})
resp.headers.add("X-Token", "12345")
resp.headers["Content-Type"] = "application/json; charset=utf-8"
return resp
app = Flask("demo")
app.add_url_rule("/api/demo", "demo", demo, methods=["GET"])
AddDocRoute(app)
if __name__ == "__main__":
app.run(port=8000)
|
docs_source_code/openapi/how_to_use_openapi/starlette_demo.py |
---|
| from typing import List
from pydantic import BaseModel, Field
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route
from pait.app.starlette import pait
from pait.field import Query
from pait.model.response import BaseResponseModel
from pait.openapi.doc_route import AddDocRoute
class MyJsonResponseModel(BaseResponseModel):
class ResponseModel(BaseModel):
class UserModel(BaseModel):
name: str = Field(..., example="so1n")
uid: int = Query.t(description="user id", gt=10, lt=1000)
age: int = Field(..., gt=0)
code: int = Field(..., ge=0)
msg: str = Field(...)
data: List[UserModel]
class HeaderModel(BaseModel):
x_token: str = Field(..., alias="X-Token")
content_type: str = Field(..., alias="Content-Type")
response_data = ResponseModel
description = "demo json response"
media_type = "application/json; charset=utf-8"
header = HeaderModel
status_code = (200, 201, 404)
@pait(response_model_list=[MyJsonResponseModel])
async def demo(
uid: int = Query.t(description="user id", gt=10, lt=1000),
age: int = Query.t(description="age", gt=0),
username: str = Query.t(description="user name", min_length=2, max_length=4),
) -> JSONResponse:
resp = JSONResponse({"code": 0, "msg": "", "data": [{"name": username, "uid": uid, "age": age}]})
resp.headers.append("X-Token", "12345")
resp.headers["Content-Type"] = "application/json; charset=utf-8"
return resp
app = Starlette(routes=[Route("/api/demo", demo, methods=["GET"])])
AddDocRoute(app)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app)
|
docs_source_code/openapi/how_to_use_openapi/sanic_demo.py |
---|
| from typing import List
from pydantic import BaseModel, Field
from sanic.app import Sanic
from sanic.response import HTTPResponse, json
from pait.app.sanic import pait
from pait.field import Query
from pait.model.response import BaseResponseModel
from pait.openapi.doc_route import AddDocRoute
class MyJsonResponseModel(BaseResponseModel):
class ResponseModel(BaseModel):
class UserModel(BaseModel):
name: str = Field(..., example="so1n")
uid: int = Query.t(description="user id", gt=10, lt=1000)
age: int = Field(..., gt=0)
code: int = Field(..., ge=0)
msg: str = Field(...)
data: List[UserModel]
class HeaderModel(BaseModel):
x_token: str = Field(..., alias="X-Token")
content_type: str = Field(..., alias="Content-Type")
response_data = ResponseModel
description = "demo json response"
media_type = "application/json; charset=utf-8"
header = HeaderModel
status_code = (200, 201, 404)
@pait(response_model_list=[MyJsonResponseModel])
async def demo(
uid: int = Query.t(description="user id", gt=10, lt=1000),
age: int = Query.t(description="age", gt=0),
username: str = Query.t(description="user name", min_length=2, max_length=4),
) -> HTTPResponse:
resp = json({"code": 0, "msg": "", "data": [{"name": username, "uid": uid, "age": age}]})
resp.headers.add("X-Token", "12345")
resp.headers["Content-Type"] = "application/json; charset=utf-8"
return resp
app = Sanic(name="demo")
app.add_route(demo, "/api/demo", methods=["GET"])
AddDocRoute(app)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app)
|
docs_source_code/openapi/how_to_use_openapi/tornado_demo.py |
---|
| from typing import List
from pydantic import BaseModel, Field
from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler
from pait.app.tornado import pait
from pait.field import Query
from pait.model.response import BaseResponseModel
from pait.openapi.doc_route import AddDocRoute
class MyJsonResponseModel(BaseResponseModel):
class ResponseModel(BaseModel):
class UserModel(BaseModel):
name: str = Field(..., example="so1n")
uid: int = Query.t(description="user id", gt=10, lt=1000)
age: int = Field(..., gt=0)
code: int = Field(..., ge=0)
msg: str = Field(...)
data: List[UserModel]
class HeaderModel(BaseModel):
x_token: str = Field(..., alias="X-Token")
content_type: str = Field(..., alias="Content-Type")
response_data = ResponseModel
description = "demo json response"
media_type = "application/json; charset=utf-8"
header = HeaderModel
status_code = (200, 201, 404)
class DemoHandler(RequestHandler):
@pait(response_model_list=[MyJsonResponseModel])
def get(
self,
uid: int = Query.t(description="user id", gt=10, lt=1000),
age: int = Query.t(description="age", gt=0),
username: str = Query.t(description="user name", min_length=2, max_length=4),
) -> None:
self.write({"code": 0, "msg": "", "data": [{"name": username, "uid": uid, "age": age}]})
self.add_header("X-Token", "12345")
self.set_header("Content-Type", "application/json; charset=utf-8")
app: Application = Application([(r"/api/demo", DemoHandler)])
AddDocRoute(app)
if __name__ == "__main__":
app.listen(8000)
IOLoop.instance().start()
|
该代码的第一段高亮代码是定义一个响应对象,它表示的Http状态码可能为200,201和404,
Media Type
是application/json
,
Header拥有X-Token
和Content-Type
两个属性,
而最重要的是定义了如下的返回数据结构:
{
"code": 0,
"msg": "",
"data": [
{
"name": "so1n",
"sex": "man",
"age": 18
}
]
}
而第二段代码则是把响应对象与路由函数进行绑定,
现在运行代码,并在浏览器访问127.0.0.1:8000/redoc,可以看到当前页面完整的展示出路由函数的OpenAPI数据,如下图
Note
由于Redoc
展示的数据比Swagger
简约了许多,本用例采用Redoc
展示数据,实际上Pait
支持多种OpenAPI的UI页面,详见OpenAPI路由。
4.Field
上一节的页面不仅包含了响应对象的数据,还包括请求参数的数据,如uid
参数被声明为required
,也就是该参数是必填的,同时也声明了它的类型是integer
且取值范围是10-1000。
这些请求参数都是通过Field
对象声明的,它们不仅可以校验参数,也可以为OpenAPI提供数据。除此之外,Field
对象有部分属性是专门为OpenAPI
服务的,它们包括:
属性 |
作用 |
links |
OpenAPI link功能,用于指定参数与某个响应对象有关联 |
media_type |
定义参数的Media Type ,目前只有Body , Json , File , Form , MultiForm 有使用,建议一个接口只采用同一种Media Type |
openapi_serialization |
定义参数的serialization ,具体请参考serialization |
example |
定义参数的示例值;Pait 支持工厂函数,但是转化为OpenAPI则会变成当下生成的固定值 |
openapi_include |
如果值为False ,那么Pait 在生成OpenAPI时不会考虑该参数 |
4.1.Links
Links是OpenAPI的一个功能,用于指定A接口的请求参数与B接口的响应对象的某个数据有关联。比如下面的例子:
docs_source_code/openapi/how_to_use_openapi/flask_link_demo.py |
---|
| import hashlib
from typing import Type
from flask import Flask
from pydantic import BaseModel, Field
from pait import field
from pait.app.flask import pait
from pait.model.response import JsonResponseModel
from pait.openapi.doc_route import AddDocRoute
from pait.openapi.openapi import LinksModel
class LoginRespModel(JsonResponseModel):
class ResponseModel(BaseModel): # type: ignore
class DataModel(BaseModel):
token: str
code: int = Field(0, description="api code")
msg: str = Field("success", description="api status msg")
data: DataModel
description: str = "login response"
response_data: Type[BaseModel] = ResponseModel
link_login_token_model: LinksModel = LinksModel(LoginRespModel, "$response.body#/data/token", desc="test links model")
@pait(response_model_list=[LoginRespModel])
def login_route(
uid: str = field.Json.i(description="user id"), password: str = field.Json.i(description="password")
) -> dict:
return {"code": 0, "msg": "", "data": {"token": hashlib.sha256((uid + password).encode("utf-8")).hexdigest()}}
@pait()
def get_user_route(token: str = field.Header.i("", description="token", links=link_login_token_model)) -> dict:
if token:
return {"code": 0, "msg": ""}
else:
return {"code": 1, "msg": ""}
app = Flask("demo")
app.add_url_rule("/api/login", "login", login_route, methods=["POST"])
app.add_url_rule("/api/get-user-info", "get_user_info", get_user_route, methods=["GET"])
AddDocRoute(app)
if __name__ == "__main__":
app.run(port=8000)
|
docs_source_code/openapi/how_to_use_openapi/starlette_link_demo.py |
---|
| import hashlib
from typing import Type
from pydantic import BaseModel, Field
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route
from pait import field
from pait.app.starlette import pait
from pait.model.response import JsonResponseModel
from pait.openapi.doc_route import AddDocRoute
from pait.openapi.openapi import LinksModel
class LoginRespModel(JsonResponseModel):
class ResponseModel(BaseModel): # type: ignore
class DataModel(BaseModel):
token: str
code: int = Field(0, description="api code")
msg: str = Field("success", description="api status msg")
data: DataModel
description: str = "login response"
response_data: Type[BaseModel] = ResponseModel
link_login_token_model: LinksModel = LinksModel(LoginRespModel, "$response.body#/data/token", desc="test links model")
@pait(response_model_list=[LoginRespModel])
async def login_route(
uid: str = field.Json.i(description="user id"), password: str = field.Json.i(description="password")
) -> JSONResponse:
return JSONResponse(
{"code": 0, "msg": "", "data": {"token": hashlib.sha256((uid + password).encode("utf-8")).hexdigest()}}
)
@pait()
async def get_user_route(
token: str = field.Header.i(
"",
description="token",
links=link_login_token_model,
)
) -> JSONResponse:
if token:
return JSONResponse({"code": 0, "msg": ""})
else:
return JSONResponse({"code": 1, "msg": ""})
app = Starlette(
routes=[
Route("/api/login", login_route, methods=["POST"]),
Route("/api/get-user-info", get_user_route, methods=["GET"]),
]
)
AddDocRoute(app)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app)
|
docs_source_code/openapi/how_to_use_openapi/sanic_link_demo.py |
---|
| import hashlib
from typing import Type
from pydantic import BaseModel, Field
from sanic.app import Sanic
from sanic.response import HTTPResponse, json
from pait import field
from pait.app.sanic import pait
from pait.model.response import JsonResponseModel
from pait.openapi.doc_route import AddDocRoute
from pait.openapi.openapi import LinksModel
class LoginRespModel(JsonResponseModel):
class ResponseModel(BaseModel): # type: ignore
class DataModel(BaseModel):
token: str
code: int = Field(0, description="api code")
msg: str = Field("success", description="api status msg")
data: DataModel
description: str = "login response"
response_data: Type[BaseModel] = ResponseModel
link_login_token_model: LinksModel = LinksModel(LoginRespModel, "$response.body#/data/token", desc="test links model")
@pait(response_model_list=[LoginRespModel])
async def login_route(
uid: str = field.Json.i(description="user id"), password: str = field.Json.i(description="password")
) -> HTTPResponse:
return json({"code": 0, "msg": "", "data": {"token": hashlib.sha256((uid + password).encode("utf-8")).hexdigest()}})
@pait()
def get_user_route(
token: str = field.Header.i(
"",
description="token",
links=link_login_token_model,
)
) -> HTTPResponse:
if token:
return json({"code": 0, "msg": ""})
else:
return json({"code": 1, "msg": ""})
app = Sanic(name="demo")
app.add_route(login_route, "/api/login", methods=["POST"])
app.add_route(get_user_route, "/api/get-user-info", methods=["GET"])
AddDocRoute(app)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app)
|
docs_source_code/openapi/how_to_use_openapi/tornado_link_demo.py |
---|
| import hashlib
from typing import Type
from pydantic import BaseModel, Field
from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler
from pait import field
from pait.app.tornado import pait
from pait.model.response import JsonResponseModel
from pait.openapi.doc_route import AddDocRoute
from pait.openapi.openapi import LinksModel
class LoginRespModel(JsonResponseModel):
class ResponseModel(BaseModel): # type: ignore
class DataModel(BaseModel):
token: str
code: int = Field(0, description="api code")
msg: str = Field("success", description="api status msg")
data: DataModel
description: str = "login response"
response_data: Type[BaseModel] = ResponseModel
link_login_token_model: LinksModel = LinksModel(LoginRespModel, "$response.body#/data/token", desc="test links model")
class LoginHandler(RequestHandler):
@pait(response_model_list=[LoginRespModel])
async def post(
self, uid: str = field.Json.i(description="user id"), password: str = field.Json.i(description="password")
) -> None:
self.write(
{"code": 0, "msg": "", "data": {"token": hashlib.sha256((uid + password).encode("utf-8")).hexdigest()}}
)
class GetUserHandler(RequestHandler):
@pait()
def get(
self,
token: str = field.Header.i(
"",
description="token",
links=link_login_token_model,
),
) -> None:
if token:
self.write({"code": 0, "msg": ""})
else:
self.write({"code": 1, "msg": ""})
app: Application = Application(
[(r"/api/login", LoginHandler), (r"/api/get-user-info", GetUserHandler)],
)
AddDocRoute(app)
if __name__ == "__main__":
app.listen(8000)
IOLoop.instance().start()
|
这个例子定义了一个登陆路由函数--login_route
以及获取用户详情的路由函数--get_user_route
,
其中获取用户详情的路由函数需要一个token参数来校验用户并获取用户的id,而这个token参数是通过登陆路由函数生成的,
所以用户详情路由函数的token参数与登陆路由函数的响应数据中的token是有关联的。
为了能让OpenAPI识别到token参数与响应对象中的token有关联,
首先创建一个名为link_login_token_model
的实例,这个实例与LoginRespModel
响应对象绑定,且通过表达式"$response.body#/data/token"表明要绑定的参数。
接着把
link_login_token_model赋值到
get_user_route路由函数中
token的
Field的
links`属性,这样就完成了一次关联。
在运行代码并在浏览器访问http://127.0.0.1:8000/swagger后会出现如下页面:
可以看到登陆接口的Response
那一栏的最右边展示了Links
的数据。
5.OpenAPI生成
在OpenAPI生态中,它的核心是一份符合OpenAPI格式的json或者yaml文本,这个文本可以在Swagger等OpenAPI页面中使用,也可以导入到Postman等工具中使用。
Pait
会把收集到的数据委托给AnyAPI处理并生成一个OpenAPI对象,OpenAPI对象支持被转化为各种人类易读的文本或者页面。
下面是一个生成OpenAPI对象以及基于OpenAPI对象生成输出内容的示例:
docs_source_code/docs_source_code/openapi/how_to_use_openapi/flask_with_output_demo.py |
---|
| from typing import Any, Type
from any_api.openapi.to.markdown import Markdown
from flask import Flask, Response, jsonify
from pydantic import BaseModel, Field
from pait.app.flask import pait
from pait.field import Json
from pait.g import config
from pait.model.response import JsonResponseModel
from pait.openapi.openapi import OpenAPI
class DemoResponseModel(JsonResponseModel):
class ResponseModel(BaseModel):
uid: int = Field()
user_name: str = Field()
description: str = "demo response"
response_data: Type[BaseModel] = ResponseModel
@pait(response_model_list=[DemoResponseModel])
def demo_post(
uid: int = Json.t(description="user id", gt=10, lt=1000),
username: str = Json.t(description="user name", min_length=2, max_length=4),
) -> Response:
return jsonify({"uid": uid, "user_name": username})
app = Flask("demo")
app.add_url_rule("/api", "demo", demo_post, methods=["POST"])
def my_serialization(content: str, **kwargs: Any) -> str:
import json
import yaml # type: ignore
return yaml.dump(json.loads(json.dumps(content, cls=config.json_encoder), **kwargs))
openapi_model = OpenAPI(app)
print("json", openapi_model.content())
print("yaml", openapi_model.content(serialization_callback=my_serialization))
for i18n_lang in ("zh-cn", "en"):
print(f"{i18n_lang} md", Markdown(openapi_model, i18n_lang=i18n_lang).content)
|
docs_source_code/docs_source_code/openapi/how_to_use_openapi/starlette_with_output_demo.py |
---|
| from typing import Any, List
from any_api.openapi.to.markdown import Markdown
from pydantic import BaseModel, Field
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route
from pait.app.starlette import pait
from pait.field import Query
from pait.g import config
from pait.model.response import BaseResponseModel
from pait.openapi.openapi import OpenAPI
class MyJsonResponseModel(BaseResponseModel):
class ResponseModel(BaseModel):
class UserModel(BaseModel):
name: str = Field(..., example="so1n")
uid: int = Query.t(description="user id", gt=10, lt=1000)
age: int = Field(..., gt=0)
code: int = Field(..., ge=0)
msg: str = Field(...)
data: List[UserModel]
class HeaderModel(BaseModel):
x_token: str = Field(..., alias="X-Token")
content_type: str = Field(..., alias="Content-Type")
response_data = ResponseModel
description = "demo json response"
media_type = "application/json; charset=utf-8"
header = HeaderModel
status_code = (200, 201, 404)
@pait(response_model_list=[MyJsonResponseModel])
async def demo(
uid: int = Query.t(description="user id", gt=10, lt=1000),
age: int = Query.t(description="age", gt=0),
username: str = Query.t(description="user name", min_length=2, max_length=4),
) -> JSONResponse:
resp = JSONResponse({"code": 0, "msg": "", "data": [{"name": username, "uid": uid, "age": age}]})
resp.headers.append("X-Token", "12345")
resp.headers["Content-Type"] = "application/json; charset=utf-8"
return resp
app = Starlette(routes=[Route("/api/demo", demo, methods=["GET"])])
def my_serialization(content: str, **kwargs: Any) -> str:
import json
import yaml # type: ignore
return yaml.dump(json.loads(json.dumps(content, cls=config.json_encoder), **kwargs))
openapi_model = OpenAPI(app)
print("json", openapi_model.content())
print("yaml", openapi_model.content(serialization_callback=my_serialization))
for i18n_lang in ("zh-cn", "en"):
print(f"{i18n_lang} md", Markdown(openapi_model, i18n_lang=i18n_lang).content)
|
docs_source_code/docs_source_code/openapi/how_to_use_openapi/sanic_with_output_demo.py |
---|
| from typing import Any, List
from any_api.openapi.to.markdown import Markdown
from pydantic import BaseModel, Field
from sanic.app import Sanic
from sanic.response import HTTPResponse, json
from pait.app.sanic import pait
from pait.field import Query
from pait.g import config
from pait.model.response import BaseResponseModel
from pait.openapi.openapi import OpenAPI
class MyJsonResponseModel(BaseResponseModel):
class ResponseModel(BaseModel):
class UserModel(BaseModel):
name: str = Field(..., example="so1n")
uid: int = Query.t(description="user id", gt=10, lt=1000)
age: int = Field(..., gt=0)
code: int = Field(..., ge=0)
msg: str = Field(...)
data: List[UserModel]
class HeaderModel(BaseModel):
x_token: str = Field(..., alias="X-Token")
content_type: str = Field(..., alias="Content-Type")
response_data = ResponseModel
description = "demo json response"
media_type = "application/json; charset=utf-8"
header = HeaderModel
status_code = (200, 201, 404)
@pait(response_model_list=[MyJsonResponseModel])
async def demo(
uid: int = Query.t(description="user id", gt=10, lt=1000),
age: int = Query.t(description="age", gt=0),
username: str = Query.t(description="user name", min_length=2, max_length=4),
) -> HTTPResponse:
resp = json({"code": 0, "msg": "", "data": [{"name": username, "uid": uid, "age": age}]})
resp.headers.add("X-Token", "12345")
resp.headers["Content-Type"] = "application/json; charset=utf-8"
return resp
app = Sanic(name="demo")
app.add_route(demo, "/api/demo", methods=["GET"])
def my_serialization(content: str, **kwargs: Any) -> str:
import json
import yaml # type: ignore
return yaml.dump(json.loads(json.dumps(content, cls=config.json_encoder), **kwargs))
openapi_model = OpenAPI(app)
print("json", openapi_model.content())
print("yaml", openapi_model.content(serialization_callback=my_serialization))
for i18n_lang in ("zh-cn", "en"):
print(f"{i18n_lang} md", Markdown(openapi_model, i18n_lang=i18n_lang).content)
|
docs_source_code/docs_source_code/openapi/how_to_use_openapi/tornado_with_output_demo.py |
---|
| from typing import Any, List
from any_api.openapi.to.markdown import Markdown
from pydantic import BaseModel, Field
from tornado.web import Application, RequestHandler
from pait.app.tornado import pait
from pait.field import Query
from pait.g import config
from pait.model.response import BaseResponseModel
from pait.openapi.openapi import OpenAPI
class MyJsonResponseModel(BaseResponseModel):
class ResponseModel(BaseModel):
class UserModel(BaseModel):
name: str = Field(..., example="so1n")
uid: int = Query.t(description="user id", gt=10, lt=1000)
age: int = Field(..., gt=0)
code: int = Field(..., ge=0)
msg: str = Field(...)
data: List[UserModel]
class HeaderModel(BaseModel):
x_token: str = Field(..., alias="X-Token")
content_type: str = Field(..., alias="Content-Type")
response_data = ResponseModel
description = "demo json response"
media_type = "application/json; charset=utf-8"
header = HeaderModel
status_code = (200, 201, 404)
class DemoHandler(RequestHandler):
@pait(response_model_list=[MyJsonResponseModel])
def get(
self,
uid: int = Query.t(description="user id", gt=10, lt=1000),
age: int = Query.t(description="age", gt=0),
username: str = Query.t(description="user name", min_length=2, max_length=4),
) -> None:
self.write({"code": 0, "msg": "", "data": [{"name": username, "uid": uid, "age": age}]})
self.add_header("X-Token", "12345")
self.set_header("Content-Type", "application/json; charset=utf-8")
app: Application = Application([(r"/api/demo", DemoHandler)])
def my_serialization(content: str, **kwargs: Any) -> str:
import json
import yaml # type: ignore
return yaml.dump(json.loads(json.dumps(content, cls=config.json_encoder), **kwargs))
openapi_model = OpenAPI(app)
print("json", openapi_model.content())
print("yaml", openapi_model.content(serialization_callback=my_serialization))
for i18n_lang in ("zh-cn", "en"):
print(f"{i18n_lang} md", Markdown(openapi_model, i18n_lang=i18n_lang).content)
|
该示例代码第一步是创建openapi_model
,它在创建的过程中会获取到app
对应的接口以及数据。
第二步是调用openapi_model
的content
方法,该方法的serialization_callback
参数的默认值为json.dump
。所以直接调用openapi_model.content()
会生成如下JSON文本:
Json示例(示例文本较长,请按需打开)
{
"openapi": "3.0.0",
"info": {
"title": "AnyApi",
"description": "API Documentation",
"version": "0.0.1"
},
"servers": [],
"paths": {
"/api": {
"options": {
"tags": [
"default"
],
"summary": "",
"description": "",
"operationId": "demo_options",
"parameters": [],
"requestBody": {
"description": "",
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Demo_Post__Main___Demo_Post"
}
}
}
},
"responses": {
"200": {
"description": "demo response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ResponseModel"
}
}
}
}
},
"deprecated": false,
"pait_info": {
"group": "root",
"status": "undefined",
"author": [
""
],
"pait_id": "__main___demo_post"
}
},
"post": {
"tags": [
"default"
],
"summary": "",
"description": "",
"operationId": "demo_post",
"parameters": [],
"requestBody": {
"description": "",
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Demo_Post__Main___Demo_Post"
}
}
}
},
"responses": {
"200": {
"description": "demo response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ResponseModel"
}
}
}
}
},
"deprecated": false,
"pait_info": {
"group": "root",
"status": "undefined",
"author": [
""
],
"pait_id": "__main___demo_post"
}
}
}
},
"tags": [
{
"name": "default",
"description": ""
}
],
"components": {
"schemas": {
"Demo_Post__Main___Demo_Post": {
"title": "Demo_Post__Main___Demo_Post",
"type": "object",
"properties": {
"uid": {
"title": "Uid",
"description": "user id",
"exclusiveMinimum": 10,
"exclusiveMaximum": 1000,
"type": "integer"
},
"username": {
"title": "Username",
"description": "user name",
"maxLength": 4,
"minLength": 2,
"type": "string"
}
},
"required": [
"uid",
"username"
]
},
"ResponseModel": {
"title": "ResponseModel",
"type": "object",
"properties": {
"uid": {
"title": "Uid",
"type": "integer"
},
"user_name": {
"title": "User Name",
"type": "string"
}
},
"required": [
"uid",
"user_name"
]
}
}
}
}
此外,示例代码中还自定义了一个序列化为yaml的函数--my_serialization
,并通过openapi_model.content(serialization_callback=my_serialization)
生成如下yaml文本:
Yson示例(示例文本较长,请按需打开)
components:
schemas:
Demo_Post__Main___Demo_Post:
properties:
uid:
description: user id
exclusiveMaximum: 1000
exclusiveMinimum: 10
title: Uid
type: integer
username:
description: user name
maxLength: 4
minLength: 2
title: Username
type: string
required:
- uid
- username
title: Demo_Post__Main___Demo_Post
type: object
ResponseModel:
properties:
uid:
title: Uid
type: integer
user_name:
title: User Name
type: string
required:
- uid
- user_name
title: ResponseModel
type: object
info:
description: API Documentation
title: AnyApi
version: 0.0.1
openapi: 3.0.0
paths:
/api:
options:
deprecated: false
description: ''
operationId: demo_options
pait_info:
author:
- ''
group: root
pait_id: __main___demo_post
status: undefined
parameters: []
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Demo_Post__Main___Demo_Post'
description: ''
required: false
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/ResponseModel'
description: demo response
summary: ''
tags:
- default
post:
deprecated: false
description: ''
operationId: demo_post
pait_info:
author:
- ''
group: root
pait_id: __main___demo_post
status: undefined
parameters: []
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Demo_Post__Main___Demo_Post'
description: ''
required: false
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/ResponseModel'
description: demo response
summary: ''
tags:
- default
servers: []
tags:
- description: ''
name: default
Finally, Markdown documents in different languages are also generated via the Markdown
method of AnyAPI.
Chinese Markdown example (the example text is long, please open it as needed, only native data is displayed)
# AnyApi
### 名称: demo.demo
**标签**: default
- 路径: /api/demo
- 方法: get
- 请求:
**query**
|名称|默认|类型|描述|示例|其它|
|---|---|---|---|---|---|
|uid|`必填`|integer|user id||exclusiveMinimum:10;<br>exclusiveMaximum:1000|
|age|`必填`|integer|age||exclusiveMinimum:0|
|username|`必填`|string|user name||maxLength:4;<br>minLength:2|
- 响应
**描述**: demo json response
*Header*
- 200:application/json; charset=utf-8
**响应信息**
|名称|默认|类型|描述|示例|其它|
|---|---|---|---|---|---|
|code|`必填`|integer|||minimum:0|
|msg|`必填`|string||||
|data|`必填`|array|||items:{'$ref': '#/components/schemas/UserModel'}|
**响应示例**
```json
{
"code": 0,
"msg": "",
"data": {
"name": "so1n",
"uid": 0,
"age": 0
}
}
```
**描述**: demo json response
*Header*
- 201:application/json; charset=utf-8
**响应信息**
|名称|默认|类型|描述|示例|其它|
|---|---|---|---|---|---|
|code|`必填`|integer|||minimum:0|
|msg|`必填`|string||||
|data|`必填`|array|||items:{'$ref': '#/components/schemas/UserModel'}|
**响应示例**
```json
{
"code": 0,
"msg": "",
"data": {
"name": "so1n",
"uid": 0,
"age": 0
}
}
```
**描述**: demo json response
*Header*
- 404:application/json; charset=utf-8
**响应信息**
|名称|默认|类型|描述|示例|其它|
|---|---|---|---|---|---|
|code|`必填`|integer|||minimum:0|
|msg|`必填`|string||||
|data|`必填`|array|||items:{'$ref': '#/components/schemas/UserModel'}|
**响应示例**
```json
{
"code": 0,
"msg": "",
"data": {
"name": "so1n",
"uid": 0,
"age": 0
}
}
```
English Markdown example (the example text is long, please open it as needed, only native data is displayed)
# AnyApi
### Name: demo.demo
**Tag**: default
- Path: /api/demo
- Method: get
- Request:
**query**
|Name|Default|Type|Desc|Example|Other|
|---|---|---|---|---|---|
|uid|`Required`|integer|user id||exclusiveMinimum:10;<br>exclusiveMaximum:1000|
|age|`Required`|integer|age||exclusiveMinimum:0|
|username|`Required`|string|user name||maxLength:4;<br>minLength:2|
- Response
**Desc**: demo json response
*Header*
- 200:application/json; charset=utf-8
**Response Info**
|Name|Default|Type|Desc|Example|Other|
|---|---|---|---|---|---|
|code|`Required`|integer|||minimum:0|
|msg|`Required`|string||||
|data|`Required`|array|||items:{'$ref': '#/components/schemas/UserModel'}|
**Response Example**
```json
{
"code": 0,
"msg": "",
"data": {
"name": "so1n",
"uid": 0,
"age": 0
}
}
```
**Desc**: demo json response
*Header*
- 201:application/json; charset=utf-8
**Response Info**
|Name|Default|Type|Desc|Example|Other|
|---|---|---|---|---|---|
|code|`Required`|integer|||minimum:0|
|msg|`Required`|string||||
|data|`Required`|array|||items:{'$ref': '#/components/schemas/UserModel'}|
**Response Example**
```json
{
"code": 0,
"msg": "",
"data": {
"name": "so1n",
"uid": 0,
"age": 0
}
}
```
**Desc**: demo json response
*Header*
- 404:application/json; charset=utf-8
**Response Info**
|Name|Default|Type|Desc|Example|Other|
|---|---|---|---|---|---|
|code|`Required`|integer|||minimum:0|
|msg|`Required`|string||||
|data|`Required`|array|||items:{'$ref': '#/components/schemas/UserModel'}|
**Response Example**
```json
{
"code": 0,
"msg": "",
"data": {
"name": "so1n",
"uid": 0,
"age": 0
}
}
```