ASGI Web App

I wanted to start coding immediately, but I have one important thing for you to say before: some why many ASGI frameworks implemented lifespan as side task [uvicorn, granian]. And this breaks an idea to use contextvars, but we can embed ASGI Web server into our application, and ASGIServerUnit would help to do it

All this article is about how to use contextvars with ASGI servers. If you use different DI solution use this article as just funny story

ASGIServerUnit is implementation-agnostic and depends on protocol only. Also I've created useful asgi_server_factory_decorator, helping you to minify boilerplate code

Let's create ASGI server factories!

# lib.create_asgi_server
from typing import NotRequired, TypedDict, Any

from systempy.unit.asgi_server import (
    asgi_server_factory_decorator,
    ASGIAppFactory,
    ASGIServerProtocol,
)

from uvicorn import Config, Server


class ExampleConfigObject(TypedDict):
    # Hint: this may be ANY object, because you handle it by your code
    host: NotRequired[str]
    port: NotRequired[int]


def create_settings(example_param: Any) -> ExampleConfigObject:
    # Hint: it may be useful to parametrize this function, and
    # `example_param` demonstrates it. Use partial to curry this function
    return {"port": 12345}


@asgi_server_factory_decorator
def create_asgi_server(
    app_factory: ASGIAppFactory,
    settings: ExampleConfigObject,
) -> ASGIServerProtocol:
    config = Config(
        app_factory,
        factory=True,
        **settings,
    )
    return Server(config)
# lib.create_asgi_server
from typing import NotRequired, TypedDict, Any

from systempy.unit.asgi_server import (
    asgi_server_factory_decorator,
    ASGIAppFactory,
    ASGIServerProtocol,
)
from granian.server.embed import Server
from granian.constants import Interfaces


class ExampleConfigObject(TypedDict):
    # Hint: this may be ANY object, because you handle it by your code
    address: NotRequired[str]
    port: NotRequired[int]


def create_settings(example_param: Any) -> ExampleConfigObject:
    # Hint: it may be useful to parametrize this function, and
    # `example_param` demonstrates it. Use partial to curry this function
    return {"port": 12345}


@asgi_server_factory_decorator
def create_asgi_server(
    app_factory: ASGIAppFactory,
    settings: ExampleConfigObject,
) -> ASGIServerProtocol:
    return Server(
        app_factory,
        interface=Interfaces.ASGI,
        factory=True,
        **settings
    )

And now we are ready to use it:

# web_app.py

from functools import partial

from fastapi import FastAPI
from systempy.unit.asgi_server import ASGIServerUnit

from lib.create_asgi_server import create_asgi_server, create_settings

from .views import router


# Hint: use `functools.partial` to curry your `create_settings` if required
@create_asgi_server(partial(create_settings, "example_param_value"))
def create_app() -> FastAPI:
    app = FastAPI()
    app.include_router(router)
    return app


class ExampleWebApp(
    ASGIServerUnit,
    # ... more units
): ...


if __name__ == '__main__':
    ExampleWebApp.launch(asgi_server_factory=create_app)
# web_app.py

from functools import partial

from litestar import Litestar
from systempy.unit.asgi_server import ASGIServerUnit

from lib.create_asgi_server import create_asgi_server, create_settings

from .views import router


# Hint: use `functools.partial` to curry your `create_settings` if required
@create_asgi_server(partial(create_settings, "example_param_value"))
def create_app() -> Litestar:
    return Litestar(
        route_handlers=[router],
    )


class ExampleWebApp(
    ASGIServerUnit,
    # ... more units
): ...


if __name__ == '__main__':
    ExampleWebApp.launch(asgi_server_factory=create_app)