Version 21.12 (LTS)

Table of Contents

Introduction#

This is the final release of the version 21 release cycle. Version 21 will now enter long-term support and will be supported for two years until December 2023.

What to know#

More details in the Changelog. Notable new or breaking features, and what to upgrade...

Strict application and blueprint names#

In v21.6 application and blueprint names were required to conform to a new set of restrictions. That change is now being enforced at startup time.

Names must:

  1. Only use alphanumeric characters (a-zA-Z0-9)
  2. May contain a hyphen (-) or an underscore (_)
  3. Must begin with a letter or underscore (a-zA-Z_)

Strict application and blueprint properties#

The old leniency to allow directly setting properties of a Sanic or Blueprint object was deprecated and no longer allowed. You must use the ctx object.

app = Sanic("MyApp")
app.ctx.db = Database()

Removals#

The following deprecated features no longer exist:

  • sanic.exceptions.abort
  • sanic.views.CompositionView
  • sanic.response.StreamingHTTPResponse

Upgrade your streaming responses (if not already)#

The sanic.response.stream response method has been deprecated and will be removed in v22.6. If you are sill using an old school streaming response, please upgrade it.

OLD - Deprecated

async def sample_streaming_fn(response):
    await response.write("foo,")
    await response.write("bar")

@app.route("/")
async def test(request: Request):
    return stream(sample_streaming_fn, content_type="text/csv")

Current

async def sample_streaming_fn(response):
    await response.write("foo,")
    await response.write("bar")

@app.route("/")
async def test(request: Request):
    response = await request.respond(content_type="text/csv")
    await response.send("foo,")
    await response.send("bar")

CLI overhaul and MOTD (Message of the Day)#

The Sanic CLI has received a fairly extensive upgrade. It adds a bunch of new features to make it on par with app.run(). It also includes a new MOTD display to provide quick, at-a-glance highlights about your running environment. The MOTD is TTY-aware, and therefore will be less verbose in server logs. It is mainly intended as a convenience during application development.

$ sanic --help
usage: sanic [-h] [--version] [--factory] [-s] [-H HOST] [-p PORT] [-u UNIX] [--cert CERT] [--key KEY] [--tls DIR] [--tls-strict-host]
             [-w WORKERS | --fast] [--access-logs | --no-access-logs] [--debug] [-d] [-r] [-R PATH] [--motd | --no-motd] [-v]
             [--noisy-exceptions | --no-noisy-exceptions]
             module

   ▄███ █████ ██      ▄█▄      ██       █   █   ▄██████████
  ██                 █   █     █ ██     █   █  ██
   ▀███████ ███▄    ▀     █    █   ██   ▄   █  ██
               ██  █████████   █     ██ █   █  ▄▄
  ████ ████████▀  █         █  █       ██   █   ▀██ ███████

 To start running a Sanic application, provide a path to the module, where
 app is a Sanic() instance:

     $ sanic path.to.server:app

 Or, a path to a callable that returns a Sanic() instance:

     $ sanic path.to.factory:create_app --factory

 Or, a path to a directory to run as a simple HTTP server:

     $ sanic ./path/to/static --simple

Required
========
  Positional:
    module                         Path to your Sanic app. Example: path.to.server:app
                                   If running a Simple Server, path to directory to serve. Example: ./

Optional
========
  General:
    -h, --help                     show this help message and exit
    --version                      show program's version number and exit

  Application:
    --factory                      Treat app as an application factory, i.e. a () -> <Sanic app> callable
    -s, --simple                   Run Sanic as a Simple Server, and serve the contents of a directory
                                   (module arg should be a path)

  Socket binding:
    -H HOST, --host HOST           Host address [default 127.0.0.1]
    -p PORT, --port PORT           Port to serve on [default 8000]
    -u UNIX, --unix UNIX           location of unix socket

  TLS certificate:
    --cert CERT                    Location of fullchain.pem, bundle.crt or equivalent
    --key KEY                      Location of privkey.pem or equivalent .key file
    --tls DIR                      TLS certificate folder with fullchain.pem and privkey.pem
                                   May be specified multiple times to choose multiple certificates
    --tls-strict-host              Only allow clients that send an SNI matching server certs

  Worker:
    -w WORKERS, --workers WORKERS  Number of worker processes [default 1]
    --fast                         Set the number of workers to max allowed
    --access-logs                  Display access logs
    --no-access-logs               No display access logs

  Development:
    --debug                        Run the server in debug mode
    -d, --dev                      Currently is an alias for --debug. But starting in v22.3, 
                                   --debug will no longer automatically trigger auto_restart. 
                                   However, --dev will continue, effectively making it the 
                                   same as debug + auto_reload.
    -r, --reload, --auto-reload    Watch source directory for file changes and reload on changes
    -R PATH, --reload-dir PATH     Extra directories to watch and reload on changes

  Output:
    --motd                         Show the startup display
    --no-motd                      No show the startup display
    -v, --verbosity                Control logging noise, eg. -vv or --verbosity=2 [default 0]
    --noisy-exceptions             Output stack traces for all exceptions
    --no-noisy-exceptions          No output stack traces for all exceptions

Server running modes and changes coming to debug#

There are now two running modes: DEV and PRODUCTION. By default, Sanic server will run under PRODUCTION mode. This is intended for deployments.

Currently, DEV mode will operate very similarly to how debug=True does in older Sanic versions. However, in v22.3. debug=True will no longer enable auto-reload. If you would like to have debugging and auto-reload, you should enable DEV mode.

DEVELOPMENT

$ sanic server:app --dev
app.run(debug=True, auto_reload=True)

PRODUCTION

$ sanic server:app
app.run()

Beginning in v22.3, PRODUCTION mode will no longer enable access logs by default.

A summary of the changes are as follows:

Flag Mode Tracebacks Logging Access logs Reload Max workers
--debug DEBUG yes DEBUG yes ^1
PROD no INFO ^2 ^3
--dev DEBUG yes DEBUG yes yes
--fast yes
  • ^1 --debug to deprecate auto-reloading and remove in 22.3
  • ^2 After 22.3 this moves to WARNING
  • ^3 After 22.3: no

Max allowed workers#

You can easily spin up the maximum number of allowed workers using --fast.

$ sanic server:app --fast
app.run(fast=True)

First-class Sanic Extensions support#

Sanic Extensions provides a number of additional features specifically intended for API developers. You can now easily implement all of the functionality it has to offer without additional setup as long as the package is in the environment. These features include:

  • Auto create HEAD, OPTIONS, and TRACE endpoints
  • CORS protection
  • Predefined, endpoint-specific response serializers
  • Dependency injection into route handlers
  • OpenAPI documentation with Redoc and/or Swagger
  • Request query arguments and body input validation

The preferred method is to install it along with Sanic, but you can also install the packages on their own.

$ pip install sanic[ext]
$ pip install sanic sanic-ext

After that, no additional configuration is required. Sanic Extensions will be attached to your application and provide all of the additional functionality with no further configuration.

If you want to change how it works, or provide additional configuration, you can change Sanic extensions using app.extend. The following examples are equivalent. The Config object is to provide helpful type annotations for IDE development.

# This is optional, not required
app = Sanic("MyApp")
app.extend(config={"oas_url_prefix": "/apidocs"})
# This is optional, not required
app = Sanic("MyApp")
app.config.OAS_URL_PREFIX = "/apidocs"
# This is optional, not required
from sanic_ext import Config

app = Sanic("MyApp")
app.extend(config=Config(oas_url_prefix="/apidocs"))

Contextual exceptions#

In v21.9 we added default messages to exceptions that simplify the ability to consistently raise exceptions throughout your application.

class TeapotError(SanicException):
    status_code = 418
    message = "Sorry, I cannot brew coffee"

raise TeapotError

But this lacked two things:

  1. A dynamic and predictable message format
  2. The ability to add additional context to an error message (more on this in a moment)

The current release allows any Sanic exception to have additional information to when raised to provide context when writing an error message:

class TeapotError(SanicException):
    status_code = 418

    @property
    def message(self):
        return f"Sorry {self.extra['name']}, I cannot make you coffee"

raise TeapotError(extra={"name": "Adam"})

The new feature allows the passing of extra meta to the exception instance. This extra info object will be suppressed when in PRODUCTION mode, but displayed in DEVELOPMENT mode.

PRODUCTION

image

DEVELOPMENT

image

Getting back to item 2 from above: The ability to add additional context to an error message

This is particularly useful when creating microservices or an API that you intend to pass error messages back in JSON format. In this use case, we want to have some context around the exception beyond just a parseable error message to return details to the client.

raise TeapotError(context={"foo": "bar"})

This is information that we want to always be passed in the error (when it is available). Here is what it should look like:

PRODUCTION

{
  "description": "I'm a teapot",
  "status": 418,
  "message": "Sorry Adam, I cannot make you coffee",
  "context": {
    "foo": "bar"
  }
}

DEVELOPMENT

{
  "description": "I'm a teapot",
  "status": 418,
  "message": "Sorry Adam, I cannot make you coffee",
  "context": {
    "foo": "bar"
  },
  "extra": {
    "name": "Adam",
    "more": "lines",
    "complex": {
      "one": "two"
    }
  },
  "path": "/",
  "args": {},
  "exceptions": [
    {
      "type": "TeapotError",
      "exception": "Sorry Adam, I cannot make you coffee",
      "frames": [
        {
          "file": "handle_request",
          "line": 83,
          "name": "handle_request",
          "src": ""
        },
        {
          "file": "/tmp/p.py",
          "line": 17,
          "name": "handler",
          "src": "raise TeapotError("
        }
      ]
    }
  ]
}

Background task management#

When using the app.add_task method to create a background task, there now is the option to pass an optional name keyword argument that allows it to be fetched, or cancelled.

app.add_task(dummy, name="dummy_task")
task = app.get_task("dummy_task")

app.cancel_task("dummy_task")

Route context kwargs in definitions#

When a route is defined, you can add any number of keyword arguments with a ctx_ prefix. These values will be injected into the route ctx object.

@app.get("/1", ctx_label="something")
async def handler1(request):
    ...

@app.get("/2", ctx_label="something")
async def handler2(request):
    ...

@app.get("/99")
async def handler99(request):
    ...

@app.on_request
async def do_something(request):
    if request.route.ctx.label == "something":
        ...

Blueprints can be registered at any time#

In previous versions of Sanic, there was a strict ordering of when a Blueprint could be attached to an application. If you ran app.blueprint(bp) before attaching all objects to the Blueprint instance, they would be missed.

Now, you can attach a Blueprint at anytime and everything attached to it will be included at startup.

Noisy exceptions (force all exceptions to logs)#

There is a new NOISY_EXCEPTIONS config value. When it is False (which is the default), Sanic will respect the quiet property of any SanicException. This means that an exception with quiet=True will not be displayed to the log output.

However, when setting NOISY_EXCEPTIONS=True, all exceptions will be logged regardless of the quiet value.

This can be helpful when debugging.

app.config.NOISY_EXCEPTIONS = True

Signal events as Enum#

There is an Enum with all of the built-in signal values for convenience.

from sanic.signals import Event

@app.signal(Event.HTTP_LIFECYCLE_BEGIN)
async def connection_opened(conn_info):
    ...

Custom type casting of environment variables#

By default, Sanic will convert an int, float, or a bool value when applying environment variables to the config instance. You can extend this with your own converter:

app = Sanic(..., config=Config(converters=[UUID]))

Disable uvloop by configuration value#

The usage of uvloop can be controlled by configuration value:

app.config.USE_UVLOOP = False

Run Sanic server with multiple TLS certificates#

Sanic can be run with multiple TLS certificates:

app.run(
    ssl=[
        "/etc/letsencrypt/live/example.com/",
        "/etc/letsencrypt/live/mysite.example/",
    ]
)

News#

Coming Soon: Python Web Development with Sanic#

A book about Sanic is coming soon by one of the core developers, @ahopkins.

Learn more at sanicbook.com.

Get equipped with the practical knowledge of working with Sanic to increase the performance and scalability of your web applications. While doing that, we will level-up your development skills as you learn to customize your application to meet the changing business needs without having to significantly over-engineer the app.

A portion of book proceeds goes into the Sanic Community Organization to help fund the development and operation of Sanic. So, buying the book is another way you can support Sanic.

Dark mode for the docs#

If you have not already noticed, this Sanic website is now available in a native dark mode. You can toggle the theme at the top right of the page.

Thank you#

Thank you to everyone that participated in this release: :clap:

@adarsharegmi @ahopkins @ashleysommer @ChihweiLHBird @cnicodeme @kianmeng @meysam81 @nuxion @prryplatypus @realDragonium @SaidBySolo @sjsadowski @Tronic @Varriount @vltr @whos4n3

And, a special thank you to @miss85246 and @ZinkLu for their tremendous work keeping the documentation synced and translated into Chinese.


If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: financial contributions.