Technical Documentation: sanic-org/sanic
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Guide Content En Index (`guide/content/en/index.md`)
title: The lightning-fast asynchronous Python web framework
layout: home
features:
- title: Simple and lightweight
details: Intuitive API with smart defaults and no bloat allows you to get straight to work building your app. - title: Unopinionated and flexible
details: Build the way you want to build without letting your tooling constrain you. - title: Performant and scalable
details: Built from the ground up with speed and scalability as a main concern. It is ready to power web applications big and small. - title: Production ready
details: Out of the box, it comes bundled with a web server ready to power your web applications. - title: Trusted by millions
details: Sanic is one of the overall most popular frameworks on PyPI, and the top async enabled framework - title: Community driven
details: The project is maintained and run by the community for the community.
⚡ The lightning-fast asynchronous Python web framework
.. attrs::
:class: columns is-multiline mt-6
.. attrs::
:class: column is-4
#### Simple and lightweight
Intuitive API with smart defaults and no bloat allows you to get straight to work building your app.
.. attrs::
:class: column is-4
#### Unopinionated and flexible
Build the way you want to build without letting your tooling constrain you.
.. attrs::
:class: column is-4
#### Performant and scalable
Built from the ground up with speed and scalability as a main concern. It is ready to power web applications big and small.
.. attrs::
:class: column is-4
#### Production ready
Out of the box, it comes bundled with a web server ready to power your web applications.
.. attrs::
:class: column is-4
#### Trusted by millions
Sanic is one of the overall most popular frameworks on PyPI, and the top async enabled framework
.. attrs::
:class: column is-4
#### Community driven
The project is maintained and run by the community for the community... attrs::
:class: is-size-3 mt-6
**With the features and tools you'd expect.**.. attrs::
:class: is-size-3 ml-6
**And some {span:has-text-primary:you wouldn't believe}.**.. tab:: Production-grade
After installing, Sanic has all the tools you need for a scalable, production-grade server—out of the box!
Including [full TLS support](/en/guide/how-to/tls.md).
```python
from sanic import Sanic
from sanic.response import text
app = Sanic("MyHelloWorldApp")
@app.get("/")
async def hello_world(request):
return text("Hello, world.")
```
```sh
sanic path.to.server:app
[2023-01-31 12:34:56 +0000] [999996] [INFO] Sanic v22.12.0
[2023-01-31 12:34:56 +0000] [999996] [INFO] Goin' Fast @ http://127.0.0.1:8000
[2023-01-31 12:34:56 +0000] [999996] [INFO] mode: production, single worker
[2023-01-31 12:34:56 +0000] [999996] [INFO] server: sanic, HTTP/1.1
[2023-01-31 12:34:56 +0000] [999996] [INFO] python: 3.10.9
[2023-01-31 12:34:56 +0000] [999996] [INFO] platform: SomeOS-9.8.7
[2023-01-31 12:34:56 +0000] [999996] [INFO] packages: sanic-routing==22.8.0
[2023-01-31 12:34:56 +0000] [999997] [INFO] Starting worker [999997]
```.. tab:: TLS server
Running Sanic with TLS enabled is as simple as passing it the file paths...
```sh
sanic path.to.server:app --cert=/path/to/bundle.crt --key=/path/to/privkey.pem
```
... or the a directory containing `fullchain.pem` and `privkey.pem`
```sh
sanic path.to.server:app --tls=/path/to/certs
```
**Even better**, while you are developing, let Sanic handle setting up local TLS certificates so you can access your site over TLS at [https://localhost:8443](https://localhost:8443)
```sh
sanic path.to.server:app --dev --auto-tls
```.. tab:: Websockets
Up and running with websockets in no time using the [websockets](https://websockets.readthedocs.io) package.
```python
from sanic import Request, Websocket
@app.websocket("/feed")
async def feed(request: Request, ws: Websocket):
async for msg in ws:
await ws.send(msg)
```.. tab:: Static files
Serving static files is of course intuitive and easy. Just name an endpoint and either a file or directory that should be served.
```python
app.static("/", "/path/to/index.html")
app.static("/uploads/", "/path/to/uploads/")
```
Moreover, serving a directory has two additional features: automatically serving an index, and automatically serving a file browser.
Sanic can automatically serve `index.html` (or any other named file) as an index page in a directory or its subdirectories.
```python
app.static(
"/uploads/",
"/path/to/uploads/",
index="index.html"
)
```
And/or, setup Sanic to display a file browser.
```python
app.static(
"/uploads/",
"/path/to/uploads/",
directory_view=True
)
```.. tab:: Lifecycle
Beginning or ending a route with functionality is as simple as adding a decorator.
```python
@app.on_request
async def add_key(request):
request.ctx.foo = "bar"
@app.on_response
async def custom_banner(request, response):
response.headers["X-Foo"] = request.ctx.foo
```
Same with server events.
```python
@app.before_server_start
async def setup_db(app):
app.ctx.db_pool = await db_setup()
@app.after_server_stop
async def setup_db(app):
await app.ctx.db_pool.shutdown()
```
But, Sanic also allows you to tie into a bunch of built-in events (called signals), or create and dispatch your own.
```python
@app.signal("http.lifecycle.complete") # built-in
async def my_signal_handler(conn_info):
print("Connection has been closed")
@app.signal("something.happened.ohmy") # custom
async def my_signal_handler():
print("something happened")
await app.dispatch("something.happened.ohmy")
```.. tab:: Smart error handling
Raising errors will intuitively result in proper HTTP errors:
```python
raise sanic.exceptions.NotFound # Automatically responds with HTTP 404
```
Or, make your own:
```python
from sanic.exceptions import SanicException
class TeapotError(SanicException):
status_code = 418
message = "Sorry, I cannot brew coffee"
raise TeapotError
```
And, when an error does happen, Sanic's beautiful DEV mode error page will help you drill down to the bug quickly.
Regardless, Sanic comes with an algorithm that attempts to respond with HTML, JSON, or text-based errors as appropriate. Don't worry, it is super easy to setup and customize your error handling to your exact needs... tab:: App Inspector
Check in on your live, running applications (whether local or remote).
```/* Detailed source-code truncated for AI context efficiency. */
And, issue commands like `reload`, `shutdown`, `scale`...
```sh
sanic inspect scale 4
```
... or even create your own!
```sh
sanic inspect migrations
```
.. tab:: Extendable
In addition to the tools that Sanic comes with, the officially supported [Sanic Extensions](./plugins/sanic-ext/getting-started.md) provides lots of extra goodies to make development easier.
- **CORS** protection
- Template rendering with **Jinja**
- **Dependency injection** into route handlers
- OpenAPI documentation with **Redoc** and/or **Swagger**
- Predefined, endpoint-specific response **serializers**
- Request query arguments and body input **validation**
- **Auto create** HEAD, OPTIONS, and TRACE endpoints
- Live **health monitor**
.. tab:: Developer Experience
Sanic is **built for building**.
From the moment it is installed, Sanic includes helpful tools to help the developer get their job done.
- **One server** - Develop locally in DEV mode on the same server that will run your PRODUCTION application
- **Auto reload** - Reload running applications every time you save a Python file, but also auto-reload **on any arbitrary directory** like HTML template directories
- **Debugging tools** - Super helpful (and beautiful) [error pages](/en/guide/best-practices/exceptions) that help you traverse the trace stack easily
- **Auto TLS** - Running a localhost website with `https` can be difficult, [Sanic makes it easy](/en/guide/how-to/tls.md)
- **Streamlined testing** - Built-in testing capabilities, making it easier for developers to create and run tests, ensuring the quality and reliability of their services
- **Modern Python** - Thoughtful use of type hints to help the developer IDE experience
---
### Guide Content En Guide Getting Started (`guide/content/en/guide/getting-started.md`)
# Getting Started
Before we begin, make sure you are running Python 3.9 or higher. Currently, Sanic is works with Python versions 3.9 – 3.13.
## Install
```sh
pip install sanicHello, world application
.. column::
If you have ever used one of the many decorator based frameworks, this probably looks somewhat familiar to you.
.. note::
If you are coming from Flask or another framework, there are a few important things to point out. Remember, Sanic aims for performance, flexibility, and ease of use. These guiding principles have tangible impact on the API and how it works... column::
```python
from sanic import Sanic
from sanic.response import text
app = Sanic("MyHelloWorldApp")
@app.get("/")
async def hello_world(request):
return text("Hello, world.")
```Important to note
- Every request handler can either be sync (
def hello_world) or async (async def hello_world). Unless you have a clear reason for it, always go withasync. - The
requestobject is always the first argument of your handler. Other frameworks pass this around in a context variable to be imported. In theasyncworld, this would not work so well and it is far easier (not to mention cleaner and more performant) to be explicit about it. - You must use a response type. MANY other frameworks allow you to have a return value like this:
return "Hello, world."or this:return {"foo": "bar"}. But, in order to do this implicit calling, somewhere in the chain needs to spend valuable time trying to determine what you meant. So, at the expense of this ease, Sanic has decided to require an explicit call.
Running
.. column::
Let's save the above file as `server.py`. And launch it... column::
```sh
sanic server
```.. note::
This **another** important distinction. Other frameworks come with a built in development server and explicitly say that it is _only_ intended for development use. The opposite is true with Sanic.
**The packaged server is production ready.**Sanic Extensions
Sanic intentionally aims for a clean and unopinionated feature list. The project does not want to require you to build your application in a certain way, and tries to avoid prescribing specific development patterns. There are a number of third-party plugins that are built and maintained by the community to add additional features that do not otherwise meet the requirements of the core repository.
However, in order to help API developers, the Sanic organization maintains an official plugin called Sanic Extensions to provide all sorts of goodies, including:
- OpenAPI documentation with Redoc and/or Swagger
- CORS protection
- Dependency injection into route handlers
- Request query arguments and body input validation
- Auto create
HEAD,OPTIONS, andTRACEendpoints - Predefined, endpoint-specific response serializers
The preferred method to set it up is to install it along with Sanic, but you can also install the packages on their own.
.. column::
```sh
pip install sanic[ext]
```.. column::
```sh
pip install sanic sanic-ext
```Starting in v21.12, Sanic will automatically setup Sanic Extensions if it is in the same environment. You will also have access to two additional application properties:
app.extend()- used to configure Sanic Extensionsapp.ext- theExtendinstance attached to the application
See the plugin documentation for more information about how to use and work with the plugin
Guide Content En Plugins Sanic Ext Getting Started (`guide/content/en/plugins/sanic-ext/getting-started.md`)
title: Sanic Extensions - Getting Started
Getting Started
Sanic Extensions is an officially supported plugin developed, and maintained by the SCO. The primary goal of this project is to add additional features to help Web API and Web application development easier.
Features
- CORS protection
- Template rendering with Jinja
- Dependency injection into route handlers
- OpenAPI documentation with Redoc and/or Swagger
- Predefined, endpoint-specific response serializers
- Request query arguments and body input validation
- Auto create
HEAD,OPTIONS, andTRACEendpoints
Minimum requirements
- Python: 3.8+
- Sanic: 21.9+
Install
The best method is to just install Sanic Extensions along with Sanic itself:
pip install sanic[ext]You can of course also just install it by itself.
pip install sanic-extExtend your application
Out of the box, Sanic Extensions will enable a bunch of features for you.
.. column::
To setup Sanic Extensions (v21.12+), you need to do: **nothing**. If it is installed in the environment, it is setup and ready to go.
This code is the Hello, world app in the [Sanic Getting Started page](../../guide/getting-started.md) _without any changes_, but using Sanic Extensions with `sanic-ext` installed in the background... column::
```python
from sanic import Sanic
from sanic.response import text
app = Sanic("MyHelloWorldApp")
@app.get("/")
async def hello_world(request):
return text("Hello, world.")
```.. column::
**_OLD DEPRECATED SETUP_**
In v21.9, the easiest way to get started is to instantiate it with `Extend`.
If you look back at the Hello, world app in the [Sanic Getting Started page](../../guide/getting-started.md), you will see the only additions here are the two highlighted lines... column::
```python
from sanic import Sanic
from sanic.response import text
from sanic_ext import Extend
app = Sanic("MyHelloWorldApp")
Extend(app)
@app.get("/")
async def hello_world(request):
return text("Hello, world.")
```Regardless of how it is setup, you should now be able to view the OpenAPI documentation and see some of the functionality in action: http://localhost:8000/docs.
Guide Content En Plugins Sanic Testing Getting Started (`guide/content/en/plugins/sanic-testing/getting-started.md`)
title: Sanic Testing - Getting Started
Getting Started
Sanic Testing is the official testing client for Sanic. Its primary use is to power the tests of the Sanic project itself. However, it is also meant as an easy-to-use client for getting your API tests up and running quickly.
Minimum requirements
- Python: 3.7+
- Sanic: 21.3+
Versions of Sanic older than 21.3 have this module integrated into Sanic itself as sanic.testing.
Install
Sanic Testing can be installed from PyPI:
pip install sanic-testingBasic Usage
As long as the sanic-testing package is in the environment, there is nothing you need to do to start using it.
Writing a sync test
In order to use the test client, you just need to access the property test_client on your application instance:
import pytest
from sanic import Sanic, response
@pytest.fixture
def app():
sanic_app = Sanic("TestSanic")
@sanic_app.get("/")
def basic(request):
return response.text("foo")
return sanic_app
def test_basic_test_client(app):
request, response = app.test_client.get("/")
assert request.method.lower() == "get"
assert response.body == b"foo"
assert response.status == 200Writing an async test
In order to use the async test client in pytest, you should install the pytest-asyncio plugin.
pip install pytest-asyncioYou can then create an async test and use the ASGI client:
import pytest
from sanic import Sanic, response
@pytest.fixture
def app():
sanic_app = Sanic(__name__)
@sanic_app.get("/")
def basic(request):
return response.text("foo")
return sanic_app
@pytest.mark.asyncio
async def test_basic_asgi_client(app):
request, response = await app.asgi_client.get("/")
assert request.method.lower() == "get"
assert response.body == b"foo"
assert response.status == 200Index (`docs/index.rst`)
.. include:: ../README.rst
User Guide
To learn about using Sanic, checkout the User Guide <https://sanicframework.org/guide/>__.
API
.. toctree::
:maxdepth: 3
👥 User Guide https://sanicframework.org/guide/
sanic/api_reference
💻 Source code https://github.com/sanic-org/sanic/
sanic/changelog
sanic/contributing
❓ Support https://community.sanicframework.org/
💬 Chat https://discord.gg/FARQzAEMAA
Module Documentation
.. toctree::
- :ref:
genindex - :ref:
modindex
Guide Requirements (`guide/requirements.txt`)
sanic>=23.12
sanic-ext>=23.12
msgspec
python-frontmatter
pygments
docstring-parser
libsass
mistune
Sanic Api Reference (`docs/sanic/api_reference.rst`)
📑 API Reference
.. toctree::
:maxdepth: 2
api/app
api/blueprints
api/core
api/exceptions
api/router
api/server
api/utility
Sanic Changelog (`docs/sanic/changelog.rst`)
📜 Changelog
| 🔶 Current release
| 🔷 In support release
|
.. mdinclude:: ./releases/23/23.6.md
.. mdinclude:: ./releases/23/23.3.md
.. mdinclude:: ./releases/22/22.12.md
.. mdinclude:: ./releases/22/22.9.md
.. mdinclude:: ./releases/22/22.6.md
.. mdinclude:: ./releases/22/22.3.md
.. mdinclude:: ./releases/21/21.12.md
.. mdinclude:: ./releases/21/21.9.md
.. include:: ../../CHANGELOG.rst
Sanic Contributing (`docs/sanic/contributing.rst`)
♥️ Contributing
.. include:: ../../CONTRIBUTING.rst
Sanic Api App (`docs/sanic/api/app.rst`)
Application
sanic.app
.. automodule:: sanic.app
:members:
:show-inheritance:
:inherited-members:
sanic.config
.. automodule:: sanic.config
:members:
:show-inheritance:
sanic.application.constants
.. automodule:: sanic.application.constants
:exclude-members: StrEnum
:members:
:show-inheritance:
:inherited-members:
sanic.application.state
.. automodule:: sanic.application.state
:members:
:show-inheritance:
Sanic Api Blueprints (`docs/sanic/api/blueprints.rst`)
Blueprints
sanic.blueprints
.. automodule:: sanic.blueprints
:members:
:show-inheritance:
:inherited-members:
sanic.blueprint_group
.. automodule:: sanic.blueprint_group
:members:
:special-members:
Sanic Api Core (`docs/sanic/api/core.rst`)
Core
sanic.cookies
.. automodule:: sanic.cookies
:members:
:show-inheritance:
sanic.handlers
.. automodule:: sanic.handlers
:members:
:show-inheritance:
sanic.headers
.. automodule:: sanic.headers
:members:
:show-inheritance:
sanic.request
.. automodule:: sanic.request
:members:
:show-inheritance:
sanic.response
.. automodule:: sanic.response
:members:
:show-inheritance:
sanic.views
.. automodule:: sanic.views
:members:
:show-inheritance:
Sanic Api Exceptions (`docs/sanic/api/exceptions.rst`)
Exceptions
sanic.errorpages
.. automodule:: sanic.errorpages
:members:
:show-inheritance:
sanic.exceptions
.. automodule:: sanic.exceptions
:members:
:show-inheritance:
Sanic Api Router (`docs/sanic/api/router.rst`)
Routing
sanic_routing models
.. autoclass:: sanic_routing.route::Route
:members:
.. autoclass:: sanic_routing.group::RouteGroup
:members:
sanic.router
.. automodule:: sanic.router
:members:
:show-inheritance:
Sanic Api Server (`docs/sanic/api/server.rst`)
Sanic Server
sanic.http
.. automodule:: sanic.http
:members:
:show-inheritance:
sanic.server
.. automodule:: sanic.server
:members:
:show-inheritance:
Sanic Api Utility (`docs/sanic/api/utility.rst`)
Utility
sanic.compat
.. automodule:: sanic.compat
:members:
:show-inheritance:
sanic.log
.. automodule:: sanic.log
:members:
:show-inheritance:
Guide Config En Navbar.Yaml (`guide/config/en/navbar.yaml`)
root:
- label: Home
path: index.html
- label: Community
items:
- label: Forums
href: https://community.sanicframework.org
- label: Discord
href: https://discord.gg/FARQzAEMAA
- label: Twitter
href: https://twitter.com/sanicframework
- label: Help
path: ./help.html
- label: GitHub
href: https://github.com/sanic-org/sanic
Guide Config En Sidebar.Yaml (`guide/config/en/sidebar.yaml`)
root:
- label: User Guide
items:
- label: General
items:
- label: Introduction
path: guide/introduction.html
- label: Getting Started
path: guide/getting-started.html
- label: Basics
items:
- label: Sanic Application
path: guide/basics/app.html
- label: Handlers
path: guide/basics/handlers.html
- label: Request
path: guide/basics/request.html
- label: Response
path: guide/basics/response.html
- label: Routing
path: guide/basics/routing.html
- label: Listeners
path: guide/basics/listeners.html
- label: Middleware
path: guide/basics/middleware.html
- label: Headers
path: guide/basics/headers.html
- label: Cookies
path: guide/basics/cookies.html
- label: Background Tasks
path: guide/basics/tasks.html
- label: Advanced
items:
- label: Class Based Views
path: guide/advanced/class-based-views.html
- label: Proxy Configuration
path: guide/advanced/proxy-headers.html
- label: Streaming
path: guide/advanced/streaming.html
- label: Websockets
path: guide/advanced/websockets.html
- label: Versioning
path: guide/advanced/versioning.html
- label: Signals
path: guide/advanced/signals.html
- label: Custom CLI Commands
path: guide/advanced/commands.html
- label: Best Practices
items:
- label: Blueprints
path: guide/best-practices/blueprints.html
- label: Exceptions
path: guide/best-practices/exceptions.html
- label: Decorators
path: guide/best-practices/decorators.html
- label: Logging
path: guide/best-practices/logging.html
- label: Testing
path: guide/best-practices/testing.html
- label: Running Sanic
items:
- label: Configuration
path: guide/running/configuration.html
- label: Development
path: guide/running/development.html
- label: Running Sanic
path: guide/running/running.html
- label: Worker Manager
path: guide/running/manager.html
- label: Dynamic Applications
path: guide/running/app-loader.html
- label: Inspector
path: guide/running/inspector.html
- label: Deployment
items:
- label: Caddy
path: guide/deployment/caddy.html
- label: Nginx
path: guide/deployment/nginx.html
- label: Docker
path: guide/deployment/docker.html
- label: How to ...
items:
- label: Table of Contents
path: guide/how-to/table-of-contents.html
- label: Application Mounting
path: guide/how-to/mounting.html
- label: Authentication
path: guide/how-to/authentication.html
- label: Autodiscovery
path: guide/how-to/autodiscovery.html
- label: CORS
path: guide/how-to/cors.html
- label: ORM
path: guide/how-to/orm.html
- label: Static Redirects
path: guide/how-to/static-redirects.html
- label: TLS/SSL/HTTPS
path: guide/how-to/tls.html
- label: Plugins
items:
- label: Sanic Extensions
items:
- label: Getting Started
path: plugins/sanic-ext/getting-started.html
- label: HTTP - Methods
path: plugins/sanic-ext/http/methods.html
- label: HTTP - CORS Protection
path: plugins/sanic-ext/http/cors.html
- label: OpenAPI - Basics
path: plugins/sanic-ext/openapi/basics.html
- label: OpenAPI - UI
path: plugins/sanic-ext/openapi/ui.html
- label: OpenAPI - Decorators
path: plugins/sanic-ext/openapi/decorators.html
# - label: OpenAPI - Advanced
# path: plugins/sanic-ext/openapi/advanced.html
- label: OpenAPI - Auto Documentation
path: plugins/sanic-ext/openapi/autodoc.html
- label: OpenAPI - Security
path: plugins/sanic-ext/openapi/security.html
- label: Convenience
path: plugins/sanic-ext/convenience.html
- label: Templating - Jinja
path: plugins/sanic-ext/templating/jinja.html
- label: Templating - html5tagger
path: plugins/sanic-ext/templating/html5tagger.html
- label: Dependency Injection
path: plugins/sanic-ext/injection.html
- label: Validation
path: plugins/sanic-ext/validation.html
- label: Health Monitor
path: plugins/sanic-ext/health-monitor.html
- label: Background Logger
path: plugins/sanic-ext/logger.html
- label: Configuration
path: plugins/sanic-ext/configuration.html
- label: Custom Extensions
path: plugins/sanic-ext/custom.html
- label: Sanic Testing
items:
- label: Getting Started
path: plugins/sanic-testing/getting-started.html
- label: Test Clients
path: plugins/sanic-testing/clients.html
- label: Release Notes
items:
- label: "2025"
items:
- label: Sanic 25.12
path: release-notes/2025/v25.12.html
- label: Sanic 25.3
path: release-notes/2025/v25.3.html
- label: "2024"
items:
- label: Sanic 24.12
path: release-notes/2024/v24.12.html
- label: Sanic 24.6
path: release-notes/2024/v24.6.html
- label: "2023"
items:
- label: Sanic 23.12
path: release-notes/2023/v23.12.html
- label: Sanic 23.9
path: release-notes/2023/v23.9.html
- label: Sanic 23.6
path: release-notes/2023/v23.6.html
- label: Sanic 23.3
path: release-notes/2023/v23.3.html
- label: "2022"
items:
- label: Sanic 22.12
path: release-notes/2022/v22.12.html
- label: Sanic 22.9
path: release-notes/2022/v22.9.html
- label: Sanic 22.6
path: release-notes/2022/v22.6.html
- label: Sanic 22.3
path: release-notes/2022/v22.3.html
- label: "2021"
items:
- label: Sanic 21.12
path: release-notes/2021/v21.12.html
- label: Sanic 21.9
path: release-notes/2021/v21.9.html
- label: Sanic 21.6
path: release-notes/2021/v21.6.html
- label: Sanic 21.3
path: release-notes/2021/v21.3.html
- label: Changelog
path: release-notes/changelog.html
- label: Organization
items:
- label: Contributing
path: organization/contributing.html
- label: Code of Conduct
path: organization/code-of-conduct.html
- label: S.C.O.P.E. (Governance)
path: organization/scope.html
- label: Policies
path: organization/policies.html
- label: API Reference
items:
- label: Application
items:
- label: sanic.app
path: /api/sanic.app.html
- label: sanic.config
path: /api/sanic.config.html
- label: sanic.application
path: /api/sanic.application.html
- label: Blueprint
items:
- label: sanic.blueprints
path: /api/sanic.blueprints.html
- label: sanic.blueprint_group
path: /api/sanic.blueprint_group.html
- label: Constant
items:
- label: sanic.constants
path: /api/sanic.constants.html
- label: Core
items:
- label: sanic.cookies
path: /api/sanic.cookies.html
- label: sanic.handlers
path: /api/sanic.handlers.html
- label: sanic.headers
path: /api/sanic.headers.html
- label: sanic.middleware
path: /api/sanic.middleware.html
- label: sanic.mixins
path: /api/sanic.mixins.html
- label: sanic.request
path: /api/sanic.request.html
- label: sanic.response
path: /api/sanic.response.html
- label: sanic.views
path: /api/sanic.views.html
- label: Display
items:
- label: sanic.pages
path: /api/sanic.pages.html
- label: Exception
items:
- label: sanic.errorpages
path: /api/sanic.errorpages.html
- label: sanic.exceptions
path: /api/sanic.exceptions.html
- label: Model
items:
- label: sanic.models
path: /api/sanic.models.html
- label: Routing
items:
- label: sanic.router
path: /api/sanic.router.html
- label: sanic.signals
path: /api/sanic.signals.html
- label: Server
items:
- label: sanic.http
path: /api/sanic.http.html
- label: sanic.server
path: /api/sanic.server.html
- label: sanic.worker
path: /api/sanic.worker.html
- label: Utility
items:
- label: sanic.compat
path: /api/sanic.compat.html
- label: sanic.helpers
path: /api/sanic.helpers.html
- label: sanic.logging
path: /api/sanic.logging.html
- label: sanic.utils
path: /api/sanic.utils.html
Guide Content En Built With Sanic (`guide/content/en/built-with-sanic.md`)
title: Full Speed Ahead - How We Built This Site with Sanic layout: main
.. attrs::
:class: title
Full Speed Ahead:.. attrs::
:class: subtitle
How We Built This Site with SanicWelcome to our little corner of the Internet where we proudly say, "Yes, we built this with Sanic!" This isn't just a website; it's our playground, our test lab, our battlefield, and, well, our home.
The Story: "We Drink Our Own Champagne"
We believe in Sanic so much that we decided to put it to the ultimate test—running our own website. It's like a chef eating at their own restaurant, only with less risk of food poisoning.
Why? Because building a website or web application is hard. There are countless moving parts, a plethora of challenges, and the ever-present need for speed and reliability. We want to show you just one of the many ways you could do it.
In this high-stakes digital kitchen, Sanic is our secret ingredient. By deploying our own website on Sanic, we're not just showcasing its capabilities; we're stress-testing them in the real world. This is our chance to walk the walk, proving that Sanic isn't just good on paper—it's a robust, high-performance framework that can handle everything from the smallest blog to the busiest e-commerce site.
So, here we are, sipping our own champagne, confident in the knowledge that if Sanic can run our site, it can power yours too. Cheers to coding at the speed of thought! 🥂
The Setup: Digital Ocean, Ahoy!
We launched our site on Digital Ocean's App Platform because we love high-performance cloud sailing. Think of it as having a Ferrari in the cloud—fast, sleek, but way easier to handle.
Why go for simplicity? With a lean team and no DevOps gurus, we needed a no-fuss, straightforward solution. Digital Ocean gives us that smooth sailing platform-as-a-service (PaaS) experience. It’s perfect for our needs: easy setup, automatic deployments, and the kind of reliability that lets you sleep soundly.
Our choice reflects our ethos: focus on your strengths and let the platform do the heavy lifting. For us, it means creating amazing web experiences with Sanic, supported by a deployment solution that's simple yet powerful. ⛵
The Code: GitHub's Where It's At
All our code is out in the open, basking in the glory of public scrutiny on GitHub. Why hide the magic? It's right there, in full view, at our GitHub repository. Go ahead, take a peek, fork it, play with it, break it (and then kindly fix it).
Open-source isn't just a buzzword for us; it's our ethos. It's about building something bigger than ourselves, together. Our code is a testament to collaborative innovation, a playground for development, and a real-life example of Sanic in action.
Every line of code, every commit, reflects our journey with Sanic, showcasing how we leverage its speed and scalability. Your contributions, whether fixing a bug, suggesting a feature, or enhancing documentation, are what propel this project forward.
So, dive in, contribute your genius, and let's keep shaping the future of web development with Sanic. Together, we're not just coding – we're creating a community-driven powerhouse. 🚀
The Invitation: Write, Code, Break, Fix!
Documentarians: Love making complex stuff sound easy? Our docs are your canvas. Paint away in words! 🎨
Code Ninjas: Find bugs? Squash 'em. Got ideas? Code 'em. Make pull requests rain! 🥷
Bug Hunters: If you find bugs, don't just stare. Let us know. We love a good bug hunt. 🐛
The Bottom Line
We built this site with Sanic to show off what it can do. It's fast, it's fun, and it's what we use. So, if things load swiftly, pat us on the back. If they don't, well, uh... we blame cosmic rays?
Join us in making Sanic not just good, but "I-can't-believe-it's-not-butter" good!
Cheers,
The Sanic Team (who occasionally wear capes)
Guide Content En Help (`guide/content/en/help.md`)
title: Need some help? layout: main
Need some help?
As an active community of developers, we try to support each other. If you need some help, try one of the following:
.. column::
### Discord 💬
Best place to turn for quick answers and live chat
`#sanic-support` channel on the [Discord server](https://discord.gg/FARQzAEMAA).. column::
### Community Forums 👥
Good for sharing snippets of code and longer support queries
`Questions and Help` category on the [Forums](https://community.sanicframework.org/c/questions-and-help/6)We also actively monitor the [sanic] tag on Stack Overflow.
Guide Public Web Robots (`guide/public/web/robots.txt`)
User-agent: *
Allow: /
Sitemap: https://sanic.dev/sitemap.xml
Host: https://sanic.dev
Sanic Releases 21 21.12 (`docs/sanic/releases/21/21.12.md`)
Version 21.12.1 🔷
Current LTS version
- #2349 Only display MOTD on startup
- #2354 Ignore name argument in Python 3.7
- #2355 Add config.update support for all config values
Version 21.12.0 🔹
Features
- #2260 Allow early Blueprint registrations to still apply later added objects
- #2262 Noisy exceptions - force logging of all exceptions
- #2264 Optional
uvloopby configuration - #2270 Vhost support using multiple TLS certificates
- #2277 Change signal routing for increased consistency
- BREAKING CHANGE: If you were manually routing signals there is a breaking change. The signal router's
getis no longer 100% determinative. There is now an additional step to loop thru the returned signals for proper matching on the requirements. If signals are being dispatched usingapp.dispatchorbp.dispatch, there is no change.
- BREAKING CHANGE: If you were manually routing signals there is a breaking change. The signal router's
- #2290 Add contextual exceptions
- #2291 Increase join concat performance
- #2295, #2316, #2331 Restructure of CLI and application state with new displays and more command parity with
app.run - #2302 Add route context at definition time
- #2304 Named tasks and new API for managing background tasks
- #2307 On app auto-reload, provide insight of changed files
- #2308 Auto extend application with Sanic Extensions if it is installed, and provide first class support for accessing the extensions
- #2309 Builtin signals changed to
Enum - #2313 Support additional config implementation use case
- #2321 Refactor environment variable hydration logic
- #2327 Prevent sending multiple or mixed responses on a single request
- #2330 Custom type casting on environment variables
- #2332 Make all deprecation notices consistent
- #2335 Allow underscore to start instance names
Bugfixes
- #2273 Replace assignation by typing for
websocket_handshake - #2285 Fix IPv6 display in startup logs
- #2299 Dispatch
http.lifecyle.responsefrom exception handler
Deprecations and Removals
- #2306 Removal of deprecated items
SanicandBlueprintmay no longer have arbitrary properties attached to themSanicandBlueprintforced to have compliant names- alphanumeric +
_+- - must start with letter or
_
- alphanumeric +
load_envkeyword argument ofSanicsanic.exceptions.abortsanic.views.CompositionViewsanic.response.StreamingHTTPResponse- NOTE: the
stream()response method (where you pass a callable streaming function) has been deprecated and will be removed in v22.6. You should upgrade all streaming responses to the new style: https://sanicframework.org/en/guide/advanced/streaming.html#response-streaming
- NOTE: the
- #2320 Remove app instance from Config for error handler setting
Developer infrastructure
- #2251 Change dev install command
- #2286 Change codeclimate complexity threshold from 5 to 10
- #2287 Update host test function names so they are not overwritten
- #2292 Fail CI on error
- #2311, #2324 Do not run tests for draft PRs
- #2336 Remove paths from coverage checks
- #2338 Cleanup ports on tests
Improved Documentation
Miscellaneous
- #2257, #2294, #2341 Add Python 3.10 support
- #2279, #2317, #2322 Add/correct missing type annotations
- #2305 Fix examples to use modern implementations
Sanic Releases 21 21.9 (`docs/sanic/releases/21/21.9.md`)
Version 21.9.3
Rerelease of v21.9.2 with some cleanup
Version 21.9.2
- #2268 Make HTTP connections start in IDLE stage, avoiding delays and error messages
- #2310 More consistent config setting with post-FALLBACK_ERROR_FORMAT apply
Version 21.9.1
- #2259 Allow non-conforming ErrorHandlers
Version 21.9.0
Features
- #2158, #2248 Complete overhaul of I/O to websockets
- #2160 Add new 17 signals into server and request lifecycles
- #2162 Smarter
autofallback formatting upon exception - #2184 Introduce implementation for copying a Blueprint
- #2200 Accept header parsing
- #2207 Log remote address if available
- #2209 Add convenience methods to BP groups
- #2216 Add default messages to SanicExceptions
- #2225 Type annotation convenience for annotated handlers with path parameters
- #2236 Allow Falsey (but not-None) responses from route handlers
- #2238 Add
exceptiondecorator to Blueprint Groups - #2244 Explicit static directive for serving file or dir (ex:
static(..., resource_type="file")) - #2245 Close HTTP loop when connection task cancelled
Bugfixes
- #2188 Fix the handling of the end of a chunked request
- #2195 Resolve unexpected error handling on static requests
- #2208 Make blueprint-based exceptions attach and trigger in a more intuitive manner
- #2211 Fixed for handling exceptions of asgi app call
- #2213 Fix bug where ws exceptions not being logged
- #2231 Cleaner closing of tasks by using
abort()in strategic places to avoid dangling sockets - #2247 Fix logging of auto-reload status in debug mode
- #2246 Account for BP with exception handler but no routes
Developer infrastructure
- #2194 HTTP unit tests with raw client
- #2199 Switch to codeclimate
- #2214 Try Reopening Windows Tests
- #2229 Refactor
HttpProtocolinto a base class - #2230 Refactor
server.pyinto multi-file module
Miscellaneous
- #2173 Remove Duplicated Dependencies and PEP 517 Support
- #2193, #2196, #2217 Type annotation changes
Sanic Releases 22 22.12 (`docs/sanic/releases/22/22.12.md`)
Version 22.12.0 🔷
Current version
Features
- #2569 Add
JSONResponseclass with some convenient methods when updating a response object - #2598 Change
uvlooprequirement to>=0.15.0 - #2609 Add compatibility with
websocketsv11.0 - #2610 Kill server early on worker error
- Raise deadlock timeout to 30s
- #2617 Scale number of running server workers
- #2621 #2634 Send
SIGKILLon subsequentctrl+cto force worker exit - #2622 Add API to restart all workers from the multiplexer
- #2624 Default to
spawnfor all subprocesses unless specifically set:pythonfrom sanic import Sanic Sanic.start_method = "fork" - #2625 Filename normalisation of form-data/multipart file uploads
- #2626 Move to HTTP Inspector:
- Remote access to inspect running Sanic instances
- TLS support for encrypted calls to Inspector
- Authentication to Inspector with API key
- Ability to extend Inspector with custom commands
- #2632 Control order of restart operations
- #2633 Move reload interval to class variable
- #2636 Add
prioritytoregister_middlewaremethod - #2639 Add
unquotetoadd_routemethod - #2640 ASGI websockets to receive
textorbytes
Bugfixes
- #2607 Force socket shutdown before close to allow rebinding
- #2590 Use actual
StrEnumin Python 3.11+ - #2615 Ensure middleware executes only once per request timeout
- #2627 Crash ASGI application on lifespan failure
- #2635 Resolve error with low-level server creation on Windows
Deprecations and Removals
- #2608 #2630 Signal conditions and triggers saved on
signal.extra - #2626 Move to HTTP Inspector
- 🚨 BREAKING CHANGE: Moves the Inspector to a Sanic app from a simple TCP socket with a custom protocol
- DEPRECATE: The
--inspect*commands have been deprecated in favor ofinspect ...commands
- #2628 Replace deprecated
distutils.strtobool
Developer infrastructure
- #2612 Add CI testing for Python 3.11
Sanic Releases 22 22.3 (`docs/sanic/releases/22/22.3.md`)
Version 22.3.0
Features
- #2347 API for multi-application server
- 🚨 BREAKING CHANGE: The old
sanic.worker.GunicornWorkerhas been removed. To run Sanic withgunicorn, you should use it thruuvicornas described in their docs. - 🧁 SIDE EFFECT: Named background tasks are now supported, even in Python 3.7
- 🚨 BREAKING CHANGE: The old
- #2357 Parse
Authorizationheader asRequest.credentials - #2361 Add config option to skip
Touchupstep in application startup - #2372 Updates to CLI help messaging
- #2382 Downgrade warnings to backwater debug messages
- #2396 Allow for
multidictv0.6 - #2401 Upgrade CLI catching for alternative application run types
- #2402 Conditionally inject CLI arguments into factory
- #2413 Add new start and stop event listeners to reloader process
- #2414 Remove loop as required listener arg
- #2415 Better exception for bad URL parsing
- sanic-routing#47 Add a new extention parameter type:
<file:ext>,<file:ext=jpg>,<file:ext=jpg|png|gif|svg>,<file=int:ext>,<file=int:ext=jpg|png|gif|svg>,<file=float:ext=tar.gz>- 👶 BETA FEATURE: This feature will not work with
pathtype matching, and is being released as a beta feature only.
- 👶 BETA FEATURE: This feature will not work with
- sanic-routing#57 Change
register_patternto accept astrorPattern - sanic-routing#58 Default matching on non-empty strings only, and new
stroremptypattern type- 🚨 BREAKING CHANGE: 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>.
- 🚨 BREAKING CHANGE: Previously a route with a dynamic string parameter (
Bugfixes
- #2373 Remove
error_loggeron websockets - #2381 Fix newly assigned
Nonein task registry - sanic-routing#52 Add type casting to regex route matching
- sanic-routing#60 Add requirements check on regex routes (this resolves, for example, multiple static directories with differing
hostvalues)
Deprecations and Removals
- #2362 22.3 Deprecations and changes
debug=Trueand--debugdo NOT automatically runauto_reload- Default error render is with plain text (browsers still get HTML by default because
autolooks at headers) configis required forErrorHandler.finalizeErrorHandler.lookuprequires two positional args- Unused websocket protocol args removed
- #2344 Deprecate loading of lowercase environment variables
Developer infrastructure
- #2363 Revert code coverage back to Codecov
- #2405 Upgrade tests for
sanic-routingchanges - sanic-testing#35 Allow for httpx v0.22
Improved Documentation
- #2350 Fix link in README for ASGI
- #2398 Document middleware on_request and on_response
- #2409 Add missing documentation for
Request.respond
Miscellaneous
- #2376 Fix typing for
ListenerMixin.listener - #2383 Clear deprecation warning in
asyncio.wait - #2387 Cleanup
__slots__implementations - #2390 Clear deprecation warning in
asyncio.get_event_loop
Sanic Releases 22 22.6 (`docs/sanic/releases/22/22.6.md`)
Version 22.6.2
Bugfixes
- #2522 Always show server location in ASGI
Version 22.6.1
Bugfixes
- #2477 Sanic static directory fails when folder name ends with ".."
Version 22.6.0
Features
- #2378 Introduce HTTP/3 and autogeneration of TLS certificates in
DEBUGmode- 👶 EARLY RELEASE FEATURE: Serving Sanic over HTTP/3 is an early release feature. It does not yet fully cover the HTTP/3 spec, but instead aims for feature parity with Sanic's existing HTTP/1.1 server. Websockets, WebTransport, push responses are examples of some features not yet implemented.
- 📦 EXTRA REQUIREMENT: Not all HTTP clients are capable of interfacing with HTTP/3 servers. You may need to install a HTTP/3 capable client.
- 📦 EXTRA REQUIREMENT: In order to use TLS autogeneration, you must install either mkcert or trustme.
- #2416 Add message to
task.cancel - #2420 Add exception aliases for more consistent naming with standard HTTP response types (
BadRequest,MethodNotAllowed,RangeNotSatisfiable) - #2432 Expose ASGI
scopeas a property on theRequestobject - #2438 Easier access to websocket class for annotation:
from sanic import Websocket - #2439 New API for reading form values with options:
Request.get_form - #2445 Add custom
loadsfunction - #2447, #2486 Improved API to support setting cache control headers
- #2453 Move verbosity filtering to logger
- #2475 Expose getter for current request using
Request.get_current()
Bugfixes
- #2448 Fix to allow running with
pythonw.exeor places where there is nosys.stdout - #2451 Trigger
http.lifecycle.requestsignal in ASGI mode - #2455 Resolve typing of stacked route definitions
- #2463 Properly catch websocket CancelledError in websocket handler in Python 3.7
Deprecations and Removals
- #2487 v22.6 deprecations and changes
- Optional application registry
- Execution of custom handlers after some part of response was sent
- Configuring fallback handlers on the
ErrorHandler - Custom
LOGOsetting sanic.response.streamAsyncioServer.init
Developer infrastructure
Improved Documentation
- #2461 Update example to match current application naming standards
- #2466 Better type annotation for
Extend - #2485 Improved help messages in CLI
Sanic Releases 22 22.9 (`docs/sanic/releases/22/22.9.md`)
Version 22.9.1
Features
- #2585 Improved error message when no applications have been registered
Bugfixes
- #2578 Add certificate loader for in process certificate creation
- #2591 Do not use sentinel identity for
spawncompatibility - #2592 Fix properties in nested blueprint groups
- #2595 Introduce sleep interval on new worker reloader
Deprecations and Removals
Developer infrastructure
- #2588 Markdown templates on issue forms
Improved Documentation
Version 22.9.0
Features
- #2445 Add custom loads function
- #2490 Make
WebsocketImplProtocolasync iterable - #2499 Sanic Server WorkerManager refactor
- #2506 Use
pathlibfor path resolution (for static file serving) - #2508 Use
path.partsinstead ofmatch(for static file serving) - #2513 Better request cancel handling
- #2516 Add request properties for HTTP method info:
request.is_saferequest.is_idempotentrequest.is_cacheable- See MDN docs for more information about when these apply
- #2522 Always show server location in ASGI
- #2526 Cache control support for static files for returning 304 when appropriate
- #2533 Refactor
_static_request_handler - #2540 Add signals before and after handler execution
http.handler.beforehttp.handler.after
- #2542 Add [redacted] to CLI :)
- #2546 Add deprecation warning filter
- #2550 Middleware priority and performance enhancements
Bugfixes
- #2495 Prevent directory traversion with static files
- #2515 Do not apply double slash to paths in certain static dirs in Blueprints
Deprecations and Removals
- #2525 Warn on duplicate route names, will be prevented outright in v23.3
- #2537 Raise warning and deprecation notice on duplicate exceptions, will be prevented outright in v23.3
Developer infrastructure
- #2504 Cleanup test suite
- #2505 Replace Unsupported Python Version Number from the Contributing Doc
- #2530 Do not include tests folder in installed package resolver
Improved Documentation
Sanic Releases 23 23.3 (`docs/sanic/releases/23/23.3.md`)
Version 23.3.0
Features
#2545 Standardize init of exceptions for more consistent control of HTTP responses using exceptions
#2606 Decode headers as UTF-8 also in ASGI
#2646 Separate ASGI request and lifespan callables
#2659 Use
FALLBACK_ERROR_FORMATfor handlers that returnempty()#2662 Add basic file browser (HTML page) and auto-index serving
#2667 Nicer traceback formatting (HTML page)
#2668 Smarter error page rendering format selection; more reliant upon header and "common sense" defaults
#2680 Check the status of socket before shutting down with
SHUT_RDWR#2687 Refresh
Request.acceptfunctionality to be more performant and spec-compliant#2696 Add header accessors as properties
textExample-Field: Foo, Bar Example-Field: Bazpythonrequest.headers.example_field == "Foo, Bar,Baz"#2700 Simpler CLI targets
sh$ sanic path.to.module:app # global app instance $ sanic path.to.module:create_app # factory pattern $ sanic ./path/to/directory/ # simple serve#2701 API to define a number of workers in managed processes
#2704 Add convenience for dynamic changes to routing
#2706 Add convenience methods for cookie creation and deletion
pythonresponse = text("...") response.add_cookie("test", "It worked!", domain=".yummy-yummy-cookie.com")#2707 Simplified
parse_content_headerescaping to be RFC-compliant and remove outdated FF hack#2710 Stricter charset handling and escaping of request URLs
#2711 Consume body on
DELETEby default#2719 Allow
passwordto be passed to TLS context#2720 Skip middleware on
RequestCancelled#2721 Change access logging format to
%s#2722 Add
CertLoaderas application option for directly controllingSSLContextobjects#2725 Worker sync state tolerance on race condition
Bugfixes
- #2651 ASGI websocket to pass thru bytes as is
- #2697 Fix comparison between datetime aware and naive in
filewhen usingIf-Modified-Since
Deprecations and Removals
- #2666 Remove deprecated
__blueprintname__property
Improved Documentation
- #2712 Improved example using
'https'to create the redirect
Sanic Releases 23 23.6 (`docs/sanic/releases/23/23.6.md`)
Version 23.6.0 🔶
Features
- #2670 Increase
KEEP_ALIVE_TIMEOUTdefault to 120 seconds - #2716 Adding allow route overwrite option in blueprint
- #2724 and #2792 Add a new exception signal for ALL exceptions raised anywhere in application
- #2727 Add name prefixing to BP groups
- #2754 Update request type on middleware types
- #2770 Better exception message on startup time application induced import error
- #2776 Set multiprocessing start method early
- #2785 Add custom typing to config and ctx objects
- #2790 Add
request.client_ip
Bugfixes
- #2728 Fix traversals for intended results
- #2729 Handle case when headers argument of ResponseStream constructor is None
- #2737 Fix type annotation for
JSONREsponsedefault content type - #2740 Use Sanic's serializer for JSON responses in the Inspector
- #2760 Support for
Request.get_currentin ASGI mode - #2773 Alow Blueprint routes to explicitly define error_format
- #2774 Resolve headers on different renderers
- #2782 Resolve pypy compatibility issues
Deprecations and Removals
- #2777 Remove Python 3.7 support
Developer infrastructure
Improved Documentation
- #2741 Better documentation examples about running Sanic
From that list, the items to highlight in the release notes:
Guide Content En Guide Introduction (`guide/content/en/guide/introduction.md`)
Introduction
Sanic is a Python 3.10+ web server and web framework that's written to go fast. It allows the usage of the async/await syntax added in Python 3.5, which makes your code non-blocking and speedy.
.. attrs::
:class: introduction-table
| | |
|--|--|
| Build | [](https://github.com/sanic-org/sanic/actions/workflows/tests.yml) |
| Docs | [](https://sanicframework.org/) [](http://sanic.readthedocs.io/en/latest/?badge=latest) |
| Package | [](https://pypi.python.org/pypi/sanic/) [](https://pypi.python.org/pypi/sanic/) [](https://pypi.python.org/pypi/sanic) [](https://pypi.python.org/pypi/sanic) [](https://github.com/ambv/black) |
| Support | [](https://community.sanicframework.org/) [](https://discord.gg/FARQzAEMAA) [](https://github.com/mekicha/awesome-sanic) |
| Stats | [](https://pepy.tech/project/sanic) [](https://pepy.tech/project/sanic) [](https://anaconda.org/conda-forge/sanic) |What is it?
First things first, before you jump in the water, you should know that Sanic is different than other frameworks.
Right there in that first sentence there is a huge mistake because Sanic is both a framework and a web server. In the deployment section we will talk a little bit more about this.
But, remember, out of the box Sanic comes with everything you need to write, deploy, and scale a production grade web application. 🚀
Goal
To provide a simple way to get up and running a highly performant HTTP server that is easy to build, to expand, and ultimately to scale.
Features
.. column::
### Core
- Built in, **_fast_** web server
- Production ready
- Highly scalable
- ASGI compliant
- Simple and intuitive API design
- By the community, for the community.. column::
### Sanic Extensions [[learn more](../plugins/sanic-ext/getting-started.md)]
- CORS protection
- Template rendering with Jinja
- Dependency injection into route handlers
- OpenAPI documentation with Redoc and/or Swagger
- Predefined, endpoint-specific response serializers
- Request query arguments and body input validation
- Auto create `HEAD`, `OPTIONS`, and `TRACE` endpointsSponsor
Check out open collective to learn more about helping to fund Sanic.
Join the Community
The main channel for discussion is at the community forums. There also is a Discord Server for live discussion and chat.
The Stackoverflow [sanic] tag is actively monitored by project maintainers.
Contribution
We are always happy to have new contributions. We have marked issues good for anyone looking to get started, and welcome questions/answers/discussion on the forums. Please take a look at our Contribution guidelines.
Guide Content En Organization Code Of Conduct (`guide/content/en/organization/code-of-conduct.md`)
Contributor Covenant Code of Conduct
Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
Our Standards
Examples of behavior that contributes to creating a positive environment
include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or
advances - Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a
professional setting
Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [email protected]. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
Attribution
This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
available at http://contributor-covenant.org/version/1/4
Guide Content En Organization Contributing (`guide/content/en/organization/contributing.md`)
Contributing
Thank you for your interest! Sanic is always looking for contributors. If you don't feel comfortable contributing code, adding docstrings to the source files, or helping with the Sanic User Guide by providing documentation or implementation examples would be appreciated!
We are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, disability, ethnicity, religion, or similar personal characteristic. Our code of conduct sets the standards for behavior.
Installation
To develop on Sanic (and mainly to just run the tests) it is highly recommend to install from sources.
So assume you have already cloned the repo and are in the working directory with a virtual environment already set up, then run:
pip install -e ".[dev]"Dependency Changes
Sanic doesn't use requirements*.txt files to manage any kind of dependencies related to it in order to simplify the effort required in managing the dependencies. Please make sure you have read and understood the following section of the document that explains the way sanic manages dependencies inside the setup.py file.
| Dependency Type | Usage | Installation |
|---|---|---|
| requirements | Bare minimum dependencies required for sanic to function | pip3 install -e . |
| tests_require / extras_require['test'] | Dependencies required to run the Unit Tests for sanic |
pip3 install -e '.[test]' |
| extras_require['dev'] | Additional Development requirements to add contributing | pip3 install -e '.[dev]' |
| extras_require['docs'] | Dependencies required to enable building and enhancing sanic documentation | pip3 install -e '.[docs]' |
Running all tests
To run the tests for Sanic it is recommended to use tox like so:
toxSee it's that simple!
tox.ini contains different environments. Running tox without any arguments will
run all unittests, perform lint and other checks.
Run unittests
tox environment -> [testenv]
To execute only unittests, run tox with environment like so:
tox -e py37 -v -- tests/test_config.py
# or
tox -e py310 -v -- tests/test_config.pyRun lint checks
tox environment -> [testenv:lint]
Permform flake8\ , black and isort checks.
tox -e lintRun type annotation checks
tox environment -> [testenv:type-checking]
Permform mypy checks.
tox -e type-checkingRun other checks
tox environment -> [testenv:check]
Perform other checks.
tox -e checkRun Static Analysis
tox environment -> [testenv:security]
Perform static analysis security scan
tox -e securityRun Documentation sanity check
tox environment -> [testenv:docs]
Perform sanity check on documentation
tox -e docsCode Style
To maintain the code consistency, Sanic uses the following tools:
isort
isort sorts Python imports. It divides imports into three categories sorted each in alphabetical order:
- built-in
- third-party
- project-specific
black
black is a Python code formatter.
flake8
flake8 is a Python style guide that wraps the following tools into one:
- PyFlakes
- pycodestyle
- Ned Batchelder's McCabe script
slotscheck
slotscheck ensures that there are no problems with __slots__ (e.g., overlaps, or missing slots in base classes).
isort, black, flake8, and slotscheck checks are performed during tox lint checks.
The easiest way to make your code conform is to run the following before committing:
make prettyRefer to tox documentation for more details.
Pull requests
So the pull request approval rules are pretty simple:
- All pull requests must pass unit tests.
- All pull requests must be reviewed and approved by at least one current member of the Core Developer team.
- All pull requests must pass flake8 checks.
- All pull requests must match
isortandblackrequirements. - All pull requests must be PROPERLY type annotated, unless exemption is given.
- All pull requests must be consistent with the existing code.
- If you decide to remove/change anything from any common interface a deprecation message should accompany it in accordance with our deprecation policy.
- If you implement a new feature you should have at least one unit test to accompany it.
- An example must be one of the following:
- Example of how to use Sanic
- Example of how to use Sanic extensions
- Example of how to use Sanic and asynchronous library
Documentation
Check back. We are reworking our documentation so this will change.
Guide Content En Organization Policies (`guide/content/en/organization/policies.md`)
Policies
Versioning
Sanic uses calendar versioning, aka "calver". To be more specific, the pattern follows:
YY.MM.MICROGenerally, versions are referred to in their YY.MM form. The MICRO number indicates an incremental patch version, starting at 0.
Reporting a Vulnerability
If you discover a security vulnerability, we ask that you do not create an issue on GitHub. Instead, please send a message to the core-devs on the community forums. Once logged in, you can send a message to the core-devs by clicking the message button.
Alternatively, you can send a private message to Adam Hopkins on Discord. Find him on the Sanic discord server.
This will help to not publicize the issue until the team can address it and resolve it.
Release Schedule
There are four (4) scheduled releases per year: March, June, September, and December. Therefore, there are four (4) released versions per year: YY.3, YY.6, YY.9, and YY.12.
This release schedule provides:
- a predictable release cadence,
- relatively short development windows allowing features to be regularly released,
- controlled deprecations, and
- consistent stability with a yearly LTS.
We also use the yearly release cycle in conjunction with our governance model, covered by the S.C.O.P.E.
Long term support v Interim releases
Sanic releases a long term support release (aka "LTS") once a year in December. The LTS releases receive bug fixes and security updates for 24 months. Interim releases throughout the year occur every three months, and are supported until the subsequent release.
| Version | Release | LTS | Supported |
|---|---|---|---|
| 24.12 | 2024-12-31 | until 2026-12 | ✅ |
| 24.6 | 2024-06-30 | ⚪ | |
| 23.12 | 2023-12-31 | until 2025-12 | ☑️ |
| 23.6 | 2023-07-25 | ⚪ | |
| 23.3 | 2023-03-26 | ⚪ | |
| 22.12 | 2022-12-27 | ☑️ | |
| 22.9 | 2022-09-29 | ⚪ | |
| 22.6 | 2022-06-30 | ⚪ | |
| 22.3 | 2022-03-31 | ⚪ | |
| 21.12 | 2021-12-26 | ⚪ | |
| 21.9 | 2021-09-30 | ⚪ | |
| 21.6 | 2021-06-27 | ⚪ | |
| 21.3 | 2021-03-21 | ⚪ | |
| 20.12 | 2020-12-29 | ⚪ | |
| 20.9 | 2020-09-30 | ⚪ | |
| 20.6 | 2020-06-28 | ⚪ | |
| 20.3 | 2020-05-14 | ⚪ | |
| 19.12 | 2019-12-27 | ⚪ | |
| 19.9 | 2019-10-12 | ⚪ | |
| 19.6 | 2019-06-21 | ⚪ | |
| 19.3 | 2019-03-23 | ⚪ | |
| 18.12 | 2018-12-27 | ⚪ | |
| 0.8.3 | 2018-09-13 | ⚪ | |
| 0.7.0 | 2017-12-06 | ⚪ | |
| 0.6.0 | 2017-08-03 | ⚪ | |
| 0.5.4 | 2017-05-09 | ⚪ | |
| 0.4.1 | 2017-02-28 | ⚪ | |
| 0.3.1 | 2017-02-09 | ⚪ | |
| 0.2.0 | 2017-01-14 | ⚪ | |
| 0.1.9 | 2016-12-25 | ⚪ | |
| 0.1.0 | 2016-10-16 | ⚪ |
☑️ = security fixes
✅ = full support
⚪ = no support
Deprecation
Before a feature is deprecated, or breaking changes are introduced into the API, it shall be publicized and shall appear with deprecation warnings through two release cycles. No deprecations shall be made in an LTS release.
Breaking changes or feature removal may happen outside of these guidelines when absolutely warranted. These circumstances should be rare. For example, it might happen when no alternative is available to curtail a major security issue.
Guide Content En Organization Scope (`guide/content/en/organization/scope.md`)
Sanic Community Organization Policy E-manual (SCOPE)
.. attrs::
:class: is-size-7
_December 2019, version 1_Goals
To create a sustainable, community-driven organization around the Sanic projects that promote: (1) stability and predictability, (2) quick iteration and enhancement cycles, (3) engagement from outside contributors, (4) overall reliable software, and (5) a safe, rewarding environment for the community members.
Overview
This Policy is the governance model for the Sanic Community Organization (“SCO”). The SCO is a meritocratic, consensus-based community organization responsible for all projects adopted by it. Anyone with an interest in one of the projects can join the community, contribute to the community or projects, and participate in the decision making process. This document describes how that participation takes place and how to set about earning merit within the project community.
Structure
The SCO has multiple projects. Each project is represented by a single GitHub repository under the Sanic community umbrella. These projects are used by users, developed by contributors, governed by core developers, released by release managers, and ultimately overseen by a steering council. If this sounds similar to the Python project and PEP 8016 that is because it is intentionally designed that way.
Roles and responsibilities
Users
Users are community members who have a need for the projects. They are the developers and personnel that download and install the packages. Users are the most important members of the community and without them the projects would have no purpose. Anyone can be a user and the licenses adopted by the projects shall be appropriate open source licenses.
The SCO asks its users to participate in the project and community as much as possible.
User contributions enable the project team to ensure that they are satisfying the needs of those users. Common user contributions include (but are not limited to):
- evangelizing about the project (e.g. a link on a website and word-of-mouth awareness raising)
- informing developers of strengths and weaknesses from a new user perspective
- providing moral support (a ‘thank you’ goes a long way)
- providing financial support (the software is open source, but its developers need to eat)
Users who continue to engage with the SCO, its projects, and its community will often become more and more involved. Such users may find themselves becoming contributors, as described in the next section.
Contributors
Contributors are community members who contribute in concrete ways to one or more of the projects. Anyone can become a contributor and contributions can take many forms. Contributions and requirements are governed by each project separately by a contribution policy.
There is no expectation of commitment to the project, no specific skill requirements and no selection process.
In addition to their actions as users, contributors may also find themselves doing one or more of the following:
- supporting new users (existing users are often the best people to support new users)
- reporting bugs
- identifying requirements
- providing graphics and web design
- Programming
- example use cases
- assisting with project infrastructure
- writing documentation
- fixing bugs
- adding features
- providing constructive opinions and engaging in community discourse
Contributors engage with the projects through GitHub and the Community Forums. They submit changes to the projects itself via pull requests, which will be considered for inclusion in the project by the community at large. The Community Forums are the most appropriate place to ask for help when making that first contribution.
Indeed one of the most important roles of a contributor may be to simply engage in the community conversation. Most decisions about the direction of a project are made by consensus. This is discussed in more detail below. In general, however, it is helpful for the health and direction of the projects for the contributors to speak freely (within the confines of the code of conduct) and express their opinions and experiences to help drive the consensus building.
As contributors gain experience and familiarity with a project, their profile within, and commitment to, the community will increase. At some stage, they may find themselves being nominated for a core developer team.
Core Developer
Each project under the SCO umbrella has its own team of core developers. They are the people in charge of that project.
What is a core developer?
Core developers are community members who have shown that they are committed to the continued development of the project through ongoing engagement with the community. Being a core developer allows contributors to more easily carry on with their project related activities by giving them direct access to the project’s resources. They can make changes directly to the project repository without having to submit changes via pull requests from a fork.
This does not mean that a core developer is free to do what they want. In fact, core developers have no more direct authority over the final release of a package than do contributors. While this honor does indicate a valued member of the community who has demonstrated a healthy respect for the project’s aims and objectives, their work continues to be reviewed by the community before acceptance in an official release.
What can a core developer do on a project?
Each project might define this role slightly differently. However, the general usage of this designation is that an individual has risen to a level of trust within the community such that they now are given some control. This comes in the form of push rights to non-protected branches, and the ability to have a voice in the approval of pull requests.
The projects employ various communication mechanisms to ensure that all contributions are reviewed by the community as a whole. This includes tools provided by GitHub, as well as the Community Forums. By the time a contributor is invited to become a core developer, they should be familiar with the various tools and workflows as a user and then as a contributor.
How to become a core developer?
Anyone can become a core developer; there are no special requirements, other than to have shown a willingness and ability to positively participate in the project as a team player.
Typically, a potential core developer will need to show that they have an understanding of the project, its objectives and its strategy. They will also have provided valuable contributions to the project over a period of time. However, there is no technical or other skill requirement for eligibility.
New core developers can be nominated by any existing core developer at any time. At least twice a year (April and October) there will be a ballot process run by the Steering Council. Voting should be done by secret ballot. Each existing core developer for that project receives a number of votes equivalent to the number of nominees on the ballot. For example, if there are four nominees, then each existing core developer has four votes. The core developer may cast those votes however they choose, but may not vote for a single nominee more than once. A nominee must receive two-thirds approval from the number of cast ballots (not the number of eligible ballots). Once accepted by the core developers, it is the responsibility of the Steering Council to approve and finalize the nomination. The Steering Council does not have the right to determine whether a nominee is meritorious enough to receive the core developer title. However, they do retain the right to override a vote in cases where the health of the community would so require.
Once the vote has been held, the aggregated voting results are published on the Community Forums. The nominee is entitled to request an explanation of any override against them. A nominee that fails to be admitted as a core developer may be nominated again in the future.
It is important to recognize that being a core developer is a privilege, not a right. That privilege must be earned and once earned it can be removed by the Steering Council (see next section) in extreme circumstances. However, under normal circumstances the core developer title exists for as long as the individual wishes to continue engaging with the project and community.
A committer who shows an above-average level of contribution to the project, particularly with respect to its strategic direction and long-term health, may be nominated to become a member of the Steering Council, or a Release Manager. This role is described below.
What are the rights and responsibilities of core developers?
As discussed, the majority of decisions to be made are by consensus building. In certain circumstances where an issue has become more contentious, or a major decision needs to be made, the Release Manager or Steering Council may decide (or be required) to implement the RFC process, which is outlined in more detail below.
It is also incumbent upon core developers to have a voice in the governance of the community. All core developers for all of the projects have the ability to be nominated to be on the Steering Council and vote in their elections.
This Policy (the “SCOPE”) may only be changed under the authority of two-thirds of active core developers, except that in the first six (6) months after adoption, the core developers reserve the right to make changes under the authority of a simple majority of active core developers.
What if a core developer becomes inactive?
It is hoped that all core developers participate and remain active on a regular basis in their projects. However, it is also understood that such commitments may not be realistic or possible from time to time.
Therefore, the Steering Council has the duty to encourage participation and the responsibility to place core developers into an inactive status if they are no longer willing or capable to participate. The main purpose of this is not to punish a person for behavior, but to help the development process to continue for those that do remain active.
To this end, a core developer that becomes “inactive” shall not have commit rights to a repository, and shall not participate in any votes. To be eligible to vote in an election, a core developer must have been active at the time of the previous scheduled project release.
Inactive members may ask the Steering Council to reinstate their status at any time, and upon such request the Steering Council shall make the core developer active again.
Individuals that know they will be unable to maintain their active status for a period are asked to be in communication with the Steering Council and declare themselves inactive if necessary.
An “active” core developer is an individual that has participated in a meaningful way during the previous six months. Any further definition is within the discretion of the Steering Council.
Release Manager
Core developers shall have access only to make commits and merges on non-protected branches. The “master” branch and other protected branches are controlled by the release management team for that project. Release managers shall be elected from the core development team by the core development team, and shall serve for a full release cycle.
Each core developer team may decide how many release managers to have for each release cycle. It is highly encouraged that there be at least two release managers for a release cycle to help divide the responsibilities and not force too much effort upon a single person. However, there also should not be so many managers that their efforts are impeded.
The main responsibilities of the release management team include:
- push the development cycle forward by monitoring and facilitating technical discussions
- establish a release calendar and perform actions required to release packages
- approve pull requests to the master branch and other protected branches
- merge pull requests to the master branch and other protected branches
The release managers do not have the authority to veto or withhold a merge of a pull request that otherwise meets contribution criteria and has been accepted by the community. It is not their responsibility to decide what should be developed, but rather that the decisions of the community are carried out and that the project is being moved forward.
From time to time, a decision may need to be made that cannot be achieved through consensus. In that case, the release managers have the authority to call upon the removal of the decision to the RFC process. This should not occur regularly (unless required as discussed below), and its use should be discouraged in favor of the more communal consensus building strategy.
Since not all projects have the same requirements, the specifics governing release managers on a project shall be set forth in an Appendix to this Policy, or in the project’s contribution guidelines.
If necessary, the Steering Council has the right to remove a release manager that is derelict in their duties, or for other good cause.
Steering Council
The Steering Council is the governing body consisting of those individuals identified as the “project owner” and having control of the resources and assets of the SCO. Their ultimate goal is to ensure the smooth operation of the projects by removing impediments, and assisting the members as needed. It is expected that they will be regular voices in the community.
What can the Steering Council do?
The members of the Steering Council do not individually have any more authority than any other core developer, and shall not have any additional rights to make decisions, commits, merges, or the like on a project.
However, as a body, the Steering Council has the following capacity:
- accept, remand, and reject all RFCs
- enforce the community code of conduct
- administer community assets such as repositories, servers, forums, integration services, and the like (or, to delegate such authority to someone else)
- place core developers into inactive status where appropriate take any other enforcement measures afforded to it in this Policy, including, in extreme cases, removing core developers
- adopt or remove projects from the community umbrella
It is highly encouraged that the Steering Council delegate its authority as much as possible, and where appropriate, to other willing community members.
The Steering Council does not have the authority to change this Policy.
How many members are on the Steering Council?
Four.
While it seems like a committee with four votes may potentially end in a deadlock with no way to break a majority vote, the Steering Council is discouraged from voting as much as possible. Instead, it should try to work by consensus, and requires three consenting votes when it is necessary to vote on a matter.
How long do members serve on the Steering Council?
A single term shall be for two calendar years starting in January. Terms shall be staggered so that each year there are two members continuing from the previous year’s council.
Therefore, the inaugural vote shall have two positions available for a two year term, and two positions available for a one year term.
There are no limits to the number of terms that can be served, and it is possible for an individual to serve consecutive terms.
Who runs the Steering Council?
After the Steering Council is elected, the group shall collectively decide upon one person to act as the Chair. The Chair does not have any additional rights or authority over any other member of the Steering Council.
The role of the Chair is merely as a coordinator and facilitator. The Chair is expected to ensure that all governance processes are adhered to. The position is more administrative and clerical, and is expected that the Chair sets agendas and coordinates discussion of the group.
How are council members elected?
Once a year, all eligible core developers for each of the projects shall have the right to elect members to the Steering Council.
Nominations shall be open from September 1 and shall close on September 30. After that, voting shall begin on October 1 and shall close on October 31. Every core developer active on the date of the June release of the Sanic Framework for that year shall be eligible to receive one vote per vacant seat on the Steering Council. For the sake of clarity, to be eligible to vote, a core developer does not need to be a core developer on Sanic Framework, but rather just have been active within their respective project on that date.
The top recipients of votes shall be declared the winners. If there is any tie, it is highly encouraged that the tied nominees themselves resolve the dispute before a decision is made at random.
In regards to the inaugural vote of the Steering Council, the top two vote-recipients shall serve for two years, and the next two vote-recipients shall assume the one-year seats.
To be an eligible candidate for the Steering Council, the individual must have been a core developer in active status on at least one project for the previous twelve months.
What if there is a vacancy?
If a vacancy on the Steering Council exists during a term, then the next highest vote-recipient in the previous election shall be offered to complete the remainder of the term. If one cannot be found this way, the Steering Council may decide the most appropriate course of action to fill the seat (whether by appointment, vote, or other means).
If a member of the Steering Council becomes inactive, then that individual shall be removed from the Steering Council immediately and the seat shall become vacant.
In extreme cases, the body of all core developers has the right to bring a vote to remove a member of the Steering Council for cause by a two-thirds majority of all eligible voting core developers.
How shall the Steering Council conduct its business?
As much as possible, the Steering Council shall conduct its business and discussions in the open. Any member of the community should be allowed to enter the conversation with them. However, at times it may be necessary or appropriate for discussions to be held privately. Selecting the proper venue for conversations is part of the administrative duties of the Chair.
While the specifics of how to operate are beyond the scope of the Policy, it is encouraged that the Steering Council attempt to meet at least one time per quarter in a “real-time” discussion. This could be achieved via video conferencing, live chatting, or other appropriate means.
Support
All participants in the community are encouraged to provide support for users within the project management infrastructure. This support is provided as a way of growing the community. Those seeking support should recognize that all support activity within the project is voluntary and is therefore provided as and when time allows. A user requiring guaranteed response times or results should therefore seek to purchase a support contract from a community member. However, for those willing to engage with the project on its own terms, and willing to help support other users, the community support channels are ideal.
Decision making process
Decisions about the future of the projects are made through discussion with all members of the community, from the newest user to the most experienced member. Everyone has a voice.
All non-sensitive project management discussion takes place on the community forums, or other designated channels. Occasionally, sensitive discussions may occur in private.
In order to ensure that the project is not bogged down by endless discussion and continual voting, the project operates a policy of lazy consensus. This allows the majority of decisions to be made without resorting to a formal vote. For any major decision (as defined below), there is a separate Request for Comment (RFC) process.
Technical decisions
Pull requests and technical decisions should generally fall into the following categories.
- Routine: Documentation fixes, code changes that are for cleanup or additional testing. No functionality changes.
- Minor: Changes to the code base that either fix a bug, or introduce a trivial feature. No breaking changes.
- Major: Any change to the code base that breaks or deprecates existing API, alters operation in a non-trivial manner, or adds a significant feature.
It is generally the responsibility of the release managers to make sure that changes to the repositories receive the proper authorization before merge.
The release managers retain the authority to individually review and accept routine decisions that meet standards for code quality without additional input.
Lazy consensus
Decision making (whether by the community or Steering Council) typically involves the following steps:
- proposal
- discussion
- vote (if consensus is not reached through discussion)
- decision
Any community member can make a proposal for consideration by the community. In order to initiate a discussion about a new idea, they should post a message on the appropriate channel on the Community forums, or submit a pull request implementing the idea on GitHub. This will prompt a review and, if necessary, a discussion of the idea.
The goal of this review and discussion is to gain approval for the contribution. Since most people in the project community have a shared vision, there is often little need for discussion in order to reach consensus.
In general, as long as nobody explicitly opposes a proposal or patch, it is recognized as having the support of the community. This is called lazy consensus; that is, those who have not stated their opinion explicitly have implicitly agreed to the implementation of the proposal.
Lazy consensus is a very important concept within the SCO. It is this process that allows a large group of people to efficiently reach consensus, as someone with no objections to a proposal need not spend time stating their position, and others need not spend time reading such messages.
For lazy consensus to be effective, it is necessary to allow an appropriate amount of time before assuming that there are no objections to the proposal. This is somewhat dependent upon the circumstances, but it is generally assumed that 72 hours is reasonable. This requirement ensures that everyone is given enough time to read, digest and respond to the proposal. This time period is chosen so as to be as inclusive as possible of all participants, regardless of their location and time commitments. The facilitators of discussion (whether it be the Chair or the Release Managers, where applicable) shall be charged with determining the proper length of time for such consensus to be reached.
As discussed above regarding so-called routine decisions, the release managers have the right to make decisions within a shorter period of time. In such cases, lazy consensus shall be implied.
Request for Comment (RFC)
The Steering Council shall be in charge of overseeing the RFC process. It shall be a process that remains open to debate to all members of the community, and shall allow for ample time to consider a proposal and for members to respond and engage in meaningful discussion.
The final decision is vested with the Steering Council. However, it is strongly discouraged that the Steering Council adopt a decision that is contrary to any consensus that may exist in the community. From time to time this may happen if there is a conflict between consensus and the overall project and community goals.
An RFC shall be initiated by submission to the Steering Council in the public manner as set forth by the Steering Council. Debate shall continue and be facilitated by the Steering Council in general, and the Chair specifically.
In circumstances that the Steering Council feels it is appropriate, the RFC process may be waived in favor of lazy consensus.
Guide Content En Release Notes Changelog (`guide/content/en/release-notes/changelog.md`)
content_class: changelog
Changelog
🔶 Current release
🔷 In support LTS release
Version 25.12.0 🔶
Current LTS version
Features
- #3071 Add automatic
charset=UTF-8to text content types - #3079 Add
DetailedConverterfor advanced environment variable conversion - #3080 Update
str_to_boolfunction to includenopeas a valid false value - #3102 Better server error messaging
- #3110 Add daemon mode to Sanic CLI
- #3114 Return task when creating a task
- #3117 Explicit symlink params for static files/dirs
Bugfixes
- #3064 Change the log type to debug
- #3068 Silent on
RuntimeErrorwhenwrite_eof - #3077 Fix
WorkerManager.killon Windows - #3085 Fix
AttributeErrorinclose_if_idle()when_httpis not initialized - #3086 Fix race condition in worker restart causing spawn failure
- #3088 Fixing bad term cleanup at exit
- #3119 Fix static file serving for directories with CJK characters
- #3120 Respect
LOG_EXTRAin all cases - #3121 Respect
KEEP_ALIVEconfig - #3122 Check state in shutdown for handling uvloop double kill
Deprecations and Removals
- #3115 Remove Python 3.9 support and add Python 3.14
Developer infrastructure
- #3083 Add typing for parameters of constructor of
WorkerManager - #3084 Create baseline for bandit to remove false positives
- #3091 Use secrets for generating unique ping payloads
- #3094 Add some typing and fix some tests
- #3095 Update required Python to >=3.10
- #3101 Upgrade tracerite to latest
- #3107 Testing improvements
- #3108 Move to 2-stage coverage check
- #3109 Bump
dawidd6/action-download-artifactfrom 3 to 6
Improved Documentation
- #3035 Fixes
sanic_extopenapi component documentation - #3054 Fix 'how we built sanic' sidebar link
- #3056 Fix broken link in website
- #3057 Add correct path for Contribution guidelines
- #3065 Fixed incorrect links throughout the documentation
- #3066 Single letter typo fix for request.md documentation
Version 25.3.0
Features
- #3030 Improve
websocketsimport ordering - #3042 Add REPL context
- #3046 Support latest v14
websockets - #3049 Subclassing
HTTPMethodViewto allow generics
Bugfixes
Developer infrastructure
- #3023 Cleanup from Python 3.8 removal
- #3024 Improve type hinting
- #3028 Add missing tests
- #3041 Improve GitHub Actions checks
Version 24.12.0 🔷
Features
- #3019 Add custom commands to
sanicCLI
Bugfixes
- #2992 Fix
mixins.startup.serveUnboundLocalError - #3000 Fix type annocation for
JSONResponsemethod for return typebytesallowed fordumpscallable - #3009 Fix
SanicException.quietattribute handling when set toFalse - #3014 Cleanup some typing
- #3015 Kill the entire process group if applicable
- #3016 Fix incompatible type annotation of get method in the HTTPMethodView class
Deprecations and Removals
- #3020 Remove Python 3.8 support
Developer infrastructure
- #3017 Cleanup setup.cfg
Improved Documentation
- #3007 Fix typo in documentation for
sanic-ext
Version 24.6.0
Features
- #2838 Simplify request cookies
getlist - #2850 Unix sockets can now use
pathlib.Path - #2931 #2958 Logging improvements
- #2947 Make the .message field on exceptions non-empty
- #2961 #2964 Allow for custom name generation
Bugfixes
- #2919 Remove deprecation notice in websockets
- #2937 Resolve response streaming error when in ASGI mode
- #2959 Resolve Python 3.12 deprecation notic
- #2960 Ensure proper intent for noisy exceptions
- #2970 #2978 Fix missing dependencies for 3.12
- #2971 Fix middleware exceptions on Not Found routes with error in middleware
- #2973 Resolve cheduling logic for
transport.closeandtransport.abort - #2976 Fix deleting a cookie that was created with
secure=False - #2979 Throw error on bad body length
- #2980 Throw error on bad body encoding
Deprecations and Removals
- #2899 Remove erroneous line from REPL impacting environments without HTTPX
- #2962 Merge entity header removal
Developer infrastructure
- #2882 #2896 Apply dynamic port fixture for improving tests with port selection
- #2887 Updates to docker image builds
- #2932 Cleanup code base with Ruff
Improved Documentation
- #2924 Cleanup markdown on html5tagger page
- #2930 Cleanup typo on Sanic Extensions README.md
- #2934 Add more context to the health check documents
- #2936 Improve worker manager documentation
- #2955 Fixed wrong formatting in
request.md
Version 23.12.0 🔷
Features
- #2775 Start and restart arbitrary processes
- #2811 Cleaner process management in shutdown
- #2812 Suppress task cancel traceback on open websocket
- #2822 Listener and signal prioritization
- #2831 Reduce memory consumption
- #2837 Accept bare cookies
- #2841 Add
websocket.handler.signals - #2805 Add changed files to reload trigger listeners
- #2813 Allow for simple signals
- #2827 Improve functionality and consistency of
Sanic.event() - #2851 Allow range requests for a single byte
- #2854 Better
Request.schemefor websocket requests - #2858 Convert Sanic
Requestto a WebsocketsRequestfor handshake - #2859 Add a REPL to the
sanicCLI - #2870 Add Python 3.12 support
- #2875 Better exception on multiprocessing context conflicts
Bugfixes
- #2803 Fix MOTD display for extra data
Developer infrastructure
- #2796 Refactor unit test cases
- #2801 Fix
test_fastwhen there is only one CPU - #2807 Add constraint for autodocsum (lint issue in old package version)
- #2808 Refactor GitHub Actions
- #2814 Run CI pipeline on git push
- #2846 Drop old performance tests/benchmarks
- #2848 Makefile cleanup
- #2865
#2869
#2872
#2879
Add ruff to toolchain - #2866 Fix the alt svc test to run locally with explicit buffer nbytes
- #2877 Use Python's trusted publisher in deployments
- #2882 Introduce dynamic port fixture in targeted locations in the test suite
Improved Documentation
- #2781
#2821
#2861
#2863
Conversion of User Guide to the SHH (Sanic, html5tagger, HTMX) stack - #2810 Update README
- #2855 Edit Discord badge
- #2864 Adjust documentation for using state properties within http/https redirection document
Version 23.9.0
Due to circumstances at the time, v.23.9 was skipped.
Version 23.6.0
Features
- #2670 Increase
KEEP_ALIVE_TIMEOUTdefault to 120 seconds - #2716 Adding allow route overwrite option in blueprint
- #2724 and #2792 Add a new exception signal for ALL exceptions raised anywhere in application
- #2727 Add name prefixing to BP groups
- #2754 Update request type on middleware types
- #2770 Better exception message on startup time application induced import error
- #2776 Set multiprocessing start method early
- #2785 Add custom typing to config and ctx objects
- #2790 Add
request.client_ip
Bugfixes
- #2728 Fix traversals for intended results
- #2729 Handle case when headers argument of ResponseStream constructor is None
- #2737 Fix type annotation for
JSONREsponsedefault content type - #2740 Use Sanic's serializer for JSON responses in the Inspector
- #2760 Support for
Request.get_currentin ASGI mode - #2773 Alow Blueprint routes to explicitly define error_format
- #2774 Resolve headers on different renderers
- #2782 Resolve pypy compatibility issues
Deprecations and Removals
- #2777 Remove Python 3.7 support
Developer infrastructure
Improved Documentation
- #2741 Better documentation examples about running Sanic
From that list, the items to highlight in the release notes:
Version 23.3.0
Features
#2545 Standardize init of exceptions for more consistent control of HTTP responses using exceptions
#2606 Decode headers as UTF-8 also in ASGI
#2646 Separate ASGI request and lifespan callables
#2659 Use
FALLBACK_ERROR_FORMATfor handlers that returnempty()#2662 Add basic file browser (HTML page) and auto-index serving
#2667 Nicer traceback formatting (HTML page)
#2668 Smarter error page rendering format selection; more reliant upon header and "common sense" defaults
#2680 Check the status of socket before shutting down with
SHUT_RDWR#2687 Refresh
Request.acceptfunctionality to be more performant and spec-compliant#2696 Add header accessors as properties
textExample-Field: Foo, Bar Example-Field: Bazpythonrequest.headers.example_field == "Foo, Bar,Baz"#2700 Simpler CLI targets
sh$ sanic path.to.module:app # global app instance $ sanic path.to.module:create_app # factory pattern $ sanic ./path/to/directory/ # simple serve#2701 API to define a number of workers in managed processes
#2704 Add convenience for dynamic changes to routing
#2706 Add convenience methods for cookie creation and deletion
pythonresponse = text("...") response.add_cookie("test", "It worked!", domain=".yummy-yummy-cookie.com")#2707 Simplified
parse_content_headerescaping to be RFC-compliant and remove outdated FF hack#2710 Stricter charset handling and escaping of request URLs
#2711 Consume body on
DELETEby default#2719 Allow
passwordto be passed to TLS context#2720 Skip middleware on
RequestCancelled#2721 Change access logging format to
%s#2722 Add
CertLoaderas application option for directly controllingSSLContextobjects#2725 Worker sync state tolerance on race condition
Bugfixes
- #2651 ASGI websocket to pass thru bytes as is
- #2697 Fix comparison between datetime aware and naive in
filewhen usingIf-Modified-Since
Deprecations and Removals
- #2666 Remove deprecated
__blueprintname__property
Improved Documentation
- #2712 Improved example using
'https'to create the redirect
Version 22.12.0
Current LTS version
Features
- #2569 Add
JSONResponseclass with some convenient methods when updating a response object - #2598 Change
uvlooprequirement to>=0.15.0 - #2609 Add compatibility with
websocketsv11.0 - #2610 Kill server early on worker error
- Raise deadlock timeout to 30s
- #2617 Scale number of running server workers
- #2621 #2634 Send
SIGKILLon subsequentctrl+cto force worker exit - #2622 Add API to restart all workers from the multiplexer
- #2624 Default to
spawnfor all subprocesses unless specifically set:pythonfrom sanic import Sanic Sanic.start_method = "fork" - #2625 Filename normalisation of form-data/multipart file uploads
- #2626 Move to HTTP Inspector:
- Remote access to inspect running Sanic instances
- TLS support for encrypted calls to Inspector
- Authentication to Inspector with API key
- Ability to extend Inspector with custom commands
- #2632 Control order of restart operations
- #2633 Move reload interval to class variable
- #2636 Add
prioritytoregister_middlewaremethod - #2639 Add
unquotetoadd_routemethod - #2640 ASGI websockets to receive
textorbytes
Bugfixes
- #2607 Force socket shutdown before close to allow rebinding
- #2590 Use actual
StrEnumin Python 3.11+ - #2615 Ensure middleware executes only once per request timeout
- #2627 Crash ASGI application on lifespan failure
- #2635 Resolve error with low-level server creation on Windows
Deprecations and Removals
- #2608 #2630 Signal conditions and triggers saved on
signal.extra - #2626 Move to HTTP Inspector
- 🚨 BREAKING CHANGE: Moves the Inspector to a Sanic app from a simple TCP socket with a custom protocol
- DEPRECATE: The
--inspect*commands have been deprecated in favor ofinspect ...commands
- #2628 Replace deprecated
distutils.strtobool
Developer infrastructure
- #2612 Add CI testing for Python 3.11
Version 22.9.1
Features
- #2585 Improved error message when no applications have been registered
Bugfixes
- #2578 Add certificate loader for in process certificate creation
- #2591 Do not use sentinel identity for
spawncompatibility - #2592 Fix properties in nested blueprint groups
- #2595 Introduce sleep interval on new worker reloader
Deprecations and Removals
Developer infrastructure
- #2588 Markdown templates on issue forms
Improved Documentation
Version 22.9.0
Features
- #2445 Add custom loads function
- #2490 Make
WebsocketImplProtocolasync iterable - #2499 Sanic Server WorkerManager refactor
- #2506 Use
pathlibfor path resolution (for static file serving) - #2508 Use
path.partsinstead ofmatch(for static file serving) - #2513 Better request cancel handling
- #2516 Add request properties for HTTP method info:
request.is_saferequest.is_idempotentrequest.is_cacheable- See MDN docs for more information about when these apply
- #2522 Always show server location in ASGI
- #2526 Cache control support for static files for returning 304 when appropriate
- #2533 Refactor
_static_request_handler - #2540 Add signals before and after handler execution
http.handler.beforehttp.handler.after
- #2542 Add [redacted] to CLI :)
- #2546 Add deprecation warning filter
- #2550 Middleware priority and performance enhancements
Bugfixes
- #2495 Prevent directory traversion with static files
- #2515 Do not apply double slash to paths in certain static dirs in Blueprints
Deprecations and Removals
- #2525 Warn on duplicate route names, will be prevented outright in v23.3
- #2537 Raise warning and deprecation notice on duplicate exceptions, will be prevented outright in v23.3
Developer infrastructure
- #2504 Cleanup test suite
- #2505 Replace Unsupported Python Version Number from the Contributing Doc
- #2530 Do not include tests folder in installed package resolver
Improved Documentation
Version 22.6.2
Bugfixes
- #2522 Always show server location in ASGI
Version 22.6.1
Bugfixes
- #2477 Sanic static directory fails when folder name ends with ".."
Version 22.6.0
Features
- #2378 Introduce HTTP/3 and autogeneration of TLS certificates in
DEBUGmode- 👶 EARLY RELEASE FEATURE: Serving Sanic over HTTP/3 is an early release feature. It does not yet fully cover the HTTP/3 spec, but instead aims for feature parity with Sanic's existing HTTP/1.1 server. Websockets, WebTransport, push responses are examples of some features not yet implemented.
- 📦 EXTRA REQUIREMENT: Not all HTTP clients are capable of interfacing with HTTP/3 servers. You may need to install a HTTP/3 capable client.
- 📦 EXTRA REQUIREMENT: In order to use TLS autogeneration, you must install either mkcert or trustme.
- #2416 Add message to
task.cancel - #2420 Add exception aliases for more consistent naming with standard HTTP response types (
BadRequest,MethodNotAllowed,RangeNotSatisfiable) - #2432 Expose ASGI
scopeas a property on theRequestobject - #2438 Easier access to websocket class for annotation:
from sanic import Websocket - #2439 New API for reading form values with options:
Request.get_form - #2445 Add custom
loadsfunction - #2447, #2486 Improved API to support setting cache control headers
- #2453 Move verbosity filtering to logger
- #2475 Expose getter for current request using
Request.get_current()
Bugfixes
- #2448 Fix to allow running with
pythonw.exeor places where there is nosys.stdout - #2451 Trigger
http.lifecycle.requestsignal in ASGI mode - #2455 Resolve typing of stacked route definitions
- #2463 Properly catch websocket CancelledError in websocket handler in Python 3.7
Deprecations and Removals
- #2487 v22.6 deprecations and changes
- Optional application registry
- Execution of custom handlers after some part of response was sent
- Configuring fallback handlers on the
ErrorHandler - Custom
LOGOsetting sanic.response.streamAsyncioServer.init
Developer infrastructure
Improved Documentation
- #2461 Update example to match current application naming standards
- #2466 Better type annotation for
Extend - #2485 Improved help messages in CLI
Version 22.3.0
Features
- #2347 API for multi-application server
- 🚨 BREAKING CHANGE: The old
sanic.worker.GunicornWorkerhas been removed. To run Sanic withgunicorn, you should use it thruuvicornas described in their docs. - 🧁 SIDE EFFECT: Named background tasks are now supported, even in Python 3.7
- 🚨 BREAKING CHANGE: The old
- #2357 Parse
Authorizationheader asRequest.credentials - #2361 Add config option to skip
Touchupstep in application startup - #2372 Updates to CLI help messaging
- #2382 Downgrade warnings to backwater debug messages
- #2396 Allow for
multidictv0.6 - #2401 Upgrade CLI catching for alternative application run types
- #2402 Conditionally inject CLI arguments into factory
- #2413 Add new start and stop event listeners to reloader process
- #2414 Remove loop as required listener arg
- #2415 Better exception for bad URL parsing
- sanic-routing#47 Add a new extention parameter type:
<file:ext>,<file:ext=jpg>,<file:ext=jpg|png|gif|svg>,<file=int:ext>,<file=int:ext=jpg|png|gif|svg>,<file=float:ext=tar.gz>- 👶 BETA FEATURE: This feature will not work with
pathtype matching, and is being released as a beta feature only.
- 👶 BETA FEATURE: This feature will not work with
- sanic-routing#57 Change
register_patternto accept astrorPattern - sanic-routing#58 Default matching on non-empty strings only, and new
stroremptypattern type- 🚨 BREAKING CHANGE: 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>.
- 🚨 BREAKING CHANGE: Previously a route with a dynamic string parameter (
Bugfixes
- #2373 Remove
error_loggeron websockets - #2381 Fix newly assigned
Nonein task registry - sanic-routing#52 Add type casting to regex route matching
- sanic-routing#60 Add requirements check on regex routes (this resolves, for example, multiple static directories with differing
hostvalues)
Deprecations and Removals
- #2362 22.3 Deprecations and changes
debug=Trueand--debugdo NOT automatically runauto_reload- Default error render is with plain text (browsers still get HTML by default because
autolooks at headers) configis required forErrorHandler.finalizeErrorHandler.lookuprequires two positional args- Unused websocket protocol args removed
- #2344 Deprecate loading of lowercase environment variables
Developer infrastructure
- #2363 Revert code coverage back to Codecov
- #2405 Upgrade tests for
sanic-routingchanges - sanic-testing#35 Allow for httpx v0.22
Improved Documentation
- #2350 Fix link in README for ASGI
- #2398 Document middleware on_request and on_response
- #2409 Add missing documentation for
Request.respond
Miscellaneous
- #2376 Fix typing for
ListenerMixin.listener - #2383 Clear deprecation warning in
asyncio.wait - #2387 Cleanup
__slots__implementations - #2390 Clear deprecation warning in
asyncio.get_event_loop
Version 21.12.1
- #2349 Only display MOTD on startup
- #2354 Ignore name argument in Python 3.7
- #2355 Add config.update support for all config values
Version 21.12.0
Features
- #2260 Allow early Blueprint registrations to still apply later added objects
- #2262 Noisy exceptions - force logging of all exceptions
- #2264 Optional
uvloopby configuration - #2270 Vhost support using multiple TLS certificates
- #2277 Change signal routing for increased consistency
- BREAKING CHANGE: If you were manually routing signals there is a breaking change. The signal router's
getis no longer 100% determinative. There is now an additional step to loop thru the returned signals for proper matching on the requirements. If signals are being dispatched usingapp.dispatchorbp.dispatch, there is no change.
- BREAKING CHANGE: If you were manually routing signals there is a breaking change. The signal router's
- #2290 Add contextual exceptions
- #2291 Increase join concat performance
- #2295, #2316, #2331 Restructure of CLI and application state with new displays and more command parity with
app.run - #2302 Add route context at definition time
- #2304 Named tasks and new API for managing background tasks
- #2307 On app auto-reload, provide insight of changed files
- #2308 Auto extend application with Sanic Extensions if it is installed, and provide first class support for accessing the extensions
- #2309 Builtin signals changed to
Enum - #2313 Support additional config implementation use case
- #2321 Refactor environment variable hydration logic
- #2327 Prevent sending multiple or mixed responses on a single request
- #2330 Custom type casting on environment variables
- #2332 Make all deprecation notices consistent
- #2335 Allow underscore to start instance names
Bugfixes
- #2273 Replace assignation by typing for
websocket_handshake - #2285 Fix IPv6 display in startup logs
- #2299 Dispatch
http.lifecyle.responsefrom exception handler
Deprecations and Removals
- #2306 Removal of deprecated items
SanicandBlueprintmay no longer have arbitrary properties attached to themSanicandBlueprintforced to have compliant names- alphanumeric +
_+- - must start with letter or
_
- alphanumeric +
load_envkeyword argument ofSanicsanic.exceptions.abortsanic.views.CompositionViewsanic.response.StreamingHTTPResponse- NOTE: the
stream()response method (where you pass a callable streaming function) has been deprecated and will be removed in v22.6. You should upgrade all streaming responses to the new style: https://sanicframework.org/en/guide/advanced/streaming.html#response-streaming
- NOTE: the
- #2320 Remove app instance from Config for error handler setting
Developer infrastructure
- #2251 Change dev install command
- #2286 Change codeclimate complexity threshold from 5 to 10
- #2287 Update host test function names so they are not overwritten
- #2292 Fail CI on error
- #2311, #2324 Do not run tests for draft PRs
- #2336 Remove paths from coverage checks
- #2338 Cleanup ports on tests
Improved Documentation
Miscellaneous
- #2257, #2294, #2341 Add Python 3.10 support
- #2279, #2317, #2322 Add/correct missing type annotations
- #2305 Fix examples to use modern implementations
Version 21.9.3
Rerelease of v21.9.2 with some cleanup
Version 21.9.2
- #2268 Make HTTP connections start in IDLE stage, avoiding delays and error messages
- #2310 More consistent config setting with post-FALLBACK_ERROR_FORMAT apply
Version 21.9.1
- #2259 Allow non-conforming ErrorHandlers
Version 21.9.0
Features
- #2158, #2248 Complete overhaul of I/O to websockets
- #2160 Add new 17 signals into server and request lifecycles
- #2162 Smarter
autofallback formatting upon exception - #2184 Introduce implementation for copying a Blueprint
- #2200 Accept header parsing
- #2207 Log remote address if available
- #2209 Add convenience methods to BP groups
- #2216 Add default messages to SanicExceptions
- #2225 Type annotation convenience for annotated handlers with path parameters
- #2236 Allow Falsey (but not-None) responses from route handlers
- #2238 Add
exceptiondecorator to Blueprint Groups - #2244 Explicit static directive for serving file or dir (ex:
static(..., resource_type="file")) - #2245 Close HTTP loop when connection task cancelled
Bugfixes
- #2188 Fix the handling of the end of a chunked request
- #2195 Resolve unexpected error handling on static requests
- #2208 Make blueprint-based exceptions attach and trigger in a more intuitive manner
- #2211 Fixed for handling exceptions of asgi app call
- #2213 Fix bug where ws exceptions not being logged
- #2231 Cleaner closing of tasks by using
abort()in strategic places to avoid dangling sockets - #2247 Fix logging of auto-reload status in debug mode
- #2246 Account for BP with exception handler but no routes
Developer infrastructure
- #2194 HTTP unit tests with raw client
- #2199 Switch to codeclimate
- #2214 Try Reopening Windows Tests
- #2229 Refactor
HttpProtocolinto a base class - #2230 Refactor
server.pyinto multi-file module
Miscellaneous
- #2173 Remove Duplicated Dependencies and PEP 517 Support
- #2193, #2196, #2217 Type annotation changes
Version 21.6.1
Bugfixes
- #2178 Update
sanic-routing to allow for better splitting of complex URI
templates - #2183 Proper
handling of chunked request bodies to resolve phantom 503 in logs - #2181 Resolve
regression in exception logging - #2201 Cleanup
request info in pipelined requests
Version 21.6.0
Features
#2094 Add
response.eof()method for closing a stream in a handler#2097 Allow
case-insensitive HTTP Upgrade header#2104 Explicit
usage of CIMultiDict getters#2109 Consistent
use of error loggers#2114 New
client_ipaccess of connection info instance#2119 Alternatate
classes on instantiation forConfigandSanic.ctx#2133 Implement
new version of AST router- Proper differentiation between
alphaandstringparam
types - Adds a
slugparam type, example:<foo:slug> - Deprecates
<foo:string>in favor of<foo:str> - Deprecates
<foo:number>in favor of<foo:float> - Adds a
route.uriaccessor
- Proper differentiation between
#2136 CLI
improvements with new optional params#2137 Add
version_prefixto URL builders#2140 Event
autoregistration withEVENT_AUTOREGISTER#2146,
#2147 Require
stricter names onSanic()andBlueprint()#2150 Infinitely
reusable and nestableBlueprintandBlueprintGroup#2154 Upgrade
websocketsdependency to min version#2155 Allow for
maximum header sizes to be increased:REQUEST_MAX_HEADER_SIZE#2157 Allow app
factory pattern in CLI#2165 Change HTTP
methods to enums#2167 Allow
auto-reloading on additional directories#2168 Add simple
HTTP server to CLI#2170 Additional
methods for attachingHTTPMethodView
Bugfixes
- #2091 Fix
UserWarningin ASGI mode for missing__slots__ - #2099 Fix static
request handler logging exception on 404 - #2110 Fix
request.args.pop removes parameters inconsistently - #2107 Fix type
hinting for load_env - #2127 Make sure
ASGI ws subprotocols is a list - #2128 Fix issue
where Blueprint exception handlers do not consistently route to
proper handler
Deprecations and Removals
- #2156 Remove
config valueREQUEST_BUFFER_QUEUE_SIZE - #2170
CompositionViewdeprecated and marked for removal in 21.12 - #2172 Deprecate
StreamingHTTPResponse
Developer infrastructure
- #2149 Remove
Travis CI in favor of GitHub Actions
Improved Documentation
Version 21.3.2
Bugfixes
- #2081 Disable
response timeout on websocket connections - #2085 Make sure
that blueprints with no slash is maintained when applied
Version 21.3.1
Bugfixes
- #2076 Static files
inside subfolders are not accessible (404)
Version 21.3.0
Features
- #1876 Unified
streaming server - #2005 New
Request.idproperty - #2008 Allow
Pathlib Path objects to be passed toapp.static()helper - #2010,
#2031 New
startup-optimized router - #2018
#2064 Listeners
for main server process - #2032 Add raw
header info to request object - #2042
#2060
#2061 Introduce
Signals API - #2043 Add
__str__and__repr__to Sanic and Blueprint - #2047 Enable
versioning and strict slash on BlueprintGroup - #2053 Make
get_appname argument optional - #2055 JSON encoder
change via app - #2063 App and
connection level context objects
Bugfixes
- Resolve #1420
url_forwherestrict_slashesare on for a path ending in/ - Resolve #1525
Routing is incorrect with some special characters - Resolve #1653 ASGI
headers in body - Resolve #1722
Using curl in chunk mode - Resolve #1730
Extra content in ASGI streaming response - Resolve #1749
Restore broken middleware edge cases - Resolve #1785
#1804 Synchronous
error handlers - Resolve #1790
Protocol errors did not support async error handlers #1790 - Resolve #1824
Timeout on specific methods - Resolve #1875
Response timeout error from all routes after returning several
timeouts from a specific route - Resolve #1988
Handling of safe methods with body - #2001 Raise
ValueError when cookie max-age is not an integer
Deprecations and Removals
- #2007 * Config
usingfrom_envvar* Config usingfrom_pyfile* Config usingfrom_object - #2009 Remove Sanic
test client to its own package - #2036,
#2037 Drop Python
3.6 support Request.endpointdeprecated in favor ofRequest.name- handler type name prefixes removed (static, websocket, etc)
Developer infrastructure
- #1995 Create
FUNDING.yml - #2013 Add codeql
to CI pipeline - #2038 Codecov
configuration updates - #2049 Updated
setup.py to usefind_packages
Improved Documentation
- #1218
Documentation for sanic.log.* is missing - #1608 Add
documentation on calver and LTS - #1731 Support
mounting application elsewhere than at root path - #2006 Upgraded
type annotations and improved docstrings and API documentation - #2052 Fix some
examples and docs
Miscellaneous
Request.routeproperty- Better websocket subprotocols support
- Resolve bug with middleware in Blueprint Group when passed
callable - Moves common logic between Blueprint and Sanic into mixins
- Route naming changed to be more consistent
- request endpoint is the route name
- route names are fully namespaced
- Some new convenience decorators:
@app.main_process_start@app.main_process_stop@app.before_server_start@app.after_server_start@app.before_server_stop@app.after_server_stop@app.on_request@app.on_response
- Fixes
Allowheader that did not includeHEAD - Using "name" keyword in
url_forfor a "static" route where
name does not exist - Cannot have multiple
app.static()without using the named param - Using "filename" keyword in
url_foron a file route unquotein route def (not automatic)routes_allis tuples- Handler arguments are kwarg only
request.match_infois now a cached (and not computed) property- Unknown static file mimetype is sent as
application/octet-stream _hostkeyword inurl_for- Add charset default to
utf-8for text and js content types if
not specified - Version for a route can be str, float, or int
- Route has ctx property
- App has
routes_static,routes_dynamic,routes_regex - #2044 Code cleanup
and refactoring - #2072 Remove
BaseSanicmetaclass - #2074 Performance
adjustments inhandle_request_
Version 20.12.3
Bugfixes
- #2021 Remove
prefix from websocket handler name
Version 20.12.2
Dependencies
- #2026 Fix uvloop
to 0.14 because 0.15 drops Python 3.6 support - #2029 Remove old
chardet requirement, add in hard multidict requirement
Version 19.12.5
Dependencies
- #2025 Fix uvloop
to 0.14 because 0.15 drops Python 3.6 support - #2027 Remove old
chardet requirement, add in hard multidict requirement
Version 20.12.0
Features
- #1993 Add disable
app registry - #1945 Static route
more verbose if file not found - #1954 Fix static
routes registration on a blueprint - #1961 Add Python
3.9 support - #1962 Sanic CLI
upgrade - #1967 Update
aiofile version requirements - #1969 Update
multidict version requirements - #1970 Add py.typed
file - #1972 Speed
optimization in request handler - #1979 Add app
registry and Sanic class level app retrieval
Bugfixes
- #1965 Fix Chunked
Transport-Encoding in ASGI streaming response
Deprecations and Removals
- #1981 Cleanup and
remove deprecated code
Developer infrastructure
Improved Documentation
- #1951
Documentation improvements - #1983 Remove
duplicate contents in testing.rst - #1984 Fix typo in
routing.rst
Version 20.9.1
Bugfixes
- #1954 Fix static
route registration on blueprints - #1957 Removes
duplicate headers in ASGI streaming body
Version 19.12.3
Bugfixes
- #1959 Removes
duplicate headers in ASGI streaming body
Version 20.9.0
Features
- #1887 Pass
subprotocols in websockets (both sanic server and ASGI) - #1894
Automatically settest_modeflag on app instance - #1903 Add new
unified method for updating app values - #1906,
#1909 Adds
WEBSOCKET_PING_TIMEOUT and WEBSOCKET_PING_INTERVAL configuration
values - #1935 httpx
version dependency updated, it is slated for removal as a
dependency in v20.12 - #1937 Added auto,
text, and json fallback error handlers (in v21.3, the default will
change form html to auto)
Bugfixes
- #1897 Resolves
exception from unread bytes in stream
Deprecations and Removals
- #1903
config.from_envar, config.from_pyfile, and config.from_object are
deprecated and set to be removed in v21.3
Developer infrastructure
- #1890,
#1891 Update isort
calls to be compatible with new API - #1893 Remove
version section from setup.cfg - #1924 Adding
--strict-markers for pytest
Improved Documentation
- #1922 Add explicit
ASGI compliance to the README
Version 20.6.3
Bugfixes
- #1884 Revert
change to multiprocessing mode
Version 20.6.2
Features
- #1641 Socket
binding implemented properly for IPv6 and UNIX sockets
Version 20.6.1
Features
- #1760 Add version
parameter to websocket routes - #1866 Add
sanic
as an entry point command - #1880 Add handler
names for websockets for url_for usage
Bugfixes
- #1776 Bug fix for
host parameter issue with lists - #1842 Fix static
_handler pickling error - #1827 Fix reloader
on OSX py38 and Windows - #1848 Reverse
named_response_middlware execution order, to match normal response
middleware execution order - #1853 Fix pickle
error when attempting to pickle an application which contains
websocket routes
Deprecations and Removals
- #1739 Deprecate
body_bytes to merge into body
Developer infrastructure
- #1852 Fix naming
of CI test env on Python nightlies - #1857 Adjust
websockets version to setup.py - #1869 Wrap
run()'s "protocol" type annotation in Optional[]
Improved Documentation
- #1846 Update docs
to clarify response middleware execution order - #1865 Fixing rst
format issue that was hiding documentation
Version 20.6.0
Released, but unintentionally omitting PR #1880, so was replaced by
20.6.1
Version 20.3.0
Features
- #1762 Add
srv.start_serving()andsrv.serve_forever()toAsyncioServer - #1767 Make Sanic
usable onhypercorn -k trio myweb.app - #1768 No
tracebacks on normal errors and prettier error pages - #1769 Code cleanup
in file responses - #1793 and
#1819 Upgradestr.format()to f-strings - #1798 Allow
multiple workers on MacOS with Python 3.8 - #1820 Do not set
content-type and content-length headers in exceptions
Bugfixes
- #1748 Remove loop
argument inasyncio.Eventin Python 3.8 - #1764 Allow route
decorators to stack up again - #1789 Fix tests
using hosts yielding incorrecturl_for - #1808 Fix Ctrl+C
and tests on Windows
Deprecations and Removals
- #1800 Begin
deprecation in way of first-class streaming, removal ofbody_init,body_push, andbody_finish - #1801 Complete
deprecation from
#1666 of
dictionary context onrequestobjects. - #1807 Remove
server config args that can be read directly from app - #1818 Complete
deprecation ofapp.remove_routeandrequest.raw_args
Dependencies
- #1794 Bump
httpx
to 0.11.1 - #1806 Import
ASGIDispatchfrom top-levelhttpx(from third-party
deprecation)
Developer infrastructure
- #1833 Resolve
broken documentation builds
Improved Documentation
- #1755 Usage of
response.empty() - #1778 Update
README - #1783 Fix typo
- #1784 Corrected
changelog for docs move of MD to RST
(#1691) - #1803 Update
config docs to match DEFAULT_CONFIG - #1814 Update
getting_started.rst - #1821 Update to
deployment - #1822 Update docs
with changes done in 20.3 - #1834 Order of
listeners
Version 19.12.0
Bugfixes
Fix blueprint middleware application
Currently, any blueprint middleware registered, irrespective of
which blueprint was used to do so, was being applied to all of the
routes created by the@appand@blueprintalike.As part of this change, the blueprint based middleware application
is enforced based on where they are registered.- If you register a middleware via
@blueprint.middlewarethen it
will apply only to the routes defined by the blueprint. - If you register a middleware via
@blueprint_group.middleware
then it will apply to all blueprint based routes that are part
of the group. - If you define a middleware via
@app.middlewarethen it will be
applied on all available routes
(#37)
- If you register a middleware via
Fix [url_for]{.title-ref} behavior with missing SERVER_NAME
If the [SERVER_NAME]{.title-ref} was missing in the
[app.config]{.title-ref} entity, the [url_for]{.title-ref} on the
[request]{.title-ref} and [app]{.title-ref} were failing due to an
[AttributeError]{.title-ref}. This fix makes the availability of
[SERVER_NAME]{.title-ref} on our [app.config]{.title-ref} an
optional behavior.
(#1707)
Improved Documentation
Move docs from MD to RST
Moved all docs from markdown to restructured text like the rest of
the docs to unify the scheme and make it easier in the future to
update documentation.
(#1691)Fix documentation for [get]{.title-ref} and [getlist]{.title-ref} of
the [request.args]{.title-ref}Add additional example for showing the usage of
[getlist]{.title-ref} and fix the documentation string for
[request.args]{.title-ref} behavior
(#1704)
Version 19.6.3
Features
Enable Towncrier Support
As part of this feature, [towncrier]{.title-ref} is being introduced
as a mechanism to partially automate the process of generating and
managing change logs as part of each of pull requests.
(#1631)
Improved Documentation
- Documentation infrastructure changes
- Enable having a single common [CHANGELOG]{.title-ref} file for
both GitHub page and documentation - Fix Sphinix deprecation warnings
- Fix documentation warnings due to invalid [rst]{.title-ref}
indentation - Enable common contribution guidelines file across GitHub and
documentation via [CONTRIBUTING.rst]{.title-ref}
(#1631)
- Enable having a single common [CHANGELOG]{.title-ref} file for
Version 19.6.2
Features
- #1562 Remove
aiohttpdependency and create newSanicTestClientbased upon
requests-async - #1475 Added ASGI
support (Beta) - #1436 Add
Configure support from object string
Bugfixes
- #1587 Add missing
handle for Expect header. - #1560 Allow to
disable Transfer-Encoding: chunked. - #1558 Fix graceful
shutdown. - #1594 Strict
Slashes behavior fix
Deprecations and Removals
.. warning:: Warning
Sanic will not support Python 3.5 from version 19.6 and forward.
However, version 18.12LTS will have its support period extended thru
December 2020, and therefore passing Python\'s official support version
3.5, which is set to expire in September 2020.Version 19.3
Features
#1497 Add support
for zero-length and RFC 5987 encoded filename for
multipart/form-data requests.#1484 The type of
expiresattribute ofsanic.cookies.Cookieis now enforced to
be of typedatetime.#1482 Add support
for thestreamparameter ofsanic.Sanic.add_route()available
tosanic.Blueprint.add_route().#1481 Accept
negative values for route parameters with typeintornumber.#1476 Deprecated
the use ofsanic.request.Request.raw_args- it has a fundamental
flaw in which is drops repeated query string parameters. Addedsanic.request.Request.query_argsas a replacement for the
original use-case.#1472 Remove an
unwantedNonecheck in Request classreprimplementation. This
changes the defaultreprof a Request from<Request>to<Request: None />#1470 Added 2 new
parameters tosanic.app.Sanic.create_server:return_asyncio_server- whether to return an
asyncio.Server.asyncio_server_kwargs- kwargs to pass toloop.create_serverfor the event loop that sanic is using.
This is a breaking change.
#1499 Added a set
of test cases that test and benchmark route resolution.#1457 The type of
the"max-age"value in asanic.cookies.Cookieis now enforced
to be an integer. Non-integer values are replaced with0.#1445 Added the
endpointattribute to an incomingrequest, containing the name
of the handler function.#1423 Improved
request streaming.request.streamis now a bounded-size buffer
instead of an unbounded queue. Callers must now callawait request.stream.read()instead ofawait request.stream.get()to read each portion of the body.This is a breaking change.
Bugfixes
- #1502 Sanic was
prefetchingtime.time()and updating it once per second to avoid
excessivetime.time()calls. The implementation was observed to
cause memory leaks in some cases. The benefit of the prefetch
appeared to negligible, so this has been removed. Fixes
#1500 - #1501 Fix a bug in
the auto-reloader when the process was launched as a module i.e.python -m init0.mod1where the sanic server is started ininit0/mod1.pywithdebugenabled and imports another module ininit0. - #1376 Allow sanic
test client to bind to a random port by specifyingport=None
when constructing aSanicTestClient - #1399 Added the
ability to specify middleware on a blueprint group, so that all
routes produced from the blueprints in the group have the
middleware applied. - #1442 Allow the
the use theSANIC_ACCESS_LOGenvironment variable to
enable/disable the access log when not explicitly passed toapp.run(). This allows the access log to be disabled for example
when running via gunicorn.
Developer infrastructure
- #1529 Update
project PyPI credentials - #1515 fix linter
issue causing travis build failures (fix #1514) - #1490 Fix python
version in doc build - #1478 Upgrade
setuptools version and use native docutils in doc build - #1464 Upgrade
pytest, and fix caplog unit tests
Improved Documentation
- #1516 Fix typo at
the exception documentation - #1510 fix typo in
Asyncio example - #1486
Documentation typo - #1477 Fix grammar
in README.md - #1489 Added
"databases" to the extensions list - #1483 Add
sanic-zipkin to extensions list - #1487 Removed link
to deleted repo, Sanic-OAuth, from the extensions list - #1460 18.12
changelog - #1449 Add example
of amending request object - #1446 Update
README - #1444 Update
README - #1443 Update
README, including new logo - #1440 fix minor
type and pip install instruction mismatch - #1424
Documentation Enhancements
Note: 19.3.0 was skipped for packagement purposes and not released on
PyPI
Version 18.12
18.12.0
Changes:
- Improved codebase test coverage from 81% to 91%.
- Added stream_large_files and host examples in static_file
document - Added methods to append and finish body content on Request
(#1379) - Integrated with .appveyor.yml for windows ci support
- Added documentation for AF_INET6 and AF_UNIX socket usage
- Adopt black/isort for codestyle
- Cancel task when connection_lost
- Simplify request ip and port retrieval logic
- Handle config error in load config file.
- Integrate with codecov for CI
- Add missed documentation for config section.
- Deprecate Handler.log
- Pinned httptools requirement to version 0.0.10+
Fixes:
- Fix
remove_entity_headershelper function (#1415) - Fix TypeError when use Blueprint.group() to group blueprint
with default url_prefix, Use os.path.normpath to avoid invalid
url_prefix like api//v1 f8a6af1 Rename thehttpmodule tohelpersto prevent conflicts with the built-in Python http
library (fixes #1323) - Fix unittests on windows
- Fix Namespacing of sanic logger
- Fix missing quotes in decorator example
- Fix redirect with quoted param
- Fix doc for latest blueprint code
- Fix build of latex documentation relating to markdown lists
- Fix loop exception handling in app.py
- Fix content length mismatch in windows and other platform
- Fix Range header handling for static files (#1402)
- Fix the logger and make it work (#1397)
- Fix type pikcle->pickle in multiprocessing test
- Fix pickling blueprints Change the string passed in the
"name" section of the namedtuples in Blueprint to match the
name of the Blueprint module attribute name. This allows
blueprints to be pickled and unpickled, without errors, which
is a requirement of running Sanic in multiprocessing mode in
Windows. Added a test for pickling and unpickling blueprints
Added a test for pickling and unpickling sanic itself Added a
test for enabling multiprocessing on an app with a blueprint
(only useful to catch this bug if the tests are run on
Windows). - Fix document for logging
- Fix
Version 0.8
0.8.3
- Changes:
- Ownership changed to org 'sanic-org'
0.8.0
- Changes:
- Add Server-Sent Events extension (Innokenty Lebedev)
- Graceful handling of request_handler_task cancellation (Ashley
Sommer) - Sanitize URL before redirection (aveao)
- Add url_bytes to request (johndoe46)
- py37 support for travisci (yunstanford)
- Auto reloader support for OSX (garyo)
- Add UUID route support (Volodymyr Maksymiv)
- Add pausable response streams (Ashley Sommer)
- Add weakref to request slots (vopankov)
- remove ubuntu 12.04 from test fixture due to deprecation
(yunstanford) - Allow streaming handlers in add_route (kinware)
- use travis_retry for tox (Raphael Deem)
- update aiohttp version for test client (yunstanford)
- add redirect import for clarity (yingshaoxo)
- Update HTTP Entity headers (Arnulfo Solís)
- Add register_listener method (Stephan Fitzpatrick)
- Remove uvloop/ujson dependencies for Windows (abuckenheimer)
- Content-length header on 204/304 responses (Arnulfo Solís)
- Extend WebSocketProtocol arguments and add docs (Bob Olde
Hampsink, yunstanford) - Update development status from pre-alpha to beta (Maksim
Anisenkov) - KeepAlive Timeout log level changed to debug (Arnulfo Solís)
- Pin pytest to 3.3.2 because of pytest-dev/pytest#3170 (Maksim
Aniskenov) - Install Python 3.5 and 3.6 on docker container for tests (Shahin
Azad) - Add support for blueprint groups and nesting (Elias Tarhini)
- Remove uvloop for windows setup (Aleksandr Kurlov)
- Auto Reload (Yaser Amari)
- Documentation updates/fixups (multiple contributors)
- Fixes:
- Fix: auto_reload in Linux (Ashley Sommer)
- Fix: broken tests for aiohttp >= 3.3.0 (Ashley Sommer)
- Fix: disable auto_reload by default on windows (abuckenheimer)
- Fix (1143): Turn off access log with gunicorn (hqy)
- Fix (1268): Support status code for file response (Cosmo Borsky)
- Fix (1266): Add content_type flag to Sanic.static (Cosmo Borsky)
- Fix: subprotocols parameter missing from add_websocket_route
(ciscorn) - Fix (1242): Responses for CI header (yunstanford)
- Fix (1237): add version constraint for websockets (yunstanford)
- Fix (1231): memory leak - always release resource (Phillip Xu)
- Fix (1221): make request truthy if transport exists (Raphael
Deem) - Fix failing tests for aiohttp>=3.1.0 (Ashley Sommer)
- Fix try_everything examples (PyManiacGR, kot83)
- Fix (1158): default to auto_reload in debug mode (Raphael Deem)
- Fix (1136): ErrorHandler.response handler call too restrictive
(Julien Castiaux) - Fix: raw requires bytes-like object (cloudship)
- Fix (1120): passing a list in to a route decorator's host arg
(Timothy Ebiuwhe) - Fix: Bug in multipart/form-data parser (DirkGuijt)
- Fix: Exception for missing parameter when value is null
(NyanKiyoshi) - Fix: Parameter check (Howie Hu)
- Fix (1089): Routing issue with named parameters and different
methods (yunstanford) - Fix (1085): Signal handling in multi-worker mode (yunstanford)
- Fix: single quote in readme.rst (Cosven)
- Fix: method typos (Dmitry Dygalo)
- Fix: log_response correct output for ip and port (Wibowo
Arindrarto) - Fix (1042): Exception Handling (Raphael Deem)
- Fix: Chinese URIs (Howie Hu)
- Fix (1079): timeout bug when self.transport is None (Raphael
Deem) - Fix (1074): fix strict_slashes when route has slash (Raphael
Deem) - Fix (1050): add samesite cookie to cookie keys (Raphael Deem)
- Fix (1065): allow add_task after server starts (Raphael Deem)
- Fix (1061): double quotes in unauthorized exception (Raphael
Deem) - Fix (1062): inject the app in add_task method (Raphael Deem)
- Fix: update environment.yml for readthedocs (Eli Uriegas)
- Fix: Cancel request task when response timeout is triggered
(Jeong YunWon) - Fix (1052): Method not allowed response for RFC7231 compliance
(Raphael Deem) - Fix: IPv6 Address and Socket Data Format (Dan Palmer)
Note: Changelog was unmaintained between 0.1 and 0.7
Version 0.1
0.1.7
- Reversed static url and directory arguments to meet spec
0.1.6
- Static files
- Lazy Cookie Loading
0.1.5
- Cookies
- Blueprint listeners and ordering
- Faster Router
- Fix: Incomplete file reads on medium+ sized post requests
- Breaking: after_start and before_stop now pass sanic as their
first argument
0.1.4
- Multiprocessing
0.1.3
- Blueprint support
- Faster Response processing
0.1.1 - 0.1.2
- Struggling to update pypi via CI
0.1.0
- Released to public
Guide Content En Guide Advanced Class Based Views (`guide/content/en/guide/advanced/class-based-views.md`)
Class Based Views
Why use them?
.. column::
### The problem
A common pattern when designing an API is to have multiple functionality on the same endpoint that depends upon the HTTP method.
While both of these options work, they are not good design practices and may be hard to maintain over time as your project grows... column::
```python
@app.get("/foo")
async def foo_get(request):
...
@app.post("/foo")
async def foo_post(request):
...
@app.put("/foo")
async def foo_put(request):
...
@app.route("/bar", methods=["GET", "POST", "PATCH"])
async def bar(request):
if request.method == "GET":
...
elif request.method == "POST":
...
elif request.method == "PATCH":
...
```.. column::
### The solution
Class-based views are simply classes that implement response behavior to requests. They provide a way to compartmentalize handling of different HTTP request types at the same endpoint... column::
```python
from sanic.views import HTTPMethodView
class FooBar(HTTPMethodView):
async def get(self, request):
...
async def post(self, request):
...
async def put(self, request):
...
app.add_route(FooBar.as_view(), "/foobar")
```Defining a view
A class-based view should subclass :class:sanic.views.HTTPMethodView. You can then implement class methods with the name of the corresponding HTTP method. If a request is received that has no defined method, a 405: Method not allowed response will be generated.
.. column::
To register a class-based view on an endpoint, the `app.add_route` method is used. The first argument should be the defined class with the method `as_view` invoked, and the second should be the URL endpoint.
The available methods are:
- get
- post
- put
- patch
- delete
- head
- options.. column::
```python
from sanic.views import HTTPMethodView
from sanic.response import text
class SimpleView(HTTPMethodView):
def get(self, request):
return text("I am get method")
# You can also use async syntax
async def post(self, request):
return text("I am post method")
def put(self, request):
return text("I am put method")
def patch(self, request):
return text("I am patch method")
def delete(self, request):
return text("I am delete method")
app.add_route(SimpleView.as_view(), "/")
```Path parameters
.. column::
You can use path parameters exactly as discussed in [the routing section](../basics/routing.md)... column::
```python
class NameView(HTTPMethodView):
def get(self, request, name):
return text("Hello {}".format(name))
app.add_route(NameView.as_view(), "/<name>")
```Decorators
As discussed in the decorators section, often you will need to add functionality to endpoints with the use of decorators. You have two options with CBV:
- Apply to all HTTP methods in the view
- Apply individually to HTTP methods in the view
Let's see what the options look like:
.. column::
### Apply to all methods
If you want to add any decorators to the class, you can set the `decorators` class variable. These will be applied to the class when `as_view` is called... column::
```python
class ViewWithDecorator(HTTPMethodView):
decorators = [some_decorator_here]
def get(self, request, name):
return text("Hello I have a decorator")
def post(self, request, name):
return text("Hello I also have a decorator")
app.add_route(ViewWithDecorator.as_view(), "/url")
```.. column::
### Apply to individual methods
But if you just want to decorate some methods and not all methods, you can as shown here... column::
```python
class ViewWithSomeDecorator(HTTPMethodView):
@staticmethod
@some_decorator_here
def get(request, name):
return text("Hello I have a decorator")
def post(self, request, name):
return text("Hello I do not have any decorators")
@some_decorator_here
def patch(self, request, name):
return text("Hello I have a decorator")
```Generating a URL
.. column::
This works just like [generating any other URL](../basics/routing.md#generating-a-url), except that the class name is a part of the endpoint... column::
```python
@app.route("/")
def index(request):
url = app.url_for("SpecialClassView")
return redirect(url)
class SpecialClassView(HTTPMethodView):
def get(self, request):
return text("Hello from the Special Class View!")
app.add_route(SpecialClassView.as_view(), "/special_class_view")
```Guide Content En Guide Advanced Commands (`guide/content/en/guide/advanced/commands.md`)
Custom CLI Commands
Sanic ships with a CLI for running the Sanic server. Sometimes, you may have the need to enhance that CLI to run your own custom commands. Commands are invoked using the following basic pattern:
sanic path.to:app exec <command> [--arg=value].. column::
To enable this, you can use your `Sanic` app instance to wrap functions that can be callable from the CLI using the `@app.command` decorator... column::
```python
@app.command
async def hello(name="world"):
print(f"Hello, {name}.")
```.. column::
Now, you can easily invoke this command using the `exec` action... column::
```sh
sanic path.to:app exec hello --name=Adam
```Command handlers can be either synchronous or asynchronous. The handler can accept any number of keyword arguments, which will be passed in from the CLI.
.. column::
By default, the name of the function will be the command name. You can override this by passing the `name` argument to the decorator... column::
```python
@app.command(name="greet")
async def hello(name="world"):
print(f"Hello, {name}.")
```
```sh
sanic path.to:app exec greet --name=Adam
```.. warning::
This feature is still in **BETA** and may change in future versions. There is no type coercion or validation on the arguments passed in from the CLI, and the CLI will ignore any return values from the command handler. Future enhancements and changes are likely.Added in v24.12
Guide Content En Guide Advanced Proxy Headers (`guide/content/en/guide/advanced/proxy-headers.md`)
Proxy configuration
When you use a reverse proxy server (e.g. nginx), the value of request.ip will contain the IP of a proxy, typically 127.0.0.1. Almost always, this is not what you will want.
Sanic may be configured to use proxy headers for determining the true client IP, available as request.remote_addr. The full external URL is also constructed from header fields if available.
.. tip:: Heads up
Without proper precautions, a malicious client may use proxy headers to spoof its own IP. To avoid such issues, Sanic does not use any proxy headers unless explicitly enabled... column::
Services behind reverse proxies must configure one or more of the following [configuration values](../deployment/configuration.md):
- `FORWARDED_SECRET`
- `REAL_IP_HEADER`
- `PROXIES_COUNT`.. column::
```python
app.config.FORWARDED_SECRET = "super-duper-secret"
app.config.REAL_IP_HEADER = "CF-Connecting-IP"
app.config.PROXIES_COUNT = 2
```Forwarded header
In order to use the Forwarded header, you should set app.config.FORWARDED_SECRET to a value known to the trusted proxy server. The secret is used to securely identify a specific proxy server.
Sanic ignores any elements without the secret key, and will not even parse the header if no secret is set.
All other proxy headers are ignored once a trusted forwarded element is found, as it already carries complete information about the client.
To learn more about the Forwarded header, read the related MDN and Nginx articles.
Traditional proxy headers
IP Headers
When your proxy forwards you the IP address in a known header, you can tell Sanic what that is with the REAL_IP_HEADER config value.
X-Forwarded-For
This header typically contains a chain of IP addresses through each layer of a proxy. Setting PROXIES_COUNT tells Sanic how deep to look to get an actual IP address for the client. This value should equal the expected number of IP addresses in the chain.
Other X-headers
If a client IP is found by one of these methods, Sanic uses the following headers for URL parts:
- x-forwarded-proto
- x-forwarded-host
- x-forwarded-port
- x-forwarded-path
- x-scheme
Examples
In the following examples, all requests will assume that the endpoint looks like this:
@app.route("/fwd")
async def forwarded(request):
return json(
{
"remote_addr": request.remote_addr,
"scheme": request.scheme,
"server_name": request.server_name,
"server_port": request.server_port,
"forwarded": request.forwarded,
}
)Example 1
Without configured FORWARDED_SECRET, x-headers should be respected
curl localhost:8000/fwd \
-H 'Forwarded: for=1.1.1.1, for=injected;host=", for="[::2]";proto=https;host=me.tld;path="/app/";secret=mySecret,for=broken;;secret=b0rked, for=127.0.0.3;scheme=http;port=1234' \
-H "X-Real-IP: 127.0.0.2" \
-H "X-Forwarded-For: 127.0.1.1" \
-H "X-Scheme: ws" \
-H "Host: local.site" | jq.. column::
```python
# Sanic application config
app.config.PROXIES_COUNT = 1
app.config.REAL_IP_HEADER = "x-real-ip"
```.. column::
```bash
# curl response
{
"remote_addr": "127.0.0.2",
"scheme": "ws",
"server_name": "local.site",
"server_port": 80,
"forwarded": {
"for": "127.0.0.2",
"proto": "ws"
}
}
```Example 2
FORWARDED_SECRET now configured
curl localhost:8000/fwd \
-H 'Forwarded: for=1.1.1.1, for=injected;host=", for="[::2]";proto=https;host=me.tld;path="/app/";secret=mySecret,for=broken;;secret=b0rked, for=127.0.0.3;scheme=http;port=1234' \
-H "X-Real-IP: 127.0.0.2" \
-H "X-Forwarded-For: 127.0.1.1" \
-H "X-Scheme: ws" \
-H "Host: local.site" | jq.. column::
```python
# Sanic application config
app.config.PROXIES_COUNT = 1
app.config.REAL_IP_HEADER = "x-real-ip"
app.config.FORWARDED_SECRET = "mySecret"
```.. column::
```bash
# curl response
{
"remote_addr": "[::2]",
"scheme": "https",
"server_name": "me.tld",
"server_port": 443,
"forwarded": {
"for": "[::2]",
"proto": "https",
"host": "me.tld",
"path": "/app/",
"secret": "mySecret"
}
}
```Example 3
Empty Forwarded header -> use X-headers
curl localhost:8000/fwd \
-H "X-Real-IP: 127.0.0.2" \
-H "X-Forwarded-For: 127.0.1.1" \
-H "X-Scheme: ws" \
-H "Host: local.site" | jq.. column::
```python
# Sanic application config
app.config.PROXIES_COUNT = 1
app.config.REAL_IP_HEADER = "x-real-ip"
app.config.FORWARDED_SECRET = "mySecret"
```.. column::
```bash
# curl response
{
"remote_addr": "127.0.0.2",
"scheme": "ws",
"server_name": "local.site",
"server_port": 80,
"forwarded": {
"for": "127.0.0.2",
"proto": "ws"
}
}
```Example 4
Header present but not matching anything
curl localhost:8000/fwd \
-H "Forwarded: nomatch" | jq.. column::
```python
# Sanic application config
app.config.PROXIES_COUNT = 1
app.config.REAL_IP_HEADER = "x-real-ip"
app.config.FORWARDED_SECRET = "mySecret"
```.. column::
```bash
# curl response
{
"remote_addr": "",
"scheme": "http",
"server_name": "localhost",
"server_port": 8000,
"forwarded": {}
}
```Example 5
Forwarded header present but no matching secret -> use X-headers
curl localhost:8000/fwd \
-H "Forwarded: for=1.1.1.1;secret=x, for=127.0.0.1" \
-H "X-Real-IP: 127.0.0.2" | jq.. column::
```python
# Sanic application config
app.config.PROXIES_COUNT = 1
app.config.REAL_IP_HEADER = "x-real-ip"
app.config.FORWARDED_SECRET = "mySecret"
```.. column::
```bash
# curl response
{
"remote_addr": "127.0.0.2",
"scheme": "http",
"server_name": "localhost",
"server_port": 8000,
"forwarded": {
"for": "127.0.0.2"
}
}
```Example 6
Different formatting and hitting both ends of the header
curl localhost:8000/fwd \
-H 'Forwarded: Secret="mySecret";For=127.0.0.4;Port=1234' | jq.. column::
```python
# Sanic application config
app.config.PROXIES_COUNT = 1
app.config.REAL_IP_HEADER = "x-real-ip"
app.config.FORWARDED_SECRET = "mySecret"
```.. column::
```bash
# curl response
{
"remote_addr": "127.0.0.4",
"scheme": "http",
"server_name": "localhost",
"server_port": 1234,
"forwarded": {
"secret": "mySecret",
"for": "127.0.0.4",
"port": 1234
}
}
```Example 7
Test escapes (modify this if you see anyone implementing quoted-pairs)
curl localhost:8000/fwd \
-H 'Forwarded: for=test;quoted="\,x=x;y=\";secret=mySecret' | jq.. column::
```python
# Sanic application config
app.config.PROXIES_COUNT = 1
app.config.REAL_IP_HEADER = "x-real-ip"
app.config.FORWARDED_SECRET = "mySecret"
```.. column::
```bash
# curl response
{
"remote_addr": "test",
"scheme": "http",
"server_name": "localhost",
"server_port": 8000,
"forwarded": {
"for": "test",
"quoted": "\\,x=x;y=\\",
"secret": "mySecret"
}
}
```Example 8
Secret insulated by malformed field #1
curl localhost:8000/fwd \
-H 'Forwarded: for=test;secret=mySecret;b0rked;proto=wss;' | jq.. column::
```python
# Sanic application config
app.config.PROXIES_COUNT = 1
app.config.REAL_IP_HEADER = "x-real-ip"
app.config.FORWARDED_SECRET = "mySecret"
```.. column::
```bash
# curl response
{
"remote_addr": "test",
"scheme": "http",
"server_name": "localhost",
"server_port": 8000,
"forwarded": {
"for": "test",
"secret": "mySecret"
}
}
```Example 9
Secret insulated by malformed field #2
curl localhost:8000/fwd \
-H 'Forwarded: for=test;b0rked;secret=mySecret;proto=wss' | jq.. column::
```python
# Sanic application config
app.config.PROXIES_COUNT = 1
app.config.REAL_IP_HEADER = "x-real-ip"
app.config.FORWARDED_SECRET = "mySecret"
```.. column::
```bash
# curl response
{
"remote_addr": "",
"scheme": "wss",
"server_name": "localhost",
"server_port": 8000,
"forwarded": {
"secret": "mySecret",
"proto": "wss"
}
}
```Example 10
Unexpected termination should not lose existing acceptable values
curl localhost:8000/fwd \
-H 'Forwarded: b0rked;secret=mySecret;proto=wss' | jq.. column::
```python
# Sanic application config
app.config.PROXIES_COUNT = 1
app.config.REAL_IP_HEADER = "x-real-ip"
app.config.FORWARDED_SECRET = "mySecret"
```.. column::
```bash
# curl response
{
"remote_addr": "",
"scheme": "wss",
"server_name": "localhost",
"server_port": 8000,
"forwarded": {
"secret": "mySecret",
"proto": "wss"
}
}
```Example 11
Field normalization
curl localhost:8000/fwd \
-H 'Forwarded: PROTO=WSS;BY="CAFE::8000";FOR=unknown;PORT=X;HOST="A:2";PATH="/With%20Spaces%22Quoted%22/sanicApp?key=val";SECRET=mySecret' | jq.. column::
```python
# Sanic application config
app.config.PROXIES_COUNT = 1
app.config.REAL_IP_HEADER = "x-real-ip"
app.config.FORWARDED_SECRET = "mySecret"
```.. column::
```bash
# curl response
{
"remote_addr": "",
"scheme": "wss",
"server_name": "a",
"server_port": 2,
"forwarded": {
"proto": "wss",
"by": "[cafe::8000]",
"host": "a:2",
"path": "/With Spaces\"Quoted\"/sanicApp?key=val",
"secret": "mySecret"
}
}
```Example 12
Using "by" field as secret
curl localhost:8000/fwd \
-H 'Forwarded: for=1.2.3.4; by=_proxySecret' | jq.. column::
```python
# Sanic application config
app.config.PROXIES_COUNT = 1
app.config.REAL_IP_HEADER = "x-real-ip"
app.config.FORWARDED_SECRET = "_proxySecret"
```.. column::
```bash
# curl response
{
"remote_addr": "1.2.3.4",
"scheme": "http",
"server_name": "localhost",
"server_port": 8000,
"forwarded": {
"for": "1.2.3.4",
"by": "_proxySecret"
}
}
```Guide Content En Guide Advanced Signals (`guide/content/en/guide/advanced/signals.md`)
Signals
Signals provide a way for one part of your application to tell another part that something happened.
@app.signal("user.registration.created")
async def send_registration_email(**context):
await send_email(context["email"], template="registration")
@app.post("/register")
async def handle_registration(request):
await do_registration(request)
await request.app.dispatch(
"user.registration.created",
context={"email": request.json.email}
})Adding a signal
.. column::
The API for adding a signal is very similar to adding a route... column::
```python
async def my_signal_handler():
print("something happened")
app.add_signal(my_signal_handler, "something.happened.ohmy")
```.. column::
But, perhaps a slightly more convenient method is to use the built-in decorators... column::
```python
@app.signal("something.happened.ohmy")
async def my_signal_handler():
print("something happened")
```.. column::
If the signal requires conditions, make sure to add them while adding the handler... column::
```python
async def my_signal_handler1():
print("something happened")
app.add_signal(
my_signal_handler,
"something.happened.ohmy1",
conditions={"some_condition": "value"}
)
@app.signal("something.happened.ohmy2", conditions={"some_condition": "value"})
async def my_signal_handler2():
print("something happened")
```.. column::
Signals can also be declared on blueprints.. column::
```python
bp = Blueprint("foo")
@bp.signal("something.happened.ohmy")
async def my_signal_handler():
print("something happened")
```Built-in signals
In addition to creating a new signal, there are a number of built-in signals that are dispatched from Sanic itself. These signals exist to provide developers with more opportunities to add functionality into the request and server lifecycles.
Added in v21.9
.. column::
You can attach them just like any other signal to an application or blueprint instance... column::
```python
@app.signal("http.lifecycle.complete")
async def my_signal_handler(conn_info):
print("Connection has been closed")
```These signals are the signals that are available, along with the arguments that the handlers take, and the conditions that attach (if any).
| Event name | Arguments | Conditions |
|---|---|---|
http.routing.before |
request | |
http.routing.after |
request, route, kwargs, handler | |
http.handler.before |
request | |
http.handler.after |
request | |
http.lifecycle.begin |
conn_info | |
http.lifecycle.read_head |
head | |
http.lifecycle.request |
request | |
http.lifecycle.handle |
request | |
http.lifecycle.read_body |
body | |
http.lifecycle.exception |
request, exception | |
http.lifecycle.response |
request, response | |
http.lifecycle.send |
data | |
http.lifecycle.complete |
conn_info | |
http.middleware.before |
request, response | {"attach_to": "request"} or {"attach_to": "response"} |
http.middleware.after |
request, response | {"attach_to": "request"} or {"attach_to": "response"} |
server.exception.report |
app, exception | |
server.init.before |
app, loop | |
server.init.after |
app, loop | |
server.shutdown.before |
app, loop | |
server.shutdown.after |
app, loop |
Version 22.9 added http.handler.before and http.handler.after.
Version 23.6 added server.exception.report.
.. column::
To make using the built-in signals easier, there is an `Enum` object that contains all of the allowed built-ins. With a modern IDE this will help so that you do not need to remember the full list of event names as strings.
*Added in v21.12*.. column::
```python
from sanic.signals import Event
@app.signal(Event.HTTP_LIFECYCLE_COMPLETE)
async def my_signal_handler(conn_info):
print("Connection has been closed")
```Events
.. column::
Signals are based off of an _event_. An event, is simply a string in the following pattern:.. column::
```
namespace.reference.action
```.. tip:: Events must have three parts. If you do not know what to use, try these patterns:
- `my_app.something.happened`
- `sanic.notice.hello`Event parameters
.. column::
An event can be "dynamic" and declared using the same syntax as [path parameters](../basics/routing.md#path-parameters). This allows matching based upon arbitrary values... column::
```python
@app.signal("foo.bar.<thing>")
async def signal_handler(thing):
print(f"[signal_handler] {thing=}")
@app.get("/")
async def trigger(request):
await app.dispatch("foo.bar.baz")
return response.text("Done.")
```Checkout path parameters for more information on allowed type definitions.
.. info:: Only the third part of an event (the action) may be dynamic:
- `foo.bar.` 🆗
- `foo..baz` ❌Waiting
.. column::
In addition to executing a signal handler, your application can wait for an event to be triggered... column::
```python
await app.event("foo.bar.baz")
```.. column::
**IMPORTANT**: waiting is a blocking function. Therefore, you likely will want this to run in a [background task](../basics/tasks.md)... column::
```python
async def wait_for_event(app):
while True:
print("> waiting")
await app.event("foo.bar.baz")
print("> event found\n")
@app.after_server_start
async def after_server_start(app, loop):
app.add_task(wait_for_event(app))
```.. column::
If your event was defined with a dynamic path, you can use `*` to catch any action... column::
```python
@app.signal("foo.bar.<thing>")
...
await app.event("foo.bar.*")
```Dispatching
In the future, Sanic will dispatch some events automatically to assist developers to hook into life cycle events.
.. column::
Dispatching an event will do two things:
1. execute any signal handlers defined on the event, and
2. resolve anything that is "waiting" for the event to complete... column::
```python
@app.signal("foo.bar.<thing>")
async def foo_bar(thing):
print(f"{thing=}")
await app.dispatch("foo.bar.baz")
```
```
thing=baz
```Context
.. column::
Sometimes you may find the need to pass extra information into the signal handler. In our first example above, we wanted our email registration process to have the email address for the user... column::
```python
@app.signal("user.registration.created")
async def send_registration_email(**context):
print(context)
await app.dispatch(
"user.registration.created",
context={"hello": "world"}
)
```
```
{'hello': 'world'}
```.. tip:: FYI
Signals are dispatched in a background task.Blueprints
Dispatching blueprint signals works similar in concept to middleware. Anything that is done from the app level, will trickle down to the blueprints. However, dispatching on a blueprint, will only execute the signals that are defined on that blueprint.
.. column::
Perhaps an example is easier to explain:.. column::
```python
bp = Blueprint("bp")
app_counter = 0
bp_counter = 0
@app.signal("foo.bar.baz")
def app_signal():
nonlocal app_counter
app_counter += 1
@bp.signal("foo.bar.baz")
def bp_signal():
nonlocal bp_counter
bp_counter += 1
```.. column::
Running `app.dispatch("foo.bar.baz")` will execute both signals... column::
```python
await app.dispatch("foo.bar.baz")
assert app_counter == 1
assert bp_counter == 1
```.. column::
Running `bp.dispatch("foo.bar.baz")` will execute only the blueprint signal... column::
```python
await bp.dispatch("foo.bar.baz")
assert app_counter == 1
assert bp_counter == 2
```Guide Content En Guide Advanced Streaming (`guide/content/en/guide/advanced/streaming.md`)
Streaming
Request streaming
Sanic allows you to stream data sent by the client to begin processing data as the bytes arrive.
.. column::
When enabled on an endpoint, you can stream the request body using `await request.stream.read()`.
That method will return `None` when the body is completed... column::
```python
from sanic.views import stream
class SimpleView(HTTPMethodView):
@stream
async def post(self, request):
result = ""
while True:
body = await request.stream.read()
if body is None:
break
result += body.decode("utf-8")
return text(result)
```.. column::
It also can be enabled with a keyword argument in the decorator..... column::
```python
@app.post("/stream", stream=True)
async def handler(request):
...
body = await request.stream.read()
...
```.. column::
... or the `add_route()` method... column::
```python
bp.add_route(
bp_handler,
"/bp_stream",
methods=["POST"],
stream=True,
)
```.. tip:: FYI
Only post, put and patch decorators have stream argument.Response streaming
.. column::
Sanic allows you to stream content to the client... column::
```python
@app.route("/")
async def test(request):
response = await request.respond(content_type="text/csv")
await response.send("foo,")
await response.send("bar")
# Optionally, you can explicitly end the stream by calling:
await response.eof()
```This is useful in situations where you want to stream content to the client that originates in an external service, like a database. For example, you can stream database records to the client with the asynchronous cursor that asyncpg provides.
@app.route("/")
async def index(request):
response = await request.respond()
conn = await asyncpg.connect(database='test')
async with conn.transaction():
async for record in conn.cursor('SELECT generate_series(0, 10)'):
await response.send(record[0])You can explicitly end a stream by calling await response.eof(). It a convenience method to replace await response.send("", True). It should be called one time after your handler has determined that it has nothing left to send back to the client. While it is optional to use with Sanic server, if you are running Sanic in ASGI mode, then you must explicitly terminate the stream.
Calling eof became optional in v21.6
File streaming
.. column::
Sanic provides `sanic.response.file_stream` function that is useful when you want to send a large file. It returns a `StreamingHTTPResponse` object and will use chunked transfer encoding by default; for this reason Sanic doesn’t add `Content-Length` HTTP header in the response.
A typical use case might be streaming an video file... column::
```python
@app.route("/mp4")
async def handler_file_stream(request):
return await response.file_stream(
"/path/to/sample.mp4",
chunk_size=1024,
mime_type="application/metalink4+xml",
headers={
"Content-Disposition": 'Attachment; filename="nicer_name.meta4"',
"Content-Type": "application/metalink4+xml",
},
)
```.. column::
If you want to use the `Content-Length` header, you can disable chunked transfer encoding and add it manually simply by adding the `Content-Length` header... column::
```python
from aiofiles import os as async_os
from sanic.response import file_stream
@app.route("/")
async def index(request):
file_path = "/srv/www/whatever.png"
file_stat = await async_os.stat(file_path)
headers = {"Content-Length": str(file_stat.st_size)}
return await file_stream(
file_path,
headers=headers,
)
```Guide Content En Guide Advanced Versioning (`guide/content/en/guide/advanced/versioning.md`)
Versioning
It is standard practice in API building to add versions to your endpoints. This allows you to easily differentiate incompatible endpoints when you try and change your API down the road in a breaking manner.
Adding a version will add a /v{version} url prefix to your endpoints.
The version can be a int, float, or str. Acceptable values:
1,2,31.1,2.25,3.0"1","v1","v1.1"
Per route
.. column::
You can pass a version number to the routes directly... column::
```python
# /v1/text
@app.route("/text", version=1)
def handle_request(request):
return response.text("Hello world! Version 1")
# /v2/text
@app.route("/text", version=2)
def handle_request(request):
return response.text("Hello world! Version 2")
```Per Blueprint
.. column::
You can also pass a version number to the blueprint, which will apply to all routes in that blueprint... column::
```python
bp = Blueprint("test", url_prefix="/foo", version=1)
# /v1/foo/html
@bp.route("/html")
def handle_request(request):
return response.html("<p>Hello world!</p>")
```Per Blueprint Group
.. column::
In order to simplify the management of the versioned blueprints, you can provide a version number in the blueprint
group. The same will be inherited to all the blueprint grouped under it if the blueprints don't already override the
same information with a value specified while creating a blueprint instance.
When using blueprint groups for managing the versions, the following order is followed to apply the Version prefix to
the routes being registered.
1. Route Level configuration
2. Blueprint level configuration
3. Blueprint Group level configuration
If we find a more pointed versioning specification, we will pick that over the more generic versioning specification
provided under the Blueprint or Blueprint Group.. column::
```python
from sanic.blueprints import Blueprint
from sanic.response import json
bp1 = Blueprint(
name="blueprint-1",
url_prefix="/bp1",
version=1.25,
)
bp2 = Blueprint(
name="blueprint-2",
url_prefix="/bp2",
)
group = Blueprint.group(
[bp1, bp2],
url_prefix="/bp-group",
version="v2",
)
# GET /v1.25/bp-group/bp1/endpoint-1
@bp1.get("/endpoint-1")
async def handle_endpoint_1_bp1(request):
return json({"Source": "blueprint-1/endpoint-1"})
# GET /v2/bp-group/bp2/endpoint-2
@bp2.get("/endpoint-1")
async def handle_endpoint_1_bp2(request):
return json({"Source": "blueprint-2/endpoint-1"})
# GET /v1/bp-group/bp2/endpoint-2
@bp2.get("/endpoint-2", version=1)
async def handle_endpoint_2_bp2(request):
return json({"Source": "blueprint-2/endpoint-2"})
```Version prefix
As seen above, the version that is applied to a route is always the first segment in the generated URI path. Therefore, to make it possible to add path segments before the version, every place that a version argument is passed, you can also pass version_prefix.
The version_prefix argument can be defined in:
app.routeandbp.routedecorators (and all the convenience decorators also)BlueprintinstantiationBlueprint.groupconstructorBlueprintGroupinstantiationapp.blueprintregistration
If there are definitions in multiple places, a more specific definition overrides a more general. This list provides that hierarchy.
The default value of version_prefix is /v.
.. column::
An often requested feature is to be able to mount versioned routes on `/api`. This can easily be accomplished with `version_prefix`... column::
```python
# /v1/my/path
app.route("/my/path", version=1, version_prefix="/api/v")
```.. column::
Perhaps a more compelling usage is to load all `/api` routes into a single `BlueprintGroup`... column::
```python
# /v1/my/path
app = Sanic(__name__)
v2ip = Blueprint("v2ip", url_prefix="/ip", version=2)
api = Blueprint.group(v2ip, version_prefix="/api/version")
# /api/version2/ip
@v2ip.get("/")
async def handler(request):
return text(request.ip)
app.blueprint(api)
```We can therefore learn that a route's URI is:
version_prefix + version + url_prefix + URI definition.. tip::
Just like with `url_prefix`, it is possible to define path parameters inside a `version_prefix`. It is perfectly legitimate to do this. Just remember that every route will have that parameter injected into the handler.
```python
version_prefix="/<foo:str>/v"
```Added in v21.6
Guide Content En Guide Advanced Websockets (`guide/content/en/guide/advanced/websockets.md`)
Websockets
Sanic provides an easy to use abstraction on top of websockets.
Routing
.. column::
Websocket handlers can be hooked up to the router similar to regular handlers... column::
```python
from sanic import Request, Websocket
async def feed(request: Request, ws: Websocket):
pass
app.add_websocket_route(feed, "/feed")
```
```python
from sanic import Request, Websocket
@app.websocket("/feed")
async def feed(request: Request, ws: Websocket):
pass
```Handler
.. column::
Typically, a websocket handler will want to hold open a loop.
It can then use the `send()` and `recv()` methods on the second object injected into the handler.
This example is a simple endpoint that echos back to the client messages that it receives... column::
```python
from sanic import Request, Websocket
@app.websocket("/feed")
async def feed(request: Request, ws: Websocket):
while True:
data = "hello!"
print("Sending: " + data)
await ws.send(data)
data = await ws.recv()
print("Received: " + data)
```.. column::
You can simplify your loop by just iterating over the `Websocket` object in a for loop.
*Added in v22.9*.. column::
```python
from sanic import Request, Websocket
@app.websocket("/feed")
async def feed(request: Request, ws: Websocket):
async for msg in ws:
await ws.send(msg)
```Configuration
See configuration section for more details, however the defaults are shown below.
app.config.WEBSOCKET_MAX_SIZE = 2 ** 20
app.config.WEBSOCKET_PING_INTERVAL = 20
app.config.WEBSOCKET_PING_TIMEOUT = 20Guide Content En Guide Basics App (`guide/content/en/guide/basics/app.md`)
title: Sanic Application
Sanic Application
See API docs: sanic.app
Instance
.. column::
The most basic building block is the :class:`sanic.app.Sanic` instance. It is not required, but the custom is to instantiate this in a file called `server.py`... column::
```python
# /path/to/server.py
from sanic import Sanic
app = Sanic("MyHelloWorldApp")
```Application context
Most applications will have the need to share/reuse data or objects across different parts of the code base. Sanic helps be providing the ctx object on application instances. It is a free space for the developer to attach any objects or data that should existe throughout the lifetime of the application.
.. column::
The most common pattern is to attach a database instance to the application... column::
```python
app = Sanic("MyApp")
app.ctx.db = Database()
```.. column::
While the previous example will work and is illustrative, it is typically considered best practice to attach objects in one of the two application startup [listeners](./listeners)... column::
```python
app = Sanic("MyApp")
@app.before_server_start
async def attach_db(app, loop):
app.ctx.db = Database()
```App Registry
.. column::
When you instantiate a Sanic instance, that can be retrieved at a later time from the Sanic app registry. This can be useful, for example, if you need to access your Sanic instance from a location where it is not otherwise accessible... column::
```python
# ./path/to/server.py
from sanic import Sanic
app = Sanic("my_awesome_server")
# ./path/to/somewhere_else.py
from sanic import Sanic
app = Sanic.get_app("my_awesome_server")
```.. column::
If you call `Sanic.get_app("non-existing")` on an app that does not exist, it will raise :class:`sanic.exceptions.SanicException` by default. You can, instead, force the method to return a new instance of Sanic with that name... column::
```python
app = Sanic.get_app(
"non-existing",
force_create=True,
)
```.. column::
If there is **only one** Sanic instance registered, then calling `Sanic.get_app()` with no arguments will return that instance.. column::
```python
Sanic("My only app")
app = Sanic.get_app()
```Configuration
.. column::
Sanic holds the configuration in the `config` attribute of the `Sanic` instance. Configuration can be modified **either** using dot-notation **OR** like a dictionary... column::
```python
app = Sanic('myapp')
app.config.DB_NAME = 'appdb'
app.config['DB_USER'] = 'appuser'
db_settings = {
'DB_HOST': 'localhost',
'DB_NAME': 'appdb',
'DB_USER': 'appuser'
}
app.config.update(db_settings)
```.. note:: Heads up
Config keys _should_ be uppercase. But, this is mainly by convention, and lowercase will work most of the time.
```python
app.config.GOOD = "yay!"
app.config.bad = "boo"
```There is much more detail about configuration later on.
Factory pattern
Many of the examples in these docs will show the instantiation of the :class:sanic.app.Sanic instance in a file called server.py in the "global scope" (i.e. not inside a function). This is a common pattern for very simple "hello world" style applications, but it is often beneficial to use a factory pattern instead.
A "factory" is just a function that returns an instance of the object you want to use. This allows you to abstract the instantiation of the object, but also may make it easier to isolate the application instance.
.. column::
A super simple factory pattern could look like this:.. column::
```python
# ./path/to/server.py
from sanic import Sanic
from .path.to.config import MyConfig
from .path.to.some.blueprint import bp
def create_app(config=MyConfig) -> Sanic:
app = Sanic("MyApp", config=config)
app.blueprint(bp)
return app
```.. column::
When we get to running Sanic later, you will learn that the Sanic CLI can detect this pattern and use it to run your application... column::
```sh
sanic path.to.server:create_app
```Customization
The Sanic application instance can be customized for your application needs in a variety of ways at instantiation.
For complete details, see the API docs.
Custom configuration
.. column::
This simplest form of custom configuration would be to pass your own object directly into that Sanic application instance
If you create a custom configuration object, it is *highly* recommended that you subclass the :class:`sanic.config.Config` option to inherit its behavior. You could use this option for adding properties, or your own set of custom logic.
*Added in v21.6*.. column::
```python
from sanic.config import Config
class MyConfig(Config):
FOO = "bar"
app = Sanic(..., config=MyConfig())
```.. column::
A useful example of this feature would be if you wanted to use a config file in a form that differs from what is [supported](../running/configuration.md#using-sanicupdateconfig)... column::
```python
from sanic import Sanic, text
from sanic.config import Config
class TomlConfig(Config):
def __init__(self, *args, path: str, **kwargs):
super().__init__(*args, **kwargs)
with open(path, "r") as f:
self.apply(toml.load(f))
def apply(self, config):
self.update(self._to_uppercase(config))
def _to_uppercase(self, obj: Dict[str, Any]) -> Dict[str, Any]:
retval: Dict[str, Any] = {}
for key, value in obj.items():
upper_key = key.upper()
if isinstance(value, list):
retval[upper_key] = [
self._to_uppercase(item) for item in value
]
elif isinstance(value, dict):
retval[upper_key] = self._to_uppercase(value)
else:
retval[upper_key] = value
return retval
toml_config = TomlConfig(path="/path/to/config.toml")
app = Sanic(toml_config.APP_NAME, config=toml_config)
```Custom context
.. column::
By default, the application context is a [`SimpleNamespace()`](https://docs.python.org/3/library/types.html#types.SimpleNamespace) that allows you to set any properties you want on it. However, you also have the option of passing any object whatsoever instead.
*Added in v21.6*.. column::
```python
app = Sanic(..., ctx=1)
```
```python
app = Sanic(..., ctx={})
```
```python
class MyContext:
...
app = Sanic(..., ctx=MyContext())
```Custom requests
.. column::
It is sometimes helpful to have your own `Request` class, and tell Sanic to use that instead of the default. One example is if you wanted to modify the default `request.id` generator.
.. note:: Important
It is important to remember that you are passing the *class* not an instance of the class... column::
```python
import time
from sanic import Request, Sanic, text
class NanoSecondRequest(Request):
@classmethod
def generate_id(*_):
return time.time_ns()
app = Sanic(..., request_class=NanoSecondRequest)
@app.get("/")
async def handler(request):
return text(str(request.id))
```Custom error handler
.. column::
See [exception handling](../best-practices/exceptions.md#custom-error-handling) for more.. column::
```python
from sanic.handlers import ErrorHandler
class CustomErrorHandler(ErrorHandler):
def default(self, request, exception):
''' handles errors that have no error handlers assigned '''
# You custom error handling logic...
return super().default(request, exception)
app = Sanic(..., error_handler=CustomErrorHandler())
```Custom dumps function
.. column::
It may sometimes be necessary or desirable to provide a custom function that serializes an object to JSON data... column::
```python
import ujson
dumps = partial(ujson.dumps, escape_forward_slashes=False)
app = Sanic(__name__, dumps=dumps)
```.. column::
Or, perhaps use another library or create your own... column::
```python
from orjson import dumps
app = Sanic("MyApp", dumps=dumps)
```Custom loads function
.. column::
Similar to `dumps`, you can also provide a custom function for deserializing data.
*Added in v22.9*.. column::
```python
from orjson import loads
app = Sanic("MyApp", loads=loads)
```Custom typed application
Beginning in v23.6, the correct type annotation of a default Sanic application instance is:
sanic.app.Sanic[sanic.config.Config, types.SimpleNamespace]It refers to two generic types:
- The first is the type of the configuration object. It defaults to :class:
sanic.config.Config, but can be any subclass of that. - The second is the type of the application context. It defaults to
SimpleNamespace(), but can be any object as show above.
Let's look at some examples of how the type will change.
.. column::
Consider this example where we pass a custom subclass of :class:`sanic.config.Config` and a custom context object... column::
```python
from sanic import Sanic
from sanic.config import Config
class CustomConfig(Config):
pass
app = Sanic("test", config=CustomConfig())
reveal_type(app) # N: Revealed type is "sanic.app.Sanic[main.CustomConfig, types.SimpleNamespace]"
```
```
sanic.app.Sanic[main.CustomConfig, types.SimpleNamespace]
```.. column::
Similarly, when passing a custom context object, the type will change to reflect that... column::
```python
from sanic import Sanic
class Foo:
pass
app = Sanic("test", ctx=Foo())
reveal_type(app) # N: Revealed type is "sanic.app.Sanic[sanic.config.Config, main.Foo]"
```
```
sanic.app.Sanic[sanic.config.Config, main.Foo]
```.. column::
Of course, you can set both the config and context to custom types... column::
```python
from sanic import Sanic
from sanic.config import Config
class CustomConfig(Config):
pass
class Foo:
pass
app = Sanic("test", config=CustomConfig(), ctx=Foo())
reveal_type(app) # N: Revealed type is "sanic.app.Sanic[main.CustomConfig, main.Foo]"
```
```
sanic.app.Sanic[main.CustomConfig, main.Foo]
```This pattern is particularly useful if you create a custom type alias for your application instance so that you can use it to annotate listeners and handlers.
# ./path/to/types.py
from sanic.app import Sanic
from sanic.config import Config
from myapp.context import MyContext
from typing import TypeAlias
MyApp = TypeAlias("MyApp", Sanic[Config, MyContext])# ./path/to/listeners.py
from myapp.types import MyApp
def add_listeners(app: MyApp):
@app.before_server_start
async def before_server_start(app: MyApp):
# do something with your fully typed app instance
await app.ctx.db.connect()# ./path/to/server.py
from myapp.types import MyApp
from myapp.context import MyContext
from myapp.config import MyConfig
from myapp.listeners import add_listeners
app = Sanic("myapp", config=MyConfig(), ctx=MyContext())
add_listeners(app)Added in v23.6
Custom typed request
Sanic also allows you to customize the type of the request object. This is useful if you want to add custom properties to the request object, or be able to access your custom properties of a typed application instance.
The correct, default type of a Sanic request instance is:
sanic.request.Request[
sanic.app.Sanic[sanic.config.Config, types.SimpleNamespace],
types.SimpleNamespace
]It refers to two generic types:
- The first is the type of the application instance. It defaults to
sanic.app.Sanic[sanic.config.Config, types.SimpleNamespace], but can be any subclass of that. - The second is the type of the request context. It defaults to
types.SimpleNamespace, but can be any object as show above in custom requests.
Let's look at some examples of how the type will change.
.. column::
Expanding upon the full example above where there is a type alias for a customized application instance, we can also create a custom request type so that we can access those same type annotations.
Of course, you do not need type aliases for this to work. We are only showing them here to cut down on the amount of code shown... column::
```python
from sanic import Request
from myapp.types import MyApp
from types import SimpleNamespace
def add_routes(app: MyApp):
@app.get("/")
async def handler(request: Request[MyApp, SimpleNamespace]):
# do something with your fully typed app instance
results = await request.app.ctx.db.query("SELECT * FROM foo")
```.. column::
Perhaps you have a custom request object that generates a custom context object. You can type annotate it to properly access those properties with your IDE as shown here... column::
```python
from sanic import Request, Sanic
from sanic.config import Config
class CustomConfig(Config):
pass
class Foo:
pass
class RequestContext:
foo: Foo
class CustomRequest(Request[Sanic[CustomConfig, Foo], RequestContext]):
@staticmethod
def make_context() -> RequestContext:
ctx = RequestContext()
ctx.foo = Foo()
return ctx
app = Sanic(
"test", config=CustomConfig(), ctx=Foo(), request_class=CustomRequest
)
@app.get("/")
async def handler(request: CustomRequest):
# Full access to typed:
# - custom application configuration object
# - custom application context object
# - custom request context object
pass
```See more information in the custom request context section.
Added in v23.6
Guide Content En Guide Basics Cookies (`guide/content/en/guide/basics/cookies.md`)
Cookies
Reading
.. column::
Cookies can be accessed via the `Request` object’s `cookies` dictionary... column::
```python
@app.route("/cookie")
async def test(request):
test_cookie = request.cookies.get("test")
return text(f"Test cookie: {test_cookie}")
```.. tip:: FYI
💡 The `request.cookies` object is one of a few types that is a dictionary with each value being a `list`. This is because HTTP allows a single key to be reused to send multiple values.
Most of the time you will want to use the `.get()` method to access the first element and not a `list`. If you do want a `list` of all items, you can use `.getlist()`.
*Added in v23.3*Writing
.. column::
When returning a response, cookies can be set on the `Response` object: `response.cookies`. This object is an instance of `CookieJar` which is a special sort of dictionary that automatically will write the response headers for you... column::
```python
@app.route("/cookie")
async def test(request):
response = text("There's a cookie up in this response")
response.add_cookie(
"test",
"It worked!",
domain=".yummy-yummy-cookie.com",
httponly=True
)
return response
```Response cookies can be set like dictionary values and have the following parameters available:
path: str- The subset of URLs to which this cookie applies. Defaults to/.domain: str- Specifies the domain for which the cookie is valid. An explicitly specified domain must always start with a dot.max_age: int- Number of seconds the cookie should live for.expires: datetime- The time for the cookie to expire on the client’s browser. Usually it is better to use max-age instead.secure: bool- Specifies whether the cookie will only be sent via HTTPS. Defaults toTrue.httponly: bool- Specifies whether the cookie cannot be read by JavaScript.samesite: str- Available values: Lax, Strict, and None. Defaults toLax.comment: str- A comment (metadata).host_prefix: bool- Whether to add the__Host-prefix to the cookie.secure_prefix: bool- Whether to add the__Secure-prefix to the cookie.partitioned: bool- Whether to mark the cookie as partitioned.
To better understand the implications and usage of these values, it might be helpful to read the MDN documentation on setting cookies.
.. tip:: FYI
By default, Sanic will set the `secure` flag to `True` to ensure that cookies are only sent over HTTPS as a sensible default. This should not be impactful for local development since secure cookies over HTTP should still be sent to `localhost`. For more information, you should read the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies) on [secure cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Secure).Deleting
.. column::
Cookies can be removed semantically or explicitly... column::
```python
@app.route("/cookie")
async def test(request):
response = text("Time to eat some cookies muahaha")
# This cookie will be set to expire in 0 seconds
response.delete_cookie("eat_me")
# This cookie will self destruct in 5 seconds
response.add_cookie("fast_bake", "Be quick!", max_age=5)
return response
```
*Don't forget to add `path` or `domain` if needed!*Eating
.. column::
Sanic likes cookies.. column::
.. attrs::
:class: is-size-1 has-text-centered
🍪Guide Content En Guide Basics Handlers (`guide/content/en/guide/basics/handlers.md`)
Handlers
The next important building block are your handlers. These are also sometimes called "views".
In Sanic, a handler is any callable that takes at least a :class:sanic.request.Request instance as an argument, and returns either an :class:sanic.response.HTTPResponse instance, or a coroutine that does the same.
.. column::
Huh? 😕
It is a **function**; either synchronous or asynchronous.
The job of the handler is to respond to an endpoint and do something. This is where the majority of your business logic will go... column::
```python
def i_am_a_handler(request):
return HTTPResponse()
async def i_am_ALSO_a_handler(request):
return HTTPResponse()
```Two more important items to note:
You almost never will want to use :class:
sanic.response.HTTPresponsedirectly. It is much simpler to use one of the convenience methods.from sanic import jsonfrom sanic import htmlfrom sanic import redirect- etc
As we will see in the streaming section, you do not always need to return an object. If you use this lower-level API, you can control the flow of the response from within the handler, and a return object is not used.
.. tip:: Heads up
If you want to learn more about encapsulating your logic, checkout [class based views](../advanced/class-based-views.md). For now, we will continue forward with just function-based views.A simple function-based handler
The most common way to create a route handler is to decorate the function. It creates a visually simple identification of a route definition. We'll learn more about routing soon.
.. column::
Let's look at a practical example.
- We use a convenience decorator on our app instance: `@app.get()`
- And a handy convenience method for generating out response object: `text()`
Mission accomplished 💪.. column::
```python
from sanic import text
@app.get("/foo")
async def foo_handler(request):
return text("I said foo!")
```A word about _async_...
.. column::
It is entirely possible to write handlers that are synchronous.
In this example, we are using the _blocking_ `time.sleep()` to simulate 100ms of processing time. Perhaps this represents fetching data from a DB, or a 3rd-party website.
Using four (4) worker processes and a common benchmarking tool:
- **956** requests in 30.10s
- Or, about **31.76** requests/second.. column::
```python
@app.get("/sync")
def sync_handler(request):
time.sleep(0.1)
return text("Done.")
```.. column::
Just by changing to the asynchronous alternative `asyncio.sleep()`, we see an incredible change in performance. 🚀
Using the same four (4) worker processes:
- **115,590** requests in 30.08s
- Or, about **3,843.17** requests/second
.. attrs::
:class: is-size-2
🤯.. column::
```python
@app.get("/async")
async def async_handler(request):
await asyncio.sleep(0.1)
return text("Done.")
```Okay... this is a ridiculously overdramatic result. And any benchmark you see is inherently very biased. This example is meant to over-the-top show the benefit of async/await in the web world. Results will certainly vary. Tools like Sanic and other async Python libraries are not magic bullets that make things faster. They make them more efficient.
In our example, the asynchronous version is so much better because while one request is sleeping, it is able to start another one, and another one, and another one, and another one...
But, this is the point! Sanic is fast because it takes the available resources and squeezes performance out of them. It can handle many requests concurrently, which means more requests per second.
.. tip:: A common mistake!
Don't do this! You need to ping a website. What do you use? `pip install your-fav-request-library` 🙈
Instead, try using a client that is `async/await` capable. Your server will thank you. Avoid using blocking tools, and favor those that play well in the asynchronous ecosystem. If you need recommendations, check out [Awesome Sanic](https://github.com/mekicha/awesome-sanic).
Sanic uses [httpx](https://www.python-httpx.org/) inside of its testing package (sanic-testing) 😉.A fully annotated handler
For those that are using type annotations...
from sanic.response import HTTPResponse, text
from sanic.request import Request
@app.get("/typed")
async def typed_handler(request: Request) -> HTTPResponse:
return text("Done.")Naming your handlers
All handlers are named automatically. This is useful for debugging, and for generating URLs in templates. When not specified, the name that will be used is the name of the function.
.. column::
For example, this handler will be named `foo_handler`... column::
```python
# Handler name will be "foo_handler"
@app.get("/foo")
async def foo_handler(request):
return text("I said foo!")
```.. column::
However, you can override this by passing the `name` argument to the decorator... column::
```python
# Handler name will be "foo"
@app.get("/foo", name="foo")
async def foo_handler(request):
return text("I said foo!")
```.. column::
In fact, as you will, there may be times when you **MUST** supply a name. For example, if you use two decorators on the same function, you will need to supply a name for at least one of them.
If you do not, you will get an error and your app will not start. Names **must** be unique within your app... column::
```python
# Two handlers, same function,
# different names:
# - "foo_arg"
# - "foo"
@app.get("/foo/<arg>", name="foo_arg")
@app.get("/foo")
async def foo(request, arg=None):
return text("I said foo!")
```Guide Content En Guide Basics Headers (`guide/content/en/guide/basics/headers.md`)
Headers
Request and response headers are available in the Request and HTTPResponse objects, respectively. They make use of the multidict package that allows a single key to have multiple values.
.. tip:: FYI
Header keys are converted to *lowercase* when parsed. Capitalization is not considered for headers.Request
Sanic does attempt to do some normalization on request headers before presenting them to the developer, and also make some potentially meaningful extractions for common use cases.
.. column::
#### Tokens
Authorization tokens in the form `Token <token>` or `Bearer <token>` are extracted to the request object: `request.token`... column::
```python
@app.route("/")
async def handler(request):
return text(request.token)
```
```sh
curl localhost:8000 \
-H "Authorization: Token ABCDEF12345679"
ABCDEF12345679
```
```sh
curl localhost:8000 \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
```Proxy headers
Sanic has special handling for proxy headers. See the proxy headers section for more details.
Host header and dynamic URL construction
.. column::
The *effective host* is available via `request.host`. This is not necessarily the same as the host header, as it prefers proxy-forwarded host and can be forced by the server name setting.
Webapps should generally use this accessor so that they can function the same no matter how they are deployed. The actual host header, if needed, can be found via `request.headers`
The effective host is also used in dynamic URL construction via `request.url_for`, which uses the request to determine the external address of a handler.
.. tip:: Be wary of malicious clients
These URLs can be manipulated by sending misleading host headers. `app.url_for` should be used instead if this is a concern... column::
```python
app.config.SERVER_NAME = "https://example.com"
@app.route("/hosts", name="foo")
async def handler(request):
return json(
{
"effective host": request.host,
"host header": request.headers.get("host"),
"forwarded host": request.forwarded.get("host"),
"you are here": request.url_for("foo"),
}
)
```
```sh
curl localhost:8000/hosts
{
"effective host": "example.com",
"host header": "localhost:8000",
"forwarded host": null,
"you are here": "https://example.com/hosts"
}
```Other headers
.. column::
All request headers are available on `request.headers`, and can be accessed in dictionary form. Capitalization is not considered for headers, and can be accessed using either uppercase or lowercase keys... column::
```python
@app.route("/")
async def handler(request):
return json(
{
"foo_weakref": request.headers["foo"],
"foo_get": request.headers.get("Foo"),
"foo_getone": request.headers.getone("FOO"),
"foo_getall": request.headers.getall("fOo"),
"all": list(request.headers.items()),
}
)
```
```sh
curl localhost:9999/headers -H "Foo: one" -H "FOO: two"|jq
{
"foo_weakref": "one",
"foo_get": "one",
"foo_getone": "one",
"foo_getall": [
"one",
"two"
],
"all": [
[
"host",
"localhost:9999"
],
[
"user-agent",
"curl/7.76.1"
],
[
"accept",
"*/*"
],
[
"foo",
"one"
],
[
"foo",
"two"
]
]
}
```.. tip:: FYI
💡 The request.headers object is one of a few types that is a dictionary with each value being a list. This is because HTTP allows a single key to be reused to send multiple values.
Most of the time you will want to use the .get() or .getone() methods to access the first element and not a list. If you do want a list of all items, you can use .getall().Request ID
.. column::
Often it is convenient or necessary to track a request by its `X-Request-ID` header. You can easily access that as: `request.id`... column::
```python
@app.route("/")
async def handler(request):
return text(request.id)
```
```sh
curl localhost:8000 \
-H "X-Request-ID: ABCDEF12345679"
ABCDEF12345679
```Response
Sanic will automatically set the following response headers (when appropriate) for you:
content-lengthcontent-typeconnectiontransfer-encoding
In most circumstances, you should never need to worry about setting these headers.
.. column::
Any other header that you would like to set can be done either in the route handler, or a response middleware... column::
```python
@app.route("/")
async def handler(request):
return text("Done.", headers={"content-language": "en-US"})
@app.middleware("response")
async def add_csp(request, response):
response.headers["content-security-policy"] = "default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self';base-uri 'self';form-action 'self'"
```.. column::
A common [middleware](middleware.md) you might want is to add a `X-Request-ID` header to every response. As stated above: `request.id` will provide the ID from the incoming request. But, even if no ID was supplied in the request headers, one will be automatically supplied for you.
[See API docs for more details](https://sanic.readthedocs.io/en/latest/sanic/api_reference.html#sanic.request.Request.id).. column::
```python
@app.route("/")
async def handler(request):
return text(str(request.id))
@app.on_response
async def add_request_id_header(request, response):
response.headers["X-Request-ID"] = request.id
```
```sh
curl localhost:8000 -i
HTTP/1.1 200 OK
X-Request-ID: 805a958e-9906-4e7a-8fe0-cbe83590431b
content-length: 36
connection: keep-alive
content-type: text/plain; charset=utf-8
805a958e-9906-4e7a-8fe0-cbe83590431b
```Guide Content En Guide Basics Listeners (`guide/content/en/guide/basics/listeners.md`)
Listeners
Sanic provides you with eight (8) opportunities to inject an operation into the life cycle of your application server. This does not include the signals, which allow further injection customization.
There are two (2) that run only on your main Sanic process (ie, once per call to sanic server.app.)
main_process_startmain_process_stop
There are also two (2) that run only in a reloader process if auto-reload has been turned on.
reload_process_startreload_process_stop
Added reload_process_start and reload_process_stop in v22.3
There are four (4) that enable you to execute startup/teardown code as your server starts or closes.
before_server_startafter_server_startbefore_server_stopafter_server_stop
The life cycle of a worker process looks like this:
.. mermaid::
sequenceDiagram
autonumber
participant Process
participant Worker
participant Listener
participant Handler
Note over Process: sanic server.app
loop
Process->>Listener: @app.main_process_start
Listener->>Handler: Invoke event handler
end
Process->>Worker: Run workers
loop Start each worker
loop
Worker->>Listener: @app.before_server_start
Listener->>Handler: Invoke event handler
end
Note over Worker: Server status: started
loop
Worker->>Listener: @app.after_server_start
Listener->>Handler: Invoke event handler
end
Note over Worker: Server status: ready
end
Process->>Worker: Graceful shutdown
loop Stop each worker
loop
Worker->>Listener: @app.before_server_stop
Listener->>Handler: Invoke event handler
end
Note over Worker: Server status: stopped
loop
Worker->>Listener: @app.after_server_stop
Listener->>Handler: Invoke event handler
end
Note over Worker: Server status: closed
end
loop
Process->>Listener: @app.main_process_stop
Listener->>Handler: Invoke event handler
end
Note over Process: exitThe reloader process live outside of this worker process inside of a process that is responsible for starting and stopping the Sanic processes. Consider the following example:
@app.reload_process_start
async def reload_start(*_):
print(">>>>>> reload_start <<<<<<")
@app.main_process_start
async def main_start(*_):
print(">>>>>> main_start <<<<<<")
@app.before_server_start
async def before_start(*_):
print(">>>>>> before_start <<<<<<")If this application were run with auto-reload turned on, the reload_start function would be called once when the reloader process starts. The main_start function would also be called once when the main process starts. HOWEVER, the before_start function would be called once for each worker process that is started, and subsequently every time that a file is saved and the worker is restarted.
Attaching a listener
.. column::
The process to setup a function as a listener is similar to declaring a route.
The currently running `Sanic()` instance is injected into the listener... column::
```python
async def setup_db(app):
app.ctx.db = await db_setup()
app.register_listener(setup_db, "before_server_start")
```.. column::
The `Sanic` app instance also has a convenience decorator... column::
```python
@app.listener("before_server_start")
async def setup_db(app):
app.ctx.db = await db_setup()
```.. column::
Prior to v22.3, both the application instance and the current event loop were injected into the function. However, only the application instance is injected by default. If your function signature will accept both, then both the application and the loop will be injected as shown here... column::
```python
@app.listener("before_server_start")
async def setup_db(app, loop):
app.ctx.db = await db_setup()
```.. column::
You can shorten the decorator even further. This is helpful if you have an IDE with autocomplete... column::
```python
@app.before_server_start
async def setup_db(app):
app.ctx.db = await db_setup()
```Order of execution
Listeners are executed in the order they are declared during startup, and reverse order of declaration during teardown
| Phase | Order | |
|---|---|---|
main_process_start |
main startup | regular 🙂 ⬇️ |
before_server_start |
worker startup | regular 🙂 ⬇️ |
after_server_start |
worker startup | regular 🙂 ⬇️ |
before_server_stop |
worker shutdown | 🙃 ⬆️ reverse |
after_server_stop |
worker shutdown | 🙃 ⬆️ reverse |
main_process_stop |
main shutdown | 🙃 ⬆️ reverse |
Given the following setup, we should expect to see this in the console if we run two workers.
.. column::
```python
@app.listener("before_server_start")
async def listener_1(app, loop):
print("listener_1")
@app.before_server_start
async def listener_2(app, loop):
print("listener_2")
@app.listener("after_server_start")
async def listener_3(app, loop):
print("listener_3")
@app.after_server_start
async def listener_4(app, loop):
print("listener_4")
@app.listener("before_server_stop")
async def listener_5(app, loop):
print("listener_5")
@app.before_server_stop
async def listener_6(app, loop):
print("listener_6")
@app.listener("after_server_stop")
async def listener_7(app, loop):
print("listener_7")
@app.after_server_stop
async def listener_8(app, loop):
print("listener_8")
```.. column::
```bash
[pid: 1000000] [INFO] Goin' Fast @ http://127.0.0.1:9999
[pid: 1000000] [INFO] listener_0
[pid: 1111111] [INFO] listener_1
[pid: 1111111] [INFO] listener_2
[pid: 1111111] [INFO] listener_3
[pid: 1111111] [INFO] listener_4
[pid: 1111111] [INFO] Starting worker [1111111]
[pid: 1222222] [INFO] listener_1
[pid: 1222222] [INFO] listener_2
[pid: 1222222] [INFO] listener_3
[pid: 1222222] [INFO] listener_4
[pid: 1222222] [INFO] Starting worker [1222222]
[pid: 1111111] [INFO] Stopping worker [1111111]
[pid: 1222222] [INFO] Stopping worker [1222222]
[pid: 1222222] [INFO] listener_6
[pid: 1222222] [INFO] listener_5
[pid: 1222222] [INFO] listener_8
[pid: 1222222] [INFO] listener_7
[pid: 1111111] [INFO] listener_6
[pid: 1111111] [INFO] listener_5
[pid: 1111111] [INFO] listener_8
[pid: 1111111] [INFO] listener_7
[pid: 1000000] [INFO] listener_9
[pid: 1000000] [INFO] Server Stopped
```
In the above example, notice how there are three processes running:
- `pid: 1000000` - The *main* process
- `pid: 1111111` - Worker 1
- `pid: 1222222` - Worker 2
*Just because our example groups all of one worker and then all of another, in reality since these are running on separate processes, the ordering between processes is not guaranteed. But, you can be sure that a single worker will **always** maintain its order.*.. tip:: FYI
The practical result of this is that if the first listener in `before_server_start` handler setups a database connection, listeners that are registered after it can rely upon that connection being alive both when they are started and stopped.Priority
In v23.12, the priority keyword argument was added to listeners. This allows for fine-tuning the order of execution of listeners. The default priority is 0. Listeners with a higher priority will be executed first. Listeners with the same priority will be executed in the order they were registered. Furthermore, listeners attached to the app instance will be executed before listeners attached to a Blueprint instance.
Overall the rules for deciding the order of execution are as follows:
- Priority in descending order
- Application listeners before Blueprint listeners
- Registration order
.. column::
As an example, consider the following, which will print:
```bash
third
bp_third
second
bp_second
first
fourth
bp_first
```.. column::
```python
@app.before_server_start
async def first(app):
print("first")
@app.listener("before_server_start", priority=2)
async def second(app):
print("second")
@app.before_server_start(priority=3)
async def third(app):
print("third")
@bp.before_server_start
async def bp_first(app):
print("bp_first")
@bp.listener("before_server_start", priority=2)
async def bp_second(app):
print("bp_second")
@bp.before_server_start(priority=3)
async def bp_third(app):
print("bp_third")
@app.before_server_start
async def fourth(app):
print("fourth")
app.blueprint(bp)
```ASGI Mode
If you are running your application with an ASGI server, then make note of the following changes:
reload_process_startandreload_process_stopwill be ignoredmain_process_startandmain_process_stopwill be ignoredbefore_server_startwill run as early as it can, and will be beforeafter_server_start, but technically, the server is already running at that pointafter_server_stopwill run as late as it can, and will be afterbefore_server_stop, but technically, the server is still running at that point
Guide Content En Guide Basics Middleware (`guide/content/en/guide/basics/middleware.md`)
Middleware
Whereas listeners allow you to attach functionality to the lifecycle of a worker process, middleware allows you to attach functionality to the lifecycle of an HTTP stream.
@app.on_request
async def example(request):
print("I execute before the handler.")You can execute middleware either before the handler is executed, or after.
@app.on_response
async def example(request, response):
print("I execute after the handler.").. mermaid::
sequenceDiagram
autonumber
participant Worker
participant Middleware
participant MiddlewareHandler
participant RouteHandler
Note over Worker: Incoming HTTP request
loop
Worker->>Middleware: @app.on_request
Middleware->>MiddlewareHandler: Invoke middleware handler
MiddlewareHandler-->>Worker: Return response (optional)
end
rect rgba(255, 13, 104, .1)
Worker->>RouteHandler: Invoke route handler
RouteHandler->>Worker: Return response
end
loop
Worker->>Middleware: @app.on_response
Middleware->>MiddlewareHandler: Invoke middleware handler
MiddlewareHandler-->>Worker: Return response (optional)
end
Note over Worker: Deliver responseAttaching middleware
.. column::
This should probably look familiar by now. All you need to do is declare when you would like the middleware to execute: on the `request` or on the `response`... column::
```python
async def extract_user(request):
request.ctx.user = await extract_user_from_request(request)
app.register_middleware(extract_user, "request")
```.. column::
Again, the `Sanic` app instance also has a convenience decorator... column::
```python
@app.middleware("request")
async def extract_user(request):
request.ctx.user = await extract_user_from_request(request)
```.. column::
Response middleware receives both the `request` and `response` arguments... column::
```python
@app.middleware('response')
async def prevent_xss(request, response):
response.headers["x-xss-protection"] = "1; mode=block"
```.. column::
You can shorten the decorator even further. This is helpful if you have an IDE with autocomplete.
This is the preferred usage, and is what we will use going forward... column::
```python
@app.on_request
async def extract_user(request):
...
@app.on_response
async def prevent_xss(request, response):
...
```Modification
Middleware can modify the request or response parameter it is given, as long as it does not return it.
.. column::
#### Order of execution
1. Request middleware: `add_key`
2. Route handler: `index`
3. Response middleware: `prevent_xss`
4. Response middleware: `custom_banner`.. column::
```python
@app.on_request
async def add_key(request):
# Arbitrary data may be stored in request context:
request.ctx.foo = "bar"
@app.on_response
async def custom_banner(request, response):
response.headers["Server"] = "Fake-Server"
@app.on_response
async def prevent_xss(request, response):
response.headers["x-xss-protection"] = "1; mode=block"
@app.get("/")
async def index(request):
return text(request.ctx.foo)
```.. column::
You can modify the `request.match_info`. A useful feature that could be used, for example, in middleware to convert `a-slug` to `a_slug`... column::
```python
@app.on_request
def convert_slug_to_underscore(request: Request):
request.match_info["slug"] = request.match_info["slug"].replace("-", "_")
@app.get("/<slug:slug>")
async def handler(request, slug):
return text(slug)
```
```
$ curl localhost:9999/foo-bar-baz
foo_bar_baz
```Responding early
.. column::
If middleware returns a `HTTPResponse` object, the request will stop processing and the response will be returned. If this occurs to a request before the route handler is reached, the handler will **not** be called. Returning a response will also prevent any further middleware from running... tip::
You can return a `None` value to stop the execution of the middleware handler to allow the request to process as normal. This can be useful when using early return to avoid processing requests inside of that middleware handler... column::
```python
@app.on_request
async def halt_request(request):
return text("I halted the request")
@app.on_response
async def halt_response(request, response):
return text("I halted the response")
```Order of execution
Request middleware is executed in the order declared. Response middleware is executed in reverse order.
Given the following setup, we should expect to see this in the console.
.. column::
```python
@app.on_request
async def middleware_1(request):
print("middleware_1")
@app.on_request
async def middleware_2(request):
print("middleware_2")
@app.on_response
async def middleware_3(request, response):
print("middleware_3")
@app.on_response
async def middleware_4(request, response):
print("middleware_4")
@app.get("/handler")
async def handler(request):
print("~ handler ~")
return text("Done.")
```.. column::
```bash
middleware_1
middleware_2
~ handler ~
middleware_4
middleware_3
[INFO][127.0.0.1:44788]: GET http://localhost:8000/handler 200 5
```Middleware priority
.. column::
You can modify the order of execution of middleware by assigning it a higher priority. This happens inside of the middleware definition. The higher the value, the earlier it will execute relative to other middleware. The default priority for middleware is `0`... column::
```python
@app.on_request
async def low_priority(request):
...
@app.on_request(priority=99)
async def high_priority(request):
...
```Added in v22.9
Guide Content En Guide Basics Request (`guide/content/en/guide/basics/request.md`)
Request
See API docs: sanic.request
The :class:sanic.request.Request instance contains a lot of helpful information available on its parameters. Refer to the API documentation for full details.
As we saw in the section on handlers, the first argument in a route handler is usually the :class:sanic.request.Request object. Because Sanic is an async framework, the handler will run inside of a asyncio.Task and will be scheduled by the event loop. This means that the handler will be executed in an isolated context and the request object will be unique to that handler's task.
.. column::
By convention, the argument is named `request`, but you can name it whatever you want. The name of the argument is not important. Both of the following handlers are valid... column::
```python
@app.get("/foo")
async def typical_use_case(request):
return text("I said foo!")
```
```python
@app.get("/foo")
async def atypical_use_case(req):
return text("I said foo!")
```.. column::
Annotating a request object is super simple... column::
```python
from sanic.request import Request
from sanic.response import text
@app.get("/typed")
async def typed_handler(request: Request):
return text("Done.")
```.. tip::
For your convenience, assuming you are using a modern IDE, you should leverage type annotations to help with code completion and documentation. This is especially helpful when using the `request` object as it has **MANY** properties and methods.
To see the full list of available properties and methods, refer to the [API documentation](/api/sanic.request).Body
The Request object allows you to access the content of the request body in a few different ways.
JSON
.. column::
**Parameter**: `request.json`
**Description**: The parsed JSON object.. column::
```bash
$ curl localhost:8000 -d '{"foo": "bar"}'
```
```python
>>> print(request.json)
{'foo': 'bar'}
```Raw
.. column::
**Parameter**: `request.body`
**Description**: The raw bytes from the request body.. column::
```bash
$ curl localhost:8000 -d '{"foo": "bar"}'
```
```python
>>> print(request.body)
b'{"foo": "bar"}'
```Form
.. column::
**Parameter**: `request.form`
**Description**: The form data
.. tip:: FYI
The `request.form` object is one of a few types that is a dictionary with each value being a list. This is because HTTP allows a single key to be reused to send multiple values.
Most of the time you will want to use the `.get()` method to access the first element and not a list. If you do want a list of all items, you can use `.getlist()`... column::
```bash
$ curl localhost:8000 -d 'foo=bar'
```
```python
>>> print(request.body)
b'foo=bar'
>>> print(request.form)
{'foo': ['bar']}
>>> print(request.form.get("foo"))
bar
>>> print(request.form.getlist("foo"))
['bar']
```Uploaded
.. column::
**Parameter**: `request.files`
**Description**: The files uploaded to the server
.. tip:: FYI
The `request.files` object is one of a few types that is a dictionary with each value being a list. This is because HTTP allows a single key to be reused to send multiple values.
Most of the time you will want to use the `.get()` method to access the first element and not a list. If you do want a list of all items, you can use `.getlist()`... column::
```bash
$ curl -F 'my_file=@/path/to/TEST' http://localhost:8000
```
```python
>>> print(request.body)
b'--------------------------cb566ad845ad02d3\r\nContent-Disposition: form-data; name="my_file"; filename="TEST"\r\nContent-Type: application/octet-stream\r\n\r\nhello\n\r\n--------------------------cb566ad845ad02d3--\r\n'
>>> print(request.files)
{'my_file': [File(type='application/octet-stream', body=b'hello\n', name='TEST')]}
>>> print(request.files.get("my_file"))
File(type='application/octet-stream', body=b'hello\n', name='TEST')
>>> print(request.files.getlist("my_file"))
[File(type='application/octet-stream', body=b'hello\n', name='TEST')]
```Context
Request context
The request.ctx object is your playground to store whatever information you need to about the request. This lives only for the duration of the request and is unique to the request.
This can be constrasted with the app.ctx object which is shared across all requests. Be careful not to confuse them!
The request.ctx object by default is a SimpleNamespace object allowing you to set arbitrary attributes on it. Sanic will not use this object for anything, so you are free to use it however you want without worrying about name clashes.
Typical use case
This is often used to store items like authenticated user details. We will get more into middleware later, but here is a simple example.
@app.on_request
async def run_before_handler(request):
request.ctx.user = await fetch_user_by_token(request.token)
@app.route('/hi')
async def hi_my_name_is(request):
if not request.ctx.user:
return text("Hmm... I don't know you)
return text(f"Hi, my name is {request.ctx.user.name}")As you can see, the request.ctx object is a great place to store information that you need to access in multiple handlers making your code more DRY and easier to maintain. But, as we will learn in the middleware section, you can also use it to store information from one middleware that will be used in another.
Connection context
.. column::
Often times your API will need to serve multiple concurrent (or consecutive) requests to the same client. This happens, for example, very often with progressive web apps that need to query multiple endpoints to get data.
The HTTP protocol calls for an easing of overhead time caused by the connection with the use of [keep alive headers](../running/configuration.md#keep-alive-timeout).
When multiple requests share a single connection, Sanic provides a context object to allow those requests to share state... column::
```python
@app.on_request
async def increment_foo(request):
if not hasattr(request.conn_info.ctx, "foo"):
request.conn_info.ctx.foo = 0
request.conn_info.ctx.foo += 1
@app.get("/")
async def count_foo(request):
return text(f"request.conn_info.ctx.foo={request.conn_info.ctx.foo}")
```
```bash
$ curl localhost:8000 localhost:8000 localhost:8000
request.conn_info.ctx.foo=1
request.conn_info.ctx.foo=2
request.conn_info.ctx.foo=3
```.. warning::
While this looks like a convenient place to store information between requests by a single HTTP connection, do not assume that all requests on a single connection came from a single end user. This is because HTTP proxies and load balancers can multiplex multiple connections into a single connection to your server.
**DO NOT** use this to store information about a single user. Use the `request.ctx` object for that.Custom Request Objects
As discussed in application customization, you can create a subclass of :class:sanic.request.Request to add additional functionality to the request object. This is useful for adding additional attributes or methods that are specific to your application.
.. column::
For example, imagine your application sends a custom header that contains a user ID. You can create a custom request object that will parse that header and store the user ID for you... column::
```python
from sanic import Sanic, Request
class CustomRequest(Request):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.user_id = self.headers.get("X-User-ID")
app = Sanic("Example", request_class=CustomRequest)
```.. column::
Now, in your handlers, you can access the `user_id` attribute... column::
```python
@app.route("/")
async def handler(request: CustomRequest):
return text(f"User ID: {request.user_id}")
```Custom Request Context
By default, the request context (request.ctx) is a Simplenamespace object allowing you to set arbitrary attributes on it. While this is super helpful to reuse logic across your application, it can be difficult in the development experience since the IDE will not know what attributes are available.
To help with this, you can create a custom request context object that will be used instead of the default SimpleNamespace. This allows you to add type hints to the context object and have them be available in your IDE.
.. column::
Start by subclassing the :class:`sanic.request.Request` class to create a custom request type. Then, you will need to add a `make_context()` method that returns an instance of your custom context object. *NOTE: the `make_context` method should be a static method.*.. column::
```python
from sanic import Sanic, Request
from types import SimpleNamespace
class CustomRequest(Request):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.ctx.user_id = self.headers.get("X-User-ID")
@staticmethod
def make_context() -> CustomContext:
return CustomContext()
@dataclass
class CustomContext:
user_id: str = None
```.. note::
This is a Sanic poweruser feature that makes it super convenient in large codebases to have typed request context objects. It is of course not required, but can be very helpful.Added in v23.6
Parameters
.. column::
Values that are extracted from the path parameters are injected into the handler as argumets, or more specifically as keyword arguments. There is much more detail about this in the [Routing section](./routing.md)... column::
```python
@app.route('/tag/<tag>')
async def tag_handler(request, tag):
return text("Tag - {}".format(tag))
# or, explicitly as keyword arguments
@app.route('/tag/<tag>')
async def tag_handler(request, *, tag):
return text("Tag - {}".format(tag))
```Arguments
There are two attributes on the request instance to get query parameters:
request.argsrequest.query_args
These allow you to access the query parameters from the request path (the part after the ? in the URL).
Typical use case
In most use cases, you will want to use the request.args object to access the query parameters. This will be the parsed query string as a dictionary.
This is by far the most common pattern.
.. column::
Consider the example where we have a `/search` endpoint with a `q` parameter that we want to use to search for something... column::
```python
@app.get("/search")
async def search(request):
query = request.args.get("q")
if not query:
return text("No query string provided")
return text(f"Searching for: {query}")
```Parsing the query string
Sometimes, however, you may want to access the query string as a raw string or as a list of tuples. For this, you can use the request.query_string and request.query_args attributes.
It also should be noted that HTTP allows multiple values for a single key. Although request.args may seem like a regular dictionary, it is actually a special type that allows for multiple values for a single key. You can access this by using the request.args.getlist() method.
request.query_string- The raw query stringrequest.query_args- The parsed query string as a list of tuplesrequest.args- The parsed query string as a special dictionaryrequest.args.get()- Get the first value for a key (behaves like a regular dictionary)request.args.getlist()- Get all values for a key
curl "http://localhost:8000?key1=val1&key2=val2&key1=val3">>> print(request.args)
{'key1': ['val1', 'val3'], 'key2': ['val2']}
>>> print(request.args.get("key1"))
val1
>>> print(request.args.getlist("key1"))
['val1', 'val3']
>>> print(request.query_args)
[('key1', 'val1'), ('key2', 'val2'), ('key1', 'val3')]
>>> print(request.query_string)
key1=val1&key2=val2&key1=val3.. tip:: FYI
The `request.args` object is one of a few types that is a dictionary with each value being a list. This is because HTTP allows a single key to be reused to send multiple values.
Most of the time you will want to use the `.get()` method to access the first element and not a list. If you do want a list of all items, you can use `.getlist()`.Current request getter
Sometimes you may find that you need access to the current request in your application in a location where it is not accessible. A typical example might be in a logging format. You can use Request.get_current() to fetch the current request (if any).
Remember, the request object is confined to the single asyncio.Task that is running the handler. If you are not in that task, there is no request object.
import logging
from sanic import Request, Sanic, json
from sanic.exceptions import SanicException
from sanic.log import LOGGING_CONFIG_DEFAULTS
LOGGING_FORMAT = (
"%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: "
"%(request_id)s %(request)s %(message)s %(status)d %(byte)d"
)
old_factory = logging.getLogRecordFactory()
def record_factory(*args, **kwargs):
record = old_factory(*args, **kwargs)
record.request_id = ""
try:
request = Request.get_current()
except SanicException:
...
else:
record.request_id = str(request.id)
return record
logging.setLogRecordFactory(record_factory)
LOGGING_CONFIG_DEFAULTS["formatters"]["access"]["format"] = LOGGING_FORMAT
app = Sanic("Example", log_config=LOGGING_CONFIG_DEFAULTS)In this example, we are adding the request.id to every access log message.
Added in v22.6
Guide Content En Guide Basics Response (`guide/content/en/guide/basics/response.md`)
Response
All handlers usually return a response object, and middleware may optionally return a response object.
To clarify that statement:
- unless the handler is a streaming endpoint handling its own pattern for sending bytes to the client, the return value must be an instance of :class:
sanic.response.HTTPResponse(to learn more about this exception see streaming responses). In most use cases, you will need to return a response. - if a middleware does return a response object, that will be used instead of whatever the handler would do (see middleware to learn more).
A most basic handler would look like the following. The :class:sanic.response.HTTPResponse object will allow you to set the status, body, and headers to be returned to the client.
from sanic import HTTPResponse, Sanic
app = Sanic("TestApp")
@app.route("")
def handler(_):
return HTTPResponse()However, usually it is easier to use one of the convenience methods discussed below.
Methods
The easiest way to generate a response object is to use one of the convenience functions.
Text
.. column::
**Default Content-Type**: `text/plain; charset=utf-8`
**Description**: Returns plain text.. column::
```python
from sanic import text
@app.route("/")
async def handler(request):
return text("Hi 😎")
```HTML
.. column::
**Default Content-Type**: `text/html; charset=utf-8`
**Description**: Returns an HTML document.. column::
```python
from sanic import html
@app.route("/")
async def handler(request):
return html('<!DOCTYPE html><html lang="en"><meta charset="UTF-8"><div>Hi 😎</div>')
```JSON
.. column::
**Default Content-Type**: `application/json`
**Description**: Returns a JSON document.. column::
```python
from sanic import json
@app.route("/")
async def handler(request):
return json({"foo": "bar"})
```By default, Sanic ships with ujson as its JSON encoder of choice. If ujson is not installed, it will fall back to the standard library json module.
It is super simple to change this if you want.
from sanic import json
from orjson import dumps
json({"foo": "bar"}, dumps=dumps)You may additionally declare which implementation to use globally across your application at initialization:
from orjson import dumps
app = Sanic(..., dumps=dumps)File
.. column::
**Default Content-Type**: N/A
**Description**: Returns a file.. column::
```python
from sanic import file
@app.route("/")
async def handler(request):
return await file("/path/to/whatever.png")
```Sanic will examine the file, and try and guess its mime type and use an appropriate value for the content type. You could be explicit, if you would like:
file("/path/to/whatever.png", mime_type="image/png")You can also choose to override the file name:
file("/path/to/whatever.png", filename="super-awesome-incredible.png")File Streaming
.. column::
**Default Content-Type**: N/A
**Description**: Streams a file to a client, useful when streaming large files, like a video.. column::
```python
from sanic.response import file_stream
@app.route("/")
async def handler(request):
return await file_stream("/path/to/whatever.mp4")
```Like the file() method, file_stream() will attempt to determine the mime type of the file.
Raw
.. column::
**Default Content-Type**: `application/octet-stream`
**Description**: Send raw bytes without encoding the body.. column::
```python
from sanic import raw
@app.route("/")
async def handler(request):
return raw(b"raw bytes")
```Redirect
.. column::
**Default Content-Type**: `text/html; charset=utf-8`
**Description**: Send a `302` response to redirect the client to a different path.. column::
```python
from sanic import redirect
@app.route("/")
async def handler(request):
return redirect("/login")
```Empty
.. column::
**Default Content-Type**: N/A
**Description**: For responding with an empty message as defined by [RFC 2616](https://tools.ietf.org/search/rfc2616#section-7.2.1).. column::
```python
from sanic import empty
@app.route("/")
async def handler(request):
return empty()
```
Defaults to a `204` status.Default status
The default HTTP status code for the response is 200. If you need to change it, it can be done by the response method.
@app.post("/")
async def create_new(request):
new_thing = await do_create(request)
return json({"created": True, "id": new_thing.thing_id}, status=201)Returning JSON data
Starting in v22.12, When you use the sanic.json convenience method, it will return a subclass of HTTPResponse called :class:sanic.response.types.JSONResponse. This object will
have several convenient methods available to modify common JSON body.
from sanic import json
resp = json(...)resp.set_body(<raw_body>)- Set the body of the JSON object to the value passedresp.append(<value>)- Append a value to the body likelist.append(only works if the root JSON is an array)resp.extend(<value>)- Extend a value to the body likelist.extend(only works if the root JSON is an array)resp.update(<value>)- Update the body with a value likedict.update(only works if the root JSON is an object)resp.pop()- Pop a value likelist.popordict.pop(only works if the root JSON is an array or an object)
.. warning::
The raw Python object is stored on the `JSONResponse` object as `raw_body`. While it is safe to overwrite this value with a new one, you should **not** attempt to mutate it. You should instead use the methods listed above.resp = json({"foo": "bar"})
# This is OKAY
resp.raw_body = {"foo": "bar", "something": "else"}
# This is better
resp.set_body({"foo": "bar", "something": "else"})
# This is also works well
resp.update({"something": "else"})
# This is NOT OKAY
resp.raw_body.update({"something": "else"})# Or, even treat it like a list
resp = json(["foo", "bar"])
# This is OKAY
resp.raw_body = ["foo", "bar", "something", "else"]
# This is better
resp.extend(["something", "else"])
# This is also works well
resp.append("something")
resp.append("else")
# This is NOT OKAY
resp.raw_body.append("something")Added in v22.9
Guide Content En Guide Basics Routing (`guide/content/en/guide/basics/routing.md`)
Routing
.. column::
So far we have seen a lot of this decorator in different forms.
But what is it? And how do we use it?.. column::
```python
@app.route("/stairway")
...
@app.get("/to")
...
@app.post("/heaven")
...
```Adding a route
.. column::
The most basic way to wire up a handler to an endpoint is with `app.add_route()`.
See [API docs](https://sanic.readthedocs.io/en/stable/sanic/api_reference.html#sanic.app.Sanic.url_for) for more details... column::
```python
async def handler(request):
return text("OK")
app.add_route(handler, "/test")
```.. column::
By default, routes are available as an HTTP `GET` call. You can change a handler to respond to one or more HTTP methods... column::
```python
app.add_route(
handler,
'/test',
methods=["POST", "PUT"],
)
```.. column::
Using the decorator syntax, the previous example is identical to this... column::
```python
@app.route('/test', methods=["POST", "PUT"])
async def handler(request):
return text('OK')
```HTTP methods
Each of the standard HTTP methods has a convenience decorator.
GET
@app.get('/test')
async def handler(request):
return text('OK')POST
@app.post('/test')
async def handler(request):
return text('OK')PUT
@app.put('/test')
async def handler(request):
return text('OK')PATCH
@app.patch('/test')
async def handler(request):
return text('OK')DELETE
@app.delete('/test')
async def handler(request):
return text('OK')HEAD
@app.head('/test')
async def handler(request):
return empty()OPTIONS
@app.options('/test')
async def handler(request):
return empty().. warning::
By default, Sanic will **only** consume the incoming request body on non-safe HTTP methods: `POST`, `PUT`, `PATCH`, `DELETE`. If you want to receive data in the HTTP request on any other method, you will need to do one of the following two options:
**Option #1 - Tell Sanic to consume the body using `ignore_body`**
```python
@app.request("/path", ignore_body=False)
async def handler(_):
...
```
**Option #2 - Manually consume the body in the handler using `receive_body`**
```python
@app.get("/path")
async def handler(request: Request):
await request.receive_body()
```Path parameters
.. column::
Sanic allows for pattern matching, and for extracting values from URL paths. These parameters are then injected as keyword arguments in the route handler... column::
```python
@app.get("/tag/<tag>")
async def tag_handler(request, tag):
return text("Tag - {}".format(tag))
```.. column::
You can declare a type for the parameter. This will be enforced when matching, and also will type cast the variable... column::
```python
@app.get("/foo/<foo_id:uuid>")
async def uuid_handler(request, foo_id: UUID):
return text("UUID - {}".format(foo_id))
```.. column::
For some standard types like `str`, `int`, and `UUID`, Sanic can infer the path parameter type from the function signature. This means that it may not always be necessary to include the type in the path parameter definition... column::
```python
@app.get("/foo/<foo_id>") # Notice there is no :uuid in the path parameter
async def uuid_handler(request, foo_id: UUID):
return text("UUID - {}".format(foo_id))
```Supported types
`str`
.. column::
**Regular expression applied**: `r"[^/]+"`
**Cast type**: `str`
**Example matches**:
- `/path/to/Bob`
- `/path/to/Python%203`
Beginning in v22.3 `str` will *not* match on empty strings. See `strorempty` for this behavior... column::
```python
@app.route("/path/to/<foo:str>")
async def handler(request, foo: str):
...
````strorempty`
.. column::
**Regular expression applied**: `r"[^/]*"`
**Cast type**: `str`
**Example matches**:
- `/path/to/Bob`
- `/path/to/Python%203`
- `/path/to/`
Unlike the `str` path parameter type, `strorempty` can also match on an empty string path segment.
*Added in v22.3*.. column::
```python
@app.route("/path/to/<foo:strorempty>")
async def handler(request, foo: str):
...
````int`
.. column::
**Regular expression applied**: `r"-?\d+"`
**Cast type**: `int`
**Example matches**:
- `/path/to/10`
- `/path/to/-10`
_Does not match float, hex, octal, etc_.. column::
```python
@app.route("/path/to/<foo:int>")
async def handler(request, foo: int):
...
````float`
.. column::
**Regular expression applied**: `r"-?(?:\d+(?:\.\d*)?|\.\d+)"`
**Cast type**: `float`
**Example matches**:
- `/path/to/10`
- `/path/to/-10`
- `/path/to/1.5`.. column::
```python
@app.route("/path/to/<foo:float>")
async def handler(request, foo: float):
...
````alpha`
.. column::
**Regular expression applied**: `r"[A-Za-z]+"`
**Cast type**: `str`
**Example matches**:
- `/path/to/Bob`
- `/path/to/Python`
_Does not match a digit, or a space or other special character_.. column::
```python
@app.route("/path/to/<foo:alpha>")
async def handler(request, foo: str):
...
````slug`
.. column::
**Regular expression applied**: `r"[a-z0-9]+(?:-[a-z0-9]+)*"`
**Cast type**: `str`
**Example matches**:
- `/path/to/some-news-story`
- `/path/to/or-has-digits-123`
*Added in v21.6*.. column::
```python
@app.route("/path/to/<article:slug>")
async def handler(request, article: str):
...
````path`
.. column::
**Regular expression applied**: `r"[^/].*?"`
**Cast type**: `str`
**Example matches**:
- `/path/to/hello`
- `/path/to/hello.txt`
- `/path/to/hello/world.txt`.. column::
```python
@app.route("/path/to/<foo:path>")
async def handler(request, foo: str):
...
```.. warning::
Because this will match on `/`, you should be careful and thoroughly test your patterns that use `path` so they do not capture traffic intended for another endpoint. Additionally, depending on how you use this type, you may be creating a path traversal vulnerability in your application. It is your job to protect your endpoint against this, but feel free to ask in our community channels for help if you need it :)`ymd`
.. column::
**Regular expression applied**: `r"^([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))"`
**Cast type**: `datetime.date`
**Example matches**:
- `/path/to/2021-03-28`.. column::
```python
@app.route("/path/to/<foo:ymd>")
async def handler(request, foo: datetime.date):
...
````uuid`
.. column::
**Regular expression applied**: `r"[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}"`
**Cast type**: `UUID`
**Example matches**:
- `/path/to/123a123a-a12a-1a1a-a1a1-1a12a1a12345`.. column::
```python
@app.route("/path/to/<foo:uuid>")
async def handler(request, foo: UUID):
...
```ext
.. column::
**Regular expression applied**: n/a
**Cast type**: *varies*
**Example matches**:.. column::
```python
@app.route("/path/to/<foo:ext>")
async def handler(request, foo: str, ext: str):
...
```| 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" |
File extensions can be matched using the special ext parameter type. It uses a special format that allows you to specify other types of parameter types as the file name, and one or more specific extensions as shown in the example table above.
It does not support the path parameter type.
Added in v22.3
regex
.. column::
**Regular expression applied**: _whatever you insert_
**Cast type**: `str`
**Example matches**:
- `/path/to/2021-01-01`
This gives you the freedom to define specific matching patterns for your use case.
In the example shown, we are looking for a date that is in `YYYY-MM-DD` format... column::
```python
@app.route(r"/path/to/<foo:([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))>")
async def handler(request, foo: str):
...
```Regex Matching
More often than not, compared with complex routing, the above example is too simple, and we use a completely different routing matching pattern, so here we will explain the advanced usage of regex matching in detail.
Sometimes, you want to match a part of a route:
/image/123456789.jpgIf you wanted to match the file pattern, but only capture the numeric portion, you need to do some regex fun 😄:
app.route(r"/image/<img_id:(?P<img_id>\d+)\.jpg>")Further, these should all be acceptable:
@app.get(r"/<foo:[a-z]{3}.txt>") # matching on the full pattern
@app.get(r"/<foo:([a-z]{3}).txt>") # defining a single matching group
@app.get(r"/<foo:(?P<foo>[a-z]{3}).txt>") # defining a single named matching group
@app.get(r"/<foo:(?P<foo>[a-z]{3}).(?:txt)>") # defining a single named matching group, with one or more non-matching groupsAlso, if using a named matching group, it must be the same as the segment label.
@app.get(r"/<foo:(?P<foo>\d+).jpg>") # OK
@app.get(r"/<foo:(?P<bar>\d+).jpg>") # NOT OKFor more regular usage methods, please refer to Regular expression operations
Generating a URL
.. column::
Sanic provides a method to generate URLs based on the handler method name: `app.url_for()`. This is useful if you want to avoid hardcoding url paths into your app; instead, you can just reference the handler name... column::
```python
@app.route('/')
async def index(request):
# generate a URL for the endpoint `post_handler`
url = app.url_for('post_handler', post_id=5)
# Redirect to `/posts/5`
return redirect(url)
@app.route('/posts/<post_id>')
async def post_handler(request, post_id):
...
```.. column::
You can pass any arbitrary number of keyword arguments. Anything that is _not_ a request parameter will be implemented as a part of the query string... column::
```python
assert app.url_for(
"post_handler",
post_id=5,
arg_one="one",
arg_two="two",
) == "/posts/5?arg_one=one&arg_two=two"
```.. column::
Also supported is passing multiple values for a single query key... column::
```python
assert app.url_for(
"post_handler",
post_id=5,
arg_one=["one", "two"],
) == "/posts/5?arg_one=one&arg_one=two"
```Special keyword arguments
See API Docs for more details.
app.url_for("post_handler", post_id=5, arg_one="one", _anchor="anchor")
# '/posts/5?arg_one=one#anchor'
# _external requires you to pass an argument _server or set SERVER_NAME in app.config if not url will be same as no _external
app.url_for("post_handler", post_id=5, arg_one="one", _external=True)
# '//server/posts/5?arg_one=one'
# when specifying _scheme, _external must be True
app.url_for("post_handler", post_id=5, arg_one="one", _scheme="http", _external=True)
# 'http://server/posts/5?arg_one=one'
# you can pass all special arguments at once
app.url_for("post_handler", post_id=5, arg_one=["one", "two"], arg_two=2, _anchor="anchor", _scheme="http", _external=True, _server="another_server:8888")
# 'http://another_server:8888/posts/5?arg_one=one&arg_one=two&arg_two=2#anchor'Customizing a route name
.. column::
A custom route name can be used by passing a `name` argument while registering the route... column::
```python
@app.get("/get", name="get_handler")
def handler(request):
return text("OK")
```.. column::
Now, use this custom name to retrieve the URL.. column::
```python
assert app.url_for("get_handler", foo="bar") == "/get?foo=bar"
```Websockets routes
.. column::
Websocket routing works similar to HTTP methods... column::
```python
async def handler(request, ws):
message = "Start"
while True:
await ws.send(message)
message = await ws.recv()
app.add_websocket_route(handler, "/test")
```.. column::
It also has a convenience decorator... column::
```python
@app.websocket("/test")
async def handler(request, ws):
message = "Start"
while True:
await ws.send(message)
message = await ws.recv()
```Read the websockets section to learn more about how they work.
Strict slashes
.. column::
Sanic routes can be configured to strictly match on whether or not there is a trailing slash: `/`. This can be configured at a few levels and follows this order of precedence:
1. Route
2. Blueprint
3. BlueprintGroup
4. Application.. column::
```python
# provide default strict_slashes value for all routes
app = Sanic(__file__, strict_slashes=True)
```
```python
# overwrite strict_slashes value for specific route
@app.get("/get", strict_slashes=False)
def handler(request):
return text("OK")
```
```python
# it also works for blueprints
bp = Blueprint(__file__, strict_slashes=True)
@bp.get("/bp/get", strict_slashes=False)
def handler(request):
return text("OK")
```
```python
bp1 = Blueprint(name="bp1", url_prefix="/bp1")
bp2 = Blueprint(
name="bp2",
url_prefix="/bp2",
strict_slashes=False,
)
# This will enforce strict slashes check on the routes
# under bp1 but ignore bp2 as that has an explicitly
# set the strict slashes check to false
group = Blueprint.group([bp1, bp2], strict_slashes=True)
```Static files
.. column::
In order to serve static files from Sanic, use `app.static()`.
The order of arguments is important:
1. Route the files will be served from
2. Path to the files on the server
See [API docs](https://sanic.readthedocs.io/en/stable/sanic/api/app.html#sanic.app.Sanic.static) for more details... column::
```python
app.static("/static/", "/path/to/directory/")
```.. tip::
It is generally best practice to end your directory paths with a trailing slash (`/this/is/a/directory/`). This removes ambiguity by being more explicit... column::
You can also serve individual files... column::
```python
app.static("/", "/path/to/index.html")
```.. column::
It is also sometimes helpful to name your endpoint.. column::
```python
app.static(
"/user/uploads/",
"/path/to/uploads/",
name="uploads",
)
```.. column::
Retrieving the URLs works similar to handlers. But, we can also add the `filename` argument when we need a specific file inside a directory... column::
```python
assert app.url_for(
"static",
name="static",
filename="file.txt",
) == "/static/file.txt"
```
```python
assert app.url_for(
"static",
name="uploads",
filename="image.png",
) == "/user/uploads/image.png"
```.. tip::
If you are going to have multiple `static()` routes, then it is *highly* suggested that you manually name them. This will almost certainly alleviate potential hard to discover bugs.
```python
app.static("/user/uploads/", "/path/to/uploads/", name="uploads")
app.static("/user/profile/", "/path/to/profile/", name="profile_pics")
```Auto index serving
.. column::
If you have a directory of static files that should be served by an index page, you can provide the filename of the index. Now, when reaching that directory URL, the index page will be served... column::
```python
app.static("/foo/", "/path/to/foo/", index="index.html")
```Added in v23.3
File browser
.. column::
When serving a directory from a static handler, Sanic can be configured to show a basic file browser instead using `directory_view=True`... column::
```python
app.static("/uploads/", "/path/to/dir", directory_view=True)
```You now have a browsable directory in your web browser:
Added in v23.3
Symlink control
.. new:: New in v25.12
This feature was added in version 25.12.. column::
By default, Sanic will not follow symlinks that point outside the static root directory for security reasons. You can enable external symlinks separately for files and directories using `follow_external_symlink_files` and `follow_external_symlink_dirs`... column::
```python
# Allow file symlinks pointing outside static root
app.static(
"/static",
"/var/www/static",
follow_external_symlink_files=True,
)
# Allow directory symlinks pointing outside static root
app.static(
"/static",
"/var/www/static",
follow_external_symlink_dirs=True,
)
```Symlinks within the static root always work regardless of these settings. Broken symlinks are always hidden from directory listings and return 404.
Added in v25.12
Route context
.. column::
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... column::
```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":
...
```Added in v21.12
Guide Content En Guide Basics Tasks (`guide/content/en/guide/basics/tasks.md`)
title: Background tasks
Background tasks
Creating Tasks
It is often desirable and very convenient to make usage of tasks in async Python. Sanic provides a convenient method to add tasks to the currently running loop. It is somewhat similar to asyncio.create_task. For adding tasks before the 'App' loop is running, see next section.
async def notify_server_started_after_five_seconds():
await asyncio.sleep(5)
print('Server successfully started!')
app.add_task(notify_server_started_after_five_seconds()).. column::
Sanic will attempt to automatically inject the app, passing it as an argument to the task... column::
```python
async def auto_inject(app):
await asyncio.sleep(5)
print(app.name)
app.add_task(auto_inject)
```.. column::
Or you can pass the `app` argument explicitly... column::
```python
async def explicit_inject(app):
await asyncio.sleep(5)
print(app.name)
app.add_task(explicit_inject(app))
```.. column::
The `add_task` method returns the created `asyncio.Task` object, allowing you to await or check its result later.
*Added in v25.12*.. column::
```python
task = app.add_task(some_coroutine())
# Later...
result = await task
```Adding tasks before `app.run`
It is possible to add background tasks before the App is run ie. before app.run. To add a task before the App is run, it is recommended to not pass the coroutine object (ie. one created by calling the async callable), but instead just pass the callable and Sanic will create the coroutine object on each worker. Note: the tasks that are added such are run as before_server_start jobs and thus run on every worker (and not in the main process). This has certain consequences, please read this comment on this issue for further details.
To add work on the main process, consider adding work to @app.main_process_start. Note: the workers won't start until this work is completed.
.. column::
Example to add a task before `app.run`.. column::
```python
async def slow_work():
...
async def even_slower(num):
...
app = Sanic(...)
app.add_task(slow_work) # Note: we are passing the callable and not coroutine object ...
app.add_task(even_slower(10)) # ... or we can call the function and pass the coroutine.
app.run(...)
```Named tasks
.. column::
When creating a task, you can ask Sanic to keep track of it for you by providing a `name`... column::
```python
app.add_task(slow_work, name="slow_task")
```.. column::
You can now retrieve that task instance from anywhere in your application using `get_task`... column::
```python
task = app.get_task("slow_task")
```.. column::
If that task needs to be cancelled, you can do that with `cancel_task`. Make sure that you `await` it... column::
```python
await app.cancel_task("slow_task")
```.. column::
All registered tasks can be found in the `app.tasks` property. To prevent cancelled tasks from filling up, you may want to run `app.purge_tasks` that will clear out any completed or cancelled tasks... column::
```python
app.purge_tasks()
```This pattern can be particularly useful with websockets:
async def receiver(ws):
while True:
message = await ws.recv()
if not message:
break
print(f"Received: {message}")
@app.websocket("/feed")
async def feed(request, ws):
task_name = f"receiver:{request.id}"
request.app.add_task(receiver(ws), name=task_name)
try:
while True:
await request.app.event("my.custom.event")
await ws.send("A message")
finally:
# When the websocket closes, let's cleanup the task
await request.app.cancel_task(task_name)
request.app.purge_tasks()Added in v21.12
Guide Content En Guide Best Practices Blueprints (`guide/content/en/guide/best-practices/blueprints.md`)
Blueprints
Overview
Blueprints are objects that can be used for sub-routing within an application. Instead of adding routes to the application instance, blueprints define similar methods for adding routes, which are then registered with the application in a flexible and pluggable manner.
Blueprints are especially useful for larger applications, where your application logic can be broken down into several groups or areas of responsibility.
Creating and registering
.. column::
First, you must create a blueprint. It has a very similar API as the `Sanic()` app instance with many of the same decorators... column::
```python
# ./my_blueprint.py
from sanic.response import json
from sanic import Blueprint
bp = Blueprint("my_blueprint")
@bp.route("/")
async def bp_root(request):
return json({"my": "blueprint"})
```.. column::
Next, you register it with the app instance... column::
```python
from sanic import Sanic
from my_blueprint import bp
app = Sanic(__name__)
app.blueprint(bp)
```Blueprints also have the same websocket() decorator and add_websocket_route method for implementing websockets.
.. column::
Beginning in v21.12, a Blueprint may be registered before or after adding objects to it. Previously, only objects attached to the Blueprint at the time of registration would be loaded into application instance... column::
```python
app.blueprint(bp)
@bp.route("/")
async def bp_root(request):
...
```Copying
.. column::
Blueprints along with everything that is attached to them can be copied to new instances using the `copy()` method. The only required argument is to pass it a new `name`. However, you could also use this to override any of the values from the old blueprint... column::
```python
v1 = Blueprint("Version1", version=1)
@v1.route("/something")
def something(request):
pass
v2 = v1.copy("Version2", version=2)
app.blueprint(v1)
app.blueprint(v2)
```
```
Available routes:
/v1/something
/v2/something
```Added in v21.9
Blueprint groups
Blueprints may also be registered as part of a list or tuple, where the registrar will recursively cycle through any sub-sequences of blueprints and register them accordingly. The Blueprint.group method is provided to simplify this process, allowing a ‘mock’ backend directory structure mimicking what’s seen from the front end. Consider this (quite contrived) example:
api/
├──content/
│ ├──authors.py
│ ├──static.py
│ └──__init__.py
├──info.py
└──__init__.py
app.py.. column::
#### First blueprint.. column::
```python
# api/content/authors.py
from sanic import Blueprint
authors = Blueprint("content_authors", url_prefix="/authors")
```.. column::
#### Second blueprint.. column::
```python
# api/content/static.py
from sanic import Blueprint
static = Blueprint("content_static", url_prefix="/static")
```.. column::
#### Blueprint group.. column::
```python
# api/content/__init__.py
from sanic import Blueprint
from .static import static
from .authors import authors
content = Blueprint.group(static, authors, url_prefix="/content")
```.. column::
#### Third blueprint.. column::
```python
# api/info.py
from sanic import Blueprint
info = Blueprint("info", url_prefix="/info")
```.. column::
#### Another blueprint group.. column::
```python
# api/__init__.py
from sanic import Blueprint
from .content import content
from .info import info
api = Blueprint.group(content, info, url_prefix="/api")
```.. column::
#### Main server
All blueprints are now registered.. column::
```python
# app.py
from sanic import Sanic
from .api import api
app = Sanic(__name__)
app.blueprint(api)
```Blueprint group prefixes and composability
As shown in the code above, when you create a group of blueprints you can extend the URL prefix of all the blueprints in the group by passing the url_prefix argument to the Blueprint.group method. This is useful for creating a mock directory structure for your API.
In addition, there is a name_prefix argument that can be used to make blueprints reusable and composable. The is specifically necessary when applying a single blueprint to multiple groups. By doing this, the blueprint will be registered with a unique name for each group, which allows the blueprint to be registered multiple times and have its routes each properly named with a unique identifier.
.. column::
Consider this example. The routes built will be named as follows:
- `TestApp.group-a_bp1.route1`
- `TestApp.group-a_bp2.route2`
- `TestApp.group-b_bp1.route1`
- `TestApp.group-b_bp2.route2`.. column::
```python
bp1 = Blueprint("bp1", url_prefix="/bp1")
bp2 = Blueprint("bp2", url_prefix="/bp2")
bp1.add_route(lambda _: ..., "/", name="route1")
bp2.add_route(lambda _: ..., "/", name="route2")
group_a = Blueprint.group(
bp1, bp2, url_prefix="/group-a", name_prefix="group-a"
)
group_b = Blueprint.group(
bp1, bp2, url_prefix="/group-b", name_prefix="group-b"
)
app = Sanic("TestApp")
app.blueprint(group_a)
app.blueprint(group_b)
```Name prefixing added in v23.6
Middleware
.. column::
Blueprints can also have middleware that is specifically registered for its endpoints only... column::
```python
@bp.middleware
async def print_on_request(request):
print("I am a spy")
@bp.middleware("request")
async def halt_request(request):
return text("I halted the request")
@bp.middleware("response")
async def halt_response(request, response):
return text("I halted the response")
```.. column::
Similarly, using blueprint groups, it is possible to apply middleware to an entire group of nested blueprints... column::
```python
bp1 = Blueprint("bp1", url_prefix="/bp1")
bp2 = Blueprint("bp2", url_prefix="/bp2")
@bp1.middleware("request")
async def bp1_only_middleware(request):
print("applied on Blueprint : bp1 Only")
@bp1.route("/")
async def bp1_route(request):
return text("bp1")
@bp2.route("/<param>")
async def bp2_route(request, param):
return text(param)
group = Blueprint.group(bp1, bp2)
@group.middleware("request")
async def group_middleware(request):
print("common middleware applied for both bp1 and bp2")
# Register Blueprint group under the app
app.blueprint(group)
```Exceptions
.. column::
Just like other [exception handling](./exceptions.md), you can define blueprint specific handlers... column::
```python
@bp.exception(NotFound)
def ignore_404s(request, exception):
return text("Yep, I totally found the page: {}".format(request.url))
```Static files
.. column::
Blueprints can also have their own static handlers.. column::
```python
bp = Blueprint("bp", url_prefix="/bp")
bp.static("/web/path", "/folder/to/serve")
bp.static("/web/path", "/folder/to/server", name="uploads")
```.. column::
Which can then be retrieved using `url_for()`. See [routing](../basics/routing.md) for more information... column::
```python
>>> print(app.url_for("static", name="bp.uploads", filename="file.txt"))
'/bp/web/path/file.txt'
```Listeners
.. column::
Blueprints can also implement [listeners](../basics/listeners.md)... column::
```python
@bp.listener("before_server_start")
async def before_server_start(app, loop):
...
@bp.listener("after_server_stop")
async def after_server_stop(app, loop):
...
```Versioning
As discussed in the versioning section, blueprints can be used to implement different versions of a web API.
.. column::
The `version` will be prepended to the routes as `/v1` or `/v2`, etc... column::
```python
auth1 = Blueprint("auth", url_prefix="/auth", version=1)
auth2 = Blueprint("auth", url_prefix="/auth", version=2)
```.. column::
When we register our blueprints on the app, the routes `/v1/auth` and `/v2/auth` will now point to the individual blueprints, which allows the creation of sub-sites for each API version... column::
```python
from auth_blueprints import auth1, auth2
app = Sanic(__name__)
app.blueprint(auth1)
app.blueprint(auth2)
```.. column::
It is also possible to group the blueprints under a `BlueprintGroup` entity and version multiple of them together at the
same time... column::
```python
auth = Blueprint("auth", url_prefix="/auth")
metrics = Blueprint("metrics", url_prefix="/metrics")
group = Blueprint.group(auth, metrics, version="v1")
# This will provide APIs prefixed with the following URL path
# /v1/auth/ and /v1/metrics
```Composable
A Blueprint may be registered to multiple groups, and each of BlueprintGroup itself could be registered and nested further. This creates a limitless possibility Blueprint composition.
Added in v21.6
.. column::
Take a look at this example and see how the two handlers are actually mounted as five (5) distinct routes... column::
```python
app = Sanic(__name__)
blueprint_1 = Blueprint("blueprint_1", url_prefix="/bp1")
blueprint_2 = Blueprint("blueprint_2", url_prefix="/bp2")
group = Blueprint.group(
blueprint_1,
blueprint_2,
version=1,
version_prefix="/api/v",
url_prefix="/grouped",
strict_slashes=True,
)
primary = Blueprint.group(group, url_prefix="/primary")
@blueprint_1.route("/")
def blueprint_1_default_route(request):
return text("BP1_OK")
@blueprint_2.route("/")
def blueprint_2_default_route(request):
return text("BP2_OK")
app.blueprint(group)
app.blueprint(primary)
app.blueprint(blueprint_1)
# The mounted paths:
# /api/v1/grouped/bp1/
# /api/v1/grouped/bp2/
# /api/v1/primary/grouped/bp1
# /api/v1/primary/grouped/bp2
# /bp1
```Generating a URL
When generating a url with url_for(), the endpoint name will be in the form:
{blueprint_name}.{handler_name}Guide Content En Guide Best Practices Decorators (`guide/content/en/guide/best-practices/decorators.md`)
Decorators
One of the best ways to create a consistent and DRY web API is to make use of decorators to remove functionality from the handlers, and make it repeatable across your views.
.. column::
Therefore, it is very common to see a Sanic view handler with several decorators on it... column::
```python
@app.get("/orders")
@authorized("view_order")
@validate_list_params()
@inject_user()
async def get_order_details(request, params, user):
...
```Example
Here is a starter template to help you create decorators.
In this example, let’s say you want to check that a user is authorized to access a particular endpoint. You can create a decorator that wraps a handler function, checks a request if the client is authorized to access a resource, and sends the appropriate response.
from functools import wraps
from sanic.response import json
def authorized():
def decorator(f):
@wraps(f)
async def decorated_function(request, *args, **kwargs):
# run some method that checks the request
# for the client's authorization status
is_authorized = await check_request_for_authorization_status(request)
if is_authorized:
# the user is authorized.
# run the handler method and return the response
response = await f(request, *args, **kwargs)
return response
else:
# the user is not authorized.
return json({"status": "not_authorized"}, 403)
return decorated_function
return decorator
@app.route("/")
@authorized()
async def test(request):
return json({"status": "authorized"})Templates
Decorators are fundamental to building applications with Sanic. They increase the portability and maintainablity of your code.
In paraphrasing the Zen of Python: "[decorators] are one honking great idea -- let's do more of those!"
To make it easier to implement them, here are three examples of copy/pastable code to get you started.
.. column::
Don't forget to add these import statements. Although it is *not* necessary, using `@wraps` helps keep some of the metadata of your function intact. [See docs](https://docs.python.org/3/library/functools.html#functools.wraps). Also, we use the `isawaitable` pattern here to allow the route handlers to by regular or asynchronous functions... column::
```python
from inspect import isawaitable
from functools import wraps
```With args
.. column::
Often, you will want a decorator that will *always* need arguments. Therefore, when it is implemented you will always be calling it.
```python
@app.get("/")
@foobar(1, 2)
async def handler(request: Request):
return text("hi")
```.. column::
```python
def foobar(arg1, arg2):
def decorator(f):
@wraps(f)
async def decorated_function(request, *args, **kwargs):
response = f(request, *args, **kwargs)
if isawaitable(response):
response = await response
return response
return decorated_function
return decorator
```Without args
.. column::
Sometimes you want a decorator that will not take arguments. When this is the case, it is a nice convenience not to have to call it
```python
@app.get("/")
@foobar
async def handler(request: Request):
return text("hi")
```.. column::
```python
def foobar(func):
def decorator(f):
@wraps(f)
async def decorated_function(request, *args, **kwargs):
response = f(request, *args, **kwargs)
if isawaitable(response):
response = await response
return response
return decorated_function
return decorator(func)
```With or Without args
.. column::
If you want a decorator with the ability to be called or not, you can follow this pattern. Using keyword only arguments is not necessary, but might make implementation simpler.
```python
@app.get("/")
@foobar(arg1=1, arg2=2)
async def handler(request: Request):
return text("hi")
```
```python
@app.get("/")
@foobar
async def handler(request: Request):
return text("hi")
```.. column::
```python
def foobar(maybe_func=None, *, arg1=None, arg2=None):
def decorator(f):
@wraps(f)
async def decorated_function(request, *args, **kwargs):
response = f(request, *args, **kwargs)
if isawaitable(response):
response = await response
return response
return decorated_function
return decorator(maybe_func) if maybe_func else decorator
```Guide Content En Guide Best Practices Exceptions (`guide/content/en/guide/best-practices/exceptions.md`)
Exceptions
Using Sanic exceptions
Sometimes you just need to tell Sanic to halt execution of a handler and send back a status code response. You can raise a SanicException for this and Sanic will do the rest for you.
You can pass an optional status_code argument. By default, a SanicException will return an internal server error 500 response.
from sanic.exceptions import SanicException
@app.route("/youshallnotpass")
async def no_no(request):
raise SanicException("Something went wrong.", status_code=501)Sanic provides a number of standard exceptions. They each automatically will raise the appropriate HTTP status code in your response. Check the API reference for more details.
.. column::
The more common exceptions you _should_ implement yourself include:
- `BadRequest` (400)
- `Unauthorized` (401)
- `Forbidden` (403)
- `NotFound` (404)
- `ServerError` (500).. column::
```python
from sanic import exceptions
@app.route("/login")
async def login(request):
user = await some_login_func(request)
if not user:
raise exceptions.NotFound(
f"Could not find user with username={request.json.username}"
)
```Exception properties
All exceptions in Sanic derive from SanicException. That class has a few properties on it that assist the developer in consistently reporting their exceptions across an application.
messagestatus_codequietheaderscontextextra
All of these properties can be passed to the exception when it is created, but the first three can also be used as class variables as we will see.
.. column::
### `message`
The `message` property obviously controls the message that will be displayed as with any other exception in Python. What is particularly useful is that you can set the `message` property on the class definition allowing for easy standardization of language across an application.. column::
```python
class CustomError(SanicException):
message = "Something bad happened"
raise CustomError
# or
raise CustomError("Override the default message with something else")
```.. column::
### `status_code`
This property is used to set the response code when the exception is raised. This can particularly be useful when creating custom 400 series exceptions that are usually in response to bad information coming from the client... column::
```python
class TeapotError(SanicException):
status_code = 418
message = "Sorry, I cannot brew coffee"
raise TeapotError
# or
raise TeapotError(status_code=400)
```.. column::
### `quiet`
By default, exceptions will be output by Sanic to the `error_logger`. Sometimes this may not be desirable, especially if you are using exceptions to trigger events in exception handlers (see [the following section](./exceptions.md#handling)). You can suppress the log output using `quiet=True`... column::
```python
class SilentError(SanicException):
message = "Something happened, but not shown in logs"
quiet = True
raise SilentError
# or
raise InvalidUsage("blah blah", quiet=True)
```.. column::
Sometimes while debugging you may want to globally ignore the `quiet=True` property. You can force Sanic to log out all exceptions regardless of this property using `NOISY_EXCEPTIONS`
*Added in v21.12*.. column::
```python
app.config.NOISY_EXCEPTIONS = True
```.. column::
### `headers`
Using `SanicException` as a tool for creating responses is super powerful. This is in part because not only can you control the `status_code`, but you can also control reponse headers directly from the exception... column::
```python
class MyException(SanicException):
headers = {
"X-Foo": "bar"
}
raise MyException
# or
raise InvalidUsage("blah blah", headers={
"X-Foo": "bar"
})
```.. column::
### `extra`
See [contextual exceptions](./exceptions.md#contextual-exceptions)
*Added in v21.12*.. column::
```python
raise SanicException(..., extra={"name": "Adam"})
```.. column::
### `context`
See [contextual exceptions](./exceptions.md#contextual-exceptions)
*Added in v21.12*.. column::
```python
raise SanicException(..., context={"foo": "bar"})
```Handling
Sanic handles exceptions automatically by rendering an error page, so in many cases you don't need to handle them yourself. However, if you would like more control on what to do when an exception is raised, you can implement a handler yourself.
Sanic provides a decorator for this, which applies to not only the Sanic standard exceptions, but any exception that your application might throw.
.. column::
The easiest method to add a handler is to use `@app.exception()` and pass it one or more exceptions... column::
```python
from sanic.exceptions import NotFound
@app.exception(NotFound, SomeCustomException)
async def ignore_404s(request, exception):
return text("Yep, I totally found the page: {}".format(request.url))
```.. column::
You can also create a catchall handler by catching `Exception`... column::
```python
@app.exception(Exception)
async def catch_anything(request, exception):
...
```.. column::
You can also use `app.error_handler.add()` to add error handlers... column::
```python
async def server_error_handler(request, exception):
return text("Oops, server error", status=500)
app.error_handler.add(Exception, server_error_handler)
```Built-in error handling
Sanic ships with three formats for exceptions: HTML, JSON, and text. You can see examples of them below in the Fallback handler section.
.. column::
You can control _per route_ which format to use with the `error_format` keyword argument.
*Added in v21.9*.. column::
```python
@app.request("/", error_format="text")
async def handler(request):
...
```Custom error handling
In some cases, you might want to add some more error handling functionality to what is provided by default. In that case, you can subclass Sanic's default error handler as such:
from sanic.handlers import ErrorHandler
class CustomErrorHandler(ErrorHandler):
def default(self, request: Request, exception: Exception) -> HTTPResponse:
''' handles errors that have no error handlers assigned '''
# You custom error handling logic...
status_code = getattr(exception, "status_code", 500)
return json({
"error": str(exception),
"foo": "bar"
}, status=status_code)
app.error_handler = CustomErrorHandler()Fallback handler
Sanic comes with three fallback exception handlers:
- HTML
- Text
- JSON
These handlers present differing levels of detail depending upon whether your application is in debug mode or not.
By default, Sanic will be in "auto" mode, which means that it will using the incoming request and potential matching handler to choose the appropriate response format. For example, when in a browser it should always provide an HTML error page. When using curl, you might see JSON or plain text.
HTML
app.config.FALLBACK_ERROR_FORMAT = "html".. column::
```python
app.config.DEBUG = True
```.. column::
```python
app.config.DEBUG = False
```Text
app.config.FALLBACK_ERROR_FORMAT = "text".. column::
```python
app.config.DEBUG = True
```
```sh
curl localhost:8000/exc -i
HTTP/1.1 500 Internal Server Error
content-length: 620
connection: keep-alive
content-type: text/plain; charset=utf-8
⚠️ 500 — Internal Server Error
==============================
That time when that thing broke that other thing? That happened.
ServerError: That time when that thing broke that other thing? That happened. while handling path /exc
Traceback of TestApp (most recent call last):
ServerError: That time when that thing broke that other thing? That happened.
File /path/to/sanic/app.py, line 979, in handle_request
response = await response
File /path/to/server.py, line 16, in handler
do_something(cause_error=True)
File /path/to/something.py, line 9, in do_something
raise ServerError(
```.. column::
```python
app.config.DEBUG = False
```
```sh
curl localhost:8000/exc -i
HTTP/1.1 500 Internal Server Error
content-length: 134
connection: keep-alive
content-type: text/plain; charset=utf-8
⚠️ 500 — Internal Server Error
==============================
That time when that thing broke that other thing? That happened.
```JSON
app.config.FALLBACK_ERROR_FORMAT = "json".. column::
```python
app.config.DEBUG = True
```
```sh
curl localhost:8000/exc -i
HTTP/1.1 500 Internal Server Error
content-length: 572
connection: keep-alive
content-type: application/jso
{
"description": "Internal Server Error",
"status": 500,
"message": "That time when that thing broke that other thing? That happened.",
"path": "/exc",
"args": {},
"exceptions": [
{
"type": "ServerError",
"exception": "That time when that thing broke that other thing? That happened.",
"frames": [
{
"file": "/path/to/sanic/app.py",
"line": 979,
"name": "handle_request",
"src": "response = await response"
},
{
"file": "/path/to/server.py",
"line": 16,
"name": "handler",
"src": "do_something(cause_error=True)"
},
{
"file": "/path/to/something.py",
"line": 9,
"name": "do_something",
"src": "raise ServerError("
}
]
}
]
}
```.. column::
```python
app.config.DEBUG = False
```
```sh
curl localhost:8000/exc -i
HTTP/1.1 500 Internal Server Error
content-length: 129
connection: keep-alive
content-type: application/json
{
"description": "Internal Server Error",
"status": 500,
"message": "That time when that thing broke that other thing? That happened."
}
```Auto
Sanic also provides an option for guessing which fallback option to use.
app.config.FALLBACK_ERROR_FORMAT = "auto"Contextual Exceptions
Default exception messages that simplify the ability to consistently raise exceptions throughout your application.
class TeapotError(SanicException):
status_code = 418
message = "Sorry, I cannot brew coffee"
raise TeapotErrorBut this lacks two things:
- A dynamic and predictable message format
- The ability to add additional context to an error message (more on this in a moment)
Added in v21.12
Using one of Sanic's exceptions, you have two options to provide additional details at runtime:
raise TeapotError(extra={"foo": "bar"}, context={"foo": "bar"})What's the difference and when should you decide to use each?
extra- The object itself will never be sent to a production client. It is meant for internal use only. What could it be used for?- Generating (as we will see in a minute) a dynamic error message
- Providing runtime details to a logger
- Debug information (when in development mode, it is rendered)
context- This object is always sent to production clients. It is generally meant to be used to send additional details about the context of what happened. What could it be used for?- Providing alternative values on a
BadRequestvalidation issue - Responding with helpful details for your customers to open a support ticket
- Displaying state information like current logged in user info
- Providing alternative values on a
Dynamic and predictable message using `extra`
Sanic exceptions can be raised using extra keyword arguments to provide additional information to a raised exception instance.
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, which can be particularly useful as in the above example to pass dynamic data into the message text. This extra info object will be suppressed when in PRODUCTION mode, but displayed in DEVELOPMENT mode.
.. column::
**DEVELOPMENT**.. column::
**PRODUCTION**Additional `context` to an error message
Sanic exceptions can also be raised with a context argument to pass intended information along to the user about what happened. This is particularly useful when creating microservices or an API intended to pass error messages 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:
.. 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"
},
"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("
}
]
}
]
}
```Error reporting
Sanic has a signal that allows you to hook into the exception reporting process. This is useful if you want to send exception information to a third party service like Sentry or Rollbar. This can be conveniently accomplished by attaching an error reporting handler as show below:
@app.report_exception
async def catch_any_exception(app: Sanic, exception: Exception):
print("Caught exception:", exception).. note::
This handler will be dispatched into a background task and **IS NOT** intended for use to manipulate any response data. It is intended to be used for logging or reporting purposes only, and should not impact the ability of your application to return the error response to the client.Added in v23.6
Guide Content En Guide Best Practices Logging (`guide/content/en/guide/best-practices/logging.md`)
Logging
Sanic allows you to do different types of logging (access log, error log) on the requests based on the Python logging API. You should have some basic knowledge on Python logging if you want to create a new configuration.
But, don't worry, out of the box Sanic ships with some sensible logging defaults. Out of the box it uses an AutoFormatter that will format the logs depending upon whether you are in debug mode or not. We will show you how to force this later on.
Quick Start
Let's start by looking at what logging might look like in local development. For this, we will use the default logging configuration that Sanic provides and make sure to run Sanic in development mode.
.. column::
A simple example using default settings would be like this:.. column::
```python
from sanic import Sanic
from sanic.log import logger
from sanic.response import text
app = Sanic('logging_example')
@app.route('/')
async def test(request):
logger.info('Here is your log')
return text('Hello World!')
```.. column::
Because we are specifically trying to look at the development logs, we will make sure to run Sanic in development mode... column::
```sh
sanic path.to.server:app --dev
```After the server is running, you should see logs like this.
You can send a request to server and it will print the log messages.
Some important points to note:
- The default log level in production mode is
INFO. - The default log level in debug mode is
DEBUG. - When in debug mode, the log messages will not have a timestamp (except on access logs).
- Sanic will try to colorize the logs if the terminal supports it. If you are running in Docker with docker-compose, you may need to set
tty: truein yourdocker-compose.ymlfile to see the colors.
Sanic's loggers
Out of the box, Sanic ships with five loggers:
| Logger Name | Use Case |
|---|---|
sanic.root |
Used to log internal messages. |
sanic.error |
Used to log error logs. |
sanic.access |
Used to log access logs. |
sanic.server |
Used to log server logs. |
sanic.websockets |
Used to log websocket logs. |
.. column::
If you want to use these loggers yourself, you can import them from `sanic.log`... column::
```python
from sanic.log import logger, error_logger, access_logger, server_logger, websockets_logger
logger.info('This is a root logger message')
```.. warning::
Feel free to use the root logger and the error logger yourself. But, you probably don't want to use the access logger, server logger, or websockets logger directly. These are used internally by Sanic and are configured to log in a specific way. If you want to change the way these loggers log, you should change the logging configuration.Default logging configuration
Sanic ships with a default logging configuration that is used when you do not provide your own. This configuration is stored in sanic.log.LOGGING_CONFIG_DEFAULTS.
/* Detailed source-code truncated for AI context efficiency. */Changing Sanic loggers
.. column::
To use your own logging config, simply use `logging.config.dictConfig`, or pass `log_config` when you initialize Sanic app... column::
```python
app = Sanic('logging_example', log_config=LOGGING_CONFIG)
if __name__ == "__main__":
app.run(access_log=False)
```.. column::
But, what if you do not want to control the logging completely, just change the formatter for example? Here, we will import the default logging config and modify only the parts that we want to force (for example) to use the `ProdFormatter` all of the time... column::
```python
from sanic.log import LOGGING_CONFIG_DEFAULTS
LOGGING_CONFIG_DEFAULTS['formatters']['generic']['class'] = 'sanic.logging.formatter.ProdFormatter'
LOGGING_CONFIG_DEFAULTS['formatters']['access']['class'] = 'sanic.logging.formatter.ProdAccessFormatter'
app = Sanic('logging_example', log_config=LOGGING_CONFIG_DEFAULTS)
```.. tip:: FYI
Logging in Python is a relatively cheap operation. However, if you are serving a high number of requests and performance is a concern, all of that time logging out access logs adds up and becomes quite expensive.
This is a good opportunity to place Sanic behind a proxy (like nginx) and to do your access logging there. You will see a *significant* increase in overall performance by disabling the `access_log`.
For optimal production performance, it is advised to run Sanic with `debug` and `access_log` disabled: `app.run(debug=False, access_log=False)`Access logger additional parameters
Sanic provides additional parameters to the access logger.
| Log Context Parameter | Parameter Value | Datatype |
|---|---|---|
host |
request.ip |
str |
request |
request.method + " " + request.url |
str |
status |
response |
int |
byte |
len(response.body) |
int |
duration |
float |
Legacy logging
Many logging changes were introduced in Sanic 24.3. The main changes were related to logging formats. If you prefer the legacy logging format, you can use the sanic.logging.formatter.LegacyFormatter and sanic.logging.formatter.LegacyAccessFormatter formatters.
Guide Content En Guide Best Practices Testing (`guide/content/en/guide/best-practices/testing.md`)
Testing
See sanic-testing
Guide Content En Guide Deployment Caddy (`guide/content/en/guide/deployment/caddy.md`)
Caddy Deployment
Introduction
Caddy is a state-of-the-art web server and proxy that supports up to HTTP/3. Its simplicity lies in its minimalistic configuration and the inbuilt ability to automatically procure TLS certificates for your domains from Let's Encrypt. In this setup, we will configure the Sanic application to serve locally at 127.0.0.1:8001, with Caddy playing the role of the public-facing server for the domain example.com.
You may install Caddy from your favorite package menager on Windows, Linux and Mac. The package is named caddy.
Proxied Sanic app
from sanic import Sanic
from sanic.response import text
app = Sanic("proxied_example")
@app.get("/")
def index(request):
# This should display external (public) addresses:
return text(
f"{request.remote_addr} connected to {request.url_for('index')}\n"
f"Forwarded: {request.forwarded}\n"
)To run this application, save as proxied_example.py, and use the sanic command-line interface as follows:
SANIC_PROXIES_COUNT=1 sanic proxied_example --port 8001Setting the SANIC_PROXIES_COUNT environment variable instructs Sanic to trust the X-Forwarded-* headers sent by Caddy, allowing it to correctly identify the client's IP address and other information.
Caddy is simple
If you have no other web servers running, you can simply run Caddy CLI (needs sudo on Linux):
caddy reverse-proxy --from example.com --to :8001This is a complete server that includes a certificate for your domain, http-to-https redirect, proxy headers, streaming and WebSockets. Your Sanic application should now be available on the domain you specified by HTTP versions 1, 2 and 3. Remember to open up UDP/443 on your firewall to enable H3 communications.
All done?
Soon enough you'll be needing more than one server, or more control over details, which is where the configuration files come in. The above command is equivalent to this Caddyfile, serving as a good starting point for your install:
example.com {
reverse_proxy localhost:8001
}Some Linux distributions install Caddy such that it reads configuration from /etc/caddy/Caddyfile, which import /etc/caddy/conf.d/* for each site you are running. If not, you'll need to manually run caddy run as a system service, pointing it at the proper config file. Alternatively, use Caddy API mode with caddy run --resume for persistent config changes. Note that any Caddyfile loading will replace all prior configuration and thus caddy-api is not configurable in this traditional manner.
Advanced configuration
At times, you might need to mix static files and handlers at the site root for cleaner URLs. In Sanic, you'd use app.static("/", "static", index="index.html") to achieve this. However, for improved performance, you can offload serving static files to Caddy:
app.example.com {
# Look for static files first, proxy to Sanic if not found
route {
file_server {
root /srv/sanicexample/static
precompress br # brotli your large scripts and styles
pass_thru
}
reverse_proxy unix//tmp/sanic.socket # sanic --unix /tmp/sanic.socket
}
}Please refer to Caddy documentation for more options.
Guide Content En Guide Deployment Docker (`guide/content/en/guide/deployment/docker.md`)
Docker Deployment
Introduction
For a long time, the environment has always been a difficult problem for deployment. If there are conflicting configurations in your project, you have to spend a lot of time resolving them. Fortunately, virtualization provides us with a good solution. Docker is one of them. If you don't know Docker, you can visit Docker official website to learn more.
Build Image
Let's start with a simple project. We will use a Sanic project as an example. Assume the project path is /path/to/SanicDocker.
.. column::
The directory structure looks like this:.. column::
```text
# /path/to/SanicDocker
SanicDocker
├── requirements.txt
├── dockerfile
└── server.py
```.. column::
And the `server.py` code looks like this:.. column::
```python
app = Sanic("MySanicApp")
@app.get('/')
async def hello(request):
return text("OK!")
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
```.. note::
Please note that the host cannot be 127.0.0.1 . In docker container, 127.0.0.1 is the default network interface of the container, only the container can communicate with other containers. more information please visit [Docker network](https://docs.docker.com/engine/reference/commandline/network/)Code is ready, let's write the Dockerfile:
FROM sanicframework/sanic:3.8-latest
WORKDIR /sanic
COPY . .
RUN pip install -r requirements.txt
EXPOSE 8000
CMD ["python", "server.py"]Run the following command to build the image:
docker build -t my-sanic-image .Start Container
.. column::
After the image built, we can start the container use `my-sanic-image`:.. column::
```shell
docker run --name mysanic -p 8000:8000 -d my-sanic-image
```.. column::
Now we can visit `http://localhost:8000` to see the result:.. column::
```text
OK!
```Use docker-compose
If your project consist of multiple services, you can use docker-compose to manage them.
for example, we will deploy my-sanic-image and nginx, achieve through nginx access sanic server.
.. column::
First of all, we need prepare nginx configuration file. create a file named `mysanic.conf`:.. column::
```nginx
server {
listen 80;
listen [::]:80;
location / {
proxy_pass http://mysanic:8000/;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection upgrade;
proxy_set_header Accept-Encoding gzip;
}
}
```.. column::
Then, we need to prepare `docker-compose.yml` file. The content follows:.. column::
```yaml
version: "3"
services:
mysanic:
image: my-sanic-image
ports:
- "8000:8000"
restart: always
mynginx:
image: nginx:1.13.6-alpine
ports:
- "80:80"
depends_on:
- mysanic
volumes:
- ./mysanic.conf:/etc/nginx/conf.d/mysanic.conf
restart: always
networks:
default:
driver: bridge
```.. column::
After that, we can start them:.. column::
```shell
docker-compose up -d
```.. column::
Now, we can visit `http://localhost:80` to see the result:.. column::
```text
OK!
```Guide Content En Guide Deployment Nginx (`guide/content/en/guide/deployment/nginx.md`)
Nginx Deployment
Introduction
Although Sanic can be run directly on Internet, it may be useful to use a proxy
server such as Nginx in front of it. This is particularly useful for running
multiple virtual hosts on the same IP, serving NodeJS or other services beside
a single Sanic app, and it also allows for efficient serving of static files.
TLS and HTTP/2 are also easily implemented on such proxy.
We are setting the Sanic app to serve only locally at 127.0.0.1:8001, while the
Nginx installation is responsible for providing the service to public Internet
on domain example.com. Static files will be served by Nginx for maximal
performance.
Proxied Sanic app
from sanic import Sanic
from sanic.response import text
app = Sanic("proxied_example")
@app.get("/")
def index(request):
# This should display external (public) addresses:
return text(
f"{request.remote_addr} connected to {request.url_for('index')}\n"
f"Forwarded: {request.forwarded}\n"
)Since this is going to be a system service, save your code to/srv/sanicservice/proxied_example.py.
For testing, run your app in a terminal using the sanic CLI in the folder where you saved the file.
SANIC_FORWARDED_SECRET=_hostname sanic proxied_example --port 8001We provide Sanic config FORWARDED_SECRET to identify which proxy it gets
the remote addresses from. Note the _ in front of the local hostname.
This gives basic protection against users spoofing these headers and faking
their IP addresses and more.
SSL certificates
Install Certbot and obtain a certicate for all your domains. This will spin up its own webserver on port 80 for a moment to verify you control the given domain names.
certbot -d example.com -d www.example.comNginx configuration
Quite much configuration is required to allow fast transparent proxying, but
for the most part these don't need to be modified, so bear with me.
.. tip:: Note
Separate upstream section, rather than simply adding the IP after `proxy_pass`
as in most tutorials, is needed for HTTP keep-alive. We also enable streaming,
WebSockets and Nginx serving static files.The following config goes inside the http section of nginx.conf or if your
system uses multiple config files, /etc/nginx/sites-available/default or
your own files (be sure to symlink them to sites-enabled):
/* Detailed source-code truncated for AI context efficiency. */Start or restart Nginx for changes to take effect. E.g.
systemctl restart nginxYou should be able to connect your app on https://example.com. Any 404
errors and such will be handled by Sanic's error pages, and whenever a static
file is present at a given path, it will be served by Nginx.
Running as a service
This part is for Linux distributions based on systemd. Create a unit file/etc/systemd/system/sanicexample.service
[Unit]
Description=Sanic Example
[Service]
DynamicUser=Yes
WorkingDirectory=/srv/sanicservice
Environment=SANIC_PROXY_SECRET=_hostname
ExecStart=sanic proxied_example --port 8001 --fast
Restart=always
[Install]
WantedBy=multi-user.targetThen reload service files, start your service and enable it on boot:
systemctl daemon-reload
systemctl start sanicexample
systemctl enable sanicexample.. tip:: Note
For brevity we skipped setting up a separate user account and a Python virtual environment or installing your app as a Python module. There are good tutorials on those topics elsewhere that easily apply to Sanic as well. The DynamicUser setting creates a strong sandbox which basically means your application cannot store its data in files, so you may consider setting `User=sanicexample` instead if you need that.Guide Content En Guide How To Authentication (`guide/content/en/guide/how-to/authentication.md`)
Authentication
How do I control authentication and authorization?
This is an extremely complicated subject to cram into a few snippets. But, this should provide you with an idea on ways to tackle this problem. This example uses JWTs, but the concepts should be equally applicable to sessions or some other scheme.
`server.py`
from sanic import Sanic, text
from auth import protected
from login import login
app = Sanic("AuthApp")
app.config.SECRET = "KEEP_IT_SECRET_KEEP_IT_SAFE"
app.blueprint(login)
@app.get("/secret")
@protected
async def secret(request):
return text("To go fast, you must be fast.")`login.py`
import jwt
from sanic import Blueprint, text
login = Blueprint("login", url_prefix="/login")
@login.post("/")
async def do_login(request):
token = jwt.encode({}, request.app.config.SECRET)
return text(token)`auth.py`
from functools import wraps
import jwt
from sanic import text
def check_token(request):
if not request.token:
return False
try:
jwt.decode(
request.token, request.app.config.SECRET, algorithms=["HS256"]
)
except jwt.exceptions.InvalidTokenError:
return False
else:
return True
def protected(wrapped):
def decorator(f):
@wraps(f)
async def decorated_function(request, *args, **kwargs):
is_authenticated = check_token(request)
if is_authenticated:
response = await f(request, *args, **kwargs)
return response
else:
return text("You are unauthorized.", 401)
return decorated_function
return decorator(wrapped)This decorator pattern is taken from the decorators page.
$ curl localhost:9999/secret -i
HTTP/1.1 401 Unauthorized
content-length: 21
connection: keep-alive
content-type: text/plain; charset=utf-8
You are unauthorized.
$ curl localhost:9999/login -X POST
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.rjxS7ztIGt5tpiRWS8BGLUqjQFca4QOetHcZTi061DE
$ curl localhost:9999/secret -i -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.rjxS7ztIGt5tpiRWS8BGLUqjQFca4QOetHcZTi061DE"
HTTP/1.1 200 OK
content-length: 29
connection: keep-alive
content-type: text/plain; charset=utf-8
To go fast, you must be fast.
$ curl localhost:9999/secret -i -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.BAD"
HTTP/1.1 401 Unauthorized
content-length: 21
connection: keep-alive
content-type: text/plain; charset=utf-8
You are unauthorized.Also, checkout some resources from the community:
- Awesome Sanic - Authorization & Session
- EuroPython 2020 - Overcoming access control in web APIs
Guide Content En Guide How To Autodiscovery (`guide/content/en/guide/how-to/autodiscovery.md`)
title: Autodiscovery
Autodiscovery of Blueprints, Middleware, and Listeners
How do I autodiscover the components I am using to build my application?
One of the first problems someone faces when building an application, is how to structure the project. Sanic makes heavy use of decorators to register route handlers, middleware, and listeners. And, after creating blueprints, they need to be mounted to the application.
A possible solution is a single file in which everything is imported and applied to the Sanic instance. Another is passing around the Sanic instance as a global variable. Both of these solutions have their drawbacks.
An alternative is autodiscovery. You point your application at modules (already imported, or strings), and let it wire everything up.
`server.py`
from sanic import Sanic
from sanic.response import empty
import blueprints
from utility import autodiscover
app = Sanic("auto", register=True)
autodiscover(
app,
blueprints,
"parent.child",
"listeners.something",
recursive=True,
)
app.route("/")(lambda _: empty())[2021-03-02 21:37:02 +0200] [880451] [INFO] Goin' Fast @ http://127.0.0.1:9999
[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something
[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something @ nested
[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something @ level1
[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something @ level3
[2021-03-02 21:37:02 +0200] [880451] [DEBUG] something inside __init__.py
[2021-03-02 21:37:02 +0200] [880451] [INFO] Starting worker [880451]`utility.py`
from glob import glob
from importlib import import_module, util
from inspect import getmembers
from pathlib import Path
from types import ModuleType
from typing import Union
from sanic.blueprints import Blueprint
def autodiscover(
app, *module_names: Union[str, ModuleType], recursive: bool = False
):
mod = app.__module__
blueprints = set()
_imported = set()
def _find_bps(module):
nonlocal blueprints
for _, member in getmembers(module):
if isinstance(member, Blueprint):
blueprints.add(member)
for module in module_names:
if isinstance(module, str):
module = import_module(module, mod)
_imported.add(module.__file__)
_find_bps(module)
if recursive:
base = Path(module.__file__).parent
for path in glob(f"{base}/**/*.py", recursive=True):
if path not in _imported:
name = "module"
if "__init__" in path:
*_, name, __ = path.split("/")
spec = util.spec_from_file_location(name, path)
specmod = util.module_from_spec(spec)
_imported.add(path)
spec.loader.exec_module(specmod)
_find_bps(specmod)
for bp in blueprints:
app.blueprint(bp)`blueprints/level1.py`
from sanic import Blueprint
from sanic.log import logger
level1 = Blueprint("level1")
@level1.after_server_start
def print_something(app, loop):
logger.debug("something @ level1")`blueprints/one/two/level3.py`
from sanic import Blueprint
from sanic.log import logger
level3 = Blueprint("level3")
@level3.after_server_start
def print_something(app, loop):
logger.debug("something @ level3")`listeners/something.py`
from sanic import Sanic
from sanic.log import logger
app = Sanic.get_app("auto")
@app.after_server_start
def print_something(app, loop):
logger.debug("something")`parent/child/__init__.py`
from sanic import Blueprint
from sanic.log import logger
bp = Blueprint("__init__")
@bp.after_server_start
def print_something(app, loop):
logger.debug("something inside __init__.py")`parent/child/nested.py`
from sanic import Blueprint
from sanic.log import logger
nested = Blueprint("nested")
@nested.after_server_start
def print_something(app, loop):
logger.debug("something @ nested")here is the dir tree
generate with 'find . -type d -name "__pycache__" -exec rm -rf {} +; tree'
. # run 'sanic sever -d' here
├── blueprints
│ ├── __init__.py # you need add this file, just empty
│ ├── level1.py
│ └── one
│ └── two
│ └── level3.py
├── listeners
│ └── something.py
├── parent
│ └── child
│ ├── __init__.py
│ └── nested.py
├── server.py
└── utility.pysource ./.venv/bin/activate # activate the python venv which sanic is installed in
sanic sever -d # run this in the directory containing server.pyyou will see "something ***" like this:
[2023-07-12 11:23:36 +0000] [113704] [DEBUG] something
[2023-07-12 11:23:36 +0000] [113704] [DEBUG] something inside __init__.py
[2023-07-12 11:23:36 +0000] [113704] [DEBUG] something @ level3
[2023-07-12 11:23:36 +0000] [113704] [DEBUG] something @ level1
[2023-07-12 11:23:36 +0000] [113704] [DEBUG] something @ nestedGuide Content En Guide How To Cors (`guide/content/en/guide/how-to/cors.md`)
title: CORS
Cross-origin resource sharing (CORS)
How do I configure my application for CORS?
.. note::
🏆 The best solution is to use [Sanic Extensions](../../plugins/sanic-ext/http/cors.md).
However, if you would like to build your own version, you could use this limited example as a starting point.`server.py`
from sanic import Sanic, text
from cors import add_cors_headers
from options import setup_options
app = Sanic("app")
@app.route("/", methods=["GET", "POST"])
async def do_stuff(request):
return text("...")
# Add OPTIONS handlers to any route that is missing it
app.register_listener(setup_options, "before_server_start")
# Fill in CORS headers
app.register_middleware(add_cors_headers, "response")`cors.py`
from typing import Iterable
def _add_cors_headers(response, methods: Iterable[str]) -> None:
allow_methods = list(set(methods))
if "OPTIONS" not in allow_methods:
allow_methods.append("OPTIONS")
headers = {
"Access-Control-Allow-Methods": ",".join(allow_methods),
"Access-Control-Allow-Origin": "mydomain.com",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Allow-Headers": (
"origin, content-type, accept, "
"authorization, x-xsrf-token, x-request-id"
),
}
response.headers.extend(headers)
def add_cors_headers(request, response):
if request.method != "OPTIONS":
methods = [method for method in request.route.methods]
_add_cors_headers(response, methods)`options.py`
from collections import defaultdict
from typing import Dict, FrozenSet
from sanic import Sanic, response
from sanic.router import Route
from cors import _add_cors_headers
def _compile_routes_needing_options(
routes: Dict[str, Route]
) -> Dict[str, FrozenSet]:
needs_options = defaultdict(list)
# This is 21.12 and later. You will need to change this for older versions.
for route in routes.values():
if "OPTIONS" not in route.methods:
needs_options[route.uri].extend(route.methods)
return {
uri: frozenset(methods) for uri, methods in dict(needs_options).items()
}
def _options_wrapper(handler, methods):
def wrapped_handler(request, *args, **kwargs):
nonlocal methods
return handler(request, methods)
return wrapped_handler
async def options_handler(request, methods) -> response.HTTPResponse:
resp = response.empty()
_add_cors_headers(resp, methods)
return resp
def setup_options(app: Sanic, _):
app.router.reset()
needs_options = _compile_routes_needing_options(app.router.routes_all)
for uri, methods in needs_options.items():
app.add_route(
_options_wrapper(options_handler, methods),
uri,
methods=["OPTIONS"],
)
app.router.finalize()$ curl localhost:9999/ -i
HTTP/1.1 200 OK
Access-Control-Allow-Methods: OPTIONS,POST,GET
Access-Control-Allow-Origin: mydomain.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: origin, content-type, accept, authorization, x-xsrf-token, x-request-id
content-length: 3
connection: keep-alive
content-type: text/plain; charset=utf-8
...
$ curl localhost:9999/ -i -X OPTIONS
HTTP/1.1 204 No Content
Access-Control-Allow-Methods: GET,POST,OPTIONS
Access-Control-Allow-Origin: mydomain.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: origin, content-type, accept, authorization, x-xsrf-token, x-request-id
connection: keep-aliveAlso, checkout some resources from the community:
Guide Content En Guide How To Mounting (`guide/content/en/guide/how-to/mounting.md`)
Application Mounting
How do I mount my application at some path above the root?
# server.py
from sanic import Sanic, text
app = Sanic("app")
app.config.SERVER_NAME = "example.com/api"
@app.route("/foo")
def handler(request):
url = app.url_for("handler", _external=True)
return text(f"URL: {url}")# docker-compose.yml
version: "3.7"
services:
app:
image: nginx:alpine
ports:
- 80:80
volumes:
- type: bind
source: ./conf
target: /etc/nginx/conf.d/default.conf# conf
server {
listen 80;
# Computed data service
location /api/ {
proxy_pass http://<YOUR IP ADDRESS>:9999/;
proxy_set_header Host example.com;
}
}$ docker-compose up -d
$ sanic server.app --port=9999 --host=0.0.0.0$ curl localhost/api/foo
URL: http://example.com/api/fooGuide Content En Guide How To Orm (`guide/content/en/guide/how-to/orm.md`)
ORM
How do I use SQLAlchemy with Sanic ?
All ORM tools can work with Sanic, but non-async ORM tool have a impact on Sanic performance.
There are some orm packages who support
At present, there are many ORMs that support Python's async/await keywords. Some possible choices include:
Integration in to your Sanic application is fairly simple:
Mayim
Mayim ships with an extension for Sanic Extensions, which makes it super simple to get started with Sanic. It is certainly possible to run Mayim with Sanic without the extension, but it is recommended because it handles all of the lifecycle events and dependency injections.
.. column::
### Dependencies
First, we need to install the required dependencies. See [Mayim docs](https://ahopkins.github.io/mayim/guide/install.html#postgres) for the installation needed for your DB driver... column::
```shell
pip install sanic-ext
pip install mayim[postgres]
```.. column::
### Define ORM Model
Mayim allows you to use whatever you want for models. Whether it is [dataclasses](https://docs.python.org/3/library/dataclasses.html), [pydantic](https://pydantic-docs.helpmanual.io/), [attrs](https://www.attrs.org/en/stable/), or even just plain `dict` objects. Since it works very nicely [out of the box with Pydantic](https://ahopkins.github.io/mayim/guide/pydantic.html), that is what we will use here... column::
```python
# ./models.py
from pydantic import BaseModel
class City(BaseModel):
id: int
name: str
district: str
population: int
class Country(BaseModel):
code: str
name: str
continent: str
region: str
capital: City
```.. column::
### Define SQL
If you are unfamiliar, Mayim is different from other ORMs in that it is one-way, SQL-first. This means you define your own queries either inline, or in a separate `.sql` file, which is what we will do here... column::
```sql
-- ./queries/select_all_countries.sql
SELECT country.code,
country.name,
country.continent,
country.region,
(
SELECT row_to_json(q)
FROM (
SELECT city.id,
city.name,
city.district,
city.population
) q
) capital
FROM country
JOIN city ON country.capital = city.id
ORDER BY country.name ASC
LIMIT $limit OFFSET $offset;
```.. column::
### Create Sanic App and Async Engine
We need to create the app instance and attach the `SanicMayimExtension` with any executors... column::
```python
# ./server.py
from sanic import Sanic, Request, json
from sanic_ext import Extend
from mayim.executor import PostgresExecutor
from mayim.extensions import SanicMayimExtension
from models import Country
class CountryExecutor(PostgresExecutor):
async def select_all_countries(
self, limit: int = 4, offset: int = 0
) -> list[Country]:
...
app = Sanic("Test")
Extend.register(
SanicMayimExtension(
executors=[CountryExecutor],
dsn="postgres://...",
)
)
```.. column::
### Register Routes
Because we are using Mayim's extension for Sanic, we have the automatic `CountryExecutor` injection into the route handler. It makes for an easy, type-annotated development experience... column::
```python
@app.get("/")
async def handler(request: Request, executor: CountryExecutor):
countries = await executor.select_all_countries()
return json({"countries": [country.dict() for country in co
```.. column::
### Send Requests.. column::
```sh
curl 'http://127.0.0.1:8000'
{"countries":[{"code":"AFG","name":"Afghanistan","continent":"Asia","region":"Southern and Central Asia","capital":{"id":1,"name":"Kabul","district":"Kabol","population":1780000}},{"code":"ALB","name":"Albania","continent":"Europe","region":"Southern Europe","capital":{"id":34,"name":"Tirana","district":"Tirana","population":270000}},{"code":"DZA","name":"Algeria","continent":"Africa","region":"Northern Africa","capital":{"id":35,"name":"Alger","district":"Alger","population":2168000}},{"code":"ASM","name":"American Samoa","continent":"Oceania","region":"Polynesia","capital":{"id":54,"name":"Fagatogo","district":"Tutuila","population":2323}}]}
```SQLAlchemy
Because SQLAlchemy 1.4 has added native support for asyncio, Sanic can finally work well with SQLAlchemy. Be aware that this functionality is still considered beta by the SQLAlchemy project.
.. column::
### Dependencies
First, we need to install the required dependencies. In the past, the dependencies installed were `sqlalchemy` and `pymysql`, but now `sqlalchemy` and `aiomysql` are needed... column::
```shell
pip install -U sqlalchemy
pip install -U aiomysql
```.. column::
### Define ORM Model
ORM model creation remains the same... column::
```python
# ./models.py
from sqlalchemy import INTEGER, Column, ForeignKey, String
from sqlalchemy.orm import declarative_base, relationship
Base = declarative_base()
class BaseModel(Base):
__abstract__ = True
id = Column(INTEGER(), primary_key=True)
class Person(BaseModel):
__tablename__ = "person"
name = Column(String())
cars = relationship("Car")
def to_dict(self):
return {"name": self.name, "cars": [{"brand": car.brand} for car in self.cars]}
class Car(BaseModel):
__tablename__ = "car"
brand = Column(String())
user_id = Column(ForeignKey("person.id"))
user = relationship("Person", back_populates="cars")
```.. column::
### Create Sanic App and Async Engine
Here we use mysql as the database, and you can also choose PostgreSQL/SQLite. Pay attention to changing the driver from `aiomysql` to `asyncpg`/`aiosqlite`... column::
```python
# ./server.py
from sanic import Sanic
from sqlalchemy.ext.asyncio import create_async_engine
app = Sanic("my_app")
bind = create_async_engine("mysql+aiomysql://root:root@localhost/test", echo=True)
```.. column::
### Register Middlewares
The request middleware creates an usable `AsyncSession` object and set it to `request.ctx` and `_base_model_session_ctx`.
Thread-safe variable `_base_model_session_ctx` helps you to use the session object instead of fetching it from `request.ctx`... column::
```python
# ./server.py
from contextvars import ContextVar
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import sessionmaker
_sessionmaker = sessionmaker(bind, AsyncSession, expire_on_commit=False)
_base_model_session_ctx = ContextVar("session")
@app.middleware("request")
async def inject_session(request):
request.ctx.session = _sessionmaker()
request.ctx.session_ctx_token = _base_model_session_ctx.set(request.ctx.session)
@app.middleware("response")
async def close_session(request, response):
if hasattr(request.ctx, "session_ctx_token"):
_base_model_session_ctx.reset(request.ctx.session_ctx_token)
await request.ctx.session.close()
```.. column::
### Register Routes
According to sqlalchemy official docs, `session.query` will be legacy in 2.0, and the 2.0 way to query an ORM object is using `select`... column::
```python
# ./server.py
from sqlalchemy import select
from sqlalchemy.orm import selectinload
from sanic.response import json
from models import Car, Person
@app.post("/user")
async def create_user(request):
session = request.ctx.session
async with session.begin():
car = Car(brand="Tesla")
person = Person(name="foo", cars=[car])
session.add_all([person])
return json(person.to_dict())
@app.get("/user/<pk:int>")
async def get_user(request, pk):
session = request.ctx.session
async with session.begin():
stmt = select(Person).where(Person.id == pk).options(selectinload(Person.cars))
result = await session.execute(stmt)
person = result.scalar()
if not person:
return json({})
return json(person.to_dict())
```.. column::
### Send Requests.. column::
```sh
curl --location --request POST 'http://127.0.0.1:8000/user'
{"name":"foo","cars":[{"brand":"Tesla"}]}
```
```sh
curl --location --request GET 'http://127.0.0.1:8000/user/1'
{"name":"foo","cars":[{"brand":"Tesla"}]}
```Tortoise-ORM
.. column::
### Dependencies
tortoise-orm's dependency is very simple, you just need install tortoise-orm... column::
```shell
pip install -U tortoise-orm
```.. column::
### Define ORM Model
If you are familiar with Django, you should find this part very familiar... column::
```python
# ./models.py
from tortoise import Model, fields
class Users(Model):
id = fields.IntField(pk=True)
name = fields.CharField(50)
def __str__(self):
return f"I am {self.name}"
```.. column::
### Create Sanic App and Async Engine
Tortoise-orm provides a set of registration interface, which is convenient for users, and you can use it to create database connection easily... column::
```python
# ./main.py
from models import Users
from tortoise.contrib.sanic import register_tortoise
app = Sanic(__name__)
register_tortoise(
app, db_url="mysql://root:root@localhost/test", modules={"models": ["models"]}, generate_schemas=True
)
```.. column::
### Register Routes.. column::
```python
# ./main.py
from models import Users
from sanic import Sanic, response
@app.route("/user")
async def list_all(request):
users = await Users.all()
return response.json({"users": [str(user) for user in users]})
@app.route("/user/<pk:int>")
async def get_user(request, pk):
user = await Users.query(pk=pk)
return response.json({"user": str(user)})
if __name__ == "__main__":
app.run(port=5000)
```.. column::
### Send Requests.. column::
```sh
curl --location --request POST 'http://127.0.0.1:8000/user'
{"users":["I am foo", "I am bar"]}
```
```sh
curl --location --request GET 'http://127.0.0.1:8000/user/1'
{"user": "I am foo"}
```Guide Content En Guide How To Static Redirects (`guide/content/en/guide/how-to/static-redirects.md`)
"Static" Redirects
How do I configure static redirects?
`app.py`
### SETUP ###
import typing
import sanic, sanic.response
# Create the Sanic app
app = sanic.Sanic(__name__)
# This dictionary represents your "static"
# redirects. For example, these values
# could be pulled from a configuration file.
REDIRECTS = {
'/':'/hello_world', # Redirect '/' to '/hello_world'
'/hello_world':'/hello_world.html' # Redirect '/hello_world' to 'hello_world.html'
}
# This function will return another function
# that will return the configured value
# regardless of the arguments passed to it.
def get_static_function(value:typing.Any) -> typing.Callable[..., typing.Any]:
return lambda *_, **__: value
### ROUTING ###
# Iterate through the redirects
for src, dest in REDIRECTS.items():
# Create the redirect response object
response:sanic.HTTPResponse = sanic.response.redirect(dest)
# Create the handler function. Typically,
# only a sanic.Request object is passed
# to the function. This object will be
# ignored.
handler = get_static_function(response)
# Route the src path to the handler
app.route(src)(handler)
# Route some file and client resources
app.static('/files/', 'files')
app.static('/', 'client')
### RUN ###
if __name__ == '__main__':
app.run(
'127.0.0.1',
10000
)`client/hello_world.html`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World</title>
</head>
<link rel="stylesheet" href="/hello_world.css">
<body>
<div id='hello_world'>
Hello world!
</div>
</body>
</html>`client/hello_world.css`
#hello_world {
width: 1000px;
margin-left: auto;
margin-right: auto;
margin-top: 100px;
padding: 100px;
color: aqua;
text-align: center;
font-size: 100px;
font-family: monospace;
background-color: rgba(0, 0, 0, 0.75);
border-radius: 10px;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.75);
}
body {
background-image: url("/files/grottoes.jpg");
background-repeat: no-repeat;
background-size: cover;
}`files/grottoes.jpg`
Also, checkout some resources from the community:
Guide Content En Guide How To Table Of Contents (`guide/content/en/guide/how-to/table-of-contents.md`)
title: Table of Contents
Table of Contents
We have compiled fully working examples to answer common questions and user cases. For the most part, the examples are as minimal as possible, but should be complete and runnable solutions.
| Page | How do I ... |
|---|---|
| Application mounting | ... mount my application at some path above the root? |
| Authentication | ... control authentication and authorization? |
| Autodiscovery | ... autodiscover the components I am using to build my application? |
| CORS | ... configure my application for CORS? |
| ORM | ... use an ORM with Sanic? |
| "Static" Redirects | ... configure static redirects |
| TLS/SSL/HTTPS | ... run Sanic via HTTPS? ... redirect HTTP to HTTPS? |
Guide Content En Guide How To Tls (`guide/content/en/guide/how-to/tls.md`)
title: TLS/SSL/HTTPS
TLS/SSL/HTTPS
How do I run Sanic via HTTPS?
If you do not have TLS certificates yet, see the end of this page.
Single domain and single certificate
.. column::
Let Sanic automatically load your certificate files, which need to be named `fullchain.pem` and `privkey.pem` in the given folder:.. column::
```sh
sudo sanic myserver:app -H :: -p 443 \
--tls /etc/letsencrypt/live/example.com/
```
```python
app.run("::", 443, ssl="/etc/letsencrypt/live/example.com/")
```.. column::
Or, you can pass cert and key filenames separately as a dictionary:
Additionally, `password` may be added if the key is encrypted, all fields except for the password are passed to `request.conn_info.cert`... column::
```python
ssl = {
"cert": "/path/to/fullchain.pem",
"key": "/path/to/privkey.pem",
"password": "for encrypted privkey file", # Optional
}
app.run(host="0.0.0.0", port=8443, ssl=ssl)
```.. column::
Alternatively, [`ssl.SSLContext`](https://docs.python.org/3/library/ssl.html) may be passed, if you need full control over details such as which crypto algorithms are permitted. By default Sanic only allows secure algorithms, which may restrict access from very old devices... column::
```python
import ssl
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain("certs/fullchain.pem", "certs/privkey.pem")
app.run(host="0.0.0.0", port=8443, ssl=context)
```Multiple domains with separate certificates
.. column::
A list of multiple certificates may be provided, in which case Sanic chooses the one matching the hostname the user is connecting to. This occurs so early in the TLS handshake that Sanic has not sent any packets to the client yet.
If the client sends no SNI (Server Name Indication), the first certificate on the list will be used even though on the client browser it will likely fail with a TLS error due to name mismatch. To prevent this fallback and to cause immediate disconnection of clients without a known hostname, add `None` as the first entry on the list. `--tls-strict-host` is the equivalent CLI option... column::
```python
ssl = ["certs/example.com/", "certs/bigcorp.test/"]
app.run(host="0.0.0.0", port=8443, ssl=ssl)
```
```sh
sanic myserver:app
--tls certs/example.com/
--tls certs/bigcorp.test/
--tls-strict-host
```.. tip::
You may also use `None` in front of a single certificate if you do not wish to reveal your certificate, true hostname or site content to anyone connecting to the IP address instead of the proper DNS name... column::
Dictionaries can be used on the list. This allows also specifying which domains a certificate matches to, although the names present on the certificate itself cannot be controlled from here. If names are not specified, the names from the certificate itself are used.
To only allow connections to the main domain **example.com** and only to subdomains of **bigcorp.test**:.. column::
```python
ssl = [
None, # No fallback if names do not match!
{
"cert": "certs/example.com/fullchain.pem",
"key": "certs/example.com/privkey.pem",
"names": ["example.com", "*.bigcorp.test"],
}
]
app.run(host="0.0.0.0", port=8443, ssl=ssl)
```Accessing TLS information in handlers via `request.conn_info` fields
.ssl- is the connection secure (bool).cert- certificate info and dict fields of the currently active cert (dict).server_name- the SNI sent by the client (str, may be empty)
Do note that all conn_info fields are per connection, where there may be many requests over time. If a proxy is used in front of your server, these requests on the same pipe may even come from different users.
Redirect HTTP to HTTPS, with certificate requests still over HTTP
In addition to your normal server(s) running HTTPS, run another server for redirection, http_redir.py:
from sanic import Sanic, exceptions, response
app = Sanic("http_redir")
# Serve ACME/certbot files without HTTPS, for certificate renewals
app.static("/.well-known", "/var/www/.well-known", resource_type="dir")
@app.exception(exceptions.NotFound, exceptions.MethodNotSupported)
def redirect_everything_else(request, exception):
server, path = request.server_name, request.path
if server and path.startswith("/"):
return response.redirect(f"https://{server}{path}", status=308)
return response.text("Bad Request. Please use HTTPS!", status=400)It is best to setup this as a systemd unit separate of your HTTPS servers. You may need to run HTTP while initially requesting your certificates, while you cannot run the HTTPS server yet. Start for IPv4 and IPv6:
sanic http_redir:app -H 0.0.0.0 -p 80
sanic http_redir:app -H :: -p 80Alternatively, it is possible to run the HTTP redirect application from the main application:
# app == Your main application
# redirect == Your http_redir application
@app.before_server_start
async def start(app, _):
app.ctx.redirect = await redirect.create_server(
port=80, return_asyncio_server=True
)
app.add_task(runner(redirect, app.ctx.redirect))
@app.before_server_stop
async def stop(app, _):
await app.ctx.redirect.close()
async def runner(app, app_server):
app.state.is_running = True
try:
app.signalize()
app.finalize()
app.state.is_started = True
await app_server.serve_forever()
finally:
app.state.is_running = False
app.state.is_stopping = TrueGet certificates for your domain names
You can get free certificates from Let's Encrypt. Install certbot via your package manager, and request a certificate:
sudo certbot certonly --key-type ecdsa --preferred-chain "ISRG Root X1" -d example.com -d www.example.comMultiple domain names may be added by further -d arguments, all stored into a single certificate which gets saved to /etc/letsencrypt/live/example.com/ as per the first domain that you list here.
The key type and preferred chain options are necessary for getting a minimal size certificate file, essential for making your server run as fast as possible. The chain will still contain one RSA certificate until when Let's Encrypt gets their new EC chain trusted in all major browsers.
Guide Content En Guide Running App Loader (`guide/content/en/guide/running/app-loader.md`)
title: Dynamic Applications
Dynamic Applications
Running Sanic has been optimized to work with the CLI. If you have not read it yet, you should read Running Sanic to become familiar with the options.
.. column::
This includes running it as a global scope object..... column::
```sh
sanic path.to.server:app
```
```python
# server.py
app = Sanic("TestApp")
@app.get("/")
async def handler(request: Request):
return json({"foo": "bar"})
```.. column::
...or, a factory function that creates the `Sanic` application object... column::
```sh
sanic path.to.server:create_app --factory
```
```python
# server.py
def create_app():
app = Sanic("TestApp")
@app.get("/")
async def handler(request: Request):
return json({"foo": "bar"})
return app
```Sometimes, this is not enough ... 🤔
Introduced in v22.9, Sanic has an AppLoader object that is responsible for creating an application in the various worker processes. You can take advantage of this if you need to create a more dynamic startup experience for your application.
.. column::
An `AppLoader` can be passed a callable that returns a `Sanic` instance. That `AppLoader` could be used with the low-level application running API... column::
```python
import sys
from functools import partial
from sanic import Request, Sanic, json
from sanic.worker.loader import AppLoader
def attach_endpoints(app: Sanic):
@app.get("/")
async def handler(request: Request):
return json({"app_name": request.app.name})
def create_app(app_name: str) -> Sanic:
app = Sanic(app_name)
attach_endpoints(app)
return app
if __name__ == "__main__":
app_name = sys.argv[-1]
loader = AppLoader(factory=partial(create_app, app_name))
app = loader.load()
app.prepare(port=9999, dev=True)
Sanic.serve(primary=app, app_loader=loader)
```
```sh
python path/to/server.py MyTestAppName
```In the above example, the AppLoader is created with a factory that can be used to create copies of the same application across processes. When doing this, you should explicitly use the Sanic.serve pattern shown above so that the AppLoader that you create is not replaced.
Guide Content En Guide Running Configuration (`guide/content/en/guide/running/configuration.md`)
Configuration
Basics
.. column::
Sanic holds the configuration in the config attribute of the application object. The configuration object is merely an object that can be modified either using dot-notation or like a dictionary... column::
```python
app = Sanic("myapp")
app.config.DB_NAME = "appdb"
app.config["DB_USER"] = "appuser"
```.. column::
You can also use the `update()` method like on regular dictionaries... column::
```python
db_settings = {
'DB_HOST': 'localhost',
'DB_NAME': 'appdb',
'DB_USER': 'appuser'
}
app.config.update(db_settings)
```.. note::
It is standard practice in Sanic to name your config values in **uppercase letters**. Indeed, you may experience weird behaviors if you start mixing uppercase and lowercase names.Loading
Environment variables
.. column::
Any environment variables defined with the `SANIC_` prefix will be applied to the Sanic config. For example, setting `SANIC_REQUEST_TIMEOUT` will be loaded by the application automatically and fed into the `REQUEST_TIMEOUT` config variable... column::
```bash
$ export SANIC_REQUEST_TIMEOUT=10
```
```python
>>> print(app.config.REQUEST_TIMEOUT)
10
```.. column::
You can change the prefix that Sanic is expecting at startup... column::
```bash
$ export MYAPP_REQUEST_TIMEOUT=10
```
```python
>>> app = Sanic(__name__, env_prefix='MYAPP_')
>>> print(app.config.REQUEST_TIMEOUT)
10
```.. column::
You can also disable environment variable loading completely... column::
```python
app = Sanic(__name__, load_env=False)
```Using Sanic.update_config
The Sanic instance has a very versatile method for loading config: app.update_config. You can feed it a path to a file, a dictionary, a class, or just about any other sort of object.
From a file
.. column::
Let's say you have `my_config.py` file that looks like this... column::
```python
# my_config.py
A = 1
B = 2
```.. column::
You can load this as config values by passing its path to `app.update_config`... column::
```python
>>> app.update_config("/path/to/my_config.py")
>>> print(app.config.A)
1
```.. column::
This path also accepts bash style environment variables... column::
```bash
$ export my_path="/path/to"
```
```python
app.update_config("${my_path}/my_config.py")
```.. note::
Just remember that you have to provide environment variables in the format `${environment_variable}` and that `$environment_variable` is not expanded (is treated as "plain" text).From a dict
.. column::
The `app.update_config` method also works on plain dictionaries... column::
```python
app.update_config({"A": 1, "B": 2})
```From a class or object
.. column::
You can define your own config class, and pass it to `app.update_config`.. column::
```python
class MyConfig:
A = 1
B = 2
app.update_config(MyConfig)
```.. column::
It even could be instantiated... column::
```python
app.update_config(MyConfig())
```Type casting
When loading from environment variables, Sanic will attempt to cast the values to expected Python types. This particularly applies to:
intfloatbool
In regards to bool, the following case insensitive values are allowed:
True:y,yes,yep,yup,t,true,on,enable,enabled,1False:n,no,f,nope,false,off,disable,disabled,0
If a value cannot be cast, it will default to a str.
.. column::
Additionally, Sanic can be configured to cast additional types using additional type converters. This should be any callable that returns the value or raises a `ValueError`.
*Added in v21.12*.. column::
```python
app = Sanic(..., config=Config(converters=[UUID]))
```Advanced Type Converters
.. new:: New in v25.12
This feature was added in version 25.12.. column::
For more sophisticated conversion logic that needs access to the full environment variable context, you can use `DetailedConverter`. This abstract base class provides access to the full environment variable key, the raw value, and the current config defaults.
This is useful when you need to:
- Cast values to the type of their defaults
- Perform validation based on the variable name pattern
- Use default values for fallback logic
- Access configuration context during conversion.. column::
```python
from sanic.config import DetailedConverter
class DefaultsTypeCastingConverter(DetailedConverter):
def __call__(self, full_key: str, config_key: str, value: str, defaults: dict):
try:
if config_key in defaults:
return type(defaults[config_key])(value)
except (ValueError, TypeError) as e:
raise TypeError(f"Configuration environment variable '{full_key}' type mismatch: expected"
f" {type(defaults[config_key]).__name__}, got {type(value).__name__}") from e
app = Sanic(..., config=Config(converters=[DefaultsTypeCastingConverter()]))
```.. column::
The `DetailedConverter.__call__` method receives four parameters:
- `full_key`: The full environment variable name with prefix (e.g., "SANIC_DATABASE_URL")
- `config_key`: The config key without prefix (e.g., "DATABASE_URL")
- `value`: The raw string value from the environment
- `defaults`: The current default configuration values.. column::
```python
class ValidationConverter(DetailedConverter):
def __call__(self, full_key: str, config_key: str, value: str, defaults: dict):
if config_key.endswith('_PORT'):
port = int(value)
if not 1 <= port <= 65535:
raise ValueError(f"Invalid port: {port}")
return port
raise ValueError # Let other converters handle it
```Builtin values
| Variable | Default | Description |
|---|---|---|
| ACCESS_LOG | True | Disable or enable access log |
| AUTO_EXTEND | True | Control whether Sanic Extensions will load if it is in the existing virtual environment |
| AUTO_RELOAD | True | Control whether the application will automatically reload when a file changes |
| EVENT_AUTOREGISTER | True | When True using the app.event() method on a non-existing signal will automatically create it and not raise an exception |
| FALLBACK_ERROR_FORMAT | html | Format of error response if an exception is not caught and handled |
| FORWARDED_FOR_HEADER | X-Forwarded-For | The name of "X-Forwarded-For" HTTP header that contains client and proxy ip |
| FORWARDED_SECRET | None | Used to securely identify a specific proxy server (see below) |
| GRACEFUL_SHUTDOWN_TIMEOUT | 15.0 | How long to wait to force close non-idle connection (sec) |
| INSPECTOR | False | Whether to enable the Inspector |
| INSPECTOR_HOST | localhost | The host for the Inspector |
| INSPECTOR_PORT | 6457 | The port for the Inspector |
| INSPECTOR_TLS_KEY | - | The TLS key for the Inspector |
| INSPECTOR_TLS_CERT | - | The TLS certificate for the Inspector |
| INSPECTOR_API_KEY | - | The API key for the Inspector |
| KEEP_ALIVE_TIMEOUT | 120 | How long to hold a TCP connection open (sec) |
| KEEP_ALIVE | True | Disables keep-alive when False |
| MOTD | True | Whether to display the MOTD (message of the day) at startup |
| MOTD_DISPLAY | {} | Key/value pairs to display additional, arbitrary data in the MOTD |
| NOISY_EXCEPTIONS | False | Force all quiet exceptions to be logged |
| PROXIES_COUNT | None | The number of proxy servers in front of the app (e.g. nginx; see below) |
| REAL_IP_HEADER | None | The name of "X-Real-IP" HTTP header that contains real client ip |
| REGISTER | True | Whether the app registry should be enabled |
| REQUEST_BUFFER_SIZE | 65536 | Request buffer size before request is paused, default is 64 Kib |
| REQUEST_ID_HEADER | X-Request-ID | The name of "X-Request-ID" HTTP header that contains request/correlation ID |
| REQUEST_MAX_SIZE | 100000000 | How big a request may be (bytes), default is 100 megabytes |
| REQUEST_MAX_HEADER_SIZE | 8192 | How big a request header may be (bytes), default is 8192 bytes |
| REQUEST_TIMEOUT | 60 | How long a request can take to arrive (sec) |
| RESPONSE_TIMEOUT | 60 | How long a response can take to process (sec) |
| USE_UVLOOP | True | Whether to override the loop policy to use uvloop. Supported only with app.run. |
| WEBSOCKET_MAX_SIZE | 2^20 | Maximum size for incoming messages (bytes) |
| WEBSOCKET_PING_INTERVAL | 20 | A Ping frame is sent every ping_interval seconds. |
| WEBSOCKET_PING_TIMEOUT | 20 | Connection is closed when Pong is not received after ping_timeout seconds |
.. tip:: FYI
- The `USE_UVLOOP` value will be ignored if running with Gunicorn. Defaults to `False` on non-supported platforms (Windows).
- The `WEBSOCKET_` values will be ignored if in ASGI mode.
- v21.12 added: `AUTO_EXTEND`, `MOTD`, `MOTD_DISPLAY`, `NOISY_EXCEPTIONS`
- v22.9 added: `INSPECTOR`
- v22.12 added: `INSPECTOR_HOST`, `INSPECTOR_PORT`, `INSPECTOR_TLS_KEY`, `INSPECTOR_TLS_CERT`, `INSPECTOR_API_KEY`Timeouts
REQUEST_TIMEOUT
A request timeout measures the duration of time between the instant when a new open TCP connection is passed to the
Sanic backend server, and the instant when the whole HTTP request is received. If the time taken exceeds theREQUEST_TIMEOUT value (in seconds), this is considered a Client Error so Sanic generates an HTTP 408 response
and sends that to the client. Set this parameter's value higher if your clients routinely pass very large request payloads
or upload requests very slowly.
RESPONSE_TIMEOUT
A response timeout measures the duration of time between the instant the Sanic server passes the HTTP request to the Sanic App, and the instant a HTTP response is sent to the client. If the time taken exceeds the RESPONSE_TIMEOUT value (in seconds), this is considered a Server Error so Sanic generates an HTTP 503 response and sends that to the client. Set this parameter's value higher if your application is likely to have long-running process that delay the
generation of a response.
KEEP_ALIVE_TIMEOUT
What is Keep Alive? And what does the Keep Alive Timeout value do?
Keep-Alive is a HTTP feature introduced in HTTP 1.1. When sending a HTTP request, the client (usually a web browser application) can set a Keep-Alive header to indicate the http server (Sanic) to not close the TCP connection after it has send the response. This allows the client to reuse the existing TCP connection to send subsequent HTTP requests, and ensures more efficient network traffic for both the client and the server.
The KEEP_ALIVE config variable is set to True in Sanic by default. If you don't need this feature in your application, set it to False to cause all client connections to close immediately after a response is sent, regardless of the Keep-Alive header on the request.
The amount of time the server holds the TCP connection open is decided by the server itself. In Sanic, that value is configured using the KEEP_ALIVE_TIMEOUT value. By default, it is set to 120 seconds. This means that if the client sends a Keep-Alive header, the server will hold the TCP connection open for 120 seconds after sending the response, and the client can reuse the connection to send another HTTP request within that time.
For reference:
- Apache httpd server default keepalive timeout = 5 seconds
- Nginx server default keepalive timeout = 75 seconds
- Nginx performance tuning guidelines uses keepalive = 15 seconds
- Caddy server default keepalive timeout = 120 seconds
- IE (5-9) client hard keepalive limit = 60 seconds
- Firefox client hard keepalive limit = 115 seconds
- Opera 11 client hard keepalive limit = 120 seconds
- Chrome 13+ client keepalive limit > 300+ seconds
Proxy configuration
See proxy configuration section
Guide Content En Guide Running Development (`guide/content/en/guide/running/development.md`)
Development
The first thing that should be mentioned is that the webserver that is integrated into Sanic is not just a development server.
It is production ready out-of-the-box, unless you enable in debug mode.
Debug mode
By setting the debug mode, Sanic will be more verbose in its output and will disable several run-time optimizations.
# server.py
from sanic import Sanic
from sanic.response import json
app = Sanic(__name__)
@app.route("/")
async def hello_world(request):
return json({"hello": "world"})sanic server:app --host=0.0.0.0 --port=1234 --debug.. danger::
Sanic's debug mode will slow down the server's performance, and is **NOT** intended for production environments.
**DO NOT** enable debug mode in production.Automatic Reloader
.. column::
Sanic offers a way to enable or disable the Automatic Reloader. The easiest way to enable it is using the CLI's `--reload` argument to activate the Automatic Reloader. Every time a Python file is changed, the reloader will restart your application automatically. This is very convenient while developing.
.. note::
The reloader is only available when using Sanic's [worker manager](./manager.md). If you have disabled it using `--single-process` then the reloader will not be available to you... column::
```sh
sanic path.to:app --reload
```
You can also use the shorthand property
```sh
sanic path.to:app -r
```.. column::
If you have additional directories that you would like to automatically reload on file save (for example, a directory of HTML templates), you can add that using `--reload-dir`... column::
```sh
sanic path.to:app --reload --reload-dir=/path/to/templates
```
Or multiple directories, shown here using the shorthand properties
```sh
sanic path.to:app -r -R /path/to/one -R /path/to/two
```Development REPL
The Sanic CLI comes with a REPL (aka "read-eval-print loop") that can be used to interact with your application. This is useful for debugging and testing. A REPL is the interactive shell that you get when you run python without any arguments.
.. column::
You can start the REPL by passing the `--repl` argument to the Sanic CLI... column::
```sh
sanic path.to.server:app --repl
```.. column::
Or, perhaps more conveniently, when you run `--dev`, Sanic will automatically start the REPL for you. However, in this case you might be prompted to hit the "ENTER" key before actually starting the REPL... column::
```sh
sanic path.to.server:app --dev
```As seen in the screenshot above, the REPL will automatically add a few variables to the global namespace. These are:
app- The Sanic application instance. This is the same instance that is passed to thesanicCLI.sanic- Thesanicmodule. This is the same module that is imported when you runimport sanic.do- A function that will create a mockRequestobject and pass it to your application. This is useful for testing your application from the REPL.client- An instance ofhttpx.Clientthat is configured to make requests to your application. This is useful for testing your application from the REPL. Note: This is only available ifhttpxis installed in your environment.
Async/Await support
.. column::
The REPL supports `async`/`await` syntax. This means that you can use `await` in the REPL to wait for asynchronous operations to complete. This is useful for testing asynchronous code... column::
```python
>>> await app.ctx.db.fetchval("SELECT 1")
1
```The `app` variable
You need to keep in mind that the app variable is your app instance as it existed when the REPL was started. It is the instance that is loaded when running the CLI command. This means that any changes that are made to your source code and subsequently reloaded in the workers will not be reflected in the app variable. If you want to interact with the reloaded app instance, you will need to restart the REPL.
However, it is also very useful to have access to the original app instance in the REPL for adhoc testing and debugging.
The `client` variable
When httpx is installed in your environment, the client variable will be available in the REPL. This is an instance of httpx.Client that is configured to make requests to your running application.
.. column::
To use it, simply call one of the HTTP methods on the client. See the [httpx documentation](https://www.python-httpx.org/api/#client) for more information... column::
```python
>>> client.get("/")
<Response [200 OK]>
```The `do` function
As discussed above, the app instance exists as it did at the time the REPL was started, and as was modified inside the REPL. Any changes to the instance that cause a server to be reloaded will not be reflected in the app variable. This is where the do function comes in.
Let's say that you have modified your application inside the REPL to add a new route:
>>> @app.get("/new-route")
... async def new_route(request):
... return sanic.json({"hello": "world"})
...
>>>You can use the do function to mock out a request, and pass it to the application as if it were a real HTTP request. This will allow you to test your new route without having to restart the REPL.
>>> await do("/new-route")
Result(request=<Request: GET /new-route>, response=<JSONResponse: 200 application/json>)The do function returns a Result object that contains the Request and Response objects that were returned by your application. It is a NamedTuple, so you can access the values by name:
>>> result = await do("/new-route")
>>> result.request
<Request: GET /new-route>
>>> result.response
<JSONResponse: 200 application/json>Or, by destructuring the tuple:
>>> request, response = await do("/new-route")
>>> request
<Request: GET /new-route>
>>> response
<JSONResponse: 200 application/json>When to use `do` vs `client`?
.. column::
**Use `do` when ...**
- You want to test a route that does not exist in the running application
- You want to test a route that has been modified in the REPL
- You make a change to your application inside the REPL.. column::
**Use `client` when ...**
- You want to test a route that already exists in the running application
- You want to test a route that has been modified in your source code
- You want to send an actual HTTP request to your applicationAdded in v23.12
Complete development mode
.. column::
If you would like to be in debug mode **and** have the Automatic Reloader running, you can pass `dev=True`. This is equivalent to **debug + auto reload + REPL**.
*Added in v22.3*.. column::
```sh
sanic path.to:app --dev
```
You can also use the shorthand property
```sh
sanic path.to:app -d
```Added to the --dev flag in v23.12 is the ability to start a REPL. See the Development REPL section for more information.
As of v23.12, the --dev flag is roughly equivalent to --debug --reload --repl. Using --dev will require you to expressly begin the REPL by hitting "ENTER", while passing the --repl flag explicitly starts it.
Before v23.12, the --dev flag is more similar to --debug --reload.
.. column::
If you would like to disable the REPL while using the `--dev` flag, you can pass `--no-repl`... column::
```sh
sanic path.to:app --dev --no-repl
```Automatic TLS certificate
When running in DEBUG mode, you can ask Sanic to handle setting up localhost temporary TLS certificates. This is helpful if you want to access your local development environment with https://.
This functionality is provided by either mkcert or trustme. Both are good choices, but there are some differences. trustme is a Python library and can be installed into your environment with pip. This makes for easy envrionment handling, but it is not compatible when running a HTTP/3 server. mkcert might be a more involved installation process, but can install a local CA and make it easier to use.
.. column::
You can choose which platform to use by setting `config.LOCAL_CERT_CREATOR`. When set to `"auto"`, it will select either option, preferring `mkcert` if possible... column::
```python
app.config.LOCAL_CERT_CREATOR = "auto"
app.config.LOCAL_CERT_CREATOR = "mkcert"
app.config.LOCAL_CERT_CREATOR = "trustme"
```.. column::
Automatic TLS can be enabled at Sanic server run time:.. column::
```sh
sanic path.to.server:app --auto-tls --debug
```.. warning::
Localhost TLS certificates (like those generated by both `mkcert` and `trustme`) are **NOT** suitable for production environments. If you are not familiar with how to obtain a *real* TLS certificate, checkout the [How to...](../how-to/tls.md) section.Added in v22.6
Guide Content En Guide Running Inspector (`guide/content/en/guide/running/inspector.md`)
Inspector
The Sanic Inspector is a feature of Sanic Server. It is only available when running Sanic with the built-in worker manager.
It is an HTTP application that optionally runs in the background of your application to allow you to interact with the running instance of your application.
.. tip:: INFO
The Inspector was introduced in limited capacity in v22.9, but the documentation on this page assumes you are using v22.12 or higher.Getting Started
The inspector is disabled by default. To enable it, you have two options.
.. column::
Set a flag when creating your application instance... column::
```python
app = Sanic("TestApp", inspector=True)
```.. column::
Or, set a configuration value... column::
```python
app = Sanic("TestApp")
app.config.INSPECTOR = True
```.. warning::
If you are using the configuration value, it *must* be done early and before the main worker process starts. This means that it should either be an environment variable, or it should be set shortly after creating the application instance as shown above.Using the Inspector
Once the inspector is running, you will have access to it via the CLI or by directly accessing its web API via HTTP.
.. column::
**Via CLI**
```sh
sanic inspect
```.. column::
**Via HTTP**
```sh
curl http://localhost:6457
```.. note::
Remember, the Inspector is not running on your Sanic application. It is a seperate process, with a seperate application, and exposed on a seperate socket.Built-in Commands
The Inspector comes with the following built-in commands.
| CLI Command | HTTP Action | Description |
|---|---|---|
inspect |
GET / |
Display basic details about the running application. |
inspect reload |
POST /reload |
Trigger a reload of all server workers. |
inspect shutdown |
POST /shutdown |
Trigger a shutdown of all processes. |
inspect scale N |
POST /scale``{"replicas": N} |
Scale the number of workers. Where N is the target number of replicas. |
Custom Commands
The Inspector is easily extendable to add custom commands (and endpoints).
.. column::
Subclass the `Inspector` class and create arbitrary methods. As long as the method name is not preceded by an underscore (`_`), then the name of the method will be a new subcommand on the inspector... column::
```python
from sanic import json
from sanic.worker.inspector import Inspector
class MyInspector(Inspector):
async def something(self, *args, **kwargs):
print(args)
print(kwargs)
app = Sanic("TestApp", inspector_class=MyInspector, inspector=True)
```This will expose custom methods in the general pattern:
- CLI:
sanic inspect <method_name> - HTTP:
POST /<method_name>
It is important to note that the arguments that the new method accepts are derived from how you intend to use the command. For example, the above something method accepts all positional and keyword based parameters.
.. column::
In the CLI, the positional and keyword parameters are passed as either positional or keyword arguments to your method. All values will be a `str` with the following exceptions:
- A keyword parameter with no assigned value will be: `True`
- Unless the parameter is prefixed with `no-`, then it will be: `False`.. column::
```sh
sanic inspect something one two three --four --no-five --six=6
```
In your application log console, you will see:
```
('one', 'two', 'three')
{'four': True, 'five': False, 'six': '6'}
```.. column::
The same can be achieved by hitting the API directly. You can pass arguments to the method by exposing them in a JSON payload. The only thing to note is that the positional arguments should be exposed as `{"args": [...]}`... column::
```sh
curl http://localhost:6457/something \
--json '{"args":["one", "two", "three"], "four":true, "five":false, "six":6}'
```
In your application log console, you will see:
```
('one', 'two', 'three')
{'four': True, 'five': False, 'six': 6}
```Using in production
.. danger::
Before exposing the Inspector on a product, please consider all of the options in this section carefully.When running Inspector on a remote production instance, you can protect the endpoints by requiring TLS encryption, and requiring API key authentication.
TLS encryption
.. column::
To the Inspector HTTP instance over TLS, pass the paths to your certificate and key... column::
```python
app.config.INSPECTOR_TLS_CERT = "/path/to/cert.pem"
app.config.INSPECTOR_TLS_KEY = "/path/to/key.pem"
```.. column::
This will require use of the `--secure` flag, or `https://`... column::
```sh
sanic inspect --secure --host=<somewhere>
```
```sh
curl https://<somewhere>:6457
```API Key Authentication
.. column::
You can secure the API with bearer token authentication... column::
```python
app.config.INSPECTOR_API_KEY = "Super-Secret-200"
```.. column::
This will require the `--api-key` parameter, or bearer token authorization header... column::
```sh
sanic inspect --api-key=Super-Secret-200
```
```sh
curl http://localhost:6457 -H "Authorization: Bearer Super-Secret-200"
```Configuration
See configuration
Guide Content En Guide Running Manager (`guide/content/en/guide/running/manager.md`)
title: Worker Manager
Worker Manager
The worker manager and its functionality was introduced in version 22.9.
The details of this section are intended for more advanced usages and not necessary to get started.
The purpose of the manager is to create consistency and flexibility between development and production environments. Whether you intend to run a single worker, or multiple workers, whether with, or without auto-reload: the experience will be the same.
In general it looks like this:
When you run Sanic, the main process instantiates a WorkerManager. That manager is in charge of running one or more WorkerProcess. There generally are two kinds of processes:
- server processes, and
- non-server processes.
For the sake of ease, the User Guide generally will use the term "worker" or "worker process" to mean a server process, and "Manager" to mean the single worker manager running in your main process.
How Sanic Server starts processes
Sanic will start processes using the spawn start method. This means that for every process/worker, the global scope of your application will be run on its own thread. The practical impact of this that if you do not run Sanic with the CLI, you will need to nest the execution code inside a block to make sure it only runs on __main__.
if __name__ == "__main__":
app.run()If you do not, you are likely to see an error message like this:
sanic.exceptions.ServerError: Sanic server could not start: [Errno 98] Address already in use.
This may have happened if you are running Sanic in the global scope and not inside of a `if __name__ == "__main__"` block.
See more information: https://sanic.dev/en/guide/deployment/manager.html#how-sanic-server-starts-processesThe likely fix for this problem is nesting your Sanic run call inside of the __name__ == "__main__" block. If you continue to receive this message after nesting, or if you see this while using the CLI, then it means the port you are trying to use is not available on your machine and you must select another port.
Starting a worker
All worker processes must send an acknowledgement when starting. This happens under the hood, and you as a developer do not need to do anything. However, the Manager will exit with a status code 1 if one or more workers do not send that ack message, or a worker process throws an exception while trying to start. If no exceptions are encountered, the Manager will wait for up to thirty (30) seconds for the acknowledgement.
.. column::
In the situation when you know that you will need more time to start, you can monkeypatch the Manager. The threshold does not include anything inside of a listener, and is limited to the execution time of everything in the global scope of your application.
If you run into this issue, it may indicate a need to look deeper into what is causing the slow startup... column::
```python
from sanic.worker.manager import WorkerManager
WorkerManager.THRESHOLD = 100 # Value is in 0.1s
```See worker ack for more information.
.. column::
As stated above, Sanic will use [spawn](https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods) to start worker processes. If you would like to change this behavior and are aware of the implications of using different start methods, you can modify as shown here... column::
```python
from sanic import Sanic
Sanic.start_method = "fork"
```Worker ack
When all of your workers are running in a subprocess a potential problem is created: deadlock. This can occur when the child processes cease to function, but the main process is unaware that this happened. Therefore, Sanic servers will automatically send an ack message (short for acknowledge) to the main process after startup.
In version 22.9, the ack timeout was short and limited to 5s. In version 22.12, the timeout was lengthened to 30s. If your application is shutting down after thirty seconds then it might be necessary to manually increase this threshhold.
.. column::
The value of `WorkerManager.THRESHOLD` is in `0.1s` increments. Therefore, to set it to one minute, you should set the value to `600`.
This value should be set as early as possible in your application, and should ideally happen in the global scope. Setting it after the main process has started will not work... column::
```python
from sanic.worker.manager import WorkerManager
WorkerManager.THRESHOLD = 600
```Zero downtime restarts
By default, when restarting workers, Sanic will teardown the existing process first before starting a new one.
If you are intending to use the restart functionality in production then you may be interested in having zero-downtime reloading. This can be accomplished by forcing the reloader to change the order to start a new process, wait for it to ack, and then teardown the old process.
.. column::
From the multiplexer, use the `zero_downtime` argument.. column::
```python
app.m.restart(zero_downtime=True)
```Added in v22.12
Using shared context between worker processes
Python provides a few methods for exchanging objects, synchronizing, and sharing state between processes. This usually involves objects from the multiprocessing and ctypes modules.
If you are familiar with these objects and how to work with them, you will be happy to know that Sanic provides an API for sharing these objects between your worker processes. If you are not familiar, you are encouraged to read through the Python documentation linked above and try some of the examples before proceeding with implementing shared context.
Similar to how application context allows an applicaiton to share state across the lifetime of the application with app.ctx, shared context provides the same for the special objects mentioned above. This context is available as app.shared_ctx and should ONLY be used to share objects intended for this purpose.
The shared_ctx will:
- NOT share regular objects like
int,dict, orlist - NOT share state between Sanic instances running on different machines
- NOT share state to non-worker processes
- only share state between server workers managed by the same Manager
Attaching an inappropriate object to shared_ctx will likely result in a warning, and not an error. You should be careful to not accidentally add an unsafe object to shared_ctx as it may not work as expected. If you are directed here because of one of those warnings, you might have accidentally used an unsafe object in shared_ctx.
.. column::
In order to create a shared object you **must** create it in the main process and attach it inside of the `main_process_start` listener... column::
```python
from multiprocessing import Queue
@app.main_process_start
async def main_process_start(app):
app.shared_ctx.queue = Queue()
```Trying to attach to the shared_ctx object outside of this listener may result in a RuntimeError.
.. column::
After creating the objects in the `main_process_start` listener and attaching to the `shared_ctx`, they will be available in your workers wherever the application instance is available (example: listeners, middleware, request handlers)... column::
```python
from multiprocessing import Queue
@app.get("")
async def handler(request):
request.app.shared_ctx.queue.put(1)
...
```Access to the multiplexer
The application instance has access to an object that provides access to interacting with the Manager and other worker processes. The object is attached as the app.multiplexer property, but it is more easily accessed by its alias: app.m.
.. column::
For example, you can get access to the current worker state... column::
```python
@app.on_request
async def print_state(request: Request):
print(request.app.m.name)
print(request.app.m.pid)
print(request.app.m.state)
```
```
Sanic-Server-0-0
99999
{'server': True, 'state': 'ACKED', 'pid': 99999, 'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), 'starts': 2, 'restart_at': datetime.datetime(2022, 10, 1, 0, 0, 12, 861332, tzinfo=datetime.timezone.utc)}
```.. column::
The `multiplexer` also has access to terminate the Manager, or restart worker processes.. column::
```python
# shutdown the entire application and all processes
app.m.name.terminate()
# restart the current worker only
app.m.name.restart()
# restart specific workers only (comma delimited)
app.m.name.restart("Sanic-Server-4-0,Sanic-Server-7-0")
# restart ALL workers
app.m.name.restart(all_workers=True) # Available v22.12+
```Worker state
.. column::
As shown above, the `multiplexer` has access to report upon the state of the current running worker. However, it also contains the state for ALL processes running... column::
```python
@app.on_request
async def print_state(request: Request):
print(request.app.m.workers)
```
```
{
'Sanic-Main': {'pid': 99997},
'Sanic-Server-0-0': {
'server': True,
'state': 'ACKED',
'pid': 9999,
'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc),
'starts': 2,
'restart_at': datetime.datetime(2022, 10, 1, 0, 0, 12, 861332, tzinfo=datetime.timezone.utc)
},
'Sanic-Reloader-0': {
'server': False,
'state': 'STARTED',
'pid': 99998,
'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc),
'starts': 1
}
}
```The possible states are:
NONE- The worker has been created, but there is no process yetIDLE- The process has been created, but is not running yetSTARTING- The process is startingSTARTED- The process has startedACKED- The process has started and sent an acknowledgement (usually only for server processes)JOINED- The process has exited and joined the main processTERMINATED- The process has exited and terminatedRESTARTING- The process is restartingFAILED- The process encountered an exception and is no longer runningCOMPLETED- The process has completed its work and exited successfully
Built-in non-server processes
As mentioned, the Manager also has the ability to run non-server processes. Sanic comes with two built-in types of non-server processes, and allows for creating custom processes.
The two built-in processes are
- the auto-reloader, optionally enabled to watch the file system for changes and trigger a restart
- inspector, optionally enabled to provide external access to the state of the running instance
Inspector
Sanic has the ability to expose the state and the functionality of the multiplexer to the CLI. Currently, this requires the CLI command to be run on the same machine as the running Sanic instance. By default the inspector is disabled.
.. column::
To enable it, set the config value to `True`... column::
```python
app.config.INSPECTOR = True
```You will now have access to execute any of these CLI commands:
sanic inspect reload Trigger a reload of the server workers
sanic inspect shutdown Shutdown the application and all processes
sanic inspect scale N Scale the number of workers to N
sanic inspect <custom> Run a custom command.. column::
This works by exposing a small HTTP service on your machine. You can control the location using configuration values:.. column::
```python
app.config.INSPECTOR_HOST = "localhost"
app.config.INSPECTOR_PORT = 6457
```Learn more to find out what is possible with the Inspector.
Running custom processes
To run a managed custom process on Sanic, you must create a callable. If that process is meant to be long-running, then it should handle a shutdown call by a SIGINT or SIGTERM signal.
.. column::
The simplest method for doing that in Python will be to just wrap your loop in `KeyboardInterrupt`.
If you intend to run another application, like a bot, then it is likely that it already has capability to handle this signal and you likely do not need to do anything... column::
```python
from time import sleep
def my_process(foo):
try:
while True:
sleep(1)
except KeyboardInterrupt:
print("done")
```.. column::
That callable must be registered in the `main_process_ready` listener. It is important to note that is is **NOT** the same location that you should register [shared context](#using-shared-context-between-worker-processes) objects... column::
```python
@app.main_process_ready
async def ready(app: Sanic, _):
# app.manager.manage(<name>, <callable>, <kwargs>)
app.manager.manage("MyProcess", my_process, {"foo": "bar"})
```Transient v. durable processes
.. column::
When you manage a process with the `manage` method, you have the option to make it transient or durable. A transient process will be restarted by the auto-reloader, and a durable process will not.
By default, all processes are durable... column::
```python
@app.main_process_ready
async def ready(app: Sanic, _):
app.manager.manage(
"MyProcess",
my_process,
{"foo": "bar"},
transient=True,
)
```Tracked v. untracked processes
Out of the box, Sanic will track the state of all processes. This means that you can access the state of the process from the multiplexer object, or from the Inspector.
See worker state for more information.
Sometimes it is helpful to run background processes that are not long-running. You run them once until completion and then they exit. Upon completion, they will either be in FAILED or COMPLETED state.
.. column::
When you are running a non-long-running process, you can opt out of tracking it by setting `tracked=False` in the `manage` method. This means that upon completion of the process it will be removed from the list of tracked processes. You will only be able to check the state of the process while it is running... column::
```python
@app.main_process_ready
async def ready(app: Sanic, _):
app.manager.manage(
"OneAndDone",
do_once,
{},
tracked=False,
)
```Added in v23.12
Restartable custom processes
A custom process that is transient will always be restartable. That means the auto-restart will work as expected. However, what if you want to be able to manually restart a process, but not have it be restarted by the auto-reloader?
.. column::
In this scenario, you can set `restartable=True` in the `manage` method. This will allow you to manually restart the process, but it will not be restarted by the auto-reloader... column::
```python
@app.main_process_ready
async def ready(app: Sanic, _):
app.manager.manage(
"MyProcess",
my_process,
{"foo": "bar"},
restartable=True,
)
```.. column::
You could now manually restart that process from the multiplexer... column::
```python
@app.get("/restart")
async def restart_handler(request: Request):
request.app.m.restart("Sanic-MyProcess-0")
return json({"foo": request.app.m.name})
```Added in v23.12
On the fly process management
Custom processes are usually added in the main_process_ready listener. However, there may be times when you want to add a process after the application has started. For example, you may want to add a process from a request handler. The multiplexer provides a method for doing this.
.. column::
Once you have a reference to the multiplexer, you can call `manage` to add a process. It works the same as the `manage` method on the Manager... column::
```python
@app.post("/start")
async def start_handler(request: Request):
request.app.m.manage(
"MyProcess",
my_process,
{"foo": "bar"},
workers=2,
)
return json({"foo": request.app.m.name})
```Added in v23.12
Single process mode
.. column::
If you would like to opt out of running multiple processes, you can run Sanic in a single process only. In this case, the Manager will not run. You will also not have access to any features that require processes (auto-reload, the inspector, etc)... column::
```sh
sanic path.to.server:app --single-process
```
```python
if __name__ == "__main__":
app.run(single_process=True)
```
```python
if __name__ == "__main__":
app.prepare(single_process=True)
Sanic.serve_single()
```Sanic and multiprocessing
Sanic makes heavy use of the multiprocessing module to manage the worker processes. You should generally avoid lower level usage of this module (like setting the start method) as it may interfere with the functionality of Sanic.
Start methods in Python
Before explaining what Sanic tries to do, it is important to understand what the start_method is and why it is important. Python generally allows for three different methods of starting a process:
forkspawnforkserver
The fork and forkserver methods are only available on Unix systems, and spawn is the only method available on Windows. On Unix systems where you have a choice, fork is generally the default system method.
You are encouraged to read the Python documentation to learn more about the differences between these methods. However, the important thing to know is that fork basically copies the entire memory of the parent process into the child process, whereas spawn will create a new process and then load the application into that process. This is the reason why you need to nest your Sanic run call inside of the __name__ == "__main__" block if you are not using the CLI.
Sanic and start methods
By default, Sanic will try and use spawn as the start method. This is because it is the only method available on Windows, and it is the safest method on Unix systems.
.. column::
However, if you are running Sanic on a Unix system and you would like to use `fork` instead, you can do so by setting the `start_method` on the `Sanic` class. You will want to do this as early as possible in your application, and ideally in the global scope before you import any other modules... column::
```python
from sanic import Sanic
Sanic.start_method = "fork"
```Overcoming a `RuntimeError`
You might have received a RuntimeError that looks like this:
RuntimeError: Start method 'spawn' was requested, but 'fork' was already set.If so, that means somewhere in your application you are trying to set the start method that conflicts with what Sanic is trying to do. You have a few options to resolve this:
.. column::
**OPTION 1:** You can tell Sanic that the start method has been set and to not try and set it again... column::
```python
from sanic import Sanic
Sanic.START_METHOD_SET = True
```.. column::
**OPTION 2:** You could tell Sanic that you intend to use `fork` and to not try and set it to `spawn`... column::
```python
from sanic import Sanic
Sanic.start_method = "fork"
```.. column::
**OPTION 3:** You can tell Python to use `spawn` instead of `fork` by setting the `multiprocessing` start method... column::
```python
import multiprocessing
multiprocessing.set_start_method("spawn")
```In any of these options, you should run this code as early as possible in your application. Depending upon exactly what your specific scenario is, you may need to combine some of the options.
.. note::
The potential issues that arise from this problem are usually easily solved by just allowing Sanic to be in charge of multiprocessing. This usually means making use of the `main_process_start` and `main_process_ready` listeners to deal with multiprocessing issues. For example, you should move instantiating multiprocessing primitives that do a lot of work under the hood from the global scope and into a listener.
```python
# This is BAD; avoid the global scope
from multiprocessing import Queue
q = Queue()
```
```python
# This is GOOD; the queue is made in a listener and shared to all the processes on the shared_ctx
from multiprocessing import Queue
@app.main_process_start
async def main_process_start(app):
app.shared_ctx.q = Queue()
```Guide Content En Guide Running Running (`guide/content/en/guide/running/running.md`)
title: Running Sanic
Running Sanic
Sanic ships with its own internal web server. Under most circumstances, this is the preferred method for deployment. In addition, you can also deploy Sanic as an ASGI app bundled with an ASGI-able web server.
Sanic Server
The main way to run Sanic is to use the included CLI.
sanic path.to.server:appIn this example, Sanic is instructed to look for a python module called path.to.server. Inside of that module, it will look for a global variable called app, which should be an instance of Sanic(...).
# ./path/to/server.py
from sanic import Sanic, Request, json
app = Sanic("TestApp")
@app.get("/")
async def handler(request: Request):
return json({"foo": "bar"})You may also dropdown to the lower level API to call app.run as a script. However, if you choose this option you should be more comfortable handling issues that may arise with multiprocessing.
Workers
.. column::
By default, Sanic runs a main process and a single worker process (see [worker manager](./manager.md) for more details).
To crank up the juice, just specify the number of workers in the run arguments... column::
```sh
sanic server:app --host=0.0.0.0 --port=1337 --workers=4
```Sanic will automatically spin up multiple processes and route traffic between them. We recommend as many workers as you have available processors.
.. column::
The easiest way to get the maximum CPU performance is to use the `--fast` option. This will automatically run the maximum number of workers given the system constraints.
*Added in v21.12*.. column::
```sh
sanic server:app --host=0.0.0.0 --port=1337 --fast
```In version 22.9, Sanic introduced a new worker manager to provide more consistency and flexibility between development and production servers. Read about the manager for more details about workers.
.. column::
If you only want to run Sanic with a single process, specify `single_process` in the run arguments. This means that auto-reload, and the worker manager will be unavailable.
*Added in v22.9*.. column::
```sh
sanic server:app --host=0.0.0.0 --port=1337 --single-process
```Running via command
Sanic CLI
Use sanic --help to see all the options.
.. attrs::
:title: Sanic CLI help output
:class: details
```/* Detailed source-code truncated for AI context efficiency. */
#### As a module
.. column::
Sanic applications can also be called directly as a module.
.. column::
```bash
python -m sanic server.app --host=0.0.0.0 --port=1337 --workers=4
```
#### Using a factory
A very common solution is to develop your application *not* as a global variable, but instead using the factory pattern. In this context, "factory" means a function that returns an instance of `Sanic(...)`.
.. column::
Suppose that you have this in your `server.py`
.. column::
```python
from sanic import Sanic
def create_app() -> Sanic:
app = Sanic("MyApp")
return app
```
.. column::
You can run this application now by referencing it in the CLI explicitly as a factory:
.. column::
```sh
sanic server:create_app --factory
```
Or, explicitly like this:
```sh
sanic "server:create_app()"
```
Or, implicitly like this:
```sh
sanic server:create_app
```
*Implicit command added in v23.3*
### Low level `app.run`
When using `app.run` you will just call your Python file like any other script.
.. column::
`app.run` must be properly nested inside of a name-main block.
.. column::
```python
# server.py
app = Sanic("MyApp")
if __name__ == "__main__":
app.run()
```
.. danger::
Be *careful* when using this pattern. A very common mistake is to put too much logic inside of the `if __name__ == "__main__":` block.
🚫 This is a mistake
```python
from sanic import Sanic
from my.other.module import bp
app = Sanic("MyApp")
if __name__ == "__main__":
app.blueprint(bp)
app.run()
```
If you do this, your [blueprint](../best-practices/blueprints.md) will not be attached to your application. This is because the `__main__` block will only run on Sanic's main worker process, **NOT** any of its [worker processes](../deployment/manager.md). This goes for anything else that might impact your application (like attaching listeners, signals, middleware, etc). The only safe operations are anything that is meant for the main process, like the `app.main_*` listeners.
Perhaps something like this is more appropriate:
```python
from sanic import Sanic
from my.other.module import bp
app = Sanic("MyApp")
if __name__ == "__mp_main__":
app.blueprint(bp)
elif __name__ == "__main__":
app.run()
```
To use the low-level `run` API, after defining an instance of `sanic.Sanic`, we can call the run method with the following keyword arguments:
| Parameter | Default | Description |
| :-------------------: | :--------------: | :---------------------------------------------------------------------------------------- |
| **host** | `"127.0.0.1"` | Address to host the server on. |
| **port** | `8000` | Port to host the server on. |
| **unix** | `None` | Unix socket name to host the server on (instead of TCP). |
| **dev** | `False` | Equivalent to `debug=True` and `auto_reload=True`. |
| **debug** | `False` | Enables debug output (slows server). |
| **ssl** | `None` | SSLContext for SSL encryption of worker(s). |
| **sock** | `None` | Socket for the server to accept connections from. |
| **workers** | `1` | Number of worker processes to spawn. Cannot be used with fast. |
| **loop** | `None` | An asyncio-compatible event loop. If none is specified, Sanic creates its own event loop. |
| **protocol** | `HttpProtocol` | Subclass of asyncio.protocol. |
| **version** | `HTTP.VERSION_1` | The HTTP version to use (`HTTP.VERSION_1` or `HTTP.VERSION_3`). |
| **access_log** | `True` | Enables log on handling requests (significantly slows server). |
| **auto_reload** | `None` | Enables auto-reload on the source directory. |
| **reload_dir** | `None` | A path or list of paths to directories the auto-reloader should watch. |
| **noisy_exceptions** | `None` | Whether to set noisy exceptions globally. None means leave as default. |
| **motd** | `True` | Whether to display the startup message. |
| **motd_display** | `None` | A dict with extra key/value information to display in the startup message |
| **fast** | `False` | Whether to maximize worker processes. Cannot be used with workers. |
| **verbosity** | `0` | Level of logging detail. Max is 2. |
| **auto_tls** | `False` | Whether to auto-create a TLS certificate for local development. Not for production. |
| **single_process** | `False` | Whether to run Sanic in a single process. |
.. column::
For example, we can turn off the access log in order to increase performance, and bind to a custom host and port.
.. column::
```python
# server.py
app = Sanic("MyApp")
if __name__ == "__main__":
app.run(host='0.0.0.0', port=1337, access_log=False)
```
.. column::
Now, just execute the python script that has `app.run(...)`
.. column::
```sh
python server.py
```
For a slightly more advanced implementation, it is good to know that `app.run` will call `app.prepare` and `Sanic.serve` under the hood.
.. column::
Therefore, these are equivalent:
.. column::
```python
if __name__ == "__main__":
app.run(host='0.0.0.0', port=1337, access_log=False)
```
```python
if __name__ == "__main__":
app.prepare(host='0.0.0.0', port=1337, access_log=False)
Sanic.serve()
```
.. column::
This can be useful if you need to bind your appliction(s) to multiple ports.
.. column::
```python
if __name__ == "__main__":
app1.prepare(host='0.0.0.0', port=9990)
app1.prepare(host='0.0.0.0', port=9991)
app2.prepare(host='0.0.0.0', port=5555)
Sanic.serve()
```
### Sanic Simple Server
.. column::
Sometimes you just have a directory of static files that need to be served. This especially can be handy for quickly standing up a localhost server. Sanic ships with a Simple Server, where you only need to point it at a directory.
.. column::
```sh
sanic ./path/to/dir --simple
```
.. column::
This could also be paired with auto-reloading.
.. column::
```sh
sanic ./path/to/dir --simple --reload --reload-dir=./path/to/dir
```
*Added in v21.6*
### Daemon mode
.. new:: New in v25.12
This feature was added in version 25.12
.. column::
Sanic can run as a background daemon process. Use the `-D` or `--daemon` flag to start the server in the background.
.. column::
```sh
sanic path.to.server:app --daemon
sanic path.to.server:app -D
```
.. column::
You can manage the daemon with additional commands:
.. column::
```sh
sanic path.to.server:app status # Check if running
sanic path.to.server:app stop # Stop the daemon
```
.. column::
Additional options are available for daemon configuration:
.. column::
```sh
sanic path.to.server:app -D --pidfile=/var/run/sanic.pid
sanic path.to.server:app -D --logfile=/var/log/sanic.log
sanic path.to.server:app -D --user=www-data
sanic path.to.server:app -D --group=www-data
```
Lower-level commands are also available to manage processes by PID:
```sh
sanic kill --pid=<PID>
sanic kill --pidfile=/var/run/sanic.pid
sanic status --pid=<PID>
sanic status --pidfile=/var/run/sanic.pidAdded in v25.12
HTTP/3
Sanic server offers HTTP/3 support using aioquic. This must be installed to use HTTP/3:
pip install sanic aioquicpip install sanic[http3]To start HTTP/3, you must explicitly request it when running your application.
.. column::
```sh
sanic path.to.server:app --http=3
```
```sh
sanic path.to.server:app -3
```.. column::
```python
app.run(version=3)
```To run both an HTTP/3 and HTTP/1.1 server simultaneously, you can use application multi-serve introduced in v22.3. This will automatically add an Alt-Svc header to your HTTP/1.1 requests to let the client know that it is also available as HTTP/3.
.. column::
```sh
sanic path.to.server:app --http=3 --http=1
```
```sh
sanic path.to.server:app -3 -1
```.. column::
```python
app.prepare(version=3)
app.prepare(version=1)
Sanic.serve()
```Because HTTP/3 requires TLS, you cannot start a HTTP/3 server without a TLS certificate. You should set it up yourself or use mkcert if in DEBUG mode. Currently, automatic TLS setup for HTTP/3 is not compatible with trustme. See development for more details.
Added in v22.6
ASGI
Sanic is also ASGI-compliant. This means you can use your preferred ASGI webserver to run Sanic. The three main implementations of ASGI are Daphne, Uvicorn, and Hypercorn.
.. warning::
Daphne does not support the ASGI `lifespan` protocol, and therefore cannot be used to run Sanic. See [Issue #264](https://github.com/django/daphne/issues/264) for more details.Follow their documentation for the proper way to run them, but it should look something like:
uvicorn myapp:apphypercorn myapp:appA couple things to note when using ASGI:
- When using the Sanic webserver, websockets will run using the
websocketspackage. In ASGI mode, there is no need for this package since websockets are managed in the ASGI server. - The ASGI lifespan protocol https://asgi.readthedocs.io/en/latest/specs/lifespan.html, supports only two server events: startup and shutdown. Sanic has four: before startup, after startup, before shutdown, and after shutdown. Therefore, in ASGI mode, the startup and shutdown events will run consecutively and not actually around the server process beginning and ending (since that is now controlled by the ASGI server). Therefore, it is best to use
after_server_startandbefore_server_stop.
Trio
Sanic has experimental support for running on Trio with:
hypercorn -k trio myapp:appGunicorn
Gunicorn ("Green Unicorn") is a WSGI HTTP Server for UNIX based operating systems. It is a pre-fork worker model ported from Ruby’s Unicorn project.
In order to run Sanic application with Gunicorn, you need to use it with the adapter from uvicorn. Make sure uvicorn is installed and run it with uvicorn.workers.UvicornWorker for Gunicorn worker-class argument:
gunicorn myapp:app --bind 0.0.0.0:1337 --worker-class uvicorn.workers.UvicornWorkerSee the Gunicorn Docs for more information.
.. warning::
It is generally advised to not use `gunicorn` unless you need it. The Sanic Server is primed for running Sanic in production. Weigh your considerations carefully before making this choice. Gunicorn does provide a lot of configuration options, but it is not the best choice for getting Sanic to run at its fastest.Performance considerations
.. column::
When running in production, make sure you turn off `debug`... column::
```sh
sanic path.to.server:app
```.. column::
Sanic will also perform fastest if you turn off `access_log`.
If you still require access logs, but want to enjoy this performance boost, consider using [Nginx as a proxy](./../deployment/nginx.md), and letting that handle your access logging. It will be much faster than anything Python can handle... column::
```sh
sanic path.to.server:app --no-access-logs
```Guide Content En Plugins Sanic Ext Configuration (`guide/content/en/plugins/sanic-ext/configuration.md`)
title: Sanic Extensions - Configuration
Configuration
Sanic Extensions can be configured in all of the same ways that you can configure Sanic. That makes configuring Sanic Extensions very easy.
app = Sanic("MyApp")
app.config.OAS_URL_PREFIX = "/apidocs"However, there are a few more configuration options that should be considered.
Manual `extend`
.. column::
Even though Sanic Extensions will automatically attach to your application, you can manually choose `extend`. When you do that, you can pass all of the configuration values as a keyword arguments (lowercase)... column::
```python
app = Sanic("MyApp")
app.extend(oas_url_prefix="/apidocs")
```.. column::
Or, alternatively they could be passed all at once as a single `dict`... column::
```python
app = Sanic("MyApp")
app.extend(config={"oas_url_prefix": "/apidocs"})
```.. column::
Both of these solutions suffers from the fact that the names of the configuration settings are not discoverable by an IDE. Therefore, there is also a type annotated object that you can use. This should help the development experience... column::
```python
from sanic_ext import Config
app = Sanic("MyApp")
app.extend(config=Config(oas_url_prefix="/apidocs"))
```Settings
.. note::
Often, the easiest way to change these for an application (since they likely are not going to change dependent upon an environment), is to set them directly on the `app.config` object.
Simply use the capitalized version of the configuration key as shown here:
```python
app = Sanic("MyApp")
app.config.OAS_URL_PREFIX = "/apidocs"
````cors`
- Type:
bool - Default:
True - Description: Whether to enable CORS protection
`cors_allow_headers`
- Type:
str - Default:
"*" - Description: Value of the header:
access-control-allow-headers
`cors_always_send`
- Type:
bool - Default:
True - Description: Whether to always send the header:
access-control-allow-origin
`cors_automatic_options`
- Type:
bool - Default:
True - Description: Whether to automatically generate
OPTIONSendpoints for routes that do not already have one defined
`cors_expose_headers`
- Type:
str - Default:
"" - Description: Value of the header:
access-control-expose-headers
`cors_max_age`
- Type:
int - Default:
5 - Description: Value of the header:
access-control-max-age
`cors_methods`
- Type:
str - Default:
"" - Description: Value of the header:
access-control-access-control-allow-methods
`cors_origins`
- Type:
str - Default:
"" - Description: Value of the header:
access-control-allow-origin
.. warning::
Be very careful if you place `*` here. Do not do this unless you know what you are doing as it can be a security issue.`cors_send_wildcard`
- Type:
bool - Default:
False - Description: Whether to send a wildcard origin instead of the incoming request origin
`cors_supports_credentials`
- Type:
bool - Default:
False - Description: Value of the header:
access-control-allow-credentials
`cors_vary_header`
- Type:
bool - Default:
True - Description: Whether to add the
varyheader
`http_all_methods`
- Type:
bool - Default:
True - Description: Adds the HTTP
CONNECTandTRACEmethods as allowable
`http_auto_head`
- Type:
bool - Default:
True - Description: Automatically adds
HEADhandlers to anyGETroutes
`http_auto_options`
- Type:
bool - Default:
True - Description: Automatically adds
OPTIONShandlers to any routes without
`http_auto_trace`
- Type:
bool - Default:
False - Description: Automatically adds
TRACEhandlers to any routes without
`oas`
- Type:
bool - Default:
True - Description: Whether to enable OpenAPI specification generation
`oas_autodoc`
- Type:
bool - Default:
True - Description: Whether to automatically extract OpenAPI details from the docstring of a route function
`oas_ignore_head`
- Type:
bool - Default:
True - Description: WHen
True, it will not addHEADendpoints into the OpenAPI specification
`oas_ignore_options`
- Type:
bool - Default:
True - Description: WHen
True, it will not addOPTIONSendpoints into the OpenAPI specification
`oas_path_to_redoc_html`
- Type:
Optional[str] - Default:
None - Description: Path to HTML file to override the existing Redoc HTML
`oas_path_to_swagger_html`
- Type:
Optional[str] - Default:
None - Description: Path to HTML file to override the existing Swagger HTML
`oas_ui_default`
- Type:
Optional[str] - Default:
"redoc" - Description: Which OAS documentation to serve on the bare
oas_url_prefixendpoint; whenNonethere will be no documentation at that location
`oas_ui_redoc`
- Type:
bool - Default:
True - Description: Whether to enable the Redoc UI
`oas_ui_swagger`
- Type:
bool - Default:
True - Description: Whether to enable the Swagger UI
`oas_ui_swagger_version`
- Type:
str - Default:
"4.1.0" - Description: Which Swagger version to use
`oas_uri_to_config`
- Type:
str - Default:
"/swagger-config" - Description: Path to serve the Swagger configuration
`oas_uri_to_json`
- Type:
str - Default:
"/openapi.json" - Description: Path to serve the OpenAPI JSON
`oas_uri_to_redoc`
- Type:
str - Default:
"/redoc" - Description: Path to Redoc
`oas_uri_to_swagger`
- Type:
str - Default:
"/swagger" - Description: Path to Swagger
`oas_url_prefix`
- Type:
str - Default:
"/docs" - Description: URL prefix for the Blueprint that all of the OAS documentation witll attach to
`swagger_ui_configuration`
- Type:
Dict[str, Any] - Default:
{"apisSorter": "alpha", "operationsSorter": "alpha", "docExpansion": "full"} - Description: The Swagger documentation to be served to the frontend
`templating_enable_async`
- Type:
bool - Default:
True - Description: Whether to set
enable_asyncon the JinjaEnvironment
`templating_path_to_templates`
- Type:
Union[str, os.PathLike, Sequence[Union[str, os.PathLike]]] - Default:
templates - Description: A single path, or multiple paths to where your template files are located
`trace_excluded_headers`
- Type:
Sequence[str] - Default:
("authorization", "cookie") - Description: Which headers should be suppresed from responses to
TRACErequests
Guide Content En Plugins Sanic Ext Convenience (`guide/content/en/plugins/sanic-ext/convenience.md`)
title: Sanic Extensions - Convenience
Convenience
Fixed serializer
.. column::
Often when developing an application, there will be certain routes that always return the same sort of response. When this is the case, you can predefine the return serializer and on the endpoint, and then all that needs to be returned is the content... column::
```python
from sanic_ext import serializer
@app.get("/<name>")
@serializer(text)
async def hello_world(request, name: str):
if name.isnumeric():
return "hello " * int(name)
return f"Hello, {name}"
```.. column::
The `serializer` decorator also can add status codes... column::
```python
from sanic_ext import serializer
@app.post("/")
@serializer(text, status=202)
async def create_something(request):
...
```Custom serializer
.. column::
Using the `@serializer` decorator, you can also pass your own custom functions as long as they also return a valid type (`HTTPResonse`)... column::
```python
def message(retval, request, action, status):
return json(
{
"request_id": str(request.id),
"action": action,
"message": retval,
},
status=status,
)
@app.post("/<action>")
@serializer(message)
async def do_action(request, action: str):
return "This is a message"
```.. column::
Now, returning just a string should return a nice serialized output... column::
```python
$ curl localhost:8000/eat_cookies -X POST
{
"request_id": "ef81c45b-235c-46dd-9dbd-b550f8fa77f9",
"action": "eat_cookies",
"message": "This is a message"
}
```Request counter
.. column::
Sanic Extensions comes with a subclass of `Request` that can be setup to automatically keep track of the number of requests processed per worker process. To enable this, you should pass the `CountedRequest` class to your application contructor... column::
```python
from sanic_ext import CountedRequest
app = Sanic(..., request_class=CountedRequest)
```.. column::
You will now have access to the number of requests served during the lifetime of the worker process... column::
```python
@app.get("/")
async def handler(request: CountedRequest):
return json({"count": request.count})
```If possible, the request count will also be added to the worker state.
Guide Content En Plugins Sanic Ext Custom (`guide/content/en/plugins/sanic-ext/custom.md`)
title: Sanic Extensions - Custom
Custom extensions
It is possible to create your own custom extensions.
Version 22.9 added the Extend.register method. This makes it extremely easy to add custom expensions to an application.
Anatomy of an extension
All extensions must subclass Extension.
Required
name: By convention, the name is an all-lowercase stringstartup: A method that runs when the extension is added
Optional
label: A method that returns additional information about the extension in the MOTDincluded: A method that returns a boolean whether the extension should be enabled or not (could be used for example to check config state)
Example
from sanic import Request, Sanic, json
from sanic_ext import Extend, Extension
app = Sanic(__name__)
app.config.MONITOR = True
class AutoMonitor(Extension):
name = "automonitor"
def startup(self, bootstrap) -> None:
if self.included():
self.app.before_server_start(self.ensure_monitor_set)
self.app.on_request(self.monitor)
@staticmethod
async def monitor(request: Request):
if request.route and request.route.ctx.monitor:
print("....")
@staticmethod
async def ensure_monitor_set(app: Sanic):
for route in app.router.routes:
if not hasattr(route.ctx, "monitor"):
route.ctx.monitor = False
def label(self):
has_monitor = [
route
for route in self.app.router.routes
if getattr(route.ctx, "monitor", None)
]
return f"{len(has_monitor)} endpoint(s)"
def included(self):
return self.app.config.MONITOR
Extend.register(AutoMonitor)
@app.get("/", ctx_monitor=True)
async def handler(request: Request):
return json({"foo": "bar"})Extension preregistration
.. column::
`Extend.register` simplifies the addition of custom extensions... column::
```python
from sanic_ext import Extend, Extension
class MyCustomExtension(Extension):
...
Extend.register(MyCustomExtension())
```Added in v22.9
Guide Content En Plugins Sanic Ext Health Monitor (`guide/content/en/plugins/sanic-ext/health-monitor.md`)
title: Sanic Extensions - Health Monitor
Health monitor
The health monitor requires both sanic>=22.9 and sanic-ext>=22.9.
You can setup Sanic Extensions to monitor the health of your worker processes. This requires that you not be in single process mode.
Setup
.. column::
Out of the box, the health monitor is disabled. You will need to opt-in and enable the endpoint if you would like to use it... column::
```python
app.config.HEALTH = True
app.config.HEALTH_ENDPOINT = True
```How does it work
The monitor sets up a new background process that will periodically receive acknowledgements of liveliness from each worker process. If a worker process misses a report too many times, then the monitor will restart that one worker.
Diagnostics endpoint
.. column::
The health monitor will also enable a diagnostics endpoint that outputs the [worker state](../../guide/running/manager.md#worker-state). By default is id disabled.
.. danger::
The diagnostics endpoint is not secured. If you are deploying it in a production environment, you should take steps to protect it with a proxy server if you are using one. If not, you may want to consider disabling this feature in production since it will leak details about your server state... column::
```
$ curl http://localhost:8000/__health__
{
'Sanic-Main': {'pid': 99997},
'Sanic-Server-0-0': {
'server': True,
'state': 'ACKED',
'pid': 9999,
'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc),
'starts': 2,
'restart_at': datetime.datetime(2022, 10, 1, 0, 0, 12, 861332, tzinfo=datetime.timezone.utc)
},
'Sanic-Reloader-0': {
'server': False,
'state': 'STARTED',
'pid': 99998,
'start_at': datetime.datetime(2022, 10, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc),
'starts': 1
}
}
```Configuration
| Key | Type | Default | Description |
|---|---|---|---|
| HEALTH | bool |
False |
Whether to enable this extension. |
| HEALTH_ENDPOINT | bool |
False |
Whether to enable the diagnostics endpoint. |
| HEALTH_MAX_MISSES | int |
3 |
The number of consecutive misses before a worker process is restarted. |
| HEALTH_MISSED_THRESHHOLD | int |
10 |
The number of seconds the monitor checks for worker process health. |
| HEALTH_MONITOR | bool |
True |
Whether to enable the health monitor. |
| HEALTH_REPORT_INTERVAL | int |
5 |
The number of seconds between reporting each acknowledgement of liveliness. |
| HEALTH_URI_TO_INFO | str |
"" |
The URI path of the diagnostics endpoint. |
| HEALTH_URL_PREFIX | str |
"/__health__" |
The URI prefix of the diagnostics blueprint. |
Guide Content En Plugins Sanic Ext Injection (`guide/content/en/plugins/sanic-ext/injection.md`)
title: Sanic Extensions - Dependency Injection
Dependency Injection
Dependency injection is a method to add arguments to a route handler based upon the defined function signature. Specifically, it looks at the type annotations of the arguments in the handler. This can be useful in a number of cases like:
- Fetching an object based upon request headers (like the current session user)
- Recasting certain objects into a specific type
- Using the request object to prefetch data
- Auto inject services
The Extend instance has two basic methods on it used for dependency injection: a lower level add_dependency, and a higher level dependency.
Lower level: app.ext.add_dependency(...)
type: Type,: some unique class that will be the type of the objectconstructor: Optional[Callable[..., Any]],(OPTIONAL): a function that will return that type
Higher level: app.ext.dependency(...)
obj: Any: any object that you would like injectedname: Optional[str]: some name that could alternately be used as a reference
Let's explore some use cases here.
.. warning::
If you used dependency injection prior to v21.12, the lower level API method was called `injection`. It has since been renamed to `add_dependency` and starting in v21.12 `injection` is an alias for `add_dependency`. The `injection` method has been deprecated for removal in v22.6.Basic implementation
The simplest use case would be simply to recast a value.
.. column::
This could be useful if you have a model that you want to generate based upon the matched path parameters... column::
```python
@dataclass
class IceCream:
flavor: str
def __str__(self) -> str:
return f"{self.flavor.title()} (Yum!)"
app.ext.add_dependency(IceCream)
@app.get("/<flavor:str>")
async def ice_cream(request, flavor: IceCream):
return text(f"You chose: {flavor}")
```
```
$ curl localhost:8000/chocolate
You chose Chocolate (Yum!)
```.. column::
This works by passing a keyword argument to the constructor of the `type` argument. The previous example is equivalent to this... column::
```python
flavor = IceCream(flavor="chocolate")
```Additional constructors
.. column::
Sometimes you may need to also pass a constructor. This could be a function, or perhaps even a classmethod that acts as a constructor. In this example, we are creating an injection that will call `Person.create` first.
Also important to note on this example, we are actually injecting **two (2)** objects! It of course does not need to be this way, but we will inject objects based upon the function signature... column::
```python
@dataclass
class PersonID:
person_id: int
@dataclass
class Person:
person_id: PersonID
name: str
age: int
@classmethod
async def create(cls, request: Request, person_id: int):
return cls(person_id=PersonID(person_id), name="noname", age=111)
app.ext.add_dependency(Person, Person.create)
app.ext.add_dependency(PersonID)
@app.get("/person/<person_id:int>")
async def person_details(
request: Request, person_id: PersonID, person: Person
):
return text(f"{person_id}\n{person}")
```
```
$ curl localhost:8000/person/123
PersonID(person_id=123)
Person(person_id=PersonID(person_id=123), name='noname', age=111)
```When a constructor is passed to ext.add_dependency (like in this example) that will be called. If not, then the object will be created by calling the type. A couple of important things to note about passing a constructor:
- A positional
request: Requestargument is usually expected. See thePerson.createmethod above as an example using arequestand arbitrary constructors for how to use a callable that does not require arequest. - All matched path parameters are injected as keyword arguments.
- Dependencies can be chained and nested. Notice how in the previous example the
Persondataclass has aPersonID? That means thatPersonIDwill be called first, and that value is added to the keyword arguments when callingPerson.create.
Arbitrary constructors
.. column::
Sometimes you may want to construct your injectable _without_ the `Request` object. This is useful if you have arbitrary classes or functions that create your objects. If the callable does have any required arguments, then they should themselves be injectable objects.
This is very useful if you have services or other types of objects that should only exist for the lifetime of a single request. For example, you might use this pattern to pull a single connection from your database pool... column::
```python
class Alpha:
...
class Beta:
def __init__(self, alpha: Alpha) -> None:
self.alpha = alpha
app.ext.add_dependency(Alpha)
app.ext.add_dependency(Beta)
@app.get("/beta")
async def handler(request: Request, beta: Beta):
assert isinstance(beta.alpha, Alpha)
```Added in v22.9
Objects from the `Request`
.. column::
Sometimes you may want to extract details from the request and preprocess them. You could, for example, cast the request JSON to a Python object, and then add some additional logic based upon DB queries.
.. warning::
If you plan to use this method, you should note that the injection actually happens *before* Sanic has had a chance to read the request body. The headers should already have been consumed. So, if you do want access to the body, you will need to manually consume as seen in this example.
```python
await request.receive_body()
```
This could be used in cases where you otherwise might:
- use middleware to preprocess and add something to the `request.ctx`
- use decorators to preprocess and inject arguments into the request handler
In this example, we are using the `Request` object in the `compile_profile` constructor to run a fake DB query to generate and return a `UserProfile` object... column::
```python
@dataclass
class User:
name: str
@dataclass
class UserProfile:
user: User
age: int = field(default=0)
email: str = field(default="")
def __json__(self):
return ujson.dumps(
{
"name": self.user.name,
"age": self.age,
"email": self.email,
}
)
async def fake_request_to_db(body