# Version 23.6
- What to know
- Thank you
# What to know
More details in the Changelog (opens new window). Notable new or breaking features, and what to upgrade...
# Remove Python 3.7 support
Python 3.7 is due to reach its scheduled upstream end-of-life on 2023-06-27. Sanic is now dropping support for Python 3.7, and requires Python 3.8 or newer.
# Resolve pypy compatibility issues
A small patch was added to the
os module to once again allow for Sanic to run with PyPy. This workaround replaces the missing
readlink function (missing in PyPy
os module) with the function
os.path.realpath, which serves to the same purpose.
# Add custom typing to config and ctx objects
sanic.Request object have become generic types that will make it more convenient to have fully typed
In the most simple form, the
Sanic object is typed as:
from sanic import Sanic app = Sanic("test") reveal_type(app) # N: Revealed type is "sanic.app.Sanic[sanic.config.Config, types.SimpleNamespace]"
It should be noted, there is no requirement to use the generic types. The default types are
types.SimpleNamespace. This new feature is just an option for those that want to use it and existing types of
app: Sanic and
request: Request should work just fine.
Now it is possible to have a fully-type
request.ctx objects though generics. This allows for better integration with auto completion tools in IDEs improving the developer experience.
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): ...
As a side effect, now
request.ctx is lazy initialized, which should reduce some overhead when the
request.ctx is unused.
One further change you may have noticed in the above snippet is the
make_context method. This new method can be used by custom
Request types to inject an object different from a
SimpleNamespace similar to how Sanic has allowed custom application context objects for a while.
# Universal exception signal
A new exception signal added for ALL exceptions raised while the server is running:
"server.exception.reporting". This is a universal signal that will be emitted for any exception raised, and dispatched as its own task. This means that it will not block the request handler, and will not be affected by any middleware.
This is useful for catching exceptions that may occur outside of the request handler (for example in signals, or in a background task), and it intended for use to create a consistent error handling experience for the user.
from sanic.signals import Event @app.signal(Event.SERVER_LIFECYCLE_EXCEPTION) async def catch_any_exception(app: Sanic, exception: Exception): app.ctx.my_error_reporter_utility.error(exception)
This pattern can be simplified with a new decorator
@app.report_exception async def catch_any_exception(app: Sanic, exception: Exception): print("Caught exception:", exception)
It should be pointed out that this happens in a background task and is NOT for manipulation of an error response. It is only for reporting, logging, or other purposes that should be triggered when an application error occurs.
# Add name prefixing to BP groups
Sanic had been raising a warning on duplicate route names for a while, and started to enforce route name uniqueness in v23.3 (opens new window). This created a complication for blueprint composition.
New name prefixing parameter for blueprints groups has been added to alleviate this issue. It allows nesting of blueprints and groups to make them composable.
The addition is the new
name_prefix parameter as shown in this snippet.
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)
The routes built will be named as follows:
Sanic has introduced
request.client_ip, a new accessor that provides client's IP address from both local and proxy data. It allows running the application directly on Internet or behind a proxy. This is equivalent to
request.remote_addr or request.ip, providing the client IP regardless of how the application is deployed.
# Increase of
KEEP_ALIVE_TIMEOUT default to 120 seconds
KEEP_ALIVE_TIMEOUT value changed from 5 seconds to 120 seconds. It is of course still configurable, but this change should improve performance on long latency connections, where reconnecting is expensive, and better fits typical user flow browsing pages with longer-than-5-second intervals.
Sanic has historically used 5 second timeouts to quickly close idle connections. The chosen value of 120 seconds is indeed larger than Nginx default of 75, and is the same value that Caddy server has by default.
# Set multiprocessing start method early
Due to how Python handles
multiprocessing, it may be confusing to some users how to properly create synchronization primitives. This is due to how Sanic creates the
multiprocessing context. This change sets the start method early so that any primitives created will properly attach to the correct context.
For most users, this should not be noticeable or impactful. But, it should make creation of something like this easier and work as expected.
from multiprocessing import Queue @app.main_process_start async def main_process_start(app): app.shared_ctx.queue = Queue()
# Thank you
Thank you to everyone that participated in this release: 👏
@ahopkins (opens new window) @ChihweiLHBird (opens new window) @chuckds (opens new window) @deounix (opens new window) @guacs (opens new window) @liamcoatman (opens new window) @moshe742 (opens new window) @prryplatypus (opens new window) @SaidBySolo (opens new window) @Thirumalai (opens new window) @Tronic (opens new window)
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 (opens new window).