{"id":13633887,"url":"https://github.com/0b01001001/spectree","last_synced_at":"2025-05-14T16:02:35.484Z","repository":{"id":36461010,"uuid":"225120376","full_name":"0b01001001/spectree","owner":"0b01001001","description":"API spec validator and  OpenAPI document generator for Python web frameworks.","archived":false,"fork":false,"pushed_at":"2025-03-04T10:24:31.000Z","size":990,"stargazers_count":332,"open_issues_count":13,"forks_count":79,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-04-06T00:00:24.443Z","etag":null,"topics":["annotation","annotations","apispec","asgi","falcon","flask","hacktoberfest","openapi","openapi-document","pydantic","pydantic-v2","python","redoc","starlette","swagger","wsgi"],"latest_commit_sha":null,"homepage":"https://0b01001001.github.io/spectree/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/0b01001001.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["kemingy"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":null}},"created_at":"2019-12-01T07:04:07.000Z","updated_at":"2025-03-28T16:25:35.000Z","dependencies_parsed_at":"2023-02-18T20:31:15.201Z","dependency_job_id":"63e27ca6-ddfb-4c31-a940-e9c955fc080d","html_url":"https://github.com/0b01001001/spectree","commit_stats":{"total_commits":404,"total_committers":43,"mean_commits":9.395348837209303,"dds":0.2623762376237624,"last_synced_commit":"f60b7e54d3927a1cba0cada103e648a0799045dc"},"previous_names":[],"tags_count":86,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0b01001001%2Fspectree","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0b01001001%2Fspectree/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0b01001001%2Fspectree/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0b01001001%2Fspectree/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/0b01001001","download_url":"https://codeload.github.com/0b01001001/spectree/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248654052,"owners_count":21140235,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["annotation","annotations","apispec","asgi","falcon","flask","hacktoberfest","openapi","openapi-document","pydantic","pydantic-v2","python","redoc","starlette","swagger","wsgi"],"created_at":"2024-08-01T23:00:53.207Z","updated_at":"2025-04-13T01:54:51.021Z","avatar_url":"https://github.com/0b01001001.png","language":"Python","funding_links":["https://github.com/sponsors/kemingy"],"categories":["Extensions","Web","Python","OpenAPI Utilities"],"sub_categories":["API","API (REST, GraphQL...)"],"readme":"# SpecTree\n\n\n[![GitHub Actions](https://github.com/0b01001001/spectree/workflows/Python%20package/badge.svg)](https://github.com/0b01001001/spectree/actions)\n[![pypi](https://img.shields.io/pypi/v/spectree.svg)](https://pypi.python.org/pypi/spectree)\n[![versions](https://img.shields.io/pypi/pyversions/spectree.svg)](https://github.com/0b01001001/spectree)\n[![CodeQL](https://github.com/0b01001001/spectree/actions/workflows/codeql.yml/badge.svg)](https://github.com/0b01001001/spectree/actions/workflows/codeql.yml)\n[![Python document](https://github.com/0b01001001/spectree/workflows/Python%20document/badge.svg)](https://0b01001001.github.io/spectree/)\n\nYet another library to generate OpenAPI documents and validate requests \u0026 responses with Python annotations.\n\nIf all you need is a framework-agnostic library that can generate OpenAPI document, check [defspec](https://github.com/kemingy/defspec/).\n\n## Features\n\n* Less boilerplate code, only annotations, no need for YAML :sparkles:\n* Generate API document with [Redoc UI](https://github.com/Redocly/redoc), [Scalar UI](https://github.com/scalar/scalar) or [Swagger UI](https://github.com/swagger-api/swagger-ui) :yum:\n* Validate query, JSON data, response data with [pydantic](https://github.com/samuelcolvin/pydantic/) (both v1 \u0026 v2) :wink:\n* Current support:\n  * Flask [demo](#flask)\n  * Quart [demo](#quart)\n  * Falcon [demo](#falcon)\n  * Starlette [demo](#starlette)\n\n## Quick Start\n\nInstall with pip:\n\n```bash\npip install spectree\n```\n\nIf you want to install with offline OpenAPI web pages support:\n\n\u003e Offline mode doesn't support SwaggerUI OAuth2 redirection.\n\n```bash\npip install spectree[offline]\n```\n\n### Examples\n\nCheck the [examples](examples) folder.\n\n* [flask example](examples/flask_demo.py)\n* [quart example](examples/quart_demo.py)\n* [falcon example with logging when validation failed](examples/falcon_demo.py)\n* [starlette example](examples/starlette_demo.py)\n\n### Step by Step\n\n1. Define your data structure used in (query, json, headers, cookies, resp) with `pydantic.BaseModel`\n2. create `spectree.SpecTree` instance with the web framework name you are using, like `api = SpecTree('flask')`\n3. `api.validate` decorate the route with (the default value is given in parentheses):\n   * `query`\n   * `json`\n   * `headers`\n   * `cookies`\n   * `resp`\n   * `tags` *(no tags on endpoint)*\n   * `security` *(`None` - endpoint is not secured)*\n   * `deprecated` *(`False` - endpoint is not marked as deprecated)*\n4. access these data from the function annotations (see the examples below). Of course, you can still access them from the original place where the framework offered.\n5. register to the web application `api.register(app)`\n6. check the document at URL location `/apidoc/redoc` or `/apidoc/swagger` or `/apidoc/scalar`\n\nIf the request doesn't pass the validation, it will return a 422 with a JSON error message(ctx, loc, msg, type).\n\n### Falcon response validation\n\nFor 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)`\n\n### Opt-in type annotation feature\nThis 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.\n\n## How-To\n\n\u003e How to add summary and description to endpoints?\n\nJust add docs to the endpoint function. The 1st line is the summary, and the rest is the description for this endpoint.\n\n\u003e How to add a description to parameters?\n\nCheck the [pydantic](https://pydantic-docs.helpmanual.io/usage/schema/) document about description in `Field`.\n\n\u003e Any config I can change?\n\nOf course. Check the [config](https://spectree.readthedocs.io/en/latest/config.html) document.\n\nYou can update the config when init the spectree like:\n\n```py\nSpecTree('flask', title='Demo API', version='v1.0', path='doc')\n```\n\n\u003e What is `Response` and how to use it?\n\nTo build a response for the endpoint, you need to declare the status code with format `HTTP_{code}` and corresponding data (optional).\n\n```py\nResponse(HTTP_200=None, HTTP_403=ForbidModel)\nResponse('HTTP_200') # equals to Response(HTTP_200=None)\n# with custom code description\nResponse(HTTP_403=(ForbidModel, \"custom code description\"))\n```\n\n\u003e How can I skip the validation?\n\nAdd `skip_validation=True` to the decorator.\n\nBefore v1.3.0, this only skip the response validation.\n\nStarts from v1.3.0, this will skip all the validations. As an result, you won't be able to access the validated data from `context`.\n\n```py\n@api.validate(json=Profile, resp=Response(HTTP_200=Message, HTTP_403=None), skip_validation=True)\n```\n\n\u003e How can I use the validation without the OpenAPI document?\n\nThe OpenAPI endpoints are added by `spectree.register(app)`. If you don't want to add the OpenAPI endpoints, you don't need to register it to the application.\n\n\u003e How to secure API endpoints?\n\nFor 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:\n\n1. 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:\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to expand the code example:\u003c/summary\u003e\n\u003cp\u003e\n\n```py\napi = SpecTree(security_schemes=[\n        SecurityScheme(\n            name=\"auth_apiKey\",\n            data={\"type\": \"apiKey\", \"name\": \"Authorization\", \"in\": \"header\"},\n        ),\n        SecurityScheme(\n            name=\"auth_oauth2\",\n            data={\n                \"type\": \"oauth2\",\n                \"flows\": {\n                    \"authorizationCode\": {\n                        \"authorizationUrl\": \"https://example.com/oauth/authorize\",\n                        \"tokenUrl\": \"https://example.com/oauth/token\",\n                        \"scopes\": {\n                            \"read\": \"Grants read access\",\n                            \"write\": \"Grants write access\",\n                            \"admin\": \"Grants access to admin operations\",\n                        },\n                    },\n                },\n            },\n        ),\n        # ...\n    ],\n    # ...\n)\n\n\n# Not secured API endpoint\n@api.validate(\n    resp=Response(HTTP_200=None),\n)\ndef foo():\n    ...\n\n\n# API endpoint secured by API key type or OAuth2 type\n@api.validate(\n    resp=Response(HTTP_200=None),\n    security={\"auth_apiKey\": [], \"auth_oauth2\": [\"read\", \"write\"]},  # Local security type\n)\ndef bar():\n    ...\n```\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\n2. 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:\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to expand the code example:\u003c/summary\u003e\n\u003cp\u003e\n\n```py\napi = SpecTree(security_schemes=[\n        SecurityScheme(\n            name=\"auth_apiKey\",\n            data={\"type\": \"apiKey\", \"name\": \"Authorization\", \"in\": \"header\"},\n        ),\n        SecurityScheme(\n            name=\"auth_oauth2\",\n            data={\n                \"type\": \"oauth2\",\n                \"flows\": {\n                    \"authorizationCode\": {\n                        \"authorizationUrl\": \"https://example.com/oauth/authorize\",\n                        \"tokenUrl\": \"https://example.com/oauth/token\",\n                        \"scopes\": {\n                            \"read\": \"Grants read access\",\n                            \"write\": \"Grants write access\",\n                            \"admin\": \"Grants access to admin operations\",\n                        },\n                    },\n                },\n            },\n        ),\n        # ...\n    ],\n    security={\"auth_apiKey\": []},  # Global security type\n    # ...\n)\n\n# Force no security\n@api.validate(\n    resp=Response(HTTP_200=None),\n    security={}, # Locally overridden security type\n)\ndef foo():\n    ...\n\n\n# Force another type of security than global one\n@api.validate(\n    resp=Response(HTTP_200=None),\n    security={\"auth_oauth2\": [\"read\"]}, # Locally overridden security type\n)\ndef bar():\n    ...\n\n\n# Use the global security\n@api.validate(\n    resp=Response(HTTP_200=None),\n)\ndef foobar():\n    ...\n```\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n\u003e How to mark deprecated endpoint?\n\nUse `deprecated` attribute with value `True` in `api.validate()` decorator. This way, an endpoint will be marked as\n deprecated and will be marked with a strikethrough in API documentation.\n\nCode example:\n```\n@api.validate(\n    deprecated=True,\n)\ndef deprecated_endpoint():\n    ...\n```\n\n\u003e What should I return when I'm using the library?\n\nNo need to change anything. Just return what the framework required.\n\n\u003e How to log when the validation failed?\n\nValidation errors are logged with the INFO level. Details are passed into `extra`. Check the [falcon example](examples/falcon_demo.py) for details.\n\n\u003e How can I write a customized plugin for another backend framework?\n\nInherit `spectree.plugins.base.BasePlugin` and implement the functions you need. After that, init like `api = SpecTree(backend=MyCustomizedPlugin)`.\n\n\u003e How to use a customized template page?\n\n```py\nSpecTree(page_templates={\"page_name\": \"customized page contains {spec_url} for rendering\"})\n```\n\nIn 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.\n\n\u003e How can I change the response when there is a validation error? Can I record some metrics?\n\nThis 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.\n\n\u003e How to change the default `ValidationError` status code?\n\nYou can change the `validation_error_status` in SpecTree (global) or a specific endpoint (local). This also takes effect in the OpenAPI documentation.\n\n\u003e How can I return my model directly?\n\nYes, returning an instance of `BaseModel` will assume the model is valid and bypass spectree's validation and automatically call `.dict()` on the model.\n\nFor starlette you should return a `PydanticResponse`:\n```py\nfrom spectree.plugins.starlette_plugin import PydanticResponse\n\nreturn PydanticResponse(MyModel)\n```\n\n## Demo\n\nTry it with `http post :8000/api/user name=alice age=18`. (if you are using `httpie`)\n\n### Flask\n\n```py\nfrom flask import Flask, jsonify\nfrom pydantic import BaseModel, Field\n\nfrom spectree import Response, SpecTree\n\n\nclass Profile(BaseModel):\n    name: str\n    age: int = Field(..., gt=0, lt=150, description=\"user age(Human)\")\n\n    class Config:\n        schema_extra = {\n            # provide an example\n            \"example\": {\n                \"name\": \"very_important_user\",\n                \"age\": 42,\n            }\n        }\n\n\nclass Message(BaseModel):\n    text: str\n\n\napp = Flask(__name__)\nspec = SpecTree(\"flask\")\n\n\n@app.route(\"/api/user\", methods=[\"POST\"])\n@spec.validate(resp=Response(HTTP_200=Message, HTTP_403=None), tags=[\"api\"])\ndef user_profile(json: Profile):\n    \"\"\"\n    verify user profile (summary of this endpoint)\n\n    user's name, user's age, ... (long description)\n    \"\"\"\n    print(json)  # or `request.json`\n    return jsonify(text=\"it works\")  # or `Message(text='it works')`\n\n\nif __name__ == \"__main__\":\n    spec.register(app)  # if you don't register in api init step\n    app.run(port=8000)\n```\n\n### Quart\n\n```py\nfrom pydantic import BaseModel, Field\nfrom quart import Quart, jsonify\n\nfrom spectree import Response, SpecTree\n\n\nclass Profile(BaseModel):\n    name: str\n    age: int = Field(..., gt=0, lt=150, description=\"user age\")\n\n    class Config:\n        schema_extra = {\n            # provide an example\n            \"example\": {\n                \"name\": \"very_important_user\",\n                \"age\": 42,\n            }\n        }\n\n\nclass Message(BaseModel):\n    text: str\n\n\napp = Quart(__name__)\nspec = SpecTree(\"quart\")\n\n\n@app.route(\"/api/user\", methods=[\"POST\"])\n@spec.validate(resp=Response(HTTP_200=Message, HTTP_403=None), tags=[\"api\"])\nasync def user_profile(json: Profile):\n    \"\"\"\n    verify user profile (summary of this endpoint)\n\n    user's name, user's age, ... (long description)\n    \"\"\"\n    print(json)  # or `request.json`\n    return jsonify(text=\"it works\")  # or `Message(text=\"it works\")`\n\n\nif __name__ == \"__main__\":\n    spec.register(app)\n    app.run(port=8000)\n```\n\n### Falcon\n\n```py\nfrom wsgiref import simple_server\n\nimport falcon\nfrom pydantic import BaseModel, Field\n\nfrom spectree import Response, SpecTree\n\n\nclass Profile(BaseModel):\n    name: str\n    age: int = Field(..., gt=0, lt=150, description=\"user age(Human)\")\n\n\nclass Message(BaseModel):\n    text: str\n\n\nspec = SpecTree(\"falcon\")\n\n\nclass UserProfile:\n    @spec.validate(resp=Response(HTTP_200=Message, HTTP_403=None), tags=[\"api\"])\n    def on_post(self, req, resp, json: Profile):\n        \"\"\"\n        verify user profile (summary of this endpoint)\n\n        user's name, user's age, ... (long description)\n        \"\"\"\n        print(json)  # or `req.media`\n        resp.media = {\"text\": \"it works\"}  # or `resp.media = Message(text='it works')`\n\n\nif __name__ == \"__main__\":\n    app = falcon.App()\n    app.add_route(\"/api/user\", UserProfile())\n    spec.register(app)\n\n    httpd = simple_server.make_server(\"localhost\", 8000, app)\n    httpd.serve_forever()\n```\n\n### Starlette\n\n```py\nimport uvicorn\nfrom pydantic import BaseModel, Field\nfrom starlette.applications import Starlette\nfrom starlette.responses import JSONResponse\nfrom starlette.routing import Mount, Route\n\nfrom spectree import Response, SpecTree\n\n# from spectree.plugins.starlette_plugin import PydanticResponse\n\n\nclass Profile(BaseModel):\n    name: str\n    age: int = Field(..., gt=0, lt=150, description=\"user age(Human)\")\n\n\nclass Message(BaseModel):\n    text: str\n\n\nspec = SpecTree(\"starlette\")\n\n\n@spec.validate(resp=Response(HTTP_200=Message, HTTP_403=None), tags=[\"api\"])\nasync def user_profile(request, json: Profile):\n    \"\"\"\n    verify user profile (summary of this endpoint)\n\n    user's name, user's age, ... (long description)\n    \"\"\"\n    print(json)  # or await request.json()\n    return JSONResponse(\n        {\"text\": \"it works\"}\n    )  # or `return PydanticResponse(Message(text='it works'))`\n\n\nif __name__ == \"__main__\":\n    app = Starlette(\n        routes=[\n            Mount(\n                \"/api\",\n                routes=[\n                    Route(\"/user\", user_profile, methods=[\"POST\"]),\n                ],\n            )\n        ]\n    )\n    spec.register(app)\n\n    uvicorn.run(app)\n```\n\n\n## FAQ\n\n\u003e ValidationError: missing field for headers\n\nThe HTTP headers' keys in Flask are capitalized, in Falcon are upper cases, in Starlette are lower cases.\nYou can use [`pydantic.model_validator(mode=\"before\")`](https://docs.pydantic.dev/dev/concepts/validators/#model-validators) to change all the keys into lower cases or upper cases.\n\n\u003e ValidationError: value is not a valid list for the query\n\nSince there is no standard for HTTP queries with multiple values, it's hard to find a way to handle this for different web frameworks.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0b01001001%2Fspectree","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F0b01001001%2Fspectree","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0b01001001%2Fspectree/lists"}