diff --git a/README.md b/README.md index 0e14c3d0..bcddaada 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ [![downloads](https://img.shields.io/pypi/dm/spectree.svg)](https://pypistats.org/packages/spectree) [![versions](https://img.shields.io/pypi/pyversions/spectree.svg)](https://github.com/0b01001001/spectree) [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/0b01001001/spectree.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/0b01001001/spectree/context:python) +[![Documentation Status](https://readthedocs.org/projects/spectree/badge/?version=latest)](https://spectree.readthedocs.io/en/latest/?badge=latest) Yet another library to generate OpenAPI document and validate request & response with Python annotations. @@ -23,29 +24,76 @@ Yet another library to generate OpenAPI document and validate request & response install with pip: `pip install spectree` -### Demo +### Examples -```py -``` +Check the [examples](/examples) folder. ### Step by Step -1. Define your data structure with `pydantic.BaseModel` -2. create `spectree.SpecTree` instance with the web framework name you are using `api = SpecTree('flask')` -3. `validate` the route +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` -4. access these data with `context(query, json, headers, cookies)` +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` -### Examples +## Demo -Check the [examples](/examples) folder. +### 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 Message(BaseModel): + text: str + + +app = Flask(__name__) +api = SpecTree('flask') + + +@app.route('/api/user', methods=['POST']) +@api.validate(json=Profile, resp=Response('HTTP_404', HTTP_200=Message), tags=['api']) +def user_profile(): + """ + verify user profile (summary of this endpoint) + + user's name, user'age, ... (long description) + """ + print(request.context.json) # or `request.json` + return jsonify(text='it works') + + +if __name__ == "__main__": + api.register(app) # if you don't register in api init step + app.run() + +``` + +## FAQ + +> ValidationError: missing field for headers + +The HTTP headers' keys in Flask are capitalized, in Falcon are upper cases, in Starlette are lower cases. diff --git a/docs/source/conf.py b/docs/source/conf.py index ed6aa6e5..8f5504f7 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -52,4 +52,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] \ No newline at end of file +html_static_path = ['_static'] + +# read the doc +master_doc = 'index' diff --git a/docs/source/index.rst b/docs/source/index.rst index c3a94e43..d2f0ef6e 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -6,6 +6,89 @@ Welcome to spectree's documentation! ==================================== +|GitHub Actions| |pypi| |downloads| |versions| |Language grade: Python| +|Documentation Status| + +Yet another library to generate OpenAPI document and validate request & +response with Python annotations. + +Features +-------- + +- Less boilerplate code, annotations are really easy-to-use +- Generate API document with `Redoc UI`_ or `Swagger UI`_ +- Validate query, JSON data, response data with `pydantic`_ +- Current support: + + - Flask + - Falcon + - Starlette + +Quick Start +----------- + +install with pip: ``pip install spectree`` + +Examples +~~~~~~~~ + +Check the `examples`_ folder. + +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`` + +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`` + +FAQ +--- + + ValidationError: missing field for headers + +The HTTP headers’ keys in Flask are capitalized, in Falcon are upper +cases, in Starlette are lower cases. + +.. _Redoc UI: https://github.com/Redocly/redoc +.. _Swagger UI: https://github.com/swagger-api/swagger-ui +.. _pydantic: https://github.com/samuelcolvin/pydantic/ +.. _examples: https://github.com/0b01001001/spectree/blob/master/examples + +.. |GitHub Actions| image:: https://github.com/0b01001001/spectree/workflows/Python%20package/badge.svg + :target: https://github.com/0b01001001/spectree/actions +.. |pypi| image:: https://img.shields.io/pypi/v/spectree.svg + :target: https://pypi.python.org/pypi/spectree +.. |downloads| image:: https://img.shields.io/pypi/dm/spectree.svg + :target: https://pypistats.org/packages/spectree +.. |versions| image:: https://img.shields.io/pypi/pyversions/spectree.svg + :target: https://github.com/0b01001001/spectree +.. |Language grade: Python| image:: https://img.shields.io/lgtm/grade/python/g/0b01001001/spectree.svg?logo=lgtm&logoWidth=18 + :target: https://lgtm.com/projects/g/0b01001001/spectree/context:python +.. |Documentation Status| image:: https://readthedocs.org/projects/spectree/badge/?version=latest + :target: https://spectree.readthedocs.io/en/latest/?badge=latest + + .. toctree:: :maxdepth: 2 :caption: Contents: @@ -13,6 +96,7 @@ Welcome to spectree's documentation! spectree config response + plugins utils diff --git a/docs/source/plugins.rst b/docs/source/plugins.rst new file mode 100644 index 00000000..0322b046 --- /dev/null +++ b/docs/source/plugins.rst @@ -0,0 +1,14 @@ +Plugins +==================== + +.. automodule:: spectree.plugins.base + :members: + +.. automodule:: spectree.plugins.flask_plugin + :members: + +.. automodule:: spectree.plugins.falcon_plugin + :members: + +.. automodule:: spectree.plugins.starlette_plugin + :members: diff --git a/setup.py b/setup.py index 6993d1a4..3dfade64 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='spectree', - version='0.1.0', + version='0.2.0', author='Keming Yang', author_email='kemingy94@gmail.com', description=('generate OpenAPI document and validate request&response ' @@ -38,7 +38,7 @@ extras_require={ 'flask': ['flask'], 'falcon': ['falcon'], - 'starlette': ['starlette'], + 'starlette': ['starlette', 'requests'], }, zip_safe=False, entry_points={ diff --git a/spectree/plugins/base.py b/spectree/plugins/base.py index 36ff021b..4f4e7484 100644 --- a/spectree/plugins/base.py +++ b/spectree/plugins/base.py @@ -6,6 +6,8 @@ class BasePlugin: """ Base plugin for SpecTree plugin classes. + + :param spectree: :class:`spectree.SpecTree` instance """ def __init__(self, spectree): @@ -13,19 +15,46 @@ def __init__(self, spectree): self.config = spectree.config def register_route(self, app): + """ + :param app: backend framework application + + register document API routes to application + """ raise NotImplementedError def validate(self, query, json, headers, resp): + """ + validate the request and response + """ raise NotImplementedError def find_routes(self): + """ + find the routes from application + """ raise NotImplementedError def bypass(self, func, method): + """ + :param func: route function (endpoint) + :param method: HTTP method for this route function + + bypass some routes that shouldn't be shown in document + """ raise NotImplementedError def parse_path(self, route): + """ + :param route: API routes + + parse URI path to get the variables in path + """ raise NotImplementedError def parse_func(self, route): + """ + :param route: API routes + + get the endpoint function from routes + """ raise NotImplementedError diff --git a/spectree/spec.py b/spectree/spec.py index c289f3db..e56f8b7a 100644 --- a/spectree/spec.py +++ b/spectree/spec.py @@ -9,6 +9,10 @@ class SpecTree: """ Interface + + :param str backend: choose from ('flask', 'falcon', 'starlette') + :param app: backend framework application instance (you can also register to it later) + :param kwargs: update default :class:`spectree.config.Config` """ def __init__(self, backend='base', app=None, **kwargs): diff --git a/spectree/utils.py b/spectree/utils.py index e0fa567f..0caabadc 100644 --- a/spectree/utils.py +++ b/spectree/utils.py @@ -1,6 +1,7 @@ import re import inspect +# parse HTTP status code to get the code HTTP_CODE = re.compile(r'^HTTP_(?P\d{3})$') @@ -38,6 +39,9 @@ def parse_request(func): def parse_params(func, params): + """ + get spec for (query, headers, cookies) + """ if hasattr(func, 'query'): params.append({ 'name': func.query,