# Version 22.3

.. toc::

## Introduction

This is the first release of the version 22 [release cycle](../../org/policies.md#release-schedule). All of the standard SCO libraries are now entering the same release cycle and will follow the same versioning pattern. Those packages are:

- [`sanic-routing`](https://github.com/sanic-org/sanic-routing)
- [`sanic-testing`](https://github.com/sanic-org/sanic-testing)
- [`sanic-ext`](https://github.com/sanic-org/sanic-ext)

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

### Application multi-serve

The Sanic server now has an API to allow you to run multiple applications side-by-side in the same process. This is done by calling `app.prepare(...)` on one or more application instances, one or many times. Each time it should be bound to a unique host/port combination. Then, you begin serving the applications by calling `Sanic.serve()`.

```python
app = Sanic("One")
app2 = Sanic("Two")

app.prepare(port=9999)
app.prepare(port=9998)
app.prepare(port=9997)
app2.prepare(port=8888)
app2.prepare(port=8887)

Sanic.serve()
```

In the above snippet, there are two applications that will be run concurrently and bound to multiple ports. This feature is *not* supported in the CLI.

This pattern is meant to be an alternative to running `app.run(...)`. It should be noted that `app.run` is now just a shorthand for the above pattern and is still fully supported.

### 👶 *BETA FEATURE* - New path parameter type: file extensions

A very common pattern is to create a route that dynamically generates a file. The endpoint is meant to match on a file with an extension. There is a new path parameter to match files: `<foo:ext>`.

```python
@app.get("/path/to/<filename:ext>")
async def handler(request, filename, ext):
    ...
```

This will catch any pattern that ends with a file extension. You may, however want to expand this by specifying which extensions, and also by using other path parameter types for the file name. 

For example, if you want to catch a `.jpg` file that is only numbers:

```python
@app.get("/path/to/<filename=int:ext=jpg>")
async def handler(request, filename, ext):
    ...
```

Some potential examples:

| definition                        | example     | filename    | extension  |
| --------------------------------- | ----------- | ----------- | ---------- |
| \<file:ext>                        | page.txt    | `"page"`    | `"txt"`    |
| \<file:ext=jpg>                    | cat.jpg     | `"cat"`     | `"jpg"`    |
| \<file:ext=jpg\|png\|gif\|svg>     | cat.jpg     | `"cat"`     | `"jpg"`    |
| <file=int:ext>                    | 123.txt     | `123`       | `"txt"`    |
| <file=int:ext=jpg\|png\|gif\|svg> | 123.svg     | `123`       | `"svg"`    |
| <file=float:ext=tar.gz>           | 3.14.tar.gz | `3.14`      | `"tar.gz"` | 

### 🚨 *BREAKING CHANGE* - Path parameter matching of non-empty strings

A dynamic path parameter will only match on a non-empty string. 

Previously a route with a dynamic string parameter (`/<foo>` or `/<foo:str>`) would match on any string, including empty strings. It will now only match a non-empty string. To retain the old behavior, you should use the new parameter type: `/<foo:strorempty>`.

```python
@app.get("/path/to/<foo:strorempty>")
async def handler(request, foo)
    ...
```

### 🚨 *BREAKING CHANGE* - `sanic.worker.GunicornWorker` has been removed

Departing from our normal deprecation policy, the `GunicornWorker` was removed as a part of the process of upgrading the Sanic server to include multi-serve. This decision was made largely in part because even while it existed it was not an optimal strategy for deploying Sanic.

If you want to deploy Sanic using `gunicorn`, then you are advised to do it using [the strategy implemented by `uvicorn`](https://www.uvicorn.org/#running-with-gunicorn). This will effectively run Sanic as an ASGI application through `uvicorn`. You can upgrade to this pattern by installing `uvicorn`:

```
pip install uvicorn
```

Then, you should be able to run it with a pattern like this:

```
gunicorn path.to.sanic:app -k uvicorn.workers.UvicornWorker
```

### Authorization header parsing

The `Authorization` header has been partially parseable for some time now. You have been able to use `request.token` to gain access to a header that was in one of the following two forms:

```
Authorization: Token <SOME TOKEN HERE>
Authorization: Bearer <SOME TOKEN HERE>
```

Sanic can now parse more credential types like `BASIC`:

```
Authorization: Basic Z2lsLWJhdGVzOnBhc3N3b3JkMTIz
```

This can be accessed now as `request.credentials`:

```python
print(request.credentials)
# Credentials(auth_type='Basic', token='Z2lsLWJhdGVzOnBhc3N3b3JkMTIz', _username='gil-bates', _password='password123')
```

### CLI arguments optionally injected into application factory

Sanic will now attempt to inject the parsed CLI arguments into your factory if you are using one.

```python
def create_app(args):
    app = Sanic("MyApp")
    print(args)
    return app
```
```
$sanic p:create_app --factory
Namespace(module='p:create_app', factory=True, simple=False, host='127.0.0.1', port=8000, unix='', cert=None, key=None, tls=None, tlshost=False, workers=1, fast=False, access_log=False, debug=False, auto_reload=False, path=None, dev=False, motd=True, verbosity=None, noisy_exceptions=False)
```

If you are running the CLI with `--factory`, you also have the option of passing arbitrary arguments to the command, which will be injected into the argument `Namespace`.

```
sanic p:create_app --factory --foo=bar
Namespace(module='p:create_app', factory=True, simple=False, host='127.0.0.1', port=8000, unix='', cert=None, key=None, tls=None, tlshost=False, workers=1, fast=False, access_log=False, debug=False, auto_reload=False, path=None, dev=False, motd=True, verbosity=None, noisy_exceptions=False, foo='bar')
```

### New reloader process listener events

When running Sanic server with auto-reload, there are two new events that trigger a listener *only* on the reloader process:

- `reload_process_start`
- `reload_process_stop`

These are only triggered if the reloader is running.

```python
@app.reload_process_start
async def reload_start(*_):
    print(">>>>>> reload_start <<<<<<")

@app.reload_process_stop
async def reload_stop(*_):
    print(">>>>>> reload_stop <<<<<<")
```

### The event loop is no longer a required argument of a listener

You can leave out the `loop` argument of a listener. Both of these examples work as expected:

```python
@app.before_server_start
async def without(app):
    ...

@app.before_server_start
async def with(app, loop):
    ...
```

### Removal - Debug mode does not automatically start the reloader

When running with `--debug` or `debug=True`, the Sanic server will not automatically start the auto-reloader. This feature of doing both on debug was deprecated in v21 and removed in this release. If you would like to have *both* debug mode and auto-reload, you can use `--dev` or `dev=True`.

**dev = debug mode + auto reloader**

### Deprecation - Loading of lower case environment variables

Sanic loads prefixed environment variables as configuration values. It has not distinguished between uppercase and lowercase as long as the prefix matches. However, it has always been the convention that the keys should be uppercase. This is deprecated and you will receive a warning if the value is not uppercase. In v22.9 only uppercase and prefixed keys will be loaded.

## News

### Packt publishes new book on Sanic web development

.. column::

    There is a new book on **Python Web Development with Sanic** by [@ahopkins](https://github.com/ahopkins). The book is endorsed by the SCO and part of the proceeds of all sales go directly to the SCO for further development of Sanic.

    You can learn more at [sanicbook.com](https://sanicbook.com/)

.. column::

    ![Python Web Development with Sanic](https://sanicbook.com/images/SanicCoverFinal.png)

## Thank you

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

[@aericson](https://github.com/aericson)
[@ahankinson](https://github.com/ahankinson)
[@ahopkins](https://github.com/ahopkins)
[@ariebovenberg](https://github.com/ariebovenberg)
[@ashleysommer](https://github.com/ashleysommer)
[@Bluenix2](https://github.com/Bluenix2)
[@ChihweiLHBird](https://github.com/ChihweiLHBird)
[@dotlambda](https://github.com/dotlambda)
[@eric-spitler](https://github.com/eric-spitler)
[@howzitcdf](https://github.com/howzitcdf)
[@jonra1993](https://github.com/jonra1993)
[@prryplatypus](https://github.com/prryplatypus)
[@raphaelauv](https://github.com/raphaelauv)
[@SaidBySolo](https://github.com/SaidBySolo)
[@SerGeRybakov](https://github.com/SerGeRybakov)
[@Tronic](https://github.com/Tronic)

---

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