326 lines
12 KiB
Markdown
326 lines
12 KiB
Markdown
|
# Version 22.9
|
||
|
|
||
|
.. toc::
|
||
|
|
||
|
## Introduction
|
||
|
|
||
|
This is the third release of the version 22 [release cycle](../../org/policies.md#release-schedule). Version 22 will be "finalized" in the December long-term support version release.
|
||
|
|
||
|
## What to know
|
||
|
|
||
|
More details in the [Changelog](https://sanic.readthedocs.io/en/stable/sanic/changelog.html). Notable new or breaking features, and what to upgrade...
|
||
|
|
||
|
### ⚠ *IMPORTANT* - New worker manager 🚀
|
||
|
|
||
|
Sanic server has been overhauled to provide more consistency and flexbility in how it operates. More details about the motivations are outlined in [PR #2499](https://github.com/sanic-org/sanic/pull/2499) and discussed in a live stream [discussion held on YouTube](https://youtu.be/m8HCO8NK7HE).
|
||
|
|
||
|
This **does NOT apply** to Sanic in ASGI mode
|
||
|
|
||
|
#### Overview of the changes
|
||
|
|
||
|
- The worker servers will **always** run in a child process.
|
||
|
- Previously this could change depending upon one versus many workers, and the usage or not of the reloader. This should lead to much more predictable development environments that more closely match their production counterparts.
|
||
|
- Multi-workers is **now supported on Windows**.
|
||
|
- This was impossible because Sanic relied upon the `multiprocessing` module using `fork`, which is not available on Windows.
|
||
|
- Now, Sanic will always use `spawn`. This does have some noticeable differences, particularly if you are running Sanic in the global scope with `app.run` (see below re: upgrade issues).
|
||
|
- The application instance now has a new `multiplexer` object that can be used to restart one or many workers. This could, for example, be triggered by a request.
|
||
|
- There is a new Inspector that can provide details on the state of your server.
|
||
|
- Sanic worker manager can run arbitrary processes.
|
||
|
- This allows developers to add any process they want from within Sanic.
|
||
|
- Possible use cases:
|
||
|
- Health monitor, see [Sanic Extensions]()
|
||
|
- Logging queue, see [Sanic Extensions]()
|
||
|
- Background worker queue in a seperate process
|
||
|
- Running another application, like a bot
|
||
|
- There is a new listener called: `main_process_ready`. It really should only be used for adding arbitrary processes to Sanic.
|
||
|
- Passing shared objects between workers.
|
||
|
- Python does allow some types of objects to share state between processes, whether through shared memory, pipes, etc.
|
||
|
- Sanic will now allow these types of object to be shared on the `app.shared_ctx` object.
|
||
|
- Since this feature relies upon Pythons `multiprocessing` library, it obviously will only work to share state between Sanic worker instances that are instantiated from the same execution. This is *not* meant to provide an API for horizontal scaling across multiple machines for example.
|
||
|
|
||
|
#### Adding a shared context object
|
||
|
|
||
|
To share an object between worker processes, it *MUST* be assigned inside of the `main_process_start` listener.
|
||
|
|
||
|
```python
|
||
|
from multiprocessing import Queue
|
||
|
|
||
|
@app.main_process_start
|
||
|
async def main_process_start(app):
|
||
|
app.shared_ctx.queue = Queue()
|
||
|
```
|
||
|
|
||
|
All objects on `shared_ctx` will be available now within each worker process.
|
||
|
|
||
|
```python
|
||
|
@app.before_server_starts
|
||
|
async def before_server_starts(app):
|
||
|
assert isinstance(app.shared_ctx.queue, Queue)
|
||
|
|
||
|
@app.on_request
|
||
|
async def on_request(request):
|
||
|
assert isinstance(request.app.shared_ctx.queue, Queue)
|
||
|
|
||
|
@app.get("/")
|
||
|
async def handler(request):
|
||
|
assert isinstance(request.app.shared_ctx.queue, Queue)
|
||
|
```
|
||
|
|
||
|
*NOTE: Sanic will not stop you from registering an unsafe object, but may warn you. Be careful not to just add a regular list object, for example, and expect it to work. You should have an understanding of how to share state between processes.*
|
||
|
|
||
|
#### Running arbitrary processes
|
||
|
|
||
|
Sanic can run any arbitrary process for you. It should be capable of being stopped by a `SIGINT` or `SIGTERM` OS signal.
|
||
|
|
||
|
These processes should be registered inside of the `main_process_ready` listener.
|
||
|
|
||
|
```python
|
||
|
@app.main_process_ready
|
||
|
async def ready(app: Sanic, _):
|
||
|
app.manager.manage("MyProcess", my_process, {"foo": "bar"})
|
||
|
# app.manager.manage(<name>, <callable>, <kwargs>)
|
||
|
```
|
||
|
|
||
|
#### Inspector
|
||
|
|
||
|
Sanic ships with an optional Inspector, which is a special process that allows for the CLI to inspect the running state of an application and issue commands. It currently will only work if the CLI is being run on the same machine as the Sanic instance.
|
||
|
|
||
|
```
|
||
|
sanic path.to:app --inspect
|
||
|
```
|
||
|
|
||
|
![Sanic inspector](https://user-images.githubusercontent.com/166269/190099384-2f2f3fae-22d5-4529-b279-8446f6b5f9bd.png)
|
||
|
|
||
|
The new CLI commands are:
|
||
|
|
||
|
```
|
||
|
--inspect Inspect the state of a running instance, human readable
|
||
|
--inspect-raw Inspect the state of a running instance, JSON output
|
||
|
--trigger-reload Trigger worker processes to reload
|
||
|
--trigger-shutdown Trigger all processes to shutdown
|
||
|
```
|
||
|
|
||
|
This is not enabled by default. In order to have it available, you must opt in:
|
||
|
|
||
|
```python
|
||
|
app.config.INSPECTOR = True
|
||
|
```
|
||
|
|
||
|
*Note: [Sanic Extensions]() provides a [custom request](../basics/app.md#custom-requests) class that will add a request counter to the server state.
|
||
|
|
||
|
#### Application multiplexer
|
||
|
|
||
|
Many of the same information and functionality is available on the application instance itself. There is a new `multiplexer` object on the application instance that has the ability to restart one or more workers, and fetch information about the current state.
|
||
|
|
||
|
You can access it as `app.multiplexer`, or more likely by its short alias `app.m`.
|
||
|
|
||
|
```python
|
||
|
@app.on_request
|
||
|
async def print_state(request: Request):
|
||
|
print(request.app.m.state)
|
||
|
```
|
||
|
|
||
|
#### Potential upgrade issues
|
||
|
|
||
|
Because of the switch from `fork` to `spawn`, if you try running the server in the global scope you will receive an error. If you see something 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.
|
||
|
```
|
||
|
|
||
|
... then the change is simple. Make sure `app.run` is inside a block.
|
||
|
|
||
|
```python
|
||
|
if __name__ == "__main__":
|
||
|
app.run(port=9999, dev=True)
|
||
|
```
|
||
|
|
||
|
#### Opting out of the new functionality
|
||
|
|
||
|
If you would like to run Sanic without the new process manager, you may easily use the legacy runners. Please note that support for them **will be removed** in the future. A date has not yet been set, but will likely be sometime in 2023.
|
||
|
|
||
|
To opt out of the new server and use the legacy, choose the appropriate method depending upon how you run Sanic:
|
||
|
|
||
|
.. column::
|
||
|
|
||
|
If you use the CLI...
|
||
|
|
||
|
.. column::
|
||
|
|
||
|
```
|
||
|
sanic path.to:app --legacy
|
||
|
```
|
||
|
|
||
|
|
||
|
.. column::
|
||
|
|
||
|
If you use `app.run`...
|
||
|
|
||
|
.. column::
|
||
|
|
||
|
```
|
||
|
app.run(..., legacy=True)
|
||
|
```
|
||
|
|
||
|
|
||
|
.. column::
|
||
|
|
||
|
If you `app.prepare`...
|
||
|
|
||
|
.. column::
|
||
|
|
||
|
```
|
||
|
app.prepare(...)
|
||
|
Sanic.serve_legacy()
|
||
|
```
|
||
|
|
||
|
Similarly, you can force Sanic to run in a single process. This however means there will not be any access to the auto-reloader.
|
||
|
|
||
|
.. column::
|
||
|
|
||
|
If you use the CLI...
|
||
|
|
||
|
.. column::
|
||
|
|
||
|
```
|
||
|
sanic path.to:app --single-process
|
||
|
```
|
||
|
|
||
|
|
||
|
.. column::
|
||
|
|
||
|
If you use `app.run`...
|
||
|
|
||
|
.. column::
|
||
|
|
||
|
```
|
||
|
app.run(..., single_process=True)
|
||
|
```
|
||
|
|
||
|
|
||
|
.. column::
|
||
|
|
||
|
If you `app.prepare`...
|
||
|
|
||
|
.. column::
|
||
|
|
||
|
```
|
||
|
app.prepare(...)
|
||
|
Sanic.serve_single()
|
||
|
```
|
||
|
|
||
|
### Middleware priority
|
||
|
|
||
|
Middleware is executed in an order based upon when it was defined. Request middleware are executed in sequence and response middleware in reverse. This could have an unfortunate impact if your ordering is strictly based upon import ordering with global variables for example.
|
||
|
|
||
|
A new addition is to break-out of the strict construct and allow a priority to be assigned to a middleware. The higher the number for a middleware definition, the earlier in the sequence it will be executed. This applies to **both** request and response middleware.
|
||
|
|
||
|
```python
|
||
|
@app.on_request
|
||
|
async def low_priority(_):
|
||
|
...
|
||
|
|
||
|
@app.on_request(priority=10)
|
||
|
async def high_priority(_):
|
||
|
...
|
||
|
```
|
||
|
|
||
|
In the above example, even though `low_priority` is defined first, `high_priority` will run first.
|
||
|
|
||
|
### Custom `loads` function
|
||
|
|
||
|
Sanic has supported the ability to add a [custom `dumps` function](https://sanic.readthedocs.io/en/stable/sanic/api/app.html#sanic.app.Sanic) when instantiating an app. The same functionality has been extended to `loads`, which will be used when deserializing.
|
||
|
|
||
|
```python
|
||
|
from json import loads
|
||
|
|
||
|
Sanic("Test", loads=loads)
|
||
|
```
|
||
|
|
||
|
### Websocket objects are now iterable
|
||
|
|
||
|
Rather than calling `recv` in a loop on a `Websocket` object, you can iterate on it in a `for` loop.
|
||
|
|
||
|
```python
|
||
|
from sanic import Request, Websocket
|
||
|
|
||
|
@app.websocket("/ws")
|
||
|
async def ws_echo_handler(request: Request, ws: Websocket):
|
||
|
async for msg in ws:
|
||
|
await ws.send(msg)
|
||
|
```
|
||
|
|
||
|
### Appropriately respond with 304 on static files
|
||
|
|
||
|
When serving a static file, the Sanic server can respond appropriately to a request with `If-Modified-Since` using a `304` response instead of resending a file.
|
||
|
|
||
|
### Two new signals to wrap handler execution
|
||
|
|
||
|
Two new [signals](../advanced/signals.md) have been added that wrap the execution of a request handler.
|
||
|
|
||
|
- `http.handler.before` - runs after request middleware but before the route handler
|
||
|
- `http.handler.after` - runs after the route handler
|
||
|
- In *most* circumstances, this also means that it will run before response middleware. However, if you call `request.respond` from inside of a route handler, then your middleware will come first
|
||
|
|
||
|
### New Request properties for HTTP method information
|
||
|
|
||
|
The HTTP specification defines which HTTP methods are: safe, idempotent, and cacheable. New properties have been added that will respond with a boolean flag to help identify the request property based upon the method.
|
||
|
|
||
|
```python
|
||
|
request.is_safe
|
||
|
request.is_idempotent
|
||
|
request.is_cacheable
|
||
|
```
|
||
|
|
||
|
### 🚨 *BREAKING CHANGE* - Improved cancel request exception
|
||
|
|
||
|
In prior version of Sanic, if a `CancelledError` was caught it could bubble up and cause the server to respond with a `503`. This is not always the desired outcome, and it prevented the usage of that error in other circumstances. As a result, Sanic will now use a subclass of `CancelledError` called: `RequestCancelled` for this functionality. It likely should have little impact unless you were explicitly relying upon the old behavior.
|
||
|
|
||
|
For more details on the specifics of these properties, checkout the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods).
|
||
|
|
||
|
### New deprecation warning filter
|
||
|
|
||
|
You can control the level of deprecation warnings from Sanic using [standard library warning filter values](https://docs.python.org/3/library/warnings.html#the-warnings-filter). Default is `"once"`.
|
||
|
|
||
|
```python
|
||
|
app.config.DEPRECATION_FILTER = "ignore"
|
||
|
```
|
||
|
|
||
|
### Deprecations and Removals
|
||
|
|
||
|
1. *DEPRECATED* - Duplicate route names have been deprecated and will be removed in v23.3
|
||
|
1. *DEPRECATED* - Registering duplicate exception handlers has been deprecated and will be removed in v23.3
|
||
|
1. *REMOVED* - `route.ctx` not set by Sanic, and is a blank object for users, therefore ...
|
||
|
- `route.ctx.ignore_body` >> `route.extra.ignore_body`
|
||
|
- `route.ctx.stream` >> `route.extra.stream`
|
||
|
- `route.ctx.hosts` >> `route.extra.hosts`
|
||
|
- `route.ctx.static` >> `route.extra.static`
|
||
|
- `route.ctx.error_format` >> `route.extra.error_format`
|
||
|
- `route.ctx.websocket` >> `route.extra.websocket`
|
||
|
1. *REMOVED* - `app.debug` is READ-ONLY
|
||
|
1. *REMOVED* - `app.is_running` removed
|
||
|
1. *REMOVED* - `app.is_stopping` removed
|
||
|
1. *REMOVED* - `Sanic._uvloop_setting` removed
|
||
|
1. *REMOVED* - Prefixed environment variables will be ignored if not uppercase
|
||
|
|
||
|
## Thank you
|
||
|
|
||
|
Thank you to everyone that participated in this release: :clap:
|
||
|
|
||
|
[@ahopkins](https://github.com/ahopkins)
|
||
|
[@azimovMichael](https://github.com/azimovMichael)
|
||
|
[@ChihweiLHBird](https://github.com/ChihweiLHBird)
|
||
|
[@huntzhan](https://github.com/huntzhan)
|
||
|
[@monosans](https://github.com/monosans)
|
||
|
[@prryplatypus](https://github.com/prryplatypus)
|
||
|
[@SaidBySolo](https://github.com/SaidBySolo)
|
||
|
[@seemethere](https://github.com/seemethere)
|
||
|
[@sjsadowski](https://github.com/sjsadowski)
|
||
|
[@timgates42](https://github.com/timgates42)
|
||
|
[@Tronic](https://github.com/Tronic)
|
||
|
|
||
|
---
|
||
|
|
||
|
If you enjoy the project, please consider contributing. Of course we love code contributions, but we also love contributions in any form. Consider writing some documentation, showing off use cases, joining conversations and making your voice known, and if you are able: [financial contributions](https://opencollective.com/sanic-org/).
|