Version 22.9

Table of Contents

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 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:
  • 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

Sanic inspector

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__ == &quot;__main__&quot;` 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 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.

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#

  1. DEPRECATED - Duplicate route names have been deprecated and will be removed in v23.3
  2. DEPRECATED - Registering duplicate exception handlers has been deprecated and will be removed in v23.3
  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
  4. REMOVED - app.debug is READ-ONLY
  5. REMOVED - app.is_running removed
  6. REMOVED - app.is_stopping removed
  7. REMOVED - Sanic._uvloop_setting removed
  8. 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.