# Version 21.12 (LTS) .. toc:: ## Introduction This is the final release of the version 21 [release cycle](../../org/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 () -> 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/).