What is Sanic?
mainasync/await syntax for non-blocking, speedy execution.repository·main·Indexed 12 days ago
https://github.com/sanic-org/sanicA high-performance, ASGI-compliant Python web server and framework designed for speed, leveraging async/await syntax for non-blocking capabilities. It provides comprehensive tools for request and response handling, class-based views via HTTPMethodView, custom routing, and a CLI for executing custom commands.
async/await syntax for non-blocking, speedy execution.autodoc feature to automatically derive documentation from your code. Once defined, the documentation can be rendered using tools like redoc or swagger.A handler (sometimes called a "view") is a callable that processes an incoming request and returns a response.
To be a valid handler, the callable must:
sanic.request.Request instance.sanic.response.HTTPResponse instance or a coroutine that returns an HTTPResponse.Handlers can be defined as standard synchronous functions or asynchronous functions using async def.
def i_am_a_handler(request):
return HTTPResponse()
async def i_am_ALSO_a_handler(request):
return HTTPResponse()For advanced functionality, Sanic can be used with Sanic Extensions. These extensions add capabilities that go beyond the core server and framework, including:
HEAD, OPTIONS, and TRACE endpoints.Listeners follow specific rules regarding when they execute and in what order.
| Listener | 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 ⬆️ |
priority (v23.12+)You can use the priority keyword argument to control execution order. The default priority is 0. Higher priority values execute first.
The hierarchy for execution order is:
app) listeners execute before Blueprint listeners.@app.before_server_start(priority=3)
async def third(app):
print("third")
@bp.before_server_start(priority=3)
async def bp_third(app):
print("bp_third")Sanic provides a ctx object on the application instance to share or reuse data (like database connections) across different parts of your codebase.
While you can attach objects directly to app.ctx, the recommended best practice is to use application startup listeners like @app.before_server_start to ensure objects are initialized correctly during the lifecycle.
app = Sanic("MyApp")
@app.before_server_start
async def attach_db(app, loop):
app.ctx.db = Database()The Request class is also generic: Request[AppType, ContextType].
AppType: The type of the application instance (request.app).ContextType: The type of the request context (request.ctx).By providing these types, your IDE will provide full autocompletion for request.app.ctx and request.ctx.
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):
# request.app.ctx is typed as Foo
# request.ctx is typed as RequestContext
passTo create a custom extension, you must subclass sanic_ext.Extension. Extensions allow you to encapsulate logic that hooks into the Sanic application lifecycle, such as startup routines, request handling, or configuration-based enabling.
name: An all-lowercase string used to identify the extension.startup(self, bootstrap): A method that executes when the extension is added to the application. The bootstrap argument is provided by the extension registry.label(self): A method that returns a string providing additional information about the extension. This information is displayed in the Sanic MOTD (Message of the Day).included(self): A method that returns a boolean. If it returns False, the extension will not be enabled (useful for checking application configuration settings).from sanic_ext import Extension
class MyExtension(Extension):
name = "my_extension"
def startup(self, bootstrap) -> None:
# Logic to run on startup
pass
def included(self) -> bool:
# Return True to enable, False to skip
return TrueAll Sanic exceptions derive from SanicException. You can standardize error reporting by defining these properties as class variables in custom exception classes or passing them during instantiation:
message: The error message displayed in the response.status_code: The HTTP status code returned to the client.quiet: If True, the exception will not be sent to the error_logger. You can override this globally using app.config.NOISY_EXCEPTIONS = True.headers: A dictionary of HTTP headers to include in the error response.extra: Additional data for contextual exceptions.context: Additional data for contextual exceptions.from sanic.exceptions import SanicException
class TeapotError(SanicException):
status_code = 418
message = "Sorry, I cannot brew coffee"
headers = {"X-Custom": "value"}
# Usage
raise TeapotError()
# Or override at runtime
raise TeapotError(status_code=400, quiet=True)Sanic allows you to extract values from URL paths using the <name> syntax. These values are passed to your handler as keyword arguments. You can specify a type for the parameter to enforce matching and automatic type casting.
Basic parameter:
@app.get("/tag/<tag>")
async def tag_handler(request, tag):
return text(f"Tag - {tag}")Typed parameter:
@app.get("/foo/<foo_id:uuid>")
async def uuid_handler(request, foo_id: UUID):
return text(f"UUID - {foo_id}")Note: For standard types like str, int, and UUID, Sanic can often infer the type from your function signature, allowing you to omit the type in the path definition (e.g., <foo_id>).
@app.get("/tag/<tag>")
async def tag_handler(request, tag):
return text("Tag - {}".format(tag))Sanic uses two primary methods for making decisions about project direction and technical changes.
Most decisions are made via lazy consensus to ensure efficiency.
For major decisions—such as changes that break or deprecate an existing API, alter operations in a non-trivial manner, or add significant features—the formal RFC process is used.
To ensure web applications function correctly regardless of deployment (e.g., behind a proxy), use request.host to determine the effective host.
request.host: Returns the effective host (prefers proxy-forwarded host or the configured app.config.SERVER_NAME).request.headers.get('host'): Returns the raw Host header from the client.request.url_for(name): When called on a request object, it uses the effective host to construct absolute external URLs.Security Warning: request.url_for uses the request's host, which can be manipulated by malicious clients sending misleading host headers. If you need to generate URLs that are not subject to client-side host manipulation, use app.url_for instead.
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"),
}
)