OpenAPI路由
Pait
除了支持OpenAPI数据的生成外,还支持OpenAPI路由生成,默认情况下会提供openapi.json
接口以及swagger-ui
页面等,比如文档首页的示例代码:
docs_source_code/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/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/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/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()
|
通过示例代码可以看到,只需要简单的调用AddDocRoute
即可为app
绑定OpenAPI路由,具体的路由url和对应的功能如下表所示:
路由url |
描述 |
特点 |
/openapi.json |
获取OpenAPI的json响应 |
|
/elements |
使用elements展示接口文档数据 |
UI漂亮简单,支持请求调用 |
/redoc |
使用Redoc展示接口文档数据 |
UI漂亮简单,但是不支持请求调用 |
/swagger |
使用Swagger展示接口文档数据 |
通用的OpenAPI展示UI,功能齐全 |
/rapidoc |
使用RapiDoc展示接口文档数据 |
功能齐全;UI现代化;支持自定义的UI |
/rapipdf |
提供一个可以下载RapiDocpdf文档的页面 |
对非英文的支持比较差 |
1.OpenAPI路由的使用
AddDocRoute
可以方便的为app
实例绑定OpenAPI路由,同时AddDocRoute
提供了一些参数方便开发者自定义路由扩展以及解决生产环境的复杂化。
目前AddDocRoute
提供的参数有:
参数 |
描述 |
scheme |
HTTP Schema,如http或者https |
openapi_json_url_only_path |
生成的openapi.json url是拥有path部分(该参数生效时,scheme会失效) |
prefix |
路由URL前缀 |
pin_code |
一种简单的安全校验机制 |
title |
定义OpenAPI路由的Title,需要注意的是,调用多次AddDocRoute 绑定不同的OpenAPI路由时,它们的Title应该保持不同 |
doc_fn_dict |
OpenAPI路由中UI页面的实现 |
openapi |
Pait 的OpenAPI对象 |
pait |
Pait 实例,OpenAPI会基于传递的pait 去创建子pait 并使用。详见如何使用Pait |
add_multi_simple_route |
为app实例绑定路由的方法,详见SimpleRoute章节 |
not_found_exc |
pin_code错误的异常 |
1.1.scheme
通过scheme参数可以显式的指定OpenAPI路由的HTTP Schema,比如HTTP和HTTPS。
需要注意的是,HTTP Schema并不是指代当前服务使用的HTTP Schema,而是访问者使用的HTTP Schema。
比如当前服务指定的是HTTP,不过为了增强服务安全,在服务前加上一层代理以支持HTTPS,如使用Nginx。
这时用户只能通过https://127.0.0.1/openapi.json
访问服务, 为了使OpenAPI路由能够正常做出响应,在绑定OpenAPI路由时应该填写scheme="https"
。
使用方法:
| 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")
AddDocRoute(app, scheme="http")
app.run(port=8000)
|
| 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, scheme="http")
uvicorn.run(app)
|
| 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, scheme="http")
uvicorn.run(app)
|
| 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})
AddDocRoute(app, scheme="http")
app.listen(8000)
IOLoop.instance().start()
|
1.2.openapi_json_url_only_path
openapi_json_url_only_path默认为False
,此时生成的OpenAPI Json url为完整的(http://example.com/openapi.json
),
当openapi_json_url_only_path为True
的时候,生成的OpenAPI Json url为/openapi.json
。
Note
- 1.目前的OpenAPI UI都支持
/openapi.json
的URL,但不保证后续的OpenAPI UI都能支持。
- 2.使用
openapi_json_url_only_path
时,schema
参数会失效
使用方法:
| 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")
AddDocRoute(app, openapi_json_url_only_path=True)
app.run(port=8000)
|
| 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, openapi_json_url_only_path=True)
uvicorn.run(app)
|
| 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, openapi_json_url_only_path=True)
uvicorn.run(app)
|
| 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})
AddDocRoute(app, openapi_json_url_only_path=True)
app.listen(8000)
IOLoop.instance().start()
|
1.3.prefix
默认情况下AddDocRoute
会按如下URL将路由绑定到app实例中:
- /openapi.json
- /redoc
- /swagger
- /rapidoc
- /rapipdf
- /elements
不过采用默认的/
前缀是一种不太好的行为,建议在使用的时候通过prefix
指定一个符合自己习惯的URL前缀。
比如/api-doc
,那么AddDocRoute
会以如下URL绑定到路由:
- /api-doc/openapi.json
- /api-doc/redoc
- /api-doc/swagger
- /api-doc/rapidoc
- /api-doc/rapipdf
- /api-doc/elements
使用方法:
| 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")
AddDocRoute(app, prefix="/api-doc")
app.run(port=8000)
|
| 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, prefix="/api-doc")
uvicorn.run(app)
|
| 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, prefix="/api-doc")
uvicorn.run(app)
|
| 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})
AddDocRoute(app, prefix="/api-doc")
app.listen(8000)
IOLoop.instance().start()
|
1.4.pin_code
pin_code
提供了一种简单的安全机制,防止外部人员访问OpenAPI路由,它的用法如下:
| 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")
AddDocRoute(app, pin_code="6666")
app.run(port=8000)
|
| 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, pin_code="6666")
uvicorn.run(app)
|
| 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, pin_code="6666")
uvicorn.run(app)
|
| 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})
AddDocRoute(app, pin_code="6666")
app.listen(8000)
IOLoop.instance().start()
|
当你运行代码后,如果在浏览器访问http://127.0.0.1:8000/swagger
会发现找不到对应的页面,但是改用http://127.0.0.1:8000/swagger/pin_code=6666
访问则发现页面正常展示。
Note
- 1.通常情况下OpenAPI路由不应该暴露给外部人员使用,需要通过Nginx等工具来增强安全性(如IP白名单限制),这种机制的安全性是远远高于
pin_code
的。
- 2.可以通过Pre-depend为OpenAPI添加自定义的安全校验。
- 3.如果访问时携带的pin code校验不通过,那么默认情况下会返回404异常,该异常可以通过not_found_exc定制。
1.5.Title
Title有两个作用,一个是用于定义OpenAPI对象的Title属性,另外一个是指定当前绑定的OpenAPI路由的组名,所以如果对于同一个app实例调用多次AddDocRoute
时,需要确保Title参数是不一样的。
使用方法如下:
| 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")
AddDocRoute(app, title="Api Doc")
app.run(port=8000)
|
| 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, title="Api Doc")
uvicorn.run(app)
|
| 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, title="Api Doc")
uvicorn.run(app)
|
| 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})
AddDocRoute(app, title="Api Doc")
app.listen(8000)
IOLoop.instance().start()
|
1.6.doc_fn_dict
doc_fn_dict
是一个以OpenAPI UI 名为Key,生成OpenAPI Html内容函数为Value的字典。如果没有传递该参数,那么默认情况下AddDocRoute
会采纳如下的字典:
from any_api.openapi.web_ui.elements import get_elements_html
from any_api.openapi.web_ui.rapidoc import get_rapidoc_html, get_rapipdf_html
from any_api.openapi.web_ui.redoc import get_redoc_html
from any_api.openapi.web_ui.swagger import get_swagger_ui_html
default_doc_fn_dict = {
"elements": get_elements_html,
"rapidoc": get_rapidoc_html,
"rapipdf": get_rapipdf_html,
"redoc": get_redoc_html,
"swagger": get_swagger_ui_html,
}
其中,doc_fn_dict
规定的Key为字符串,Value为如下的函数:
def demo(url: str, title: str = "") -> str:
pass
该函数的第一个参数接受的值是URL,而第二个参数接受的值是Title,之后在生成路由的时候会通过doc_fn_dict
以Key为url,value为路由函数绑定到app实例中。
以下是新增一个自定义OpenAPI UI的示例:
| 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")
def demo(url: str, title: str = "") -> str:
pass
from pait.openapi.doc_route import default_doc_fn_dict
from copy import deepcopy
default_doc_fn_dict = deepcopy(default_doc_fn_dict)
default_doc_fn_dict["demo"] = demo
AddDocRoute(app, doc_fn_dict=default_doc_fn_dict)
app.run(port=8000)
|
| 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"])])
def demo(url: str, title: str = "") -> str:
pass
from pait.openapi.doc_route import default_doc_fn_dict
from copy import deepcopy
default_doc_fn_dict = deepcopy(default_doc_fn_dict)
default_doc_fn_dict["demo"] = demo
AddDocRoute(app, doc_fn_dict=default_doc_fn_dict)
uvicorn.run(app)
|
| 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"])
def demo(url: str, title: str = "") -> str:
pass
from pait.openapi.doc_route import default_doc_fn_dict
from copy import deepcopy
default_doc_fn_dict = deepcopy(default_doc_fn_dict)
default_doc_fn_dict["demo"] = demo
AddDocRoute(app, doc_fn_dict=default_doc_fn_dict)
uvicorn.run(app)
|
| 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})
def demo(url: str, title: str = "") -> str:
pass
from pait.openapi.doc_route import default_doc_fn_dict
from copy import deepcopy
default_doc_fn_dict = deepcopy(default_doc_fn_dict)
default_doc_fn_dict["demo"] = demo
AddDocRoute(app, doc_fn_dict=default_doc_fn_dict)
app.listen(8000)
IOLoop.instance().start()
|
该示例会先创建一个符合规范的demo
函数,然后新增到默认的default_doc_fn_dict
中,最后再通过AddDocRoute
与app实例绑定。
现在,可以通过http://127.0.0.1:8000/demo
访问到自定义的OpenAPI UI页面。
1.7.OpenAPI
默认情况下,AddDocRoute
会先创建一个OpenAPI对象,并根据OpenAPI对象生成json内容。
Note
openapi.json
路由中创建的OpenAPI对象的Title会被AddDocRoute
指定的title
参数覆盖,并在Server List
追加当前APP实例的地址。
不过AddDocRoute
也支持通过openapi
参数来传递定义好的OpenAPI对象,使用方法如下:
| 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")
from pait.util import partial_wrapper
from pait.openapi.openapi import OpenAPI, InfoModel
openapi = partial_wrapper(OpenAPI, openapi_info_model=InfoModel(version="1.0.0", description="Demo Doc"))
AddDocRoute(flask_app, openapi=openapi) # type: ignore
app.run(port=8000)
|
| 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"])])
from pait.util import partial_wrapper
from pait.openapi.openapi import OpenAPI, InfoModel
openapi = partial_wrapper(OpenAPI, openapi_info_model=InfoModel(version="1.0.0", description="Demo Doc"))
AddDocRoute(flask_app, openapi=openapi) # type: ignore
uvicorn.run(app)
|
| 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"])
from pait.util import partial_wrapper
from pait.openapi.openapi import OpenAPI, InfoModel
openapi = partial_wrapper(OpenAPI, openapi_info_model=InfoModel(version="1.0.0", description="Demo Doc"))
AddDocRoute(flask_app, openapi=openapi) # type: ignore
uvicorn.run(app)
|
| 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})
from pait.util import partial_wrapper
from pait.openapi.openapi import OpenAPI, InfoModel
openapi = partial_wrapper(OpenAPI, openapi_info_model=InfoModel(version="1.0.0", description="Demo Doc"))
AddDocRoute(flask_app, openapi=openapi) # type: ignore
app.listen(8000)
IOLoop.instance().start()
|
运行示例代码并访问http://127.0.0.1:8000/swagger就可以看到页面左上角的文档描述和版本号都发生了更改。
2.OpenAPI路由的模板变量
大部分接口都有鉴权机制,比如需要带上正确的Token参数才正常获取数据。
如果在OpenAPI页面请求数据时,每次都需要粘贴Token参数,非常不方便。
这时就可以使用模板变量,让OpenAPI 页面可以自动填写变量的值,代码如下:
docs_source_code/docs_source_code/openapi/openapi_route/flask_demo.py |
---|
| from flask import Flask, Response, jsonify
from pydantic import BaseModel, Field
from pait import _pydanitc_adapter
from pait.app.flask import pait
from pait.field import Json
from pait.model.template import TemplateVar
from pait.openapi.doc_route import AddDocRoute
if _pydanitc_adapter.is_v1:
class UserModel(BaseModel):
uid: int = Field(description="user id", gt=10, lt=1000, example=TemplateVar("uid"))
user_name: str = Field(description="user name", min_length=2, max_length=4)
else:
class UserModel(BaseModel): # type: ignore[no-redef]
uid: int = Field(
description="user id", gt=10, lt=1000, json_schema_extra=lambda v: v.update(example=TemplateVar("uid"))
)
user_name: str = Field(description="user name", min_length=2, max_length=4)
@pait()
def demo_post(model: UserModel = Json.i()) -> Response:
return jsonify({"result": model.dict()})
app = Flask("demo")
app.add_url_rule("/api", "demo", demo_post, methods=["POST"])
if __name__ == "__main__":
AddDocRoute(app)
app.run(port=8000)
|
docs_source_code/docs_source_code/openapi/openapi_route/starlette_demo.py |
---|
| from pydantic import BaseModel, Field
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route
from pait import _pydanitc_adapter
from pait.app.starlette import pait
from pait.field import Json
from pait.model.template import TemplateVar
from pait.openapi.doc_route import AddDocRoute
if _pydanitc_adapter.is_v1:
class UserModel(BaseModel):
uid: int = Field(description="user id", gt=10, lt=1000, example=TemplateVar("uid"))
user_name: str = Field(description="user name", min_length=2, max_length=4)
else:
class UserModel(BaseModel): # type: ignore[no-redef]
uid: int = Field(
description="user id", gt=10, lt=1000, json_schema_extra=lambda v: v.update(example=TemplateVar("uid"))
)
user_name: str = Field(description="user name", min_length=2, max_length=4)
@pait()
async def demo_post(model: UserModel = Json.i()) -> JSONResponse:
return JSONResponse({"result": model.dict()})
app = Starlette(routes=[Route("/api", demo_post, methods=["POST"])])
if __name__ == "__main__":
AddDocRoute(app)
import uvicorn
uvicorn.run(app)
|
docs_source_code/docs_source_code/openapi/openapi_route/sanic_demo.py |
---|
| from pydantic import BaseModel, Field
from sanic import HTTPResponse, Sanic, json
from pait import _pydanitc_adapter
from pait.app.sanic import pait
from pait.field import Json
from pait.model.template import TemplateVar
from pait.openapi.doc_route import AddDocRoute
if _pydanitc_adapter.is_v1:
class UserModel(BaseModel):
uid: int = Field(description="user id", gt=10, lt=1000, example=TemplateVar("uid"))
user_name: str = Field(description="user name", min_length=2, max_length=4)
else:
class UserModel(BaseModel): # type: ignore[no-redef]
uid: int = Field(
description="user id", gt=10, lt=1000, json_schema_extra=lambda v: v.update(example=TemplateVar("uid"))
)
user_name: str = Field(description="user name", min_length=2, max_length=4)
@pait()
async def demo_post(model: UserModel = Json.i()) -> HTTPResponse:
return json({"result": model.dict()})
app = Sanic(name="demo")
app.add_route(demo_post, "/api", methods=["POST"])
if __name__ == "__main__":
AddDocRoute(app)
import uvicorn
uvicorn.run(app)
|
docs_source_code/docs_source_code/openapi/openapi_route/tornado_demo.py |
---|
| from pydantic import BaseModel, Field
from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler
from pait import _pydanitc_adapter
from pait.app.tornado import pait
from pait.field import Json
from pait.model.template import TemplateVar
from pait.openapi.doc_route import AddDocRoute
if _pydanitc_adapter.is_v1:
class UserModel(BaseModel):
uid: int = Field(description="user id", gt=10, lt=1000, example=TemplateVar("uid"))
user_name: str = Field(description="user name", min_length=2, max_length=4)
else:
class UserModel(BaseModel): # type: ignore[no-redef]
uid: int = Field(
description="user id", gt=10, lt=1000, json_schema_extra=lambda v: v.update(example=TemplateVar("uid"))
)
user_name: str = Field(description="user name", min_length=2, max_length=4)
class DemoHandler(RequestHandler):
@pait()
async def post(self, model: UserModel = Json.i()) -> None:
self.write({"result": model.dict()})
app: Application = Application([(r"/api", DemoHandler)])
AddDocRoute(app)
if __name__ == "__main__":
app.listen(8000)
IOLoop.instance().start()
|
首先引入了TemplateVar
类,然后在uid
的Field的example属性中使用了TemplateVar("uid")
,这样一来Pait
就知道参数uid
的模板变量为uid
。
现在运行上面的代码,并在浏览器输入http://127.0.0.1:8000/swagger?template-uid=123
,打开后可以看到如下图:
通过图可以发现uid
的值已经被自动填充为123
,而不是默认的0了。
Pait
之所以能把用户的值设置到对应的参数中是因为这个url多了一段字符串template-uid=123
,
这样一来OpenAPI路由在收到对应的请求时会发现请求携带了一个以template-
开头的变量,知道这是用户为模板变量uid
指定了对应的值,
于是在生成OpenAPI数据时,会自动帮模板变量为uid的参数附上用户指定的值。