Skip to content

fastapi-reloader API Reference

fastapi_reloader

auto_refresh_middleware module-attribute

auto_refresh_middleware = UniversalMiddleware(
    patch_for_auto_reloading
)

This middleware combines the two middlewares above to enable the full functionality of this package.

html_injection_middleware module-attribute

html_injection_middleware = UniversalMiddleware(
    lambda app: BaseHTTPMiddleware(
        app, _injection_http_middleware
    )
)

This middleware injects the HMR client script into HTML responses.

reloader_route_middleware module-attribute

reloader_route_middleware = UniversalMiddleware(
    _wrap_asgi_app
)

This middleware wraps the app with a FastAPI app that handles reload signals.

__all__ module-attribute

__all__ = [
    "auto_refresh_middleware",
    "html_injection_middleware",
    "patch_for_auto_reloading",
    "reloader_route_middleware",
    "send_reload_signal",
]

send_reload_signal

send_reload_signal()

Broadcast a reload signal to all connected clients and break their long-polling connections.

Source code in packages/fastapi-reloader/fastapi_reloader/core.py
def send_reload_signal():
    """Broadcast a reload signal to all connected clients and break their long-polling connections."""
    for subscribers in requests.values():
        for queue in subscribers:
            queue.put_nowait(1)

patch_for_auto_reloading

patch_for_auto_reloading(app: ASGIApp)
Source code in packages/fastapi-reloader/fastapi_reloader/patcher.py
def patch_for_auto_reloading(app: ASGIApp):  # this function is preserved for backward compatibility
    if isinstance(app, Starlette):  # both FastAPI and Starlette have user_middleware attribute
        new_app = copy(app)
        new_app.user_middleware = [*app.user_middleware, html_injection_middleware]  # before compression middlewares

        # OTEL patches the app's build_middleware_stack method and keep a reference to the original build_middleware_stack.
        # But both methods are bound to the original app instance, so we need to rebind them to the new app instance.
        # The following loop generically rebinds all these methods, preventing potential issues caused by similar patches.
        for i in dir(new_app):
            if ismethod(method := getattr(new_app, i)) and method.__self__ is app:
                setattr(new_app, i, MethodType(method.__func__, new_app))

        return _wrap_asgi_app(new_app)

    new_app = _wrap_asgi_app(app)
    new_app.user_middleware.append(html_injection_middleware)  # the last middleware is the first one to be called

    return new_app

core

get_id module-attribute

get_id = __next__

requests module-attribute

requests: dict[int, list[Queue[Literal[0, 1]]]] = (
    defaultdict(list)
)

reload_router module-attribute

reload_router = APIRouter(
    prefix="/---fastapi-reloader---", tags=["hmr"]
)

send_reload_signal

send_reload_signal()

Broadcast a reload signal to all connected clients and break their long-polling connections.

Source code in packages/fastapi-reloader/fastapi_reloader/core.py
def send_reload_signal():
    """Broadcast a reload signal to all connected clients and break their long-polling connections."""
    for subscribers in requests.values():
        for queue in subscribers:
            queue.put_nowait(1)

heartbeat async

heartbeat()
Source code in packages/fastapi-reloader/fastapi_reloader/core.py
@reload_router.head("")
async def heartbeat():
    return Response(status_code=202)

subscribe async

subscribe()
Source code in packages/fastapi-reloader/fastapi_reloader/core.py
@reload_router.get("")
async def subscribe():
    async def event_generator():
        key = get_id()
        queue = Queue[Literal[0, 1]]()

        stopped = False

        async def heartbeat():
            while not stopped:
                queue.put_nowait(0)
                await sleep(1)

        requests[key].append(queue)

        heartbeat_future = ensure_future(heartbeat())

        try:
            yield "0\n"
            while True:
                value = await queue.get()
                yield f"{value}\n"
                if value == 1:
                    break
        finally:
            heartbeat_future.cancel()
            requests[key].remove(queue)

    return StreamingResponse(event_generator(), 201, media_type="text/plain")

patcher

INJECTION module-attribute

INJECTION = encode()

FLAG module-attribute

FLAG = ' fastapi-reloader-injected '

T module-attribute

T = TypeVar('T', bound=ASGIApp)

html_injection_middleware module-attribute

html_injection_middleware = UniversalMiddleware(
    lambda app: BaseHTTPMiddleware(
        app, _injection_http_middleware
    )
)

This middleware injects the HMR client script into HTML responses.

reloader_route_middleware module-attribute

reloader_route_middleware = UniversalMiddleware(
    _wrap_asgi_app
)

This middleware wraps the app with a FastAPI app that handles reload signals.

auto_refresh_middleware module-attribute

auto_refresh_middleware = UniversalMiddleware(
    patch_for_auto_reloading
)

This middleware combines the two middlewares above to enable the full functionality of this package.

UniversalMiddleware

Bases: Middleware, Generic[T]

Adapt an ASGI middleware so it can serve both Starlette/FastAPI middleware slots and plain ASGI usage.

Source code in packages/fastapi-reloader/fastapi_reloader/patcher.py
class UniversalMiddleware(Middleware, Generic[T]):  # noqa: UP046
    """Adapt an ASGI middleware so it can serve both Starlette/FastAPI middleware slots and plain ASGI usage."""

    def __init__(self, asgi_middleware: Callable[[ASGIApp], T]):
        self.fn = asgi_middleware
        super().__init__(self)

    def __call__(self, app):
        return self.fn(app)
fn instance-attribute
fn = asgi_middleware
__init__
__init__(asgi_middleware: Callable[[ASGIApp], T])
Source code in packages/fastapi-reloader/fastapi_reloader/patcher.py
def __init__(self, asgi_middleware: Callable[[ASGIApp], T]):
    self.fn = asgi_middleware
    super().__init__(self)
__call__
__call__(app)
Source code in packages/fastapi-reloader/fastapi_reloader/patcher.py
def __call__(self, app):
    return self.fn(app)

is_streaming_response

is_streaming_response(
    response: Response,
) -> TypeGuard[StreamingResponse]
Source code in packages/fastapi-reloader/fastapi_reloader/patcher.py
def is_streaming_response(response: Response) -> TypeGuard[StreamingResponse]:
    # In fact, it may not be a Starlette's StreamingResponse, but an internal one with the same interface
    return hasattr(response, "body_iterator")

_injection_http_middleware async

_injection_http_middleware(
    request: Request,
    call_next: Callable[[Request], Awaitable[Response]],
)
Source code in packages/fastapi-reloader/fastapi_reloader/patcher.py
async def _injection_http_middleware(request: Request, call_next: Callable[[Request], Awaitable[Response]]):
    res = await call_next(request)

    if request.scope.get(FLAG) or request.method != "GET" or "html" not in (res.headers.get("content-type", "")) or res.headers.get("content-encoding", "identity") != "identity":
        return res

    request.scope[FLAG] = True

    async def response():
        if is_streaming_response(res):
            async for chunk in res.body_iterator:
                yield chunk
        else:
            yield res.body

        yield INJECTION

    headers = {k: v for k, v in res.headers.items() if k.lower() not in {"content-length", "transfer-encoding"}}

    return StreamingResponse(response(), res.status_code, headers, res.media_type)

_wrap_asgi_app

_wrap_asgi_app(app: ASGIApp)
Source code in packages/fastapi-reloader/fastapi_reloader/patcher.py
def _wrap_asgi_app(app: ASGIApp):
    @asynccontextmanager
    async def lifespan(_):
        async with LifespanManager(app, inf, inf):
            yield

    new_app = FastAPI(openapi_url=None, lifespan=lifespan)
    new_app.include_router(reload_router)
    new_app.mount("/", app)

    return new_app

patch_for_auto_reloading

patch_for_auto_reloading(app: ASGIApp)
Source code in packages/fastapi-reloader/fastapi_reloader/patcher.py
def patch_for_auto_reloading(app: ASGIApp):  # this function is preserved for backward compatibility
    if isinstance(app, Starlette):  # both FastAPI and Starlette have user_middleware attribute
        new_app = copy(app)
        new_app.user_middleware = [*app.user_middleware, html_injection_middleware]  # before compression middlewares

        # OTEL patches the app's build_middleware_stack method and keep a reference to the original build_middleware_stack.
        # But both methods are bound to the original app instance, so we need to rebind them to the new app instance.
        # The following loop generically rebinds all these methods, preventing potential issues caused by similar patches.
        for i in dir(new_app):
            if ismethod(method := getattr(new_app, i)) and method.__self__ is app:
                setattr(new_app, i, MethodType(method.__func__, new_app))

        return _wrap_asgi_app(new_app)

    new_app = _wrap_asgi_app(app)
    new_app.user_middleware.append(html_injection_middleware)  # the last middleware is the first one to be called

    return new_app