Sanic

repository·main·Indexed 12 days ago

https://github.com/sanic-org/sanic

A 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.

Tokens
83.7K
Snippets
293
Records
338
Agent score
97%

What's inside Sanic

  1. What is Sanic?

    main
    Sanic is a high-performance Python 3.10+ web server and web framework. Unlike many other frameworks that require a separate ASGI server for deployment, Sanic is both a framework and a built-in web server. It is designed to be production-ready, highly scalable, and ASGI compliant, leveraging Python's async/await syntax for non-blocking, speedy execution.
  2. Generate OpenAPI documentation with Sanic Extensions

    main
    Sanic Extensions provides built-in support for generating OpenAPI (OAS) documentation. You can document your API by using decorators on your route handlers, documenting Class-Based Views (CBV), or using the autodoc feature to automatically derive documentation from your code. Once defined, the documentation can be rendered using tools like redoc or swagger.
  3. What is a Sanic handler?

    main

    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:

    1. Accept at least one argument: a sanic.request.Request instance.
    2. Return either a 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()
  4. Features provided by Sanic Extensions

    main

    For advanced functionality, Sanic can be used with Sanic Extensions. These extensions add capabilities that go beyond the core server and framework, including:

    • CORS protection: Cross-Origin Resource Sharing management.
    • Template rendering: Integration with Jinja for server-side rendering.
    • Dependency injection: Injecting dependencies directly into route handlers.
    • OpenAPI documentation: Automatic generation of documentation using Redoc and/or Swagger.
    • Response serializers: Predefined, endpoint-specific serializers for structured data.
    • Input validation: Validation for request query arguments and body input.
    • Automatic endpoints: Auto-creation of HEAD, OPTIONS, and TRACE endpoints.
  5. Understand listener execution order and priority

    main

    Listeners follow specific rules regarding when they execute and in what order.

    Execution Order by Phase

    • Startup Phase: Listeners are executed in the order they were declared (regular order).
    • Teardown Phase: Listeners are executed in the reverse order of declaration.
    ListenerPhaseOrder
    main_process_startmain startupregular ⬇️
    before_server_startworker startupregular ⬇️
    after_server_startworker startupregular ⬇️
    before_server_stopworker shutdownreverse ⬆️
    after_server_stopworker shutdownreverse ⬆️
    main_process_stopmain shutdownreverse ⬆️

    Fine-tuning with 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:

    1. Priority: Descending order (highest first).
    2. Scope: Application (app) listeners execute before Blueprint listeners.
    3. Registration: Order in which they were declared/registered.
    @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")
  6. Use the application context (app.ctx) to share data

    main

    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()
  7. Type-hinting a customized Request

    main

    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
        pass
  8. Create a custom Sanic extension

    main

    To 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.

    Required Components

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

    Optional Components

    • 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 True
  9. Customize SanicException properties

    main

    All 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)
  10. Use path parameters and type casting

    main

    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))
  11. How decision making works: Lazy Consensus and RFCs

    main

    Sanic uses two primary methods for making decisions about project direction and technical changes.

    Lazy Consensus

    Most decisions are made via lazy consensus to ensure efficiency.

    • Process: A proposal is made (via Community Forums or a GitHub Pull Request), followed by a discussion period.
    • Mechanism: If no one explicitly opposes a proposal within a reasonable timeframe (generally 72 hours), it is recognized as having community support and is accepted.
    • Goal: To allow the project to move forward without requiring formal votes for every change.

    Request for Comment (RFC)

    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.

    • Oversight: The Steering Council oversees the RFC process.
    • Process: An RFC is initiated through a public submission to the Steering Council. It remains open to debate to all community members.
    • Final Decision: The Steering Council holds the final decision-making authority for RFCs, though they are encouraged to follow community consensus.
  12. Determine the effective host and construct dynamic URLs

    main

    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"),
            }
        )