summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCoprDistGit <infra@openeuler.org>2023-04-11 22:12:08 +0000
committerCoprDistGit <infra@openeuler.org>2023-04-11 22:12:08 +0000
commit7f9fd276d94e6544677384a55d79e9a6df77d39a (patch)
tree8e34cfb617b5290688511c6bc43229a2d158b29b
parent2cee94416a9f2b2256bcd06020418a9c18642bd5 (diff)
automatic import of python-spectree
-rw-r--r--.gitignore1
-rw-r--r--python-spectree.spec1743
-rw-r--r--sources1
3 files changed, 1745 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index e69de29..e49f5fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -0,0 +1 @@
+/spectree-1.1.0.tar.gz
diff --git a/python-spectree.spec b/python-spectree.spec
new file mode 100644
index 0000000..da79b69
--- /dev/null
+++ b/python-spectree.spec
@@ -0,0 +1,1743 @@
+%global _empty_manifest_terminate_build 0
+Name: python-spectree
+Version: 1.1.0
+Release: 1
+Summary: generate OpenAPI document and validate request&response with Python annotations.
+License: Apache-2.0
+URL: https://github.com/0b01001001/spectree
+Source0: https://mirrors.nju.edu.cn/pypi/web/packages/5d/ca/35c39ce7818474ceb1e9d34529f2d7874b166a3a5cd3ff4892c270c3a080/spectree-1.1.0.tar.gz
+BuildArch: noarch
+
+Requires: python3-pydantic
+Requires: python3-pytest
+Requires: python3-flake8
+Requires: python3-black
+Requires: python3-isort
+Requires: python3-autoflake
+Requires: python3-mypy
+Requires: python3-pydantic[email]
+Requires: python3-falcon
+Requires: python3-flask
+Requires: python3-quart
+Requires: python3-starlette[full]
+
+%description
+# SpecTree
+
+
+[![GitHub Actions](https://github.com/0b01001001/spectree/workflows/Python%20package/badge.svg)](https://github.com/0b01001001/spectree/actions)
+[![pypi](https://img.shields.io/pypi/v/spectree.svg)](https://pypi.python.org/pypi/spectree)
+[![versions](https://img.shields.io/pypi/pyversions/spectree.svg)](https://github.com/0b01001001/spectree)
+[![CodeQL](https://github.com/0b01001001/spectree/actions/workflows/codeql.yml/badge.svg)](https://github.com/0b01001001/spectree/actions/workflows/codeql.yml)
+[![Python document](https://github.com/0b01001001/spectree/workflows/Python%20document/badge.svg)](https://0b01001001.github.io/spectree/)
+
+Yet another library to generate OpenAPI documents and validate requests & responses with Python annotations.
+
+## Features
+
+* Less boilerplate code, only annotations, no need for YAML :sparkles:
+* Generate API document with [Redoc UI](https://github.com/Redocly/redoc) or [Swagger UI](https://github.com/swagger-api/swagger-ui) :yum:
+* 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)
+
+## Quick Start
+
+Install with pip: `pip install spectree`. If you'd like for email fields to be validated, use `pip install spectree[email]`.
+
+### Examples
+
+Check the [examples](/examples) folder.
+
+* [flask example](/examples/flask_demo.py)
+* [falcon example with logging when validation failed](/examples/falcon_demo.py)
+* [starlette example](examples/starlette_demo.py)
+
+### Step by Step
+
+1. Define your data structure used in (query, json, headers, cookies, resp) with `pydantic.BaseModel`
+2. create `spectree.SpecTree` instance with the web framework name you are using, like `api = SpecTree('flask')`
+3. `api.validate` decorate the route with
+ * `query`
+ * `json`
+ * `headers`
+ * `cookies`
+ * `resp`
+ * `tags`
+ * `security`
+4. access these data with `context(query, json, headers, cookies)` (of course, you can access these from the original place where the framework offered)
+ * flask: `request.context`
+ * falcon: `req.context`
+ * starlette: `request.context`
+5. register to the web application `api.register(app)`
+6. check the document at URL location `/apidoc/redoc` or `/apidoc/swagger`
+
+If the request doesn't pass the validation, it will return a 422 with a JSON error message(ctx, loc, msg, type).
+
+### Falcon response validation
+
+For Falcon response, this library only validates against media as it is the serializable object. Response.text is a string representing response content and will not be validated. For no assigned media situation, `resp` parameter in `api.validate` should be like `Response(HTTP_200=None)`
+
+### Opt-in type annotation feature
+This library also supports the injection of validated fields into view function arguments along with parameter annotation-based type declaration. This works well with linters that can take advantage of typing features like mypy. See the examples section below.
+
+## How-To
+
+> How to add summary and description to endpoints?
+
+Just add docs to the endpoint function. The 1st line is the summary, and the rest is the description for this endpoint.
+
+> How to add a description to parameters?
+
+Check the [pydantic](https://pydantic-docs.helpmanual.io/usage/schema/) document about description in `Field`.
+
+> Any config I can change?
+
+Of course. Check the [config](https://spectree.readthedocs.io/en/latest/config.html) document.
+
+You can update the config when init the spectree like:
+
+```py
+SpecTree('flask', title='Demo API', version='v1.0', path='doc')
+```
+
+> What is `Response` and how to use it?
+
+To build a response for the endpoint, you need to declare the status code with format `HTTP_{code}` and corresponding data (optional).
+
+```py
+Response(HTTP_200=None, HTTP_403=ForbidModel)
+Response('HTTP_200') # equals to Response(HTTP_200=None)
+# with custom code description
+Response(HTTP_403=(ForbidModel, "custom code description"))
+```
+
+> How to secure API endpoints?
+
+For secure API endpoints, it is needed to define the `security_schemes` argument in the `SpecTree` constructor. `security_schemes` argument needs to contain an array of `SecurityScheme` objects. Then there are two ways to enforce security:
+
+1. You can enforce security on individual API endpoints by defining the `security` argument in the `api.validate` decorator of relevant function/method (this corresponds to define security section on operation level, under `paths`, in `OpenAPI`). `security` argument is defined as a dictionary, where each key is the name of security used in `security_schemes` argument of `SpecTree` constructor and its value is required security scope, as is showed in the following example:
+
+<details>
+<summary>Click to expand the code example:</summary>
+<p>
+
+```py
+api = SpecTree(security_schemes=[
+ SecurityScheme(
+ name="auth_apiKey",
+ data={"type": "apiKey", "name": "Authorization", "in": "header"},
+ ),
+ SecurityScheme(
+ name="auth_oauth2",
+ data={
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "https://example.com/oauth/authorize",
+ "tokenUrl": "https://example.com/oauth/token",
+ "scopes": {
+ "read": "Grants read access",
+ "write": "Grants write access",
+ "admin": "Grants access to admin operations",
+ },
+ },
+ },
+ },
+ ),
+ # ...
+ ],
+ # ...
+)
+
+
+# Not secured API endpoint
+@api.validate(
+ resp=Response(HTTP_200=None),
+)
+def foo():
+ ...
+
+
+# API endpoint secured by API key type or OAuth2 type
+@api.validate(
+ resp=Response(HTTP_200=None),
+ security={"auth_apiKey": [], "auth_oauth2": ["read", "write"]}, # Local security type
+)
+def bar():
+ ...
+```
+
+</p>
+</details>
+
+
+2. You can enforce security on the whole API by defining the `security` argument in the `SpecTree` constructor (this corresponds to the define security section on the root level in `OpenAPI`). It is possible to override global security by defining local security, as well as override to no security on some API endpoint, in the `security` argument of `api.validate` decorator of relevant function/method as was described in the previous point. It is also shown in the following small example:
+
+<details>
+<summary>Click to expand the code example:</summary>
+<p>
+
+```py
+api = SpecTree(security_schemes=[
+ SecurityScheme(
+ name="auth_apiKey",
+ data={"type": "apiKey", "name": "Authorization", "in": "header"},
+ ),
+ SecurityScheme(
+ name="auth_oauth2",
+ data={
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "https://example.com/oauth/authorize",
+ "tokenUrl": "https://example.com/oauth/token",
+ "scopes": {
+ "read": "Grants read access",
+ "write": "Grants write access",
+ "admin": "Grants access to admin operations",
+ },
+ },
+ },
+ },
+ ),
+ # ...
+ ],
+ security={"auth_apiKey": []}, # Global security type
+ # ...
+)
+
+# Force no security
+@api.validate(
+ resp=Response(HTTP_200=None),
+ security={}, # Locally overridden security type
+)
+def foo():
+ ...
+
+
+# Force another type of security than global one
+@api.validate(
+ resp=Response(HTTP_200=None),
+ security={"auth_oauth2": ["read"]}, # Locally overridden security type
+)
+def bar():
+ ...
+
+
+# Use the global security
+@api.validate(
+ resp=Response(HTTP_200=None),
+)
+def foobar():
+ ...
+```
+
+</p>
+</details>
+
+> What should I return when I'm using the library?
+
+No need to change anything. Just return what the framework required.
+
+> How to log when the validation failed?
+
+Validation errors are logged with the INFO level. Details are passed into `extra`. Check the [falcon example](examples/falcon_demo.py) for details.
+
+> How can I write a customized plugin for another backend framework?
+
+Inherit `spectree.plugins.base.BasePlugin` and implement the functions you need. After that, init like `api = SpecTree(backend=MyCustomizedPlugin)`.
+
+> How to use a customized template page?
+
+```py
+SpecTree(page_templates={"page_name": "customized page contains {spec_url} for rendering"})
+```
+
+In the above example, the key "page_name" will be used in the URL to access this page "/apidoc/page_name". The value should be a string that contains `{spec_url}` which will be used to access the OpenAPI JSON file.
+
+> How can I change the response when there is a validation error? Can I record some metrics?
+
+This library provides `before` and `after` hooks to do these. Check the [doc](https://spectree.readthedocs.io/en/latest) or the [test case](tests/test_plugin_flask.py). You can change the handlers for SpecTree or a specific endpoint validation.
+
+> How to change the default `ValidationError` status code?
+
+You can change the `validation_error_status` in SpecTree (global) or a specific endpoint (local). This also takes effect in the OpenAPI documentation.
+
+> How can I skip the validation?
+
+Add `skip_validation=True` to the decorator.
+
+```py
+@api.validate(json=Profile, resp=Response(HTTP_200=Message, HTTP_403=None), skip_validation=True)
+```
+
+> How can I return my model directly?
+
+Yes, returning an instance of `BaseModel` will assume the model is valid and bypass spectree's validation and automatically call `.dict()` on the model.
+
+For starlette you should return a `PydanticResponse`:
+```py
+from spectree.plugins.starlette_plugin import PydanticResponse
+
+return PydanticResponse(MyModel)
+```
+
+## Demo
+
+Try it with `http post :8000/api/user name=alice age=18`. (if you are using `httpie`)
+
+### Flask
+
+```py
+from flask import Flask, request, jsonify
+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(Human)")
+
+ class Config:
+ schema_extra = {
+ # provide an example
+ "example": {
+ "name": "very_important_user",
+ "age": 42,
+ }
+ }
+
+
+class Message(BaseModel):
+ text: str
+
+
+app = Flask(__name__)
+spec = SpecTree("flask")
+
+
+@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')`
+
+
+if __name__ == "__main__":
+ 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
+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"])
+@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')`
+```
+
+### Falcon
+
+```py
+import falcon
+from wsgiref import simple_server
+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(Human)")
+
+
+class Message(BaseModel):
+ text: str
+
+
+spec = SpecTree("falcon")
+
+
+class UserProfile:
+ @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)
+
+ 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')`
+
+
+if __name__ == "__main__":
+ app = falcon.App()
+ app.add_route("/api/user", UserProfile())
+ spec.register(app)
+
+ httpd = simple_server.make_server("localhost", 8000, app)
+ httpd.serve_forever()
+```
+
+#### Falcon with type annotations
+
+```python
+# opt in into annotations feature
+spec = SpecTree("falcon", annotations=True)
+
+
+class UserProfile:
+ @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)
+
+ 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')`
+```
+
+### Starlette
+
+```py
+import uvicorn
+from starlette.applications import Starlette
+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)")
+
+
+class Message(BaseModel):
+ text: str
+
+
+spec = SpecTree("starlette")
+
+
+@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)
+
+ 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'))`
+
+
+if __name__ == "__main__":
+ 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
+spec = SpecTree("flask", annotations=True)
+
+
+@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)
+
+ 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'))`
+```
+
+
+## FAQ
+
+> ValidationError: missing field for headers
+
+The HTTP headers' keys in Flask are capitalized, in Falcon are upper cases, in Starlette are lower cases.
+You can use [`pydantic.root_validators(pre=True)`](https://pydantic-docs.helpmanual.io/usage/validators/#root-validators) to change all the keys into lower cases or upper cases.
+
+> ValidationError: value is not a valid list for the query
+
+Since there is no standard for HTTP queries with multiple values, it's hard to find a way to handle this for different web frameworks. So I suggest not to use list type in query until I find a suitable way to fix it.
+
+
+%package -n python3-spectree
+Summary: generate OpenAPI document and validate request&response with Python annotations.
+Provides: python-spectree
+BuildRequires: python3-devel
+BuildRequires: python3-setuptools
+BuildRequires: python3-pip
+%description -n python3-spectree
+# SpecTree
+
+
+[![GitHub Actions](https://github.com/0b01001001/spectree/workflows/Python%20package/badge.svg)](https://github.com/0b01001001/spectree/actions)
+[![pypi](https://img.shields.io/pypi/v/spectree.svg)](https://pypi.python.org/pypi/spectree)
+[![versions](https://img.shields.io/pypi/pyversions/spectree.svg)](https://github.com/0b01001001/spectree)
+[![CodeQL](https://github.com/0b01001001/spectree/actions/workflows/codeql.yml/badge.svg)](https://github.com/0b01001001/spectree/actions/workflows/codeql.yml)
+[![Python document](https://github.com/0b01001001/spectree/workflows/Python%20document/badge.svg)](https://0b01001001.github.io/spectree/)
+
+Yet another library to generate OpenAPI documents and validate requests & responses with Python annotations.
+
+## Features
+
+* Less boilerplate code, only annotations, no need for YAML :sparkles:
+* Generate API document with [Redoc UI](https://github.com/Redocly/redoc) or [Swagger UI](https://github.com/swagger-api/swagger-ui) :yum:
+* 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)
+
+## Quick Start
+
+Install with pip: `pip install spectree`. If you'd like for email fields to be validated, use `pip install spectree[email]`.
+
+### Examples
+
+Check the [examples](/examples) folder.
+
+* [flask example](/examples/flask_demo.py)
+* [falcon example with logging when validation failed](/examples/falcon_demo.py)
+* [starlette example](examples/starlette_demo.py)
+
+### Step by Step
+
+1. Define your data structure used in (query, json, headers, cookies, resp) with `pydantic.BaseModel`
+2. create `spectree.SpecTree` instance with the web framework name you are using, like `api = SpecTree('flask')`
+3. `api.validate` decorate the route with
+ * `query`
+ * `json`
+ * `headers`
+ * `cookies`
+ * `resp`
+ * `tags`
+ * `security`
+4. access these data with `context(query, json, headers, cookies)` (of course, you can access these from the original place where the framework offered)
+ * flask: `request.context`
+ * falcon: `req.context`
+ * starlette: `request.context`
+5. register to the web application `api.register(app)`
+6. check the document at URL location `/apidoc/redoc` or `/apidoc/swagger`
+
+If the request doesn't pass the validation, it will return a 422 with a JSON error message(ctx, loc, msg, type).
+
+### Falcon response validation
+
+For Falcon response, this library only validates against media as it is the serializable object. Response.text is a string representing response content and will not be validated. For no assigned media situation, `resp` parameter in `api.validate` should be like `Response(HTTP_200=None)`
+
+### Opt-in type annotation feature
+This library also supports the injection of validated fields into view function arguments along with parameter annotation-based type declaration. This works well with linters that can take advantage of typing features like mypy. See the examples section below.
+
+## How-To
+
+> How to add summary and description to endpoints?
+
+Just add docs to the endpoint function. The 1st line is the summary, and the rest is the description for this endpoint.
+
+> How to add a description to parameters?
+
+Check the [pydantic](https://pydantic-docs.helpmanual.io/usage/schema/) document about description in `Field`.
+
+> Any config I can change?
+
+Of course. Check the [config](https://spectree.readthedocs.io/en/latest/config.html) document.
+
+You can update the config when init the spectree like:
+
+```py
+SpecTree('flask', title='Demo API', version='v1.0', path='doc')
+```
+
+> What is `Response` and how to use it?
+
+To build a response for the endpoint, you need to declare the status code with format `HTTP_{code}` and corresponding data (optional).
+
+```py
+Response(HTTP_200=None, HTTP_403=ForbidModel)
+Response('HTTP_200') # equals to Response(HTTP_200=None)
+# with custom code description
+Response(HTTP_403=(ForbidModel, "custom code description"))
+```
+
+> How to secure API endpoints?
+
+For secure API endpoints, it is needed to define the `security_schemes` argument in the `SpecTree` constructor. `security_schemes` argument needs to contain an array of `SecurityScheme` objects. Then there are two ways to enforce security:
+
+1. You can enforce security on individual API endpoints by defining the `security` argument in the `api.validate` decorator of relevant function/method (this corresponds to define security section on operation level, under `paths`, in `OpenAPI`). `security` argument is defined as a dictionary, where each key is the name of security used in `security_schemes` argument of `SpecTree` constructor and its value is required security scope, as is showed in the following example:
+
+<details>
+<summary>Click to expand the code example:</summary>
+<p>
+
+```py
+api = SpecTree(security_schemes=[
+ SecurityScheme(
+ name="auth_apiKey",
+ data={"type": "apiKey", "name": "Authorization", "in": "header"},
+ ),
+ SecurityScheme(
+ name="auth_oauth2",
+ data={
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "https://example.com/oauth/authorize",
+ "tokenUrl": "https://example.com/oauth/token",
+ "scopes": {
+ "read": "Grants read access",
+ "write": "Grants write access",
+ "admin": "Grants access to admin operations",
+ },
+ },
+ },
+ },
+ ),
+ # ...
+ ],
+ # ...
+)
+
+
+# Not secured API endpoint
+@api.validate(
+ resp=Response(HTTP_200=None),
+)
+def foo():
+ ...
+
+
+# API endpoint secured by API key type or OAuth2 type
+@api.validate(
+ resp=Response(HTTP_200=None),
+ security={"auth_apiKey": [], "auth_oauth2": ["read", "write"]}, # Local security type
+)
+def bar():
+ ...
+```
+
+</p>
+</details>
+
+
+2. You can enforce security on the whole API by defining the `security` argument in the `SpecTree` constructor (this corresponds to the define security section on the root level in `OpenAPI`). It is possible to override global security by defining local security, as well as override to no security on some API endpoint, in the `security` argument of `api.validate` decorator of relevant function/method as was described in the previous point. It is also shown in the following small example:
+
+<details>
+<summary>Click to expand the code example:</summary>
+<p>
+
+```py
+api = SpecTree(security_schemes=[
+ SecurityScheme(
+ name="auth_apiKey",
+ data={"type": "apiKey", "name": "Authorization", "in": "header"},
+ ),
+ SecurityScheme(
+ name="auth_oauth2",
+ data={
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "https://example.com/oauth/authorize",
+ "tokenUrl": "https://example.com/oauth/token",
+ "scopes": {
+ "read": "Grants read access",
+ "write": "Grants write access",
+ "admin": "Grants access to admin operations",
+ },
+ },
+ },
+ },
+ ),
+ # ...
+ ],
+ security={"auth_apiKey": []}, # Global security type
+ # ...
+)
+
+# Force no security
+@api.validate(
+ resp=Response(HTTP_200=None),
+ security={}, # Locally overridden security type
+)
+def foo():
+ ...
+
+
+# Force another type of security than global one
+@api.validate(
+ resp=Response(HTTP_200=None),
+ security={"auth_oauth2": ["read"]}, # Locally overridden security type
+)
+def bar():
+ ...
+
+
+# Use the global security
+@api.validate(
+ resp=Response(HTTP_200=None),
+)
+def foobar():
+ ...
+```
+
+</p>
+</details>
+
+> What should I return when I'm using the library?
+
+No need to change anything. Just return what the framework required.
+
+> How to log when the validation failed?
+
+Validation errors are logged with the INFO level. Details are passed into `extra`. Check the [falcon example](examples/falcon_demo.py) for details.
+
+> How can I write a customized plugin for another backend framework?
+
+Inherit `spectree.plugins.base.BasePlugin` and implement the functions you need. After that, init like `api = SpecTree(backend=MyCustomizedPlugin)`.
+
+> How to use a customized template page?
+
+```py
+SpecTree(page_templates={"page_name": "customized page contains {spec_url} for rendering"})
+```
+
+In the above example, the key "page_name" will be used in the URL to access this page "/apidoc/page_name". The value should be a string that contains `{spec_url}` which will be used to access the OpenAPI JSON file.
+
+> How can I change the response when there is a validation error? Can I record some metrics?
+
+This library provides `before` and `after` hooks to do these. Check the [doc](https://spectree.readthedocs.io/en/latest) or the [test case](tests/test_plugin_flask.py). You can change the handlers for SpecTree or a specific endpoint validation.
+
+> How to change the default `ValidationError` status code?
+
+You can change the `validation_error_status` in SpecTree (global) or a specific endpoint (local). This also takes effect in the OpenAPI documentation.
+
+> How can I skip the validation?
+
+Add `skip_validation=True` to the decorator.
+
+```py
+@api.validate(json=Profile, resp=Response(HTTP_200=Message, HTTP_403=None), skip_validation=True)
+```
+
+> How can I return my model directly?
+
+Yes, returning an instance of `BaseModel` will assume the model is valid and bypass spectree's validation and automatically call `.dict()` on the model.
+
+For starlette you should return a `PydanticResponse`:
+```py
+from spectree.plugins.starlette_plugin import PydanticResponse
+
+return PydanticResponse(MyModel)
+```
+
+## Demo
+
+Try it with `http post :8000/api/user name=alice age=18`. (if you are using `httpie`)
+
+### Flask
+
+```py
+from flask import Flask, request, jsonify
+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(Human)")
+
+ class Config:
+ schema_extra = {
+ # provide an example
+ "example": {
+ "name": "very_important_user",
+ "age": 42,
+ }
+ }
+
+
+class Message(BaseModel):
+ text: str
+
+
+app = Flask(__name__)
+spec = SpecTree("flask")
+
+
+@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')`
+
+
+if __name__ == "__main__":
+ 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
+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"])
+@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')`
+```
+
+### Falcon
+
+```py
+import falcon
+from wsgiref import simple_server
+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(Human)")
+
+
+class Message(BaseModel):
+ text: str
+
+
+spec = SpecTree("falcon")
+
+
+class UserProfile:
+ @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)
+
+ 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')`
+
+
+if __name__ == "__main__":
+ app = falcon.App()
+ app.add_route("/api/user", UserProfile())
+ spec.register(app)
+
+ httpd = simple_server.make_server("localhost", 8000, app)
+ httpd.serve_forever()
+```
+
+#### Falcon with type annotations
+
+```python
+# opt in into annotations feature
+spec = SpecTree("falcon", annotations=True)
+
+
+class UserProfile:
+ @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)
+
+ 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')`
+```
+
+### Starlette
+
+```py
+import uvicorn
+from starlette.applications import Starlette
+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)")
+
+
+class Message(BaseModel):
+ text: str
+
+
+spec = SpecTree("starlette")
+
+
+@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)
+
+ 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'))`
+
+
+if __name__ == "__main__":
+ 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
+spec = SpecTree("flask", annotations=True)
+
+
+@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)
+
+ 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'))`
+```
+
+
+## FAQ
+
+> ValidationError: missing field for headers
+
+The HTTP headers' keys in Flask are capitalized, in Falcon are upper cases, in Starlette are lower cases.
+You can use [`pydantic.root_validators(pre=True)`](https://pydantic-docs.helpmanual.io/usage/validators/#root-validators) to change all the keys into lower cases or upper cases.
+
+> ValidationError: value is not a valid list for the query
+
+Since there is no standard for HTTP queries with multiple values, it's hard to find a way to handle this for different web frameworks. So I suggest not to use list type in query until I find a suitable way to fix it.
+
+
+%package help
+Summary: Development documents and examples for spectree
+Provides: python3-spectree-doc
+%description help
+# SpecTree
+
+
+[![GitHub Actions](https://github.com/0b01001001/spectree/workflows/Python%20package/badge.svg)](https://github.com/0b01001001/spectree/actions)
+[![pypi](https://img.shields.io/pypi/v/spectree.svg)](https://pypi.python.org/pypi/spectree)
+[![versions](https://img.shields.io/pypi/pyversions/spectree.svg)](https://github.com/0b01001001/spectree)
+[![CodeQL](https://github.com/0b01001001/spectree/actions/workflows/codeql.yml/badge.svg)](https://github.com/0b01001001/spectree/actions/workflows/codeql.yml)
+[![Python document](https://github.com/0b01001001/spectree/workflows/Python%20document/badge.svg)](https://0b01001001.github.io/spectree/)
+
+Yet another library to generate OpenAPI documents and validate requests & responses with Python annotations.
+
+## Features
+
+* Less boilerplate code, only annotations, no need for YAML :sparkles:
+* Generate API document with [Redoc UI](https://github.com/Redocly/redoc) or [Swagger UI](https://github.com/swagger-api/swagger-ui) :yum:
+* 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)
+
+## Quick Start
+
+Install with pip: `pip install spectree`. If you'd like for email fields to be validated, use `pip install spectree[email]`.
+
+### Examples
+
+Check the [examples](/examples) folder.
+
+* [flask example](/examples/flask_demo.py)
+* [falcon example with logging when validation failed](/examples/falcon_demo.py)
+* [starlette example](examples/starlette_demo.py)
+
+### Step by Step
+
+1. Define your data structure used in (query, json, headers, cookies, resp) with `pydantic.BaseModel`
+2. create `spectree.SpecTree` instance with the web framework name you are using, like `api = SpecTree('flask')`
+3. `api.validate` decorate the route with
+ * `query`
+ * `json`
+ * `headers`
+ * `cookies`
+ * `resp`
+ * `tags`
+ * `security`
+4. access these data with `context(query, json, headers, cookies)` (of course, you can access these from the original place where the framework offered)
+ * flask: `request.context`
+ * falcon: `req.context`
+ * starlette: `request.context`
+5. register to the web application `api.register(app)`
+6. check the document at URL location `/apidoc/redoc` or `/apidoc/swagger`
+
+If the request doesn't pass the validation, it will return a 422 with a JSON error message(ctx, loc, msg, type).
+
+### Falcon response validation
+
+For Falcon response, this library only validates against media as it is the serializable object. Response.text is a string representing response content and will not be validated. For no assigned media situation, `resp` parameter in `api.validate` should be like `Response(HTTP_200=None)`
+
+### Opt-in type annotation feature
+This library also supports the injection of validated fields into view function arguments along with parameter annotation-based type declaration. This works well with linters that can take advantage of typing features like mypy. See the examples section below.
+
+## How-To
+
+> How to add summary and description to endpoints?
+
+Just add docs to the endpoint function. The 1st line is the summary, and the rest is the description for this endpoint.
+
+> How to add a description to parameters?
+
+Check the [pydantic](https://pydantic-docs.helpmanual.io/usage/schema/) document about description in `Field`.
+
+> Any config I can change?
+
+Of course. Check the [config](https://spectree.readthedocs.io/en/latest/config.html) document.
+
+You can update the config when init the spectree like:
+
+```py
+SpecTree('flask', title='Demo API', version='v1.0', path='doc')
+```
+
+> What is `Response` and how to use it?
+
+To build a response for the endpoint, you need to declare the status code with format `HTTP_{code}` and corresponding data (optional).
+
+```py
+Response(HTTP_200=None, HTTP_403=ForbidModel)
+Response('HTTP_200') # equals to Response(HTTP_200=None)
+# with custom code description
+Response(HTTP_403=(ForbidModel, "custom code description"))
+```
+
+> How to secure API endpoints?
+
+For secure API endpoints, it is needed to define the `security_schemes` argument in the `SpecTree` constructor. `security_schemes` argument needs to contain an array of `SecurityScheme` objects. Then there are two ways to enforce security:
+
+1. You can enforce security on individual API endpoints by defining the `security` argument in the `api.validate` decorator of relevant function/method (this corresponds to define security section on operation level, under `paths`, in `OpenAPI`). `security` argument is defined as a dictionary, where each key is the name of security used in `security_schemes` argument of `SpecTree` constructor and its value is required security scope, as is showed in the following example:
+
+<details>
+<summary>Click to expand the code example:</summary>
+<p>
+
+```py
+api = SpecTree(security_schemes=[
+ SecurityScheme(
+ name="auth_apiKey",
+ data={"type": "apiKey", "name": "Authorization", "in": "header"},
+ ),
+ SecurityScheme(
+ name="auth_oauth2",
+ data={
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "https://example.com/oauth/authorize",
+ "tokenUrl": "https://example.com/oauth/token",
+ "scopes": {
+ "read": "Grants read access",
+ "write": "Grants write access",
+ "admin": "Grants access to admin operations",
+ },
+ },
+ },
+ },
+ ),
+ # ...
+ ],
+ # ...
+)
+
+
+# Not secured API endpoint
+@api.validate(
+ resp=Response(HTTP_200=None),
+)
+def foo():
+ ...
+
+
+# API endpoint secured by API key type or OAuth2 type
+@api.validate(
+ resp=Response(HTTP_200=None),
+ security={"auth_apiKey": [], "auth_oauth2": ["read", "write"]}, # Local security type
+)
+def bar():
+ ...
+```
+
+</p>
+</details>
+
+
+2. You can enforce security on the whole API by defining the `security` argument in the `SpecTree` constructor (this corresponds to the define security section on the root level in `OpenAPI`). It is possible to override global security by defining local security, as well as override to no security on some API endpoint, in the `security` argument of `api.validate` decorator of relevant function/method as was described in the previous point. It is also shown in the following small example:
+
+<details>
+<summary>Click to expand the code example:</summary>
+<p>
+
+```py
+api = SpecTree(security_schemes=[
+ SecurityScheme(
+ name="auth_apiKey",
+ data={"type": "apiKey", "name": "Authorization", "in": "header"},
+ ),
+ SecurityScheme(
+ name="auth_oauth2",
+ data={
+ "type": "oauth2",
+ "flows": {
+ "authorizationCode": {
+ "authorizationUrl": "https://example.com/oauth/authorize",
+ "tokenUrl": "https://example.com/oauth/token",
+ "scopes": {
+ "read": "Grants read access",
+ "write": "Grants write access",
+ "admin": "Grants access to admin operations",
+ },
+ },
+ },
+ },
+ ),
+ # ...
+ ],
+ security={"auth_apiKey": []}, # Global security type
+ # ...
+)
+
+# Force no security
+@api.validate(
+ resp=Response(HTTP_200=None),
+ security={}, # Locally overridden security type
+)
+def foo():
+ ...
+
+
+# Force another type of security than global one
+@api.validate(
+ resp=Response(HTTP_200=None),
+ security={"auth_oauth2": ["read"]}, # Locally overridden security type
+)
+def bar():
+ ...
+
+
+# Use the global security
+@api.validate(
+ resp=Response(HTTP_200=None),
+)
+def foobar():
+ ...
+```
+
+</p>
+</details>
+
+> What should I return when I'm using the library?
+
+No need to change anything. Just return what the framework required.
+
+> How to log when the validation failed?
+
+Validation errors are logged with the INFO level. Details are passed into `extra`. Check the [falcon example](examples/falcon_demo.py) for details.
+
+> How can I write a customized plugin for another backend framework?
+
+Inherit `spectree.plugins.base.BasePlugin` and implement the functions you need. After that, init like `api = SpecTree(backend=MyCustomizedPlugin)`.
+
+> How to use a customized template page?
+
+```py
+SpecTree(page_templates={"page_name": "customized page contains {spec_url} for rendering"})
+```
+
+In the above example, the key "page_name" will be used in the URL to access this page "/apidoc/page_name". The value should be a string that contains `{spec_url}` which will be used to access the OpenAPI JSON file.
+
+> How can I change the response when there is a validation error? Can I record some metrics?
+
+This library provides `before` and `after` hooks to do these. Check the [doc](https://spectree.readthedocs.io/en/latest) or the [test case](tests/test_plugin_flask.py). You can change the handlers for SpecTree or a specific endpoint validation.
+
+> How to change the default `ValidationError` status code?
+
+You can change the `validation_error_status` in SpecTree (global) or a specific endpoint (local). This also takes effect in the OpenAPI documentation.
+
+> How can I skip the validation?
+
+Add `skip_validation=True` to the decorator.
+
+```py
+@api.validate(json=Profile, resp=Response(HTTP_200=Message, HTTP_403=None), skip_validation=True)
+```
+
+> How can I return my model directly?
+
+Yes, returning an instance of `BaseModel` will assume the model is valid and bypass spectree's validation and automatically call `.dict()` on the model.
+
+For starlette you should return a `PydanticResponse`:
+```py
+from spectree.plugins.starlette_plugin import PydanticResponse
+
+return PydanticResponse(MyModel)
+```
+
+## Demo
+
+Try it with `http post :8000/api/user name=alice age=18`. (if you are using `httpie`)
+
+### Flask
+
+```py
+from flask import Flask, request, jsonify
+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(Human)")
+
+ class Config:
+ schema_extra = {
+ # provide an example
+ "example": {
+ "name": "very_important_user",
+ "age": 42,
+ }
+ }
+
+
+class Message(BaseModel):
+ text: str
+
+
+app = Flask(__name__)
+spec = SpecTree("flask")
+
+
+@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')`
+
+
+if __name__ == "__main__":
+ 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
+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"])
+@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')`
+```
+
+### Falcon
+
+```py
+import falcon
+from wsgiref import simple_server
+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(Human)")
+
+
+class Message(BaseModel):
+ text: str
+
+
+spec = SpecTree("falcon")
+
+
+class UserProfile:
+ @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)
+
+ 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')`
+
+
+if __name__ == "__main__":
+ app = falcon.App()
+ app.add_route("/api/user", UserProfile())
+ spec.register(app)
+
+ httpd = simple_server.make_server("localhost", 8000, app)
+ httpd.serve_forever()
+```
+
+#### Falcon with type annotations
+
+```python
+# opt in into annotations feature
+spec = SpecTree("falcon", annotations=True)
+
+
+class UserProfile:
+ @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)
+
+ 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')`
+```
+
+### Starlette
+
+```py
+import uvicorn
+from starlette.applications import Starlette
+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)")
+
+
+class Message(BaseModel):
+ text: str
+
+
+spec = SpecTree("starlette")
+
+
+@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)
+
+ 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'))`
+
+
+if __name__ == "__main__":
+ 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
+spec = SpecTree("flask", annotations=True)
+
+
+@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)
+
+ 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'))`
+```
+
+
+## FAQ
+
+> ValidationError: missing field for headers
+
+The HTTP headers' keys in Flask are capitalized, in Falcon are upper cases, in Starlette are lower cases.
+You can use [`pydantic.root_validators(pre=True)`](https://pydantic-docs.helpmanual.io/usage/validators/#root-validators) to change all the keys into lower cases or upper cases.
+
+> ValidationError: value is not a valid list for the query
+
+Since there is no standard for HTTP queries with multiple values, it's hard to find a way to handle this for different web frameworks. So I suggest not to use list type in query until I find a suitable way to fix it.
+
+
+%prep
+%autosetup -n spectree-1.1.0
+
+%build
+%py3_build
+
+%install
+%py3_install
+install -d -m755 %{buildroot}/%{_pkgdocdir}
+if [ -d doc ]; then cp -arf doc %{buildroot}/%{_pkgdocdir}; fi
+if [ -d docs ]; then cp -arf docs %{buildroot}/%{_pkgdocdir}; fi
+if [ -d example ]; then cp -arf example %{buildroot}/%{_pkgdocdir}; fi
+if [ -d examples ]; then cp -arf examples %{buildroot}/%{_pkgdocdir}; fi
+pushd %{buildroot}
+if [ -d usr/lib ]; then
+ find usr/lib -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/lib64 ]; then
+ find usr/lib64 -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/bin ]; then
+ find usr/bin -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/sbin ]; then
+ find usr/sbin -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+touch doclist.lst
+if [ -d usr/share/man ]; then
+ find usr/share/man -type f -printf "/%h/%f.gz\n" >> doclist.lst
+fi
+popd
+mv %{buildroot}/filelist.lst .
+mv %{buildroot}/doclist.lst .
+
+%files -n python3-spectree -f filelist.lst
+%dir %{python3_sitelib}/*
+
+%files help -f doclist.lst
+%{_docdir}/*
+
+%changelog
+* Tue Apr 11 2023 Python_Bot <Python_Bot@openeuler.org> - 1.1.0-1
+- Package Spec generated
diff --git a/sources b/sources
new file mode 100644
index 0000000..6e58a34
--- /dev/null
+++ b/sources
@@ -0,0 +1 @@
+b7bdaf12fa3fdc94465b870d8ff87d7c spectree-1.1.0.tar.gz