Skip to main content

Sanic OpenAPI v3e

OpenAPI v3 support for Sanic. Document and describe all parameters, including sanic path params. python 3.6+; sanic 18.12.0+ but sanic 20.12.0 and later are recommended.

Pythons Code style: black Imports: isort Downloads

Installation

pip install sanic-openapi3e

Usage

Import blueprint and use simple decorators to document routes:

import sanic
import sanic.response
from sanic_openapi3e import openapi_blueprint, swagger_blueprint, doc

app = sanic.Sanic(strict_slashes=True)
app.blueprint(openapi_blueprint)
app.blueprint(swagger_blueprint)

@app.get("/user/<user_id>")
@doc.summary("Fetches a user by ID")
@doc.response(200, "The user")
async def get_user(request, user_id):
    return sanic.response.json({"user_id": user_id})

app.go_fast()

You'll now have a specification at the URL /openapi/spec.json and a YAML version at /openapi/spec.yml.

p.s.: Clicking on the /openapi/spec.yml link in a browser will generally download a file and your computer will open an application to read it. To see the YAML spec in the browser as a plain text file, add a ?as_text query string like /openapi/spec.yml?as_text instead.

Your routes will be automatically categorized by their blueprints' names.

Below are some simple examples, which you can copy/paste, run, and point your browser to http://127.0.0.1:8000/swagger/ to see them in action.

Describe route path parameters

If you have path parameters, you will want to describe them:

import sanic
import sanic.response
from sanic_openapi3e import openapi_blueprint, swagger_blueprint, doc
app = sanic.Sanic(strict_slashes=True)
app.blueprint(openapi_blueprint)
app.blueprint(swagger_blueprint)

@app.get("/examples/test_id/<an_id:int>")
@doc.parameter(name="an_id", description="An ID", required=True, _in="path")
def test_id(request, an_id):
    d = locals()
    del d["request"]  # not JSON serializable
    return sanic.response.json(d)

app.go_fast()

sanic-openapiv3e will recognise that the path parameter an_id is described with @doc.parameter and will merge the details together.

You may wish to specify that a parameter be limited to a set of choices, such as day-of-week or that it has a minimum value. These can be done for parameters in path, query, header and cookie:

import sanic
import sanic.request
import sanic.response
from sanic_openapi3e import openapi_blueprint, swagger_blueprint, doc

app = sanic.Sanic(strict_slashes=True)
app.blueprint(openapi_blueprint)
app.blueprint(swagger_blueprint)

int_min_4 = doc.Schema(
    _type="integer", _format="int32", minimum=4, description="Minimum: 4"
)  

@app.get("/test/some_ids")
@doc.parameter(
    name="ids",
    description="Some IDs",
    required=True,
    choices=[1, 3, 5, 7, 11, 13],
    _in="query",
    schema=doc.Schema.Integers,
)
def test_some_ids(request: sanic.request.Request):
    query = request.query_string
    return sanic.response.json(query)



@app.get("/examples/test_id_min/<an_id:int>")
@doc.parameter(
    name="an_id", description="An ID", required=True, _in="path", schema=int_min_4
)
def test_id_min(request, an_id: int):
    d = locals()
    del d["request"]  # not JSON serializable
    return sanic.response.json(d)

app.go_fast()

Describe your tags

OpenAPI uses "tags" (there can be more than one per route) to group the endpoints. It's nice to be able to group your endpoints into tags given by the blueprint's name, but sometimes you will want to give them better names: @doc.tag("tag name"). Better still is to give a description to these tags (which shows up nicely in Swagger UI), so @doc.tag("tag name", description="tag description").

You don't have to add the description more than once, sanic-openapiv3e will make it available, so while you'll want to decorate each endpoint with @doc.tag(...), only one of these will need the description. If you try to set different descriptions for the same tag, sanic-openapiv3e will raise an exception showing the tag name and the conflicting descriptions.

Share and reuse common parameters in your app

You probably have some common parameters that appear in many places in your API. Days of the week? Pagination where the minimum value must be greater than zero? OpenAPI v3 has the concept of "components" which can be shared. Setting them up is easy:

import sanic.request
import sanic.response
from sanic import Sanic
from sanic_openapi3e import openapi_blueprint, swagger_blueprint, doc


days_of_week = doc.Schema(
    _type="string",
    description="Days of the week, short, English",
    enum=["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
)

app = Sanic(strict_slashes=True)
app.blueprint(openapi_blueprint)
app.blueprint(swagger_blueprint)

schemas = {
    "int.min4": doc.Schema(
        title="int.min4",
        _type="integer",
        _format="int32",
        minimum=4,
        description="Minimum: 4",
    ),
    "days": days_of_week,
}
components = doc.Components(schemas=schemas)
app.config.OPENAPI_COMPONENTS = components

# ^^ the line above adds these to OAS v3's "components"
# the next two, which would ordinarily live in your blueprints's module,
# reuse these shared components.
int_min_4_ref = doc.Reference("#/components/schemas/int.min4")
dow_ref = doc.Reference("#/components/schemas/days")


@app.get("/simple/01/from/<start>/to/<end>/in/<hops:int>")
@doc.parameter(
    name="start", description="Start day", required=True, _in="path", schema=dow_ref
)
@doc.parameter(
    name="end", description="End day", required=True, _in="path", schema=dow_ref
)
@doc.parameter(
    name="hops",
    description="hops to use",
    required=True,
    _in="path",
    schema=int_min_4_ref,
)
def get_start_end_hops(request, start: str, end: str, hops: int):
    d = locals()
    del d["request"]  # not JSON serializable
    return sanic.response.json(d)


app.go_fast()

Deprecate route paths and/or parameters

A parameter can be marked as deprecated=True:

import sanic
import sanic.request
import sanic.response
from sanic_openapi3e import openapi_blueprint, swagger_blueprint, doc

app = sanic.Sanic(strict_slashes=True)
app.blueprint(openapi_blueprint)
app.blueprint(swagger_blueprint)

@app.get("/examples/test_parameter__deprecated/<an_id:int>")
@doc.parameter(
    name="an_id", description="An ID", required=True, _in="path", deprecated=True
)
@doc.summary("A path deprecated parameter")
@doc.description("The parameter should be marked as deprecated")
def param__deprecated(request, an_id: int):
    d = locals()
    del d["request"]  # not JSON serializable
    return sanic.response.json(d)

app.go_fast()

as can a whole route with @doc.deprecated():

import sanic
import sanic.request
import sanic.response
from sanic_openapi3e import openapi_blueprint, swagger_blueprint, doc

app = sanic.Sanic(strict_slashes=True)
app.blueprint(openapi_blueprint)
app.blueprint(swagger_blueprint)

@app.get("/examples/test_path__deprecated/<an_id:int>")
@doc.parameter(
    name="an_id",
    description="An ID",
    required=True,
    _in="path",
)
@doc.summary("A path with parameter examples")
@doc.description("This is marked as being deprecated")
@doc.deprecated()
def path__deprecated(request, an_id: int):
    d = locals()
    del d["request"]  # not JSON serializable
    return sanic.response.json(d)

app.go_fast()

Please note, that while many python decorators can be called without the (), this one really does require the () at the end of @doc.deprecated().

Exclude routes from appearing in the OpenAPI spec (and swagger)

Need to soft-launch an endpoint, or keep your swagger simple? Add a @doc.exclude and it won't be in the OpenAPI spec at all (unless you have set your app.config.SHOW_OPENAPI_EXCLUDED = True when a second spec at /openapi/spec.all.json will be created which will have all routes, including excluded.

import sanic
import sanic.request
import sanic.response
from sanic_openapi3e import openapi_blueprint, swagger_blueprint, doc

app = sanic.Sanic(strict_slashes=True)
app.blueprint(openapi_blueprint)
app.blueprint(swagger_blueprint)

@app.get("/test/alpha_release")
@doc.exclude
@doc.parameter(
    name="ids",
    description="Some IDs",
    required=True,
    choices=[1, 3, 5, 7, 11, 13],
    _in="query",
    schema=doc.Schema.Integers,
)
def test_some_ids(request: sanic.request.Request):
    query = request.query_string
    return sanic.response.json(query)

app.go_fast()

Predefined components for responses

There are predefimed components for common responses. You can overwrite and append these per route.

Control spec generation

app.config.API_VERSION = '1.0.0'
app.config.API_TITLE = 'An API'
app.config.API_DESCRIPTION = 'An API description'

To have a contact, set at least one of (but preferably all) app.config.API_CONTACT_NAME, app.config.API_CONTACT_URL or app.config.API_CONTACT_EMAIL.

To have a license, set app.config.API_LICENSE_NAME and optionally app.config.API_LICENSE_URL (all str, but the Swagger UI expects this to be a URL).

To have a termsOfService, set app.config.API_TERMS_OF_SERVICE_URL (a str, but the Swagger UI expects to use this as a URL).

Setting components, security and externalDocs requires you to

  • first create the relevant objects somewhere in your code (near to where you create the app),
  • set the appropriate app.config.OPENAPI_COMPONENTS, app.config.OPENAPI_SECURITY,
    app.config.OPENAPI_EXTERNAL_DOCS.

By default, the YAML spec is served on /openapi/spec.yml with the content-type of application/x-yaml but you can overwrite that by setting app.config.OPENAPI_YAML_CONTENTTYPE to something like text/plain. This lets you view your spec in a browser, and it still works with /swagger :-)

Configure how the operationId is made

The default code for creating the operationId creates unique (as they must be) strings from the following code:

def default_operation_id_fn(method: str, uri: str, route: sanic.router.Route) -> str:
    uri_for_operation_id: str = uri
    for parameter in route.parameters:
      uri_for_operation_id = re.sub(
            "<" + parameter.name + ".*?>", parameter.name, uri_for_operation_id
        )

    return "{}~~{}".format(method.upper(), uri_for_operation_id).replace("/", "~")

You can implement your own code (as long as the method signature matches this), and set that to be used in the app config: app.config.OPENAPI_OPERATION_ID_FN = my_operation_id_fn

For example, to make camelCase operationIds from the Routes' handlers (but be advised you that you must ensure your handlers' function names are globally unique, not "just" unique within the Blueprint), you could use the supplied camel_case_operation_id_fn:

def camel_case_operation_id_fn(method: str, uri: str, route: sanic.router.Route) -> str:

  if hasattr(route.handler, "__class__") and hasattr(route.handler, "handlers"):
    # These are `sanic.view.CompositeView`s
    _method_handler = route.handler.handlers.get(method.upper())
    if _method_handler:
      handler_name = method + "_" + _method_handler.__name__
    else:
      raise ValueError(f"No {method.upper()} handler found for {uri} handlers: {route.handler.handlers}")
  elif hasattr(route.handler, "__name__"):
    if len(route.methods) > 1:
      # This fn will be called many times, once per method, but we should prefix the handler_name with this
      # prefix to make the operationIds globally unique. If the route is only used by one method, use that
      # handler's name.
      handler_name = method + "_" + route.handler.__name__
    else:
      handler_name = route.handler.__name__
  else:
    raise NotImplementedError()
  return simple_snake2camel(handler_name)

and set it like app.config.OPENAPI_OPERATION_ID_FN = sanic_openapi3e.openapi.camel_case_operation_id_fn

Note that this does not change the parameter's names.

hide_openapi_self = app.config.get("HIDE_OPENAPI_SELF", True)
show_excluded = app.config.get("SHOW_OPENAPI_EXCLUDED", False)
show_unused_tags = app.config.get("SHOW_OPENAPI_UNUSED_TAGS", False)

In practice, you don't usually want to document the /swagger nor /openapi routes, but by setting app.config.HIDE_OPENAPI_SELF = False you can have them appear in the generated spec (and therefore swagger too).

Your @doc.exclude annotations are always respected, but if your config has app.config.SHOW_OPENAPI_EXCLUDED = True then a second spec at /openapi/spec.all.json is created. You generally won't want these to be on your production deployment, but you may want it for dev and test purposes.

app.config Purpose
app.config.get("HIDE_OPENAPI_SELF", True) If True, hide the /openapi and /swagger endpoints from the swagger UI. Usually set to True.
app.config.get("HIDE_SANIC_STATIC", True) If True, hide any sanic paths designed to serve up static files like images. Usually set to True.
app.config.get("SHOW_OPENAPI_EXCLUDED", False) See above.
app.config.get("SHOW_OPENAPI_UNUSED_TAGS", False) If True, your spec will include tags which have no visible routes. Usually set to False.
app.config.get("OPENAPI_OPERATION_ID_FN", default_operation_id_fn) See above.
app.config.get("OPENAPI_COMPONENTS") Allows you to build your own Components for the spec.
app.config.get("API_CONTACT_NAME") If set, creates a Contact with a .name. Combines with the other Contact attributes.
app.config.get("API_CONTACT_URL") If set, creates a Contact with a .url. Combines with the other Contact attributes.
app.config.get("API_CONTACT_EMAIL") If set, creates a Contact with a .email. Combines with the other Contact attributes.
app.config.get("API_LICENSE_NAME") If set, creates a License with a .name. Combines with the other License attributes.
app.config.get("API_LICENSE_URL") If set, creates a License with a .url. Combines with the other License attributes.
app.config.get("API_TITLE", "API") Set a better .name than "API" for your API!
app.config.get("API_DESCRIPTION", "Description") Set a better .description than "Description" for your API!
app.config.get("API_TERMS_OF_SERVICE_URL") If set, adds a termsOfService to your .info
app.config.get("API_VERSION", "v1.0.0") Set a better .version for your API!
app.config.get("OPENAPI_SERVERS") Allows you to build your own Servers for the spec.
app.config.get("OPENAPI_SECURITY") Allows you to build your own Security for the spec.
app.config.get("OPENAPI_EXTERNAL_DOCS") If set, adds an ExternalDocumentation to your spec
app.config.get("OPENAPI_YAML_CONTENTTYPE", default_yaml_content_type) See your /openapi/spec.yml in a browser by setting this to text/plain

OAS Object maturity

sanic-openapi3e is being used in production, and all of the spec is implemented. Most of the spec is known to be in production use, but some of the spec's objects are marked here as "beta" due to no known production use.

Class sanic-openapi3e maturity notes
Components production/stable
Contact production/stable
Example production/stable
ExternalDocumentation production/stable
Info production/stable
License production/stable
Link beta no known usage
MediaType production/stable
OAuthFlow production/stable
OAuthFlows production/stable
OpenAPIv3 production/stable
Operation production/stable
Parameter production/stable
PathItem production/stable
Paths production/stable
Reference production/stable
RequestBody production/stable
Responses production/stable
Schema production/stable
SecurityScheme production/stable
Server production/stable
Tag production/stable
Response production/stable no known use of the "default" value
SecurityRequirement beta only the [] empty-list override known
Discriminator beta no known usage
Encoding stable no known usage
Header production/stable
ServerVariable beta no known usage
XML beta no known usage
Callback none not implemented

sanic-openapi3e is built to create OpenAPI 3.0.2 specs.

Changelog

  • v0.9.10
    • Adds CORS Access-Control-Allow-Origin: * headers to the OpenAPI /openapi/spec* endpoints.
  • v0.9.9
    • Fixes type validation bug for oneOf.
  • v0.9.8
    • Further extends complex Schema objects by extending how allOf, oneOf, anyOf, and not are handled.
  • v0.9.7
    • Extends the ability to define complex Schema objects.
  • v0.9.6
    • Fixes a bug where None values in examples would cause a runtime error.
  • v0.9.5
    • Improves how the .components.responses are rendered.
    • Fixes issues with the "static"/"frozen" predefined Schema objects.
  • v0.9.4
    • Adds a @doc.responses() to give an easier way of documenting a route.
  • v0.9.3
    • Adds a @doc.security() to override security requirements on a route.
    • Removes entries with false values from the spec if false is the default value. This makes the specs smaller and are more idiomatic.
  • v0.9.2
    • Fixes an issue of rendering SecurityRequirement when there were no entries in the list.

Metadata

Release files for sanic-openapi3e 0.9.10

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sanic-openapi3e 0.9.10
File Size Uploaded
sanic-openapi3e-0.9.10.tar.gz 2.4 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for sanic-openapi3e 0.9.10
File Interpreter ABI Platform
sanic_openapi3e-0.9.10-py3-none-any.whl Python 3 none any Details

Total release size: 4.7 MB

Release files / sanic-openapi3e-0.9.10.tar.gz

Download URL sanic-openapi3e-0.9.10.tar.gz
Size 2.4 MB
Tags Source
SHA-256 checksum
How to use checksums
93b7363f50c9fa6ca206d78b04782abc1435bf4b174e040d7f061b965557e51d
BLAKE2b-256 checksum
How to use checksums
4121311dc3d20972bc6b7fbb5705b3f786dfc6118b783b79c92d5d4fba2a68c7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.3.0 pkginfo/1.7.0 requests/2.25.1 setuptools/49.2.1 requests-toolbelt/0.9.1 tqdm/4.57.0 CPython/3.9.1

Release files / sanic_openapi3e-0.9.10-py3-none-any.whl

Download URL sanic_openapi3e-0.9.10-py3-none-any.whl
Size 2.4 MB
Tags Python 3
SHA-256 checksum
How to use checksums
c2c8b8d8536bc33a320c0d1e063c800f08399b9454b7c6e07114d9852ba766e1
BLAKE2b-256 checksum
How to use checksums
d1220d2a12d92e4ceba5fd2725985a214cc4b4142d1ad9afce6db3057d56dffd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.3.0 pkginfo/1.7.0 requests/2.25.1 setuptools/49.2.1 requests-toolbelt/0.9.1 tqdm/4.57.0 CPython/3.9.1

Release history Release notifications | RSS feed

This release

0.9.10 This release

2 release files

0.9.9

2 release files

0.9.8

2 release files

0.9.7

2 release files

0.9.6

2 release files

0.9.5

2 release files

0.9.4

2 release files

0.9.3

2 release files

0.9.2

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.6

2 release files

0.8.5

2 release files

0.8.4

2 release files

0.8.3

2 release files

0.8.2

2 release files

0.8.0

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.5

2 release files

0.6.4

2 release files

0.6.3

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.9

2 release files

0.5.8

2 release files

0.5.7

2 release files

0.5.6

2 release files

0.5.5

2 release files

0.5.4

2 release files

0.5.3

2 release files

0.5.2

1 release file

0.5.1

1 release file

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