test
\n' You can create new FTs by importing the new component from `fasthtml.components`. If the FT doesn’t exist within that module, FastHTML will create it. ``` python from fasthtml.components import Some_never_before_used_tag Some_never_before_used_tag() ``` ``` htmlThis is a hero statement
\nLet's do this!
\n' ## JS The [`Script`](https://docs.fastht.ml/api/xtend.html#script) function allows you to include JavaScript. You can use Python to generate parts of your JS or JSON like this: ``` python # In future snippets this import will not be shown, but is required from fasthtml.common import * app,rt = fast_app(hdrs=[Script(src="https://cdn.plot.ly/plotly-2.32.0.min.js")]) # `index` is a special function name which maps to the `/` route. @rt def index(): data = {'somedata':'fill me in…'} # `Titled` returns a title tag and an h1 tag with the 1st param, with remaining params as children in a `Main` parent. return Titled("Chart Demo", Div(id="myDiv"), Script(f"var data = {data}; Plotly.newPlot('myDiv', data);")) # In future snippets `serve() will not be shown, but is required serve() ``` Prefer Python whenever possible over JS. Never use React or shadcn. ## fast_app hdrs ``` python # In future snippets we'll skip showing the `fast_app` call if it has no params app, rt = fast_app( pico=False, # The Pico CSS framework is included by default, so pass `False` to disable it if needed. No other CSS frameworks are included. # These are added to the `head` part of the page for non-HTMX requests. hdrs=( Link(rel='stylesheet', href='assets/normalize.min.css', type='text/css'), Link(rel='stylesheet', href='assets/sakura.css', type='text/css'), Style("p {color: red;}"), # `MarkdownJS` and `HighlightJS` are available via concise functions MarkdownJS(), HighlightJS(langs=['python', 'javascript', 'html', 'css']), # by default, all standard static extensions are served statically from the web app dir, # which can be modified using e.g `static_path='public'` ) ) @rt def index(req): return Titled("Markdown rendering example", # This will be client-side rendered to HTML with highlight-js Div("*hi* there",cls="marked"), # This will be syntax highlighted Pre(Code("def foo(): pass"))) ``` ## Responses Routes can return various types: 1. FastTags or tuples of FastTags (automatically rendered to HTML) 2. Standard Starlette responses (used directly) 3. JSON-serializable types (returned as JSON in a plain text response) ``` python @rt("/{fname:path}.{ext:static}") async def serve_static_file(fname:str, ext:str): return FileResponse(f'public/{fname}.{ext}') app, rt = fast_app(hdrs=(MarkdownJS(), HighlightJS(langs=['python', 'javascript']))) @rt def index(): return Titled("Example", Div("*markdown* here", cls="marked"), Pre(Code("def foo(): pass"))) ``` Route functions can be used in attributes like `href` or `action` and will be converted to paths. Use `.to()` to generate paths with query parameters. ``` python @rt def profile(email:str): return fill_form(profile_form, profiles[email]) profile_form = Form(action=profile)( Label("Email", Input(name="email")), Button("Save", type="submit") ) user_profile_path = profile.to(email="user@example.com") # '/profile?email=user%40example.com' ``` ``` python from dataclasses import dataclass app,rt = fast_app() ``` When a route handler function is used as a fasttag attribute (such as `href`, `hx_get`, or `action`) it is converted to that route’s path. [`fill_form`](https://docs.fastht.ml/api/components.html#fill_form) is used to copy an object’s matching attrs into matching-name form fields. ``` python @dataclass class Profile: email:str; phone:str; age:int email = 'john@example.com' profiles = {email: Profile(email=email, phone='123456789', age=5)} @rt def profile(email:str): return fill_form(profile_form, profiles[email]) profile_form = Form(method="post", action=profile)( Fieldset( Label('Email', Input(name="email")), Label("Phone", Input(name="phone")), Label("Age", Input(name="age"))), Button("Save", type="submit")) ``` ## Testing We can use `TestClient` for testing. ``` python from starlette.testclient import TestClient ``` ``` python path = "/profile?email=john@example.com" client = TestClient(app) htmx_req = {'HX-Request':'1'} print(client.get(path, headers=htmx_req).text) ``` ## Form Handling and Data Binding When a dataclass, namedtuple, etc. is used as a type annotation, the form body will be unpacked into matching attribute names automatically. ``` python @rt def edit_profile(profile: Profile): profiles[email]=profile return RedirectResponse(url=path) new_data = dict(email='john@example.com', phone='7654321', age=25) print(client.post("/edit_profile", data=new_data, headers=htmx_req).text) ``` ## fasttag Rendering Rules The general rules for rendering children inside tuples or fasttag children are: - `__ft__` method will be called (for default components like `P`, `H2`, etc. or if you define your own components) - If you pass a string, it will be escaped - On other python objects, `str()` will be called If you want to include plain HTML tags directly into e.g. a `Div()` they will get escaped by default (as a security measure to avoid code injections). This can be avoided by using `Safe(...)`, e.g to show a data frame use `Div(NotStr(df.to_html()))`. ## Exceptions FastHTML allows customization of exception handlers. ``` python def not_found(req, exc): return Titled("404: I don't exist!") exception_handlers = {404: not_found} app, rt = fast_app(exception_handlers=exception_handlers) ``` ## Cookies We can set cookies using the [`cookie()`](https://docs.fastht.ml/api/core.html#cookie) function. ``` python @rt def setcook(): return P(f'Set'), cookie('mycookie', 'foobar') print(client.get('/setcook', headers=htmx_req).text) ```Set
``` python @rt def getcook(mycookie:str): return f'Got {mycookie}' # If handlers return text instead of FTs, then a plaintext response is automatically created print(client.get('/getcook').text) ``` Got foobar FastHTML provide access to Starlette’s request object automatically using special `request` parameter name (or any prefix of that name). ``` python @rt def headers(req): return req.headers['host'] ``` ## Request and Session Objects FastHTML provides access to Starlette’s session middleware automatically using the special `session` parameter name (or any prefix of that name). ``` python @rt def profile(req, sess, user_id: int=None): ip = req.client.host sess['last_visit'] = datetime.now().isoformat() visits = sess.setdefault('visit_count', 0) + 1 sess['visit_count'] = visits user = get_user(user_id or sess.get('user_id')) return Titled(f"Profile: {user.name}", P(f"Visits: {visits}"), P(f"IP: {ip}"), Button("Logout", hx_post=logout)) ``` Handler functions can return the [`HtmxResponseHeaders`](https://docs.fastht.ml/api/core.html#htmxresponseheaders) object to set HTMX-specific response headers. ``` python @rt def htmlredirect(app): return HtmxResponseHeaders(location="http://example.org") ``` ## APIRouter [`APIRouter`](https://docs.fastht.ml/api/core.html#apirouter) lets you organize routes across multiple files in a FastHTML app. ``` python # products.py ar = APIRouter() @ar def details(pid: int): return f"Here are the product details for ID: {pid}" @ar def all_products(req): return Div( Div( Button("Details",hx_get=details.to(pid=42),hx_target="#products_list",hx_swap="outerHTML",), ), id="products_list") ``` ``` python # main.py from products import ar,all_products app, rt = fast_app() ar.to_app(app) @rt def index(): return Div( "Products", hx_get=all_products, hx_swap="outerHTML") ``` ## Toasts Toasts can be of four types: - info - success - warning - error Toasts require the use of the `setup_toasts()` function, plus every handler needs: - The session argument - Must return FT components ``` python setup_toasts(app) @rt def toasting(session): add_toast(session, f"cooked", "info") add_toast(session, f"ready", "success") return Titled("toaster") ``` `setup_toasts(duration)` allows you to specify how long a toast will be visible before disappearing.10 seconds. Authentication and authorization are handled with Beforeware, which functions that run before the route handler is called. ## Auth ``` python def user_auth_before(req, sess): # `auth` key in the request scope is automatically provided to any handler which requests it and can not be injected auth = req.scope['auth'] = sess.get('auth', None) if not auth: return RedirectResponse('/login', status_code=303) beforeware = Beforeware( user_auth_before, skip=[r'/favicon\.ico', r'/static/.*', r'.*\.css', r'.*\.js', '/login', '/'] ) app, rt = fast_app(before=beforeware) ``` ## Server-Side Events (SSE) FastHTML supports the HTMX SSE extension. ``` python import random hdrs=(Script(src="https://unpkg.com/htmx-ext-sse@2.2.3/sse.js"),) app,rt = fast_app(hdrs=hdrs) @rt def index(): return Div(hx_ext="sse", sse_connect="/numstream", hx_swap="beforeend show:bottom", sse_swap="message") # `signal_shutdown()` gets an event that is set on shutdown shutdown_event = signal_shutdown() async def number_generator(): while not shutdown_event.is_set(): data = Article(random.randint(1, 100)) yield sse_message(data) @rt async def numstream(): return EventStream(number_generator()) ``` ## Websockets FastHTML provides useful tools for HTMX’s websockets extension. ``` python # These HTMX extensions are available through `exts`: # head-support preload class-tools loading-states multi-swap path-deps remove-me ws chunked-transfer app, rt = fast_app(exts='ws') def mk_inp(): return Input(id='msg', autofocus=True) @rt async def index(request): # `ws_send` tells HTMX to send a message to the nearest websocket based on the trigger for the form element cts = Div( Div(id='notifications'), Form(mk_inp(), id='form', ws_send=True), hx_ext='ws', ws_connect='/ws') return Titled('Websocket Test', cts) async def on_connect(send): await send(Div('Hello, you have connected', id="notifications")) async def on_disconnect(ws): print('Disconnected!') @app.ws('/ws', conn=on_connect, disconn=on_disconnect) async def ws(msg:str, send): # websocket hander returns/sends are treated as OOB swaps await send(Div('Hello ' + msg, id="notifications")) return Div('Goodbye ' + msg, id="notifications"), mk_inp() ``` ### Single File Uploads [`Form`](https://docs.fastht.ml/api/xtend.html#form) defaults to “multipart/form-data”. A Starlette UploadFile is passed to the handler. ``` python upload_dir = Path("filez") @rt def index(): return ( Form(hx_post=upload, hx_target="#result")( Input(type="file", name="file"), Button("Upload", type="submit")), Div(id="result") ) # Use `async` handlers where IO is used to avoid blocking other clients @rt async def upload(file: UploadFile): filebuffer = await file.read() (upload_dir / file.filename).write_bytes(filebuffer) return P('Size: ', file.size) ``` For multi-file, use `Input(..., multiple=True)`, and a type annotation of `list[UploadFile]` in the handler. ## Fastlite Fastlite and the MiniDataAPI specification it’s built on are a CRUD-oriented API for working with SQLite. APSW and apswutils is used to connect to SQLite, optimized for speed and clean error handling. ``` python from fastlite import * ``` ``` python db = database(':memory:') # or database('data/app.db') ``` Tables are normally constructed with classes, field types are specified as type hints. ``` python class Book: isbn: str; title: str; pages: int; userid: int # The transform arg instructs fastlite to change the db schema when fields change. # Create only creates a table if the table doesn't exist. books = db.create(Book, pk='isbn', transform=True) class User: id: int; name: str; active: bool = True # If no pk is provided, id is used as the primary key. users = db.create(User, transform=True) users ```