-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
204 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
# Streaming of contents | ||
|
||
Internally, htpy is built with generators. Most of the time, you would render | ||
the full page with `str()`, but htpy can also incrementally generate pages which | ||
can then be streamed to the browser. If your page uses a database or other | ||
services to retrieve data, you can sending the first part of the page to the | ||
client while the page is being generated. | ||
|
||
!!! note | ||
|
||
Streaming requires a bit of discipline and care to get right. You need to | ||
ensure to avoid doing too much work up front and use lazy constructs such as | ||
generators and callables. Most of the time, rendering the page without | ||
streaming will be the easiest way to get going. Streaming can give you | ||
improved user experience from faster pages/rendering. | ||
|
||
This video shows what it looks like in the browser to generate a HTML table with [Django StreamingHttpResponse](https://docs.djangoproject.com/en/5.0/ref/request-response/#django.http.StreamingHttpResponse) ([source code](https://github.com/pelme/htpy/blob/main/examples/djangoproject/stream/views.py)): | ||
<video width="500" controls loop > | ||
<source src="/assets/stream.webm" type="video/webm"> | ||
</video> | ||
|
||
This example simulates a (very) slow fetch of data and shows the power of | ||
streaming: The browser loads CSS and gradually shows the contents. By loading | ||
CSS files in the `<head>` tag before dynamic content, the browser can start | ||
working on loading the CSS and styling the page while the server keeps | ||
generating the rest of the page. | ||
|
||
## Using generators and callables as children | ||
|
||
Django's querysets are [lazily | ||
evaluated](https://docs.djangoproject.com/en/5.0/topics/db/queries/#querysets-are-lazy). | ||
They will not execute a database query before their value is actually needed. | ||
|
||
This example shows how this property of Django querysets can be used to create a | ||
page that streams objects: | ||
```python | ||
from django.http import StreamingHttpResponse | ||
from htpy import ul, li | ||
|
||
from myapp.models import Article | ||
|
||
def article_list(request): | ||
return StreamingHttpResponse(ul[ | ||
(li[article.title] for article in Article.objects.all()) | ||
]) | ||
``` | ||
|
||
|
||
## Using callables to delay evalutation | ||
|
||
Pass a callable that does not accept any arguements as child to delay the | ||
evaluation. | ||
|
||
This example shows how the page starts rendering and outputs the `<h1>` tag and | ||
then calls `calculate_magic_number`. | ||
|
||
```python | ||
import time | ||
from htpy import div, h1 | ||
|
||
def calculate_magic_number() -> str: | ||
time.sleep(1) | ||
print(" (running the complex calculation...)") | ||
return "42" | ||
|
||
element = div[ | ||
h1["Welcome to my page"], | ||
"The magic number is ", | ||
calculate_magic_number, | ||
] | ||
|
||
for chunk in element: | ||
print(chunk) | ||
``` | ||
|
||
Output: | ||
``` | ||
<div> | ||
<h1> | ||
Welcome to my page | ||
</h1> | ||
The magic number is | ||
42 # <-- Appears after 3 seconds | ||
</div> | ||
``` | ||
|
||
You may use `lambda` to create a function without arguments to make a an expression lazy: | ||
|
||
```py | ||
from htpy import div, h1 | ||
|
||
|
||
def fib(n: int) -> int: | ||
if n == 0: | ||
return 0 | ||
elif n == 1: | ||
return 1 | ||
else: | ||
return fib(n - 1) + fib(n - 2) | ||
|
||
|
||
print( | ||
div[ | ||
h1["Fibonacci!"], | ||
"fib(20)=", | ||
lambda: str(fib(20)), | ||
] | ||
) | ||
# output: <div><h1>Fibonacci!</h1>fib(12)=6765</div> | ||
|
||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,47 @@ | ||
import random | ||
import time | ||
from collections.abc import Iterable | ||
from dataclasses import dataclass | ||
|
||
from django.http import HttpRequest, StreamingHttpResponse | ||
|
||
from .components import streaming_table_page | ||
from .items import generate_items | ||
from htpy import Element, body, h1, head, html, link, table, td, th, title, tr | ||
|
||
|
||
@dataclass | ||
class Item: | ||
count: int | ||
color: str | ||
|
||
|
||
def generate_items() -> Iterable[Item]: | ||
for x in range(10): | ||
yield Item(x + 1, random.choice(["honeydew", "azure", "ivory", "mistyrose", "aliceblue"])) | ||
time.sleep(1) | ||
|
||
|
||
def streaming_table_page(items: Iterable[Item]) -> Element: | ||
return html[ | ||
head[ | ||
title["Stream example"], | ||
link( | ||
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css", | ||
rel="stylesheet", | ||
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN", | ||
crossorigin="anonymous", | ||
), | ||
], | ||
body[ | ||
h1["Stream example"], | ||
table(".table")[ | ||
tr[th["Table row #"],], | ||
( | ||
tr[td(style=f"background-color: {item.color}")[f"#{item.count}"],] | ||
for item in items | ||
), | ||
], | ||
], | ||
] | ||
|
||
|
||
def stream(request: HttpRequest) -> StreamingHttpResponse: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import time | ||
|
||
from htpy import div, h1 | ||
|
||
|
||
def calculate_magic_number() -> str: | ||
time.sleep(3) | ||
return "42" | ||
|
||
|
||
element = div[ | ||
h1["Welcome to my page"], | ||
"The magic number is ", | ||
calculate_magic_number, | ||
] | ||
|
||
for chunk in element: | ||
print(chunk) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
from htpy import div, h1 | ||
|
||
|
||
def fib(n: int) -> int: | ||
if n == 0: | ||
return 0 | ||
elif n == 1: | ||
return 1 | ||
else: | ||
return fib(n - 1) + fib(n - 2) | ||
|
||
|
||
print( | ||
div[ | ||
h1["Fibonacci!"], | ||
"fib(20)=", | ||
lambda: str(fib(20)), | ||
] | ||
) | ||
# output: <div><h1>Fibonacci!</h1>fib(12)=6765</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters