Skip to content

Core Decorators

fasthx.hx(render, *, no_data=False, render_error=None)

Decorator that converts a FastAPI route's return value into HTML if the request was an HTMX one.

Parameters:

Name Type Description Default
render HTMLRenderer[T]

The render function converting the route's return value to HTML.

required
no_data bool

If set, the route will only accept HTMX requests.

False
render_error HTMLRenderer[Exception] | None

Optional render function for handling exceptions raised by the decorated route. If not None, it is expected to raise an error if the exception can not be rendered.

None

Returns:

Type Description
Callable[[MaybeAsyncFunc[P, T]], Callable[P, Coroutine[None, None, T | Response]]]

The rendered HTML for HTMX requests, otherwise the route's unchanged return value.

Source code in fasthx/core_decorators.py
def hx(
    render: HTMLRenderer[T],
    *,
    no_data: bool = False,
    render_error: HTMLRenderer[Exception] | None = None,
) -> Callable[[MaybeAsyncFunc[P, T]], Callable[P, Coroutine[None, None, T | Response]]]:
    """
    Decorator that converts a FastAPI route's return value into HTML if the request was
    an HTMX one.

    Arguments:
        render: The render function converting the route's return value to HTML.
        no_data: If set, the route will only accept HTMX requests.
        render_error: Optional render function for handling exceptions raised by the decorated route.
            If not `None`, it is expected to raise an error if the exception can not be rendered.

    Returns:
        The rendered HTML for HTMX requests, otherwise the route's unchanged return value.
    """

    def decorator(func: MaybeAsyncFunc[P, T]) -> Callable[P, Coroutine[None, None, T | Response]]:
        @wraps(func)  # type: ignore[arg-type]
        async def wrapper(
            *args: P.args, __hx_request: DependsHXRequest, **kwargs: P.kwargs
        ) -> T | Response:
            if no_data and __hx_request is None:
                raise HTTPException(
                    status.HTTP_400_BAD_REQUEST, "This route can only process HTMX requests."
                )

            try:
                result = await execute_maybe_sync_func(func, *args, **kwargs)
                renderer = render
            except Exception as e:
                # Reraise if not HX request, because the checks later don't differentiate between
                # error and non-error result objects.
                if render_error is None or __hx_request is None:
                    raise e

                result = e  # type: ignore[assignment]
                renderer = render_error  # type: ignore[assignment]

            if __hx_request is None or isinstance(result, Response):
                return result

            response = get_response(kwargs)
            rendered = await execute_maybe_sync_func(renderer, result, context=kwargs, request=__hx_request)

            return (
                HTMLResponse(
                    rendered,
                    # The default status code of the FastAPI Response dependency is None
                    # (not allowed by the typing but required for FastAPI).
                    status_code=getattr(response, "status_code", 200) or 200,
                    headers=getattr(response, "headers", None),
                    background=getattr(response, "background", None),
                )
                if isinstance(rendered, str)
                else rendered
            )

        return append_to_signature(
            wrapper,  # type: ignore[arg-type]
            inspect.Parameter(
                "__hx_request",
                inspect.Parameter.KEYWORD_ONLY,
                annotation=DependsHXRequest,
            ),
        )

    return decorator

fasthx.page(render, *, render_error=None)

Decorator that converts a FastAPI route's return value into HTML.

Parameters:

Name Type Description Default
render HTMLRenderer[T]

The render function converting the route's return value to HTML.

required
render_error HTMLRenderer[Exception] | None

Optional render function for handling exceptions raised by the decorated route. If not None, it is expected to raise an error if the exception can not be rendered.

None
Source code in fasthx/core_decorators.py
def page(
    render: HTMLRenderer[T],
    *,
    render_error: HTMLRenderer[Exception] | None = None,
) -> Callable[[MaybeAsyncFunc[P, T]], Callable[P, Coroutine[None, None, Response]]]:
    """
    Decorator that converts a FastAPI route's return value into HTML.

    Arguments:
        render: The render function converting the route's return value to HTML.
        render_error: Optional render function for handling exceptions raised by the decorated route.
            If not `None`, it is expected to raise an error if the exception can not be rendered.
    """

    def decorator(func: MaybeAsyncFunc[P, T]) -> Callable[P, Coroutine[None, None, Response]]:
        @wraps(func)  # type: ignore[arg-type]
        async def wrapper(
            *args: P.args, __page_request: DependsPageRequest, **kwargs: P.kwargs
        ) -> T | Response:
            try:
                result = await execute_maybe_sync_func(func, *args, **kwargs)
                renderer = render
            except Exception as e:
                if render_error is None:
                    raise e

                result = e  # type: ignore[assignment]
                renderer = render_error  # type: ignore[assignment]

            response = get_response(kwargs)
            rendered = await execute_maybe_sync_func(
                renderer, result, context=kwargs, request=__page_request
            )
            return (
                HTMLResponse(
                    rendered,
                    # The default status code of the FastAPI Response dependency is None
                    # (not allowed by the typing but required for FastAPI).
                    status_code=getattr(response, "status_code", 200) or 200,
                    headers=getattr(response, "headers", None),
                    background=getattr(response, "background", None),
                )
                if isinstance(rendered, str)
                else rendered
            )

        return append_to_signature(
            wrapper,  # type: ignore[arg-type]
            inspect.Parameter(
                "__page_request",
                inspect.Parameter.KEYWORD_ONLY,
                annotation=DependsPageRequest,
            ),
        )

    return decorator