---
title: Version 21.12 (LTS)
---

# Version 21.12 (LTS)

.. toc::

## Introduction

This is the final release of the version 21 [release cycle](../../organization/policies.md#release-schedule). 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](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade...

### Strict application and blueprint names

In [v21.6](./v21.6.md#stricter-application-and-blueprint-names-and-deprecation) 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.

```python
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**

```python
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**

```python
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
```

```python
app.run(debug=True, auto_reload=True)
```

**PRODUCTION**

```
$ sanic server:app
```

```python
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
```

```python
app.run(fast=True)
```

### First-class Sanic Extensions support

[Sanic Extensions](../../plugins/sanic-ext/getting-started.md) 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.

.. column::

    ```
    $ pip install sanic[ext]
    ```

.. column::

    ```
    $ 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.

.. column::

    ```python
    # This is optional, not required
    app = Sanic("MyApp")
    app.extend(config={"oas_url_prefix": "/apidocs"})
    ```

.. column::

    ```python
    # This is optional, not required
    app = Sanic("MyApp")
    app.config.OAS_URL_PREFIX = "/apidocs"
    ```

.. column::

    ```python
    # This is optional, not required
    from sanic_ext import Config

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

.. column::


  ### Contextual exceptions

  In [v21.9](./v21.9.md#default-exception-messages) we added default messages to exceptions that simplify the ability to consistently raise exceptions throughout your application.

  ```python
  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:

  ```python
  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.

.. column::
    
    **PRODUCTION**

    ![image](https://user-images.githubusercontent.com/166269/139014161-cda67cd1-843f-4ad2-9fa1-acb94a59fc4d.png)

.. column::

    **DEVELOPMENT**

    ![image](https://user-images.githubusercontent.com/166269/139014121-0596b084-b3c5-4adb-994e-31ba6eba6dad.png)

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.

```python
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:

.. column::

    **PRODUCTION**

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

.. column::

    **DEVELOPMENT**

    ```json
    {
      "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.

```python
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.

```python
@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.

```python
app.config.NOISY_EXCEPTIONS = True
```

### Signal events as `Enum`

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

```python
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:

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

### Disable `uvloop` by configuration value

The usage of `uvloop` can be controlled by configuration value:

```python
app.config.USE_UVLOOP = False
```

### Run Sanic server with multiple TLS certificates

Sanic can be run with multiple TLS certificates:

```python
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](https://github.com/ahopkins).

Learn more at [sanicbook.com](https://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](https://github.com/adarsharegmi)
[@ahopkins](https://github.com/ahopkins)
[@ashleysommer](https://github.com/ashleysommer)
[@ChihweiLHBird](https://github.com/ChihweiLHBird)
[@cnicodeme](https://github.com/cnicodeme)
[@kianmeng](https://github.com/kianmeng)
[@meysam81](https://github.com/meysam81)
[@nuxion](https://github.com/nuxion)
[@prryplatypus](https://github.com/prryplatypus)
[@realDragonium](https://github.com/realDragonium)
[@SaidBySolo](https://github.com/SaidBySolo)
[@sjsadowski](https://github.com/sjsadowski)
[@Tronic](https://github.com/tronic)
[@Varriount](https://github.com/Varriount)
[@vltr](https://github.com/vltr)
[@whos4n3](https://github.com/whos4n3)

And, a special thank you to [@miss85246](https://github.com/miss85246) and [@ZinkLu](https://github.com/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](https://opencollective.com/sanic-org/).
