Skip to content
Merged
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
75 changes: 52 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,30 @@ To use `datastar-py`, import the SSE generator in your app and then use
it in your route handler:

```python
import asyncio

from datastar_py import ServerSentEventGenerator as SSE
from datastar_py.sse import SSE_HEADERS
from quart import Quart, make_response
from datetime import datetime

app = Quart(__name__)

# Import frontend library via Content Distribution Network, create targets for Server Sent Events
@app.route("/")
def index():
return '''
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@main/bundles/datastar.js"></script>
<span data-on-load="@get('/updates')" id="currentTime"></span><br><span data-text="$currentTime"></div>
'''

# ... various app setup.
# The example below is for the Quart framework, and is only using the event generation helpers.

@app.route("/updates")
async def updates():
async def time_updates():
while True:
yield SSE.patch_elements(
[f"""<span id="currentTime">{datetime.now().isoformat()}"""]
f"""<span id="currentTime">{datetime.now().isoformat()}"""
)
await asyncio.sleep(1)
yield SSE.patch_signals({"currentTime": f"{datetime.now().isoformat()}"})
Expand All @@ -47,8 +59,13 @@ async def updates():
response = await make_response(time_updates(), SSE_HEADERS)
response.timeout = None
return response


app.run()
```

The example above is for the Quart framework, and is only using the event generation helpers.

## Response Helpers

A datastar response consists of 0..N datastar events. There are response
Expand All @@ -60,32 +77,45 @@ e.g. `from datastar_py.quart import DatastarResponse` The containing functions
are not shown here, as they will differ per framework.

```python
# per framework Response import, eg:
# from datastar_py.fastapi import DatastarResponse
from datastar_py import ServerSentEventGenerator as SSE

# 0 events, a 204
return DatastarResponse()
@app.get("zero")
def zero_event():
return DatastarResponse()
# 1 event
return DatastarResponse(SSE.patch_elements("<div id='mydiv'></div>"))
@app.get("one")
def one_event():
return DatastarResponse(SSE.patch_elements("<div id='mydiv'></div>"))
# 2 events
return DatastarResponse([
SSE.patch_elements("<div id='mydiv'></div>"),
SSE.patch_signals({"mysignal": "myval"}),
])
@app.get("two")
def two_event():
return DatastarResponse([
SSE.patch_elements("<div id='mydiv'></div>"),
SSE.patch_signals({"mysignal": "myval"}),
])

# N events, a long lived stream (for all frameworks but sanic)

@app.get("/updates")
async def updates():
while True:
yield SSE.patch_elements("<div id='mydiv'></div>")
await asyncio.sleep(1)
return DatastarResponse(updates())
async def _():
while True:
yield SSE.patch_elements("<div id='mydiv'></div>")
await asyncio.sleep(1)
return DatastarResponse(_())

# A long lived stream for sanic
response = await datastar_respond(request)
# which is just a helper for the following
# response = await request.respond(DatastarResponse())
while True:
await response.send(SSE.patch_elements("<div id='mydiv'></div>"))
await asyncio.sleep(1)
@app.get("/updates")
async def updates(request):
response = await datastar_respond(request)
# which is just a helper for the following
# response = await request.respond(DatastarResponse())
while True:
await response.send(SSE.patch_elements("<div id='mydiv'></div>"))
await asyncio.sleep(1)
```

### Response Decorator
Expand All @@ -109,7 +139,7 @@ def my_route(request):
```

## Signal Helpers
The current state of the datastar signals is included by default in every
The current state of the datastar signals is included by default in every
datastar request. A helper is included to load those signals for each
framework. `read_signals`

Expand All @@ -132,9 +162,8 @@ from datastar_py import attribute_generator as data
# htpy
button(data.on("click", "console.log('clicked')").debounce(1000).stop)["My Button"]
# FastHTML
Button("My Button", **data.on("click", "console.log('clicked')").debounce(1000).stop)
# After next release of FastHTML you don't have to unpack the datastar helpers e.g.
Button("My Button", data.on("click", "console.log('clicked')").debounce(1000).stop)
Button(data.on("click", "console.log('clicked')").debounce(1000).stop)("My Button")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a new format? I don't see any documentation about it. (Nor for the dictionary form we used above for that matter.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Attributes first has actually been in FastHTML for a while, but I do not think it's documented anywhere. The only place I could find it was inside this idiomatic FastHTML app Jeremy Howard recently rewrote:
https://github.com/AnswerDotAI/fasthtml/blob/main/examples/adv_app.py
It's some people's preference because it looks closer to HTML, but the default is the pythonic way of passing everything as arguments to a FT function.
The dict method is demoed here:
https://fastht.ml/docs/explains/explaining_xt_components.html#attributes

# f-strings
f"<button {data.on("click", "console.log('clicked')").debounce(1000).stop}>My Button</button>"
# Jinja, but no editor completion :(
Expand All @@ -151,4 +180,4 @@ data = AttributeGenerator(alias="data-star-")
# htmy (htmy will transform _ into - unless the attribute starts with _, which will be stripped)
data = AttributeGenerator(alias="_data-")
html.button("My Button", **data.on("click", "console.log('clicked')").debounce("1s").stop)
```
```