Version 22.9
Table of Contents
- Version 22.9
- Introduction
- What to know
- ⚠ IMPORTANT - New worker manager 🚀
- Middleware priority
- Custom loads function
- Websocket objects are now iterable
- Appropriately respond with 304 on static files
- Two new signals to wrap handler execution
- New Request properties for HTTP method information
- 🚨 BREAKING CHANGE - Improved cancel request exception
- New deprecation warning filter
- Deprecations and Removals
- Thank you
Introduction#
This is the third release of the version 22 release cycle. Version 22 will be "finalized" in the December long-term support version release.
What to know#
More details in the Changelog. 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 and discussed in a live stream discussion held on YouTube.
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 usingfork
, 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 withapp.run
(see below re: upgrade issues).
- This was impossible because Sanic relied upon the
- 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.
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.
@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.
@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
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:
app.config.INSPECTOR = True
*Note: Sanic Extensions provides a custom request 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
.
@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.
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:
If you use the CLI...
sanic path.to:app --legacy
If you use app.run
...
app.run(..., legacy=True)
If you app.prepare
...
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.
If you use the CLI...
sanic path.to:app --single-process
If you use app.run
...
app.run(..., single_process=True)
If you app.prepare
...
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.
@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 when instantiating an app. The same functionality has been extended to loads
, which will be used when deserializing.
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.
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 have been added that wrap the execution of a request handler.
http.handler.before
- runs after request middleware but before the route handlerhttp.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
- In most circumstances, this also means that it will run before response middleware. However, if you call
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.
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.
New deprecation warning filter#
You can control the level of deprecation warnings from Sanic using standard library warning filter values. Default is "once"
.
app.config.DEPRECATION_FILTER = "ignore"
Deprecations and Removals#
- DEPRECATED - Duplicate route names have been deprecated and will be removed in v23.3
- DEPRECATED - Registering duplicate exception handlers has been deprecated and will be removed in v23.3
- 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
- REMOVED -
app.debug
is READ-ONLY - REMOVED -
app.is_running
removed - REMOVED -
app.is_stopping
removed - REMOVED -
Sanic._uvloop_setting
removed - REMOVED - Prefixed environment variables will be ignored if not uppercase
Thank you#
Thank you to everyone that participated in this release: :clap:
@ahopkins @azimovMichael @ChihweiLHBird @huntzhan @monosans @prryplatypus @SaidBySolo @seemethere @sjsadowski @timgates42 @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.