Try an interactive version of this dialog: Sign up at solve.it.com, click Upload, and pass this URL.
from fasthtml.common import *
from fasthtml.jupyter import HTMX, JupyUvi
# Auth beforeware - redirects to /login if not authenticated
def before(req, sess):
auth = req.scope['auth'] = sess.get('auth', None)
if not auth: return RedirectResponse('/login', status_code=303)
bware = Beforeware(before, skip=['/login', '/'])
app = FastHTML(before=bware)
rt = app.route
srv = JupyUvi(app)
@rt('/login')
def get(): return Titled("Login", Form(
Input(id='username', placeholder='Username'),
Button('Login'), action='/login', method='post'))
@rt('/login')
def post(username: str, sess):
sess['auth'] = username
return RedirectResponse('/', status_code=303)
@rt('/')
def home(): return Titled("Home", P("Welcome! You are logged in."))
# A component that requires auth context
def UserGreeting(name):
return Div(H2(f"Hello, {name}!"), P("This is your dashboard."), cls="p-4 bg-blue-100 rounded")
# This won't work - HTMX creates a fresh client with no auth
HTMX(UserGreeting("Alice"), app=app)
The Solution
Add an optional client parameter to HTMX. If provided, reuse that client instead of creating a new one. This lets you log in once and reuse the authenticated client:
from html import escape
from fasthtml.jupyter import unqid
from starlette.testclient import TestClient
from IPython.display import HTML
def HTMX(path="/", host='localhost', app=None, port=8000, height="auto",
link=False, iframe=True, client=None):
"An iframe which displays the HTMX application in a notebook. Pass `client` to reuse a TestClient with persistent session."
if isinstance(height, int): height = f"{height}px"
scr = """{
let frame = this;
window.addEventListener('message', function(e) {
if (e.source !== frame.contentWindow) return;
if (e.data.height) frame.style.height = (e.data.height+1) + 'px';
}, false);
}""" if height == "auto" else ""
proto = 'http' if host=='localhost' else 'https'
fullpath = f"{proto}://{host}:{port}{path}" if host else path
src = f'src="{fullpath}"'
if link: display(HTML(f'<a href="{fullpath}" target="_blank">Open in new tab</a>'))
if isinstance(path, (FT,tuple,Safe)):
assert app, 'Need an app to render a component'
route = f'/{unqid()}'
res = path
app.get(route)(lambda: res)
# KEY CHANGE: Use provided client or create new one
cli = client if client is not None else TestClient(app)
page = cli.get(route).text
src = f'srcdoc="{escape(page)}"'
if iframe:
return HTML(f'<iframe {src} style="width: 100%; height: {height}; border: none;" onload="{scr}" ' + """allow="accelerometer; autoplay; camera; clipboard-read; clipboard-write; display-capture; encrypted-media; fullscreen; gamepad; geolocation; gyroscope; hid; identity-credentials-get; idle-detection; magnetometer; microphone; midi; payment; picture-in-picture; publickey-credentials-get; screen-wake-lock; serial; usb; web-share; xr-spatial-tracking"></iframe> """)
Summary
The change is minimal - just one line in HTMX:
Copied!
# Before (always creates new client):
cli = TestClient(app)
# After (reuses provided client if given):
cli = client if client is not None else TestClient(app)
This enables a workflow where you log in once and preview authenticated components throughout your notebook session.