Skip to content

Commit 3b6b1cf

Browse files
committed
Async/starlette docs.
1 parent e4a1a73 commit 3b6b1cf

File tree

8 files changed

+169
-7
lines changed

8 files changed

+169
-7
lines changed

docs/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ from a Python backend.
6161

6262
- **Works with existing Python web framework:** Works great with Django, Flask or any other Python web framework!
6363

64+
- **Full async support**: Render HTML fully async with ASGI frameworks such as Starlette and FastAPI.
65+
6466
- **Works great with htmx:** htpy makes for a great experience when writing server rendered partials/components.
6567

6668
- **Create reusable components:** Define components, snippets, complex layouts/pages as regular Python variables or functions.

docs/async.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Async rendering
2+
3+
htpy fully supports rendering HTML asynchronously. Combined with a async framework such as [Starlette/FastAPI](starlette.md), the entire web request can be processed async and the HTML page can be sent to the client incrementally as soon as it is ready.
4+
5+
# Async components
6+
7+
In addition to regular, [synchronous components](common-patterns.md), components can be defined as an `async def` coroutine. When rendering, htpy will `await` all async components:
8+
9+
```py
10+
from htpy import li
11+
import asyncio
12+
13+
async def get_text() -> str:
14+
return "hi!"
15+
16+
async def my_text() -> Renderable:
17+
results = await get_text()
18+
return p[results]
19+
```
20+
21+
## Async iterators
22+
23+
htpy will consume async iterators:
24+
25+
```py
26+
from htpy import ul, li
27+
28+
async def my_items() -> AsyncIterator[Renderable]:
29+
yield li["a"]
30+
yield li["b"]
31+
32+
def my_list() -> Renderable:
33+
return ul[my_items()]
34+
```
35+
36+
# Rendering async content
37+
38+
To retrieve results from async rendering, use the `aiter_chunks()` method. It returns an async iterator that yields the HTML document as bytes.
39+
40+
```py
41+
import asyncio
42+
43+
from htpy import p
44+
45+
my_paragraph = p["hello!"]
46+
47+
48+
async def main() -> None:
49+
async for chunk in my_paragraph.aiter_chunks():
50+
print(chunk)
51+
52+
53+
asyncio.run(main())
54+
55+
# output:
56+
# <p>
57+
# hello!
58+
# </p>
59+
```
60+
61+
The async iterator returned by `aiter_chunks()` can be passed to your web framework's streaming response class. See the [htpy Starlette docs](starlette.md) for more information how to integrate with Starlette.
62+
63+
!!! warning
64+
65+
Trying to get the string value of an async renderable like `str(element)` will result an exception:
66+
67+
```py
68+
Traceback (most recent call last):
69+
70+
File "/Users/andreas/code/htpy/examples/async_in_sync_context.py", line 7, in <module>
71+
str(div[my_async_component()])
72+
~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
73+
74+
TypeError: <coroutine object my_async_component at 0x103471010> is not a valid child element.
75+
Use the `.aiter_chunks()` method to retrieve the content: https://htpy.dev/async/
76+
```
77+
78+
Instead, use `aiter_chunks()`:
79+
80+
```py
81+
async for chunk in div[my_async_component()].aiter_chunks():
82+
print(chunk)
83+
```

docs/starlette.md

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,31 @@
1-
# Usage with Starlette
1+
# Usage with Starlette/FastAPI
22

33
htpy can be used with Starlette to generate HTML. Since FastAPI is built upon Starlette, htpy can also be used with FastAPI.
44

5-
To return HTML contents, pass a htpy element to Starlette's `HTMLResponse`:
5+
htpy supports full async rendering of all components. See [async rendering](async.md) for more information.
6+
7+
To return HTML contents, use the `HtpyResponse` class:
68

79
```py
810
from starlette.applications import Starlette
911
from starlette.requests import Request
10-
from starlette.responses import HTMLResponse
1112
from starlette.routing import Route
1213

13-
from htpy import h1
14+
from htpy import Element, h1
15+
from htpy.starlette import HtpyResponse
16+
17+
18+
async def index_component() -> Element:
19+
return h1["Hi Starlette!"]
1420

1521

16-
async def index(request: Request) -> HTMLResponse:
17-
return HTMLResponse(h1["Hi Starlette!"])
22+
async def index(request: Request) -> HtpyResponse:
23+
return HtpyResponse(index_component())
1824

1925

20-
app = Starlette(routes=[Route("/", index)])
26+
app = Starlette(
27+
routes=[
28+
Route("/", index),
29+
]
30+
)
2131
```

examples/aiter_chunks.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import asyncio
2+
3+
from htpy import p
4+
5+
my_paragraph = p["hello!"]
6+
7+
8+
async def main():
9+
async for chunk in my_paragraph.aiter_chunks():
10+
print(chunk)
11+
12+
13+
asyncio.run(main())

examples/async_in_sync_context.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from htpy import div
2+
3+
4+
async def my_async_component(): ...
5+
6+
7+
str(div[my_async_component()])

examples/async_iterators.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from collections.abc import AsyncIterator
2+
3+
from htpy import Element, li, ul
4+
5+
6+
async def my_items() -> AsyncIterator[Element]:
7+
yield li["a"]
8+
yield li["b"]
9+
10+
11+
def my_list() -> Element:
12+
return ul[my_items()]
13+
14+
15+
import asyncio # noqa: E402
16+
17+
print(my_list())
18+
19+
20+
async def main():
21+
async for chunk in my_list().aiter_chunks():
22+
print(chunk)
23+
24+
25+
asyncio.run(main())

examples/starlette_hello_world.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from starlette.applications import Starlette
2+
from starlette.requests import Request
3+
from starlette.routing import Route
4+
5+
from htpy import Renderable, h1
6+
from htpy.starlette import HtpyResponse
7+
8+
9+
async def index_component() -> Renderable:
10+
return h1["Hi Starlette!"]
11+
12+
13+
async def index(request: Request) -> HtpyResponse:
14+
return HtpyResponse(index_component())
15+
16+
17+
app = Starlette(
18+
routes=[
19+
Route("/", index),
20+
]
21+
)

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ nav:
1515
- common-patterns.md
1616
- static-typing.md
1717
- django.md
18+
- async.md
1819
- starlette.md
1920
- streaming.md
2021
- html2htpy.md

0 commit comments

Comments
 (0)