Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

SpecTree

GitHub Actions pypi versions CodeQL Python document

Yet another library to generate OpenAPI documents and validate requests & responses with Python annotations.

If all you need is a framework-agnostic library that can generate OpenAPI document, check defspec.

Features

Quick Start

Install with pip:

pip install "spectree[pydantic]"

If you want to install with offline OpenAPI web pages support:

Offline mode doesn't support SwaggerUI OAuth2 redirection.

pip install "spectree[pydantic,offline]"

If you want to use msgspec instead of pydantic:

pip install "spectree[msgspec]"

Upgrading from v2

Version 3 introduces model adapters and makes the model backend an optional dependency. Read the v2 to v3 migration guide before upgrading an existing application.

Examples

Check the examples folder.

Step by Step

  1. Define your data structures for query, json, headers, cookies, and resp with the model backend you configured for SpecTree.
  2. Create a spectree.SpecTree instance with the web framework name you are using, like spec = SpecTree('flask'). SpecTree uses the pydantic adapter by default; pass model_adapter=get_msgspec_model_adapter() to use the msgspec adapter, or pass another model adapter implementation.
  3. Decorate the route with spec.validate, using any of these arguments (default values are given in parentheses):
    • query
    • json
    • headers
    • cookies
    • resp
    • tags (no tags on endpoint)
    • security (None - endpoint is not secured)
    • deprecated (False - endpoint is not marked as deprecated)
  4. Access the validated data through the function arguments (see the examples below). You can also access the original data through the web framework.
  5. Register Spectree with the web application by calling spec.register(app). Without this call, validation still works, but Spectree does not generate the OpenAPI document.
  6. Open the generated document at /apidoc/redoc, /apidoc/swagger, or /apidoc/scalar.

If a request fails validation, Spectree returns a 422 response containing error details produced by the configured model adapter.

Adapter models and Python dataclasses

Start by defining request and response models with one of the model types supported by the adapter you choose:

  • pydantic: use pydantic.BaseModel or a standard-library @dataclass.
  • msgspec: use msgspec.Struct or a standard-library @dataclass.

For requests, either pass the model to spec.validate (json=Profile) or add it as a parameter annotation (json: Profile). Put response models in Response, for example Response(HTTP_200=Message).

Plain Python dataclasses can be used directly as request or response models, or as field types within another model.

from dataclasses import dataclass

from pydantic import BaseModel
from spectree import Response, SpecTree


spec = SpecTree("flask")


class Profile(BaseModel):
    name: str


@dataclass
class Message:
    text: str


@spec.validate(resp=Response(HTTP_200=Message))
def profile(json: Profile):
    return Message(text=f"Hello, {json.name}")

Profile is the pydantic request model selected by the json annotation. Message is the dataclass response model assigned to status 200. The configured adapter validates and serializes both models and includes them in the generated OpenAPI schema.

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 spec.validate should be like Response(HTTP_200=None)

Type annotation feature

Type annotation support is enabled by default. Spectree injects validated fields into view function arguments based on their parameter annotations. This works well with linters that take advantage of typing features such as mypy. Set annotations=False on SpecTree to opt out.

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 your model backend's field metadata and schema documentation for how to attach descriptions.

Any config I can change?

Of course. Check the Configuration source.

You can update the config when init the spectree like:

SpecTree('flask', title='Demo API', version='v1.0', path='doc')

How do I choose between pydantic and msgspec?

Install the extra for the backend you want to use, then pass the adapter when creating SpecTree:

from spectree.model_adapter import get_msgspec_model_adapter

SpecTree("falcon", model_adapter=get_msgspec_model_adapter())
SpecTree("flask")  # uses pydantic by default

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).

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 can I skip the validation?

Add skip_validation=True to the decorator.

Before v1.3.0, this only skip the response validation.

Starts 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.

@spec.validate(json=Profile, resp=Response(HTTP_200=Message, HTTP_403=None), skip_validation=True)

What is the callback signature for before and after hooks?

Both hooks now receive the active model adapter as their last argument:

def before(req, resp, req_validation_error, instance, model_adapter):
    ...


def after(req, resp, resp_validation_error, instance, model_adapter):
    ...

This is useful when you need adapter-specific error details or other model-backend behavior in a custom hook.

How can I use the validation without the OpenAPI document?

The OpenAPI endpoints are added by spec.register(app). If you don't want to add the OpenAPI endpoints, you don't need to register it to the application.

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 spec.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:
Click to expand the code example:

spec = 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
@spec.validate(
    resp=Response(HTTP_200=None),
)
def foo():
    ...


# API endpoint secured by API key type or OAuth2 type
@spec.validate(
    resp=Response(HTTP_200=None),
    security={"auth_apiKey": [], "auth_oauth2": ["read", "write"]},  # Local security type
)
def bar():
    ...

  1. 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 spec.validate decorator of relevant function/method as was described in the previous point. It is also shown in the following small example:
Click to expand the code example:

spec = 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
@spec.validate(
    resp=Response(HTTP_200=None),
    security={}, # Locally overridden security type
)
def foo():
    ...


# Force another type of security than global one
@spec.validate(
    resp=Response(HTTP_200=None),
    security={"auth_oauth2": ["read"]}, # Locally overridden security type
)
def bar():
    ...


# Use the global security
@spec.validate(
    resp=Response(HTTP_200=None),
)
def foobar():
    ...

How to mark deprecated endpoint?

Use deprecated attribute with value True in spec.validate() decorator. This way, an endpoint will be marked as deprecated and will be marked with a strikethrough in API documentation.

Code example:

@spec.validate(
    deprecated=True,
)
def deprecated_endpoint():
    ...

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 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 spec = SpecTree(backend=MyCustomizedPlugin).

How to use a customized template page?

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 documentation or the Flask adapter tests. 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 return my model directly?

Yes, returning an instance produced by your configured model backend will assume the model is valid and bypass Spectree's validation. Spectree will serialize that instance through the active model adapter.

For starlette you should return a SpecTreeStarletteResponse:

from spectree.plugins.starlette_plugin import SpecTreeStarletteResponse

return SpecTreeStarletteResponse(MyModel)

Demo

Try it with http post :8000/api/user name=alice age=18. (if you are using httpie)

Flask

from flask import Flask, jsonify
from pydantic import BaseModel, Field, ConfigDict

from spectree import Response, SpecTree


class Profile(BaseModel):
    name: str
    age: int = Field(..., gt=0, lt=150, description="user age(Human)")

    model_config = ConfigDict(
        json_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(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')`


if __name__ == "__main__":
    spec.register(app)  # if you don't register in api init step
    app.run(port=8000)

Quart

from pydantic import BaseModel, Field, ConfigDict
from quart import Quart, jsonify

from spectree import Response, SpecTree


class Profile(BaseModel):
    name: str
    age: int = Field(..., gt=0, lt=150, description="user age")

    model_config = ConfigDict(
        json_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(resp=Response(HTTP_200=Message, HTTP_403=None), tags=["api"])
async 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")`


if __name__ == "__main__":
    spec.register(app)
    app.run(port=8000)

Falcon

from wsgiref import simple_server

import falcon
from pydantic import BaseModel, Field

from spectree import Response, SpecTree


class Profile(BaseModel):
    name: str
    age: int = Field(..., gt=0, lt=150, description="user age(Human)")


class Message(BaseModel):
    text: str


spec = SpecTree("falcon")


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(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()

Starlette

import uvicorn
from pydantic import BaseModel, Field
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Mount, Route

from spectree import Response, SpecTree

# from spectree.plugins.starlette_plugin import SpecTreeStarletteResponse


class Profile(BaseModel):
    name: str
    age: int = Field(..., gt=0, lt=150, description="user age(Human)")


class Message(BaseModel):
    text: str


spec = SpecTree("starlette")


@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(json)  # or await request.json()
    return JSONResponse(
        {"text": "it works"}
    )  # or `return SpecTreeStarletteResponse(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)

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.model_validator(mode="before") 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.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

spectree-3.0.0rc1.tar.gz (57.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

spectree-3.0.0rc1-py3-none-any.whl (53.4 kB view details)

Uploaded Python 3

File details

Details for the file spectree-3.0.0rc1.tar.gz.

File metadata

  • Download URL: spectree-3.0.0rc1.tar.gz
  • Upload date:
  • Size: 57.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for spectree-3.0.0rc1.tar.gz
Algorithm Hash digest
SHA256 97eba162828f8590444b9eeca94d3c25b1b906729803ff77a69a475566fb7346
MD5 b234fa663c2d5853e92c96b6f00e8b26
BLAKE2b-256 30fdc89ec60d20206dc495efb7e85b057a81b2800f12b67fd794b2aab895d593

See more details on using hashes here.

File details

Details for the file spectree-3.0.0rc1-py3-none-any.whl.

File metadata

  • Download URL: spectree-3.0.0rc1-py3-none-any.whl
  • Upload date:
  • Size: 53.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for spectree-3.0.0rc1-py3-none-any.whl
Algorithm Hash digest
SHA256 2fad1bfba28820e9a44147fad24e569be34ad5b92ac0299c84dd977f36f7c779
MD5 bfd8f58e8dbd438a6383902a8d3bcadf
BLAKE2b-256 78e87e418d38f18b8b54b223334b7da76fc95fa1a3b781c91911fb3491a64cbe

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

3.0.0rc1 This release

2 files

2.0.3

2 files

2.0.2

2 files

2.0.1

2 files

2.0.0

2 files

1.5.8

2 files

1.5.7

2 files

1.5.6

2 files

1.5.5

2 files

1.5.4

2 files

1.5.3

2 files

1.5.2

2 files

1.5.1

2 files

1.5.0

2 files

1.4.12

2 files

1.4.11

2 files

1.4.10

2 files

1.4.9

2 files

1.4.8

2 files

1.4.7

2 files

1.4.6

2 files

1.4.5

2 files

1.4.4

2 files

1.4.3

2 files

1.4.2

2 files

1.4.1

2 files

1.4.0

2 files

1.3.0

2 files

1.2.11

2 files

1.2.10

2 files

1.2.9

2 files

1.2.8

2 files

1.2.7

2 files

1.2.6

2 files

1.2.5

2 files

1.2.4

2 files

1.2.3

2 files

1.2.2

2 files

1.2.1

2 files

1.2.0

2 files

1.1.5

2 files

1.1.4

2 files

1.1.3

2 files

1.1.2

2 files

1.1.1

2 files

1.1.0

2 files

1.0.3

2 files

1.0.2

2 files

1.0.1

2 files

0.12.1

2 files

0.12.0

2 files

0.11.0

2 files

0.10.6

2 files

0.10.5

2 files

0.10.4

2 files

0.10.3

2 files

0.10.2

2 files

0.10.1

2 files

0.10.0

2 files

0.9.2

2 files

0.9.1

2 files

0.9.0

2 files

0.8.0

2 files

0.7.6

2 files

0.7.5

2 files

0.7.4

2 files

0.7.3

2 files

0.7.2

2 files

0.7.1

2 files

0.7.0

2 files

0.6.8

2 files

0.6.7

2 files

0.6.6

2 files

0.6.5

2 files

0.6.4

2 files

0.6.3

2 files

0.6.2

2 files

0.6.1

2 files

0.6.0

2 files

0.5.4

2 files

0.5.3

2 files

0.5.2

2 files

0.5.1

2 files

0.5.0

2 files

0.4.3

2 files

0.4.2

2 files

0.4.1

2 files

0.4.0

2 files

0.3.16

2 files

0.3.15

2 files

0.3.14

2 files

0.3.13

2 files

0.3.12

2 files

0.3.11

2 files

0.3.10

2 files

0.3.9

2 files

0.3.8

2 files

0.3.7

2 files

0.3.6

2 files

0.3.5

2 files

0.3.3

2 files

0.3.2

2 files

0.3.0

2 files

0.2.9

2 files

0.2.8

2 files

0.2.7

2 files

0.2.5

2 files

0.2.4

2 files

0.2.3

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0

2 files

0.1.0

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page