From 7ca58f9c29caab41b445ffb5b77e5979bf8b73ed Mon Sep 17 00:00:00 2001 From: Keming Date: Tue, 18 Oct 2022 11:42:21 +0800 Subject: [PATCH] chore: add quart demo to readme (#268) Signed-off-by: Keming Signed-off-by: Keming --- README.md | 182 ++++++++++++++++++++++++----------- examples/falcon_asgi_demo.py | 13 ++- examples/falcon_demo.py | 12 +-- examples/flask_demo.py | 12 +-- examples/quart_demo.py | 10 +- examples/security_demo.py | 6 +- examples/starlette_demo.py | 10 +- setup.py | 2 +- 8 files changed, 156 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index 59ca7719..3215cae8 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Yet another library to generate OpenAPI documents and validate requests & respon * Validate query, JSON data, response data with [pydantic](https://github.com/samuelcolvin/pydantic/) :wink: * Current support: * Flask [demo](#flask) + * Quart [demo](#quart) * Falcon [demo](#falcon) * Starlette [demo](#starlette) @@ -274,20 +275,15 @@ from spectree import SpecTree, Response class Profile(BaseModel): - name: constr(min_length=2, max_length=40) # Constrained Str - age: int = Field( - ..., - gt=0, - lt=150, - description='user age(Human)' - ) + name: constr(min_length=2, max_length=40) # constrained str + age: int = Field(..., gt=0, lt=150, description="user age(Human)") class Config: schema_extra = { # provide an example - 'example': { - 'name': 'very_important_user', - 'age': 42, + "example": { + "name": "very_important_user", + "age": 42, } } @@ -297,44 +293,114 @@ class Message(BaseModel): app = Flask(__name__) -api = SpecTree('flask') +spec = SpecTree("flask") -@app.route('/api/user', methods=['POST']) -@api.validate(json=Profile, resp=Response(HTTP_200=Message, HTTP_403=None), tags=['api']) +@app.route("/api/user", methods=["POST"]) +@spec.validate( + json=Profile, resp=Response(HTTP_200=Message, HTTP_403=None), tags=["api"] +) def user_profile(): """ verify user profile (summary of this endpoint) user's name, user's age, ... (long description) """ - print(request.context.json) # or `request.json` - return jsonify(text='it works') # or `Message(text='it works')` + print(request.context.json) # or `request.json` + return jsonify(text="it works") # or `Message(text='it works')` if __name__ == "__main__": - api.register(app) # if you don't register in api init step + spec.register(app) # if you don't register in api init step app.run(port=8000) - ``` #### Flask example with type annotation ```python # opt in into annotations feature -api = SpecTree("flask", annotations=True) +spec = SpecTree("flask", annotations=True) + + +@app.route("/api/user", methods=["POST"]) +@spec.validate(resp=Response(HTTP_200=Message, HTTP_403=None), tags=["api"]) +def user_profile(json: Profile): + """ + verify user profile (summary of this endpoint) + + user's name, user's age, ... (long description) + """ + print(json) # or `request.json` + return jsonify(text="it works") # or `Message(text='it works')` +``` + +### Quart + +```py +from quart import Quart, jsonify, request +from pydantic import BaseModel, Field, constr + +from spectree import SpecTree, Response + + +class Profile(BaseModel): + name: constr(min_length=2, max_length=40) # constrained str + age: int = Field(..., gt=0, lt=150, description="user age") + + class Config: + schema_extra = { + # provide an example + "example": { + "name": "very_important_user", + "age": 42, + } + } + + +class Message(BaseModel): + text: str + + +app = Quart(__name__) +spec = SpecTree("quart") + + +@app.route("/api/user", methods=["POST"]) +@spec.validate( + json=Profile, resp=Response(HTTP_200=Message, HTTP_403=None), tags=["api"] +) +async def user_profile(): + """ + verify user profile (summary of this endpoint) + + user's name, user's age, ... (long description) + """ + print(request.context.json) # or `request.json` + return jsonify(text="it works") # or `Message(text="it works")` + + +if __name__ == "__main__": + spec.register(app) + app.run(port=8000) +``` + +#### Quart example with type annotation + +```python +# opt in into annotations feature +spec = SpecTree("quart", annotations=True) -@app.route('/api/user', methods=['POST']) -@api.validate(resp=Response(HTTP_200=Message, HTTP_403=None), tags=['api']) +@app.route("/api/user", methods=["POST"]) +@spec.validate(resp=Response(HTTP_200=Message, HTTP_403=None), tags=["api"]) def user_profile(json: Profile): """ verify user profile (summary of this endpoint) user's name, user's age, ... (long description) """ - print(json) # or `request.json` - return jsonify(text='it works') # or `Message(text='it works')` + print(json) # or `request.json` + return jsonify(text="it works") # or `Message(text='it works')` ``` ### Falcon @@ -348,23 +414,20 @@ from spectree import SpecTree, Response class Profile(BaseModel): name: constr(min_length=2, max_length=40) # Constrained Str - age: int = Field( - ..., - gt=0, - lt=150, - description='user age(Human)' - ) + age: int = Field(..., gt=0, lt=150, description="user age(Human)") class Message(BaseModel): text: str -api = SpecTree('falcon') +spec = SpecTree("falcon") class UserProfile: - @api.validate(json=Profile, resp=Response(HTTP_200=Message, HTTP_403=None), tags=['api']) + @spec.validate( + json=Profile, resp=Response(HTTP_200=Message, HTTP_403=None), tags=["api"] + ) def on_post(self, req, resp): """ verify user profile (summary of this endpoint) @@ -372,28 +435,27 @@ class UserProfile: user's name, user's age, ... (long description) """ print(req.context.json) # or `req.media` - resp.media = {'text': 'it works'} # or `resp.media = Message(text='it works')` + resp.media = {"text": "it works"} # or `resp.media = Message(text='it works')` if __name__ == "__main__": - app = falcon.API() - app.add_route('/api/user', UserProfile()) - api.register(app) + app = falcon.App() + app.add_route("/api/user", UserProfile()) + spec.register(app) - httpd = simple_server.make_server('localhost', 8000, app) + httpd = simple_server.make_server("localhost", 8000, app) httpd.serve_forever() - ``` #### Falcon with type annotations ```python # opt in into annotations feature -api = SpecTree("falcon", annotations=True) +spec = SpecTree("falcon", annotations=True) class UserProfile: - @api.validate(resp=Response(HTTP_200=Message, HTTP_403=None), tags=['api']) + @spec.validate(resp=Response(HTTP_200=Message, HTTP_403=None), tags=["api"]) def on_post(self, req, resp, json: Profile): """ verify user profile (summary of this endpoint) @@ -401,7 +463,7 @@ class UserProfile: user's name, user's age, ... (long description) """ print(req.context.json) # or `req.media` - resp.media = {'text': 'it works'} # or `resp.media = Message(text='it works')` + resp.media = {"text": "it works"} # or `resp.media = Message(text='it works')` ``` ### Starlette @@ -413,27 +475,25 @@ from starlette.routing import Route, Mount from starlette.responses import JSONResponse from pydantic import BaseModel, Field, constr from spectree import SpecTree, Response + # from spectree.plugins.starlette_plugin import PydanticResponse class Profile(BaseModel): name: constr(min_length=2, max_length=40) # Constrained Str - age: int = Field( - ..., - gt=0, - lt=150, - description='user age(Human)' - ) + age: int = Field(..., gt=0, lt=150, description="user age(Human)") class Message(BaseModel): text: str -api = SpecTree('starlette') +spec = SpecTree("starlette") -@api.validate(json=Profile, resp=Response(HTTP_200=Message, HTTP_403=None), tags=['api']) +@spec.validate( + json=Profile, resp=Response(HTTP_200=Message, HTTP_403=None), tags=["api"] +) async def user_profile(request): """ verify user profile (summary of this endpoint) @@ -441,29 +501,35 @@ async def user_profile(request): user's name, user's age, ... (long description) """ print(request.context.json) # or await request.json() - return JSONResponse({'text': 'it works'}) # or `return PydanticResponse(Message(text='it works'))` + return JSONResponse( + {"text": "it works"} + ) # or `return PydanticResponse(Message(text='it works'))` if __name__ == "__main__": - app = Starlette(routes=[ - Mount('api', routes=[ - Route('/user', user_profile, methods=['POST']), - ]) - ]) - api.register(app) + app = Starlette( + routes=[ + Mount( + "api", + routes=[ + Route("/user", user_profile, methods=["POST"]), + ], + ) + ] + ) + spec.register(app) uvicorn.run(app) - ``` #### Starlette example with type annotations ```python # opt in into annotations feature -api = SpecTree("flask", annotations=True) +spec = SpecTree("flask", annotations=True) -@api.validate(resp=Response(HTTP_200=Message, HTTP_403=None), tags=['api']) +@spec.validate(resp=Response(HTTP_200=Message, HTTP_403=None), tags=["api"]) async def user_profile(request, json=Profile): """ verify user profile (summary of this endpoint) @@ -471,7 +537,7 @@ async def user_profile(request, json=Profile): user's name, user's age, ... (long description) """ print(request.context.json) # or await request.json() - return JSONResponse({'text': 'it works'}) # or `return PydanticResponse(Message(text='it works'))` + return JSONResponse({"text": "it works"}) # or `return PydanticResponse(Message(text='it works'))` ``` diff --git a/examples/falcon_asgi_demo.py b/examples/falcon_asgi_demo.py index b7800087..a9265202 100644 --- a/examples/falcon_asgi_demo.py +++ b/examples/falcon_asgi_demo.py @@ -11,11 +11,10 @@ logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger() -api = SpecTree( +spec = SpecTree( "falcon-asgi", title="Demo Service", version="0.1.2", - unknown="test", ) demo = Tag(name="demo", description="😊", externalDocs={"url": "https://github.com"}) @@ -50,7 +49,7 @@ class Ping: def check(self): pass - @api.validate(tags=[demo]) + @spec.validate(tags=[demo]) async def on_get(self, req, resp): """ health check @@ -65,7 +64,7 @@ class Classification: classification demo """ - @api.validate(tags=[demo]) + @spec.validate(tags=[demo]) async def on_get(self, req, resp, source, target): """ API summary @@ -74,7 +73,7 @@ async def on_get(self, req, resp, source, target): """ resp.media = {"msg": f"hello from {source} to {target}"} - @api.validate( + @spec.validate( query=Query, json=Data, resp=Response(HTTP_200=Resp, HTTP_403=BadLuck) ) async def on_post(self, req, resp, source, target): @@ -98,7 +97,7 @@ class FileUpload: file-handling demo """ - @api.validate(form=File, resp=Response(HTTP_200=FileResp), tags=["file-upload"]) + @spec.validate(form=File, resp=Response(HTTP_200=FileResp), tags=["file-upload"]) async def on_post(self, req, resp): """ post multipart/form-data demo @@ -114,6 +113,6 @@ async def on_post(self, req, resp): app.add_route("/ping", Ping()) app.add_route("/api/{source}/{target}", Classification()) app.add_route("/api/file_upload", FileUpload()) - api.register(app) + spec.register(app) uvicorn.run(app, log_level="info") diff --git a/examples/falcon_demo.py b/examples/falcon_demo.py index 3d3506c8..2fa1582d 100644 --- a/examples/falcon_demo.py +++ b/examples/falcon_demo.py @@ -11,7 +11,7 @@ logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger() -api = SpecTree( +spec = SpecTree( "falcon", title="Demo Service", version="0.1.2", @@ -53,7 +53,7 @@ class Ping: def check(self): pass - @api.validate(tags=[demo]) + @spec.validate(tags=[demo]) def on_get(self, req, resp): """ health check @@ -68,7 +68,7 @@ class Classification: classification demo """ - @api.validate(tags=[demo]) + @spec.validate(tags=[demo]) def on_get(self, req, resp, source, target): """ API summary @@ -77,7 +77,7 @@ def on_get(self, req, resp, source, target): """ resp.media = {"msg": f"hello from {source} to {target}"} - @api.validate( + @spec.validate( query=Query, json=Data, resp=Response(HTTP_200=Resp, HTTP_403=BadLuck) ) def on_post(self, req, resp, source, target): @@ -101,7 +101,7 @@ class FileUpload: file-handling demo """ - @api.validate(form=File, resp=Response(HTTP_200=FileResp), tags=["file-upload"]) + @spec.validate(form=File, resp=Response(HTTP_200=FileResp), tags=["file-upload"]) def on_post(self, req, resp): """ post multipart/form-data demo @@ -122,7 +122,7 @@ def on_post(self, req, resp): app.add_route("/ping", Ping()) app.add_route("/api/{source}/{target}", Classification()) app.add_route("/api/file_upload", FileUpload()) - api.register(app) + spec.register(app) httpd = simple_server.make_server("localhost", 8000, app) httpd.serve_forever() diff --git a/examples/flask_demo.py b/examples/flask_demo.py index 82d327c5..a7758788 100644 --- a/examples/flask_demo.py +++ b/examples/flask_demo.py @@ -9,7 +9,7 @@ from spectree import Response, SpecTree app = Flask(__name__) -api = SpecTree("flask") +spec = SpecTree("flask") class Resp(BaseModel): @@ -52,7 +52,7 @@ class Cookie(BaseModel): @app.route( "/api/predict//", methods=["POST"] ) -@api.validate( +@spec.validate( query=Query, json=Data, resp=Response("HTTP_403", HTTP_200=Resp), tags=["model"] ) def predict(source, target): @@ -74,7 +74,7 @@ def predict(source, target): @app.route("/api/header", methods=["POST"]) -@api.validate( +@spec.validate( headers=Header, cookies=Cookie, resp=Response("HTTP_203"), tags=["test", "demo"] ) def with_code_header(): @@ -87,7 +87,7 @@ def with_code_header(): @app.route("/api/file_upload", methods=["POST"]) -@api.validate(form=File, resp=Response(HTTP_200=FileResp), tags=["file-upload"]) +@spec.validate(form=File, resp=Response(HTTP_200=FileResp), tags=["file-upload"]) def with_file(): """ post multipart/form-data demo @@ -99,7 +99,7 @@ def with_file(): class UserAPI(MethodView): - @api.validate(json=Data, resp=Response(HTTP_200=Resp), tags=["test"]) + @spec.validate(json=Data, resp=Response(HTTP_200=Resp), tags=["test"]) def post(self): return jsonify(label=int(10 * random()), score=random()) @@ -112,5 +112,5 @@ def post(self): http POST :8000/api/header Lang:zh-CN Cookie:key=hello """ app.add_url_rule("/api/user", view_func=UserAPI.as_view("user_id")) - api.register(app) + spec.register(app) app.run(port=8000) diff --git a/examples/quart_demo.py b/examples/quart_demo.py index 341f35a4..5807ff78 100644 --- a/examples/quart_demo.py +++ b/examples/quart_demo.py @@ -8,7 +8,7 @@ from spectree import Response, SpecTree app = Quart(__name__) -api = SpecTree("quart") +spec = SpecTree("quart") class Query(BaseModel): @@ -55,7 +55,7 @@ class Cookie(BaseModel): @app.route( "/api/predict//", methods=["POST"] ) -@api.validate( +@spec.validate( query=Query, json=Data, resp=Response("HTTP_403", HTTP_200=Resp), tags=["model"] ) def predict(source, target): @@ -77,7 +77,7 @@ def predict(source, target): @app.route("/api/header", methods=["POST"]) -@api.validate( +@spec.validate( headers=Header, cookies=Cookie, resp=Response("HTTP_203"), tags=["test", "demo"] ) async def with_code_header(): @@ -90,7 +90,7 @@ async def with_code_header(): class UserAPI(MethodView): - @api.validate(json=Data, resp=Response(HTTP_200=Resp), tags=["test"]) + @spec.validate(json=Data, resp=Response(HTTP_200=Resp), tags=["test"]) async def post(self): return jsonify(label=int(10 * random()), score=random()) # return Resp(label=int(10 * random()), score=random()) @@ -104,5 +104,5 @@ async def post(self): http POST :8000/api/header Lang:zh-CN Cookie:key=hello """ app.add_url_rule("/api/user", view_func=UserAPI.as_view("user_id")) - api.register(app) + spec.register(app) app.run(port=8000) diff --git a/examples/security_demo.py b/examples/security_demo.py index a6f12f28..d4071123 100644 --- a/examples/security_demo.py +++ b/examples/security_demo.py @@ -42,7 +42,7 @@ class Req(BaseModel): ] app = Flask(__name__) -api = SpecTree( +spec = SpecTree( "flask", security_schemes=security_schemes, SECURITY={"test_secure": []}, @@ -51,7 +51,7 @@ class Req(BaseModel): @app.route("/ping", methods=["POST"]) -@api.validate( +@spec.validate( json=Req, security=[{"PartnerID": [], "PartnerToken": []}, {"auth_oauth2": ["read"]}], ) @@ -65,5 +65,5 @@ def index(): if __name__ == "__main__": - api.register(app) + spec.register(app) app.run(port=8000) diff --git a/examples/starlette_demo.py b/examples/starlette_demo.py index aa83a41f..8445f2eb 100644 --- a/examples/starlette_demo.py +++ b/examples/starlette_demo.py @@ -8,7 +8,7 @@ from examples.common import File, FileResp, Query from spectree import Response, SpecTree -api = SpecTree("starlette") +spec = SpecTree("starlette") class Resp(BaseModel): @@ -30,7 +30,7 @@ class Data(BaseModel): vip: bool -@api.validate(query=Query, json=Data, resp=Response(HTTP_200=Resp), tags=["api"]) +@spec.validate(query=Query, json=Data, resp=Response(HTTP_200=Resp), tags=["api"]) async def predict(request): """ async api @@ -43,7 +43,7 @@ async def predict(request): # return PydanticResponse(Resp(label=5, score=0.5)) -@api.validate(form=File, resp=Response(HTTP_200=FileResp), tags=["file-upload"]) +@spec.validate(form=File, resp=Response(HTTP_200=FileResp), tags=["file-upload"]) async def file_upload(request): """ post multipart/form-data demo @@ -55,7 +55,7 @@ async def file_upload(request): class Ping(HTTPEndpoint): - @api.validate(tags=["health check", "api"]) + @spec.validate(tags=["health check", "api"]) def get(self, request): """ health check @@ -81,6 +81,6 @@ def get(self, request): ), ] ) - api.register(app) + spec.register(app) uvicorn.run(app, log_level="info") diff --git a/setup.py b/setup.py index 160239bd..9da96f99 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name="spectree", - version="1.0.0a2", + version="1.0.0a3", license="Apache-2.0", author="Keming Yang", author_email="kemingy94@gmail.com",