Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 36 additions & 3 deletions fasthx/htmy.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,30 @@ def page(
else self._make_error_render_function(error_component_selector),
)

async def render_component(self, component: h.Component, request: Request) -> str:
"""
Renders the given component.

This method is useful for rendering components directly, outside of the context of a route
(meaning no access to route parameters), for example in exception handlers.

The method adds all the usual data to the `htmy` rendering context, including the result of
all request processors. There is no access to route parameters though, so while `RouteParams`
will be in the context, it will be empty.

Arguments:
component: The component to render.
request: The current request.

Returns:
The rendered component.
"""
return await self.htmy.render(component, self._make_render_context(request, {}))

def _make_render_function(self, component_selector: HTMYComponentSelector[T]) -> HTMLRenderer[T]:
"""Creates a render function that uses the given component selector."""
"""
Creates a render function that uses the given component selector.
"""

async def render(result: T, *, context: dict[str, Any], request: Request) -> str:
component = (
Expand All @@ -193,7 +215,9 @@ async def render(result: T, *, context: dict[str, Any], request: Request) -> str
def _make_error_render_function(
self, component_selector: HTMYComponentSelector[Exception]
) -> HTMLRenderer[Exception]:
"""Creates an error renderer function that uses the given component selector."""
"""
Creates an error renderer function that uses the given component selector.
"""

async def render(result: Exception, *, context: dict[str, Any], request: Request) -> str:
component = (
Expand All @@ -206,7 +230,16 @@ async def render(result: Exception, *, context: dict[str, Any], request: Request
return render

def _make_render_context(self, request: Request, route_params: dict[str, Any]) -> h.Context:
"""Creates the HTMY rendering context."""
"""
Creates the `htmy` rendering context for the given request and route parameters.

Arguments:
request: The current request.
route_params: The route parameters.

Returns:
The `htmy` rendering context.
"""
# Add the current request to the context.
result = CurrentRequest.to_context(request)

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "fasthx"
version = "2.2.1"
version = "2.3.0"
description = "FastAPI server-side rendering with built-in HTMX support."
authors = ["Peter Volf <[email protected]>"]
readme = "README.md"
Expand Down
11 changes: 9 additions & 2 deletions tests/test_htmy.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest
from fastapi import FastAPI, Response
from fastapi import FastAPI, Request, Response
from fastapi.responses import HTMLResponse
from fastapi.testclient import TestClient

from fasthx.htmy import HTMY, ComponentHeader
Expand All @@ -14,7 +15,7 @@
users,
)
from .errors import RenderedError
from .htmy_components import HelloWorld, Profile, RequestProcessors, UserList
from .htmy_components import HelloWorld, Profile, RequestProcessors, UserList, UserListItem

billy_html_header = "<h1 >Billy Shears (active=True)</h1>"
billy_html_paragraph = "<p >Billy Shears (active=True)</p>"
Expand Down Expand Up @@ -117,6 +118,10 @@ def error_page(response: Response, kind: str | None = None) -> None:
def global_no_data() -> list[User]:
return []

@app.get("/render-component")
async def render_component(request: Request) -> HTMLResponse:
return HTMLResponse(await htmy.render_component(UserListItem(billy), request))

return app


Expand Down Expand Up @@ -191,6 +196,8 @@ def htmy_client(htmy_app: FastAPI) -> TestClient:
("/error-page/value-error", None, 500, "None", {}),
# Globally disabled data responses
("/global-no-data", None, 400, "", {}),
# Direct component rendering
("/render-component", None, 200, "<li >Billy Shears (active=True)</li>", {}),
),
)
def test_htmy(
Expand Down