跳转至

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并不保证nameoperation_id完全相等。
  • 2.Tag的声明应该保证全局唯一

不过namedesc属性分别可以通过路由函数名和路由函数的__doc__获取,比如下面代码中路由函数的namedesc属性与上面代码一致。

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是否为TruePaitStatus的使用方法非常简单,如下代码:

from pait.app.any import pait
from pait.model.status import PaitStatus


@pait(status=PaitStatus.test)
def demo() -> None:
    pass

该代码表示路由函数为测试中且deprecatedFalse,更多的状态以及是否为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 Typeapplication/json, Header拥有X-TokenContent-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时不会考虑该参数

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路由函数中tokenFieldlinks`属性,这样就完成了一次关联。

在运行代码并在浏览器访问http://127.0.0.1:8000/swagger后会出现如下页面: 可以看到登陆接口的Response那一栏的最右边展示了Links的数据。

Note

目前许多OpenAPI工具只提供简单的Links支持,更多关于Links的使用与说明见Swagger Links

5.OpenAPI生成

在OpenAPI生态中,它的核心是一份符合OpenAPI格式的json或者yaml文本,这个文本可以在Swagger等OpenAPI页面中使用,也可以导入到Postman等工具中使用。 Pait会把收集到的数据委托给AnyAPI处理并生成一个OpenAPI对象,OpenAPI对象支持被转化为各种人类易读的文本或者页面。

Note

AnyAPI是从Pait分离出来的,目前仅供Pait使用,在后续的版本中,AnyAPI会新增更多的功能。

下面是一个生成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_modelcontent方法,该方法的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
            }
        }
        ```