Spaces:
Sleeping
Sleeping
| <project title="FastHTML" summary='FastHTML is a python library which brings together Starlette, Uvicorn, HTMX, and fastcore's `FT` "FastTags" into a library for creating server-rendered hypermedia applications. The `FastHTML` class itself inherits from `Starlette`, and adds decorator-based routing with many additions, Beforeware, automatic `FT` to HTML rendering, and much more.'>Things to remember when writing FastHTML apps: | |
| - Although parts of its API are inspired by FastAPI, it is *not* compatible with FastAPI syntax and is not targeted at creating API services | |
| - FastHTML includes support for Pico CSS and the fastlite sqlite library, although using both are optional; sqlalchemy can be used directly or via the fastsql library, and any CSS framework can be used. Support for the Surreal and css-scope-inline libraries are also included, but both are optional | |
| - FastHTML is compatible with JS-native web components and any vanilla JS library, but not with React, Vue, or Svelte | |
| - Use `serve()` for running uvicorn (`if __name__ == "__main__"` is not needed since it's automatic) | |
| - When a title is needed with a response, use `Titled`; note that that already wraps children in `Container`, and already includes both the meta title as well as the H1 element.<docs><doc title="FastHTML concise guide" desc="A brief overview of idiomatic FastHTML apps"># Concise reference | |
| ## About FastHTML | |
| ``` python | |
| from fasthtml.common import * | |
| ``` | |
| FastHTML is a python library which brings together Starlette, Uvicorn, | |
| HTMX, and fastcore’s `FT` “FastTags” into a library for creating | |
| server-rendered hypermedia applications. The | |
| [`FastHTML`](https://www.fastht.ml/docs/api/core.html#fasthtml) class | |
| itself inherits from `Starlette`, and adds decorator-based routing with | |
| many additions, Beforeware, automatic `FT` to HTML rendering, and much | |
| more. | |
| Things to remember when writing FastHTML apps: | |
| - *Not* compatible with FastAPI syntax; FastHTML is for HTML-first apps, | |
| not API services (although it can implement APIs too) | |
| - FastHTML includes support for Pico CSS and the fastlite sqlite | |
| library, although using both are optional; sqlalchemy can be used | |
| directly or via the fastsql library, and any CSS framework can be | |
| used. MonsterUI is a richer FastHTML-first component framework with | |
| similar capabilities to shadcn | |
| - FastHTML is compatible with JS-native web components and any vanilla | |
| JS library, but not with React, Vue, or Svelte | |
| - Use [`serve()`](https://www.fastht.ml/docs/api/core.html#serve) for | |
| running uvicorn (`if __name__ == "__main__"` is not needed since it’s | |
| automatic) | |
| - When a title is needed with a response, use | |
| [`Titled`](https://www.fastht.ml/docs/api/xtend.html#titled); note | |
| that that already wraps children in | |
| [`Container`](https://www.fastht.ml/docs/api/pico.html#container), and | |
| already includes both the meta title as well as the H1 element. | |
| ## Minimal App | |
| The code examples here use fast.ai style: prefer ternary op, 1-line | |
| docstring, minimize vertical space, etc. (Normally fast.ai style uses | |
| few if any comments, but they’re added here as documentation.) | |
| A minimal FastHTML app looks something like this: | |
| ``` python | |
| # Meta-package with all key symbols from FastHTML and Starlette. Import it like this at the start of every FastHTML app. | |
| from fasthtml.common import * | |
| # The FastHTML app object and shortcut to `app.route` | |
| app,rt = fast_app() | |
| # Enums constrain the values accepted for a route parameter | |
| name = str_enum('names', 'Alice', 'Bev', 'Charlie') | |
| # Passing a path to `rt` is optional. If not passed (recommended), the function name is the route ('/foo') | |
| # Both GET and POST HTTP methods are handled by default | |
| # Type-annotated params are passed as query params (recommended) unless a path param is defined (which it isn't here) | |
| @rt | |
| def foo(nm: name): | |
| # `Title` and `P` here are FastTags: direct m-expression mappings of HTML tags to Python functions with positional and named parameters. All standard HTML tags are included in the common wildcard import. | |
| # When a tuple is returned, this returns concatenated HTML partials. HTMX by default will use a title HTML partial to set the current page name. HEAD tags (e.g. Meta, Link, etc) in the returned tuple are automatically placed in HEAD; everything else is placed in BODY. | |
| # FastHTML will automatically return a complete HTML document with appropriate headers if a normal HTTP request is received. For an HTMX request, however, just the partials are returned. | |
| return Title("FastHTML"), H1("My web app"), P(f"Hello, {name}!") | |
| # By default `serve` runs uvicorn on port 5001. Never write `if __name__ == "__main__"` since `serve` checks it internally. | |
| serve() | |
| ``` | |
| To run this web app: | |
| ``` bash | |
| python main.py # access via localhost:5001 | |
| ``` | |
| ## FastTags (aka FT Components or FTs) | |
| FTs are m-expressions plus simple sugar. Positional params map to | |
| children. Named parameters map to attributes. Aliases must be used for | |
| Python reserved words. | |
| ``` python | |
| tags = Title("FastHTML"), H1("My web app"), P(f"Let's do this!", cls="myclass") | |
| tags | |
| ``` | |
| (title(('FastHTML',),{}), | |
| h1(('My web app',),{}), | |
| p(("Let's do this!",),{'class': 'myclass'})) | |
| This example shows key aspects of how FTs handle attributes: | |
| ``` python | |
| Label( | |
| "Choose an option", | |
| Select( | |
| Option("one", value="1", selected=True), # True renders just the attribute name | |
| Option("two", value=2, selected=False), # Non-string values are converted to strings. False omits the attribute entirely | |
| cls="selector", id="counter", # 'cls' becomes 'class' | |
| **{'@click':"alert('Clicked');"}, # Dict unpacking for attributes with special chars | |
| ), | |
| _for="counter", # '_for' becomes 'for' (can also use 'fr') | |
| ) | |
| ``` | |
| Classes with `__ft__` defined are rendered using that method. | |
| ``` python | |
| class FtTest: | |
| def __ft__(self): return P('test') | |
| to_xml(FtTest()) | |
| ``` | |
| '<p>test</p>\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() | |
| ``` | |
| ``` html | |
| <some-never-before-used-tag></some-never-before-used-tag> | |
| ``` | |
| FTs can be combined by defining them as a function. | |
| ``` python | |
| def Hero(title, statement): return Div(H1(title),P(statement), cls="hero") | |
| to_xml(Hero("Hello World", "This is a hero statement")) | |
| ``` | |
| '<div class="hero">\n <h1>Hello World</h1>\n <p>This is a hero statement</p>\n</div>\n' | |
| When handling a response, FastHTML will automatically render FTs using | |
| the `to_xml` function. | |
| ``` python | |
| to_xml(tags) | |
| ``` | |
| '<title>FastHTML</title>\n<h1>My web app</h1>\n<p class="myclass">Let's do this!</p>\n' | |
| ## JS | |
| The [`Script`](https://www.fastht.ml/docs/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="[email protected]") # '/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://www.fastht.ml/docs/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 = '[email protected]' | |
| 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 = "/[email protected]" | |
| client = TestClient(app) | |
| htmx_req = {'HX-Request':'1'} | |
| print(client.get(path, headers=htmx_req).text) | |
| ``` | |
| <form enctype="multipart/form-data" method="post" action="/profile"><fieldset><label>Email <input name="email" value="[email protected]"> | |
| </label><label>Phone <input name="phone" value="123456789"> | |
| </label><label>Age <input name="age" value="5"> | |
| </label></fieldset><button type="submit">Save</button></form> | |
| ## 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='[email protected]', phone='7654321', age=25) | |
| print(client.post("/edit_profile", data=new_data, headers=htmx_req).text) | |
| ``` | |
| <form enctype="multipart/form-data" method="post" action="/profile"><fieldset><label>Email <input name="email" value="[email protected]"> | |
| </label><label>Phone <input name="phone" value="7654321"> | |
| </label><label>Age <input name="age" value="25"> | |
| </label></fieldset><button type="submit">Save</button></form> | |
| ## 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://www.fastht.ml/docs/api/core.html#cookie) function. | |
| ``` python | |
| @rt | |
| def setcook(): return P(f'Set'), cookie('mycookie', 'foobar') | |
| print(client.get('/setcook', headers=htmx_req).text) | |
| ``` | |
| <p>Set</p> | |
| ``` 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://www.fastht.ml/docs/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://www.fastht.ml/docs/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/[email protected]/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() | |
| ``` | |
| Sample chatbot that uses FastHTML’s | |
| [`setup_ws`](https://www.fastht.ml/docs/api/core.html#setup_ws) | |
| function: | |
| ``` py | |
| app = FastHTML(exts='ws') | |
| rt = app.route | |
| msgs = [] | |
| @rt('/') | |
| def home(): | |
| return Div(hx_ext='ws', ws_connect='/ws')( | |
| Div(Ul(*[Li(m) for m in msgs], id='msg-list')), | |
| Form(Input(id='msg'), id='form', ws_send=True) | |
| ) | |
| async def ws(msg:str): | |
| msgs.append(msg) | |
| await send(Ul(*[Li(m) for m in msgs], id='msg-list')) | |
| send = setup_ws(app, ws) | |
| ``` | |
| ### Single File Uploads | |
| [`Form`](https://www.fastht.ml/docs/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 | |
| ``` | |
| <Table user (id, name, active)> | |
| ### Fastlite CRUD operations | |
| Every operation in fastlite returns a full superset of dataclass | |
| functionality. | |
| ``` python | |
| user = users.insert(name='Alex',active=False) | |
| user | |
| ``` | |
| User(id=1, name='Alex', active=0) | |
| ``` python | |
| # List all records | |
| users() | |
| ``` | |
| [User(id=1, name='Alex', active=0)] | |
| ``` python | |
| # Limit, offset, and order results: | |
| users(order_by='name', limit=2, offset=1) | |
| # Filter on the results | |
| users(where="name='Alex'") | |
| # Placeholder for avoiding injection attacks | |
| users("name=?", ('Alex',)) | |
| # A single record by pk | |
| users[user.id] | |
| ``` | |
| User(id=1, name='Alex', active=0) | |
| Test if a record exists by using `in` keyword on primary key: | |
| ``` python | |
| 1 in users | |
| ``` | |
| True | |
| Updates (which take a dict or a typed object) return the updated record. | |
| ``` python | |
| user.name='Lauren' | |
| user.active=True | |
| users.update(user) | |
| ``` | |
| User(id=1, name='Lauren', active=1) | |
| `.xtra()` to automatically constrain queries, updates, and inserts from | |
| there on: | |
| ``` python | |
| users.xtra(active=True) | |
| users() | |
| ``` | |
| [User(id=1, name='Lauren', active=1)] | |
| Deleting by pk: | |
| ``` python | |
| users.delete(user.id) | |
| ``` | |
| <Table user (id, name, active)> | |
| NotFoundError is raised by pk `[]`, updates, and deletes. | |
| ``` python | |
| try: users['Amy'] | |
| except NotFoundError: print('User not found') | |
| ``` | |
| User not found | |
| ## MonsterUI | |
| MonsterUI is a shadcn-like component library for FastHTML. It adds the | |
| Tailwind-based libraries FrankenUI and DaisyUI to FastHTML, as well as | |
| Python’s mistletoe for Markdown, HighlightJS for code highlighting, and | |
| Katex for latex support, following semantic HTML patterns when possible. | |
| It is recommended for when you wish to go beyond the basics provided by | |
| FastHTML’s built-in pico support. | |
| A minimal app: | |
| ``` python | |
| from fasthtml.common import * | |
| from monsterui.all import * | |
| app, rt = fast_app(hdrs=Theme.blue.headers(highlightjs=True)) # Use MonsterUI blue theme and highlight code in markdown | |
| @rt | |
| def index(): | |
| socials = (('github','https://github.com/AnswerDotAI/MonsterUI'),) | |
| return Titled("App", | |
| Card( | |
| P("App", cls=TextPresets.muted_sm), | |
| # LabelInput, DivLAigned, and UkIconLink are non-semantic MonsterUI FT Components, | |
| LabelInput('Email', type='email', required=True), | |
| footer=DivLAligned(*[UkIconLink(icon,href=url) for icon,url in socials]))) | |
| ``` | |
| MonsterUI recommendations: | |
| - Use defaults as much as possible, for example | |
| [`Container`](https://www.fastht.ml/docs/api/pico.html#container) in | |
| monsterui already has defaults for margins | |
| - Use `*T` for button styling consistency, for example | |
| `cls=ButtonT.destructive` for a red delete button or | |
| `cls=ButtonT.primary` for a CTA button | |
| - Use `Label*` functions for forms as much as possible | |
| (e.g. `LabelInput`, `LabelRange`) which creates and links both the | |
| `FormLabel` and user input appropriately to avoid boiler plate. | |
| Flex Layout Elements (such as `DivLAligned` and `DivFullySpaced`) can be | |
| used to create layouts concisely | |
| ``` python | |
| def TeamCard(name, role, location="Remote"): | |
| icons = ("mail", "linkedin", "github") | |
| return Card( | |
| DivLAligned( | |
| DiceBearAvatar(name, h=24, w=24), | |
| Div(H3(name), P(role))), | |
| footer=DivFullySpaced( | |
| DivHStacked(UkIcon("map-pin", height=16), P(location)), | |
| DivHStacked(*(UkIconLink(icon, height=16) for icon in icons)))) | |
| ``` | |
| Forms are styled and spaced for you without significant additional | |
| classes. | |
| ``` python | |
| def MonsterForm(): | |
| relationship = ["Parent",'Sibling', "Friend", "Spouse", "Significant Other", "Relative", "Child", "Other"] | |
| return Div( | |
| DivCentered( | |
| H3("Emergency Contact Form"), | |
| P("Please fill out the form completely", cls=TextPresets.muted_sm)), | |
| Form( | |
| Grid(LabelInput("Name",id='name'),LabelInput("Email", id='email')), | |
| H3("Relationship to patient"), | |
| Grid(*[LabelCheckboxX(o) for o in relationship], cols=4, cls='space-y-3'), | |
| DivCentered(Button("Submit Form", cls=ButtonT.primary))), | |
| cls='space-y-4') | |
| ``` | |
| Text can be styled with markdown automatically with MonsterUI | |
| ```` python | |
| render_md(""" | |
| # My Document | |
| > Important note here | |
| + List item with **bold** | |
| + Another with `code` | |
| ```python | |
| def hello(): | |
| print("world") | |
| ``` | |
| """) | |
| ```` | |
| '<div><h1 class="uk-h1 text-4xl font-bold mt-12 mb-6">My Document</h1>\n<blockquote class="uk-blockquote pl-4 border-l-4 border-primary italic mb-6">\n<p class="text-lg leading-relaxed mb-6">Important note here</p>\n</blockquote>\n<ul class="uk-list uk-list-bullet space-y-2 mb-6 ml-6 text-lg">\n<li class="leading-relaxed">List item with <strong>bold</strong></li>\n<li class="leading-relaxed">Another with <code class="uk-codespan px-1">code</code></li>\n</ul>\n<pre class="bg-base-200 rounded-lg p-4 mb-6"><code class="language-python uk-codespan px-1 uk-codespan px-1 block overflow-x-auto">def hello():\n print("world")\n</code></pre>\n</div>' | |
| Or using semantic HTML: | |
| ``` python | |
| def SemanticText(): | |
| return Card( | |
| H1("MonsterUI's Semantic Text"), | |
| P( | |
| Strong("MonsterUI"), " brings the power of semantic HTML to life with ", | |
| Em("beautiful styling"), " and ", Mark("zero configuration"), "."), | |
| Blockquote( | |
| P("Write semantic HTML in pure Python, get modern styling for free."), | |
| Cite("MonsterUI Team")), | |
| footer=Small("Released February 2025"),) | |
| ```</doc><doc title="HTMX reference" desc="Brief description of all HTMX attributes, CSS classes, headers, events, extensions, js lib methods, and config options">+++ | |
| title = "Reference" | |
| +++ | |
| ## Contents | |
| * [htmx Core Attributes](#attributes) | |
| * [htmx Additional Attributes](#attributes-additional) | |
| * [htmx CSS Classes](#classes) | |
| * [htmx Request Headers](#request_headers) | |
| * [htmx Response Headers](#response_headers) | |
| * [htmx Events](#events) | |
| * [htmx Extensions](/extensions) | |
| * [JavaScript API](#api) | |
| * [Configuration Options](#config) | |
| ## Core Attribute Reference {#attributes} | |
| The most common attributes when using htmx. | |
| <div class="info-table"> | |
| | Attribute | Description | | |
| |--------------------------------------------------|--------------------------------------------------------------------------------------------------------------------| | |
| | [`hx-get`](@/attributes/hx-get.md) | issues a `GET` to the specified URL | | |
| | [`hx-post`](@/attributes/hx-post.md) | issues a `POST` to the specified URL | | |
| | [`hx-on*`](@/attributes/hx-on.md) | handle events with inline scripts on elements | | |
| | [`hx-push-url`](@/attributes/hx-push-url.md) | push a URL into the browser location bar to create history | | |
| | [`hx-select`](@/attributes/hx-select.md) | select content to swap in from a response | | |
| | [`hx-select-oob`](@/attributes/hx-select-oob.md) | select content to swap in from a response, somewhere other than the target (out of band) | | |
| | [`hx-swap`](@/attributes/hx-swap.md) | controls how content will swap in (`outerHTML`, `beforeend`, `afterend`, ...) | | |
| | [`hx-swap-oob`](@/attributes/hx-swap-oob.md) | mark element to swap in from a response (out of band) | | |
| | [`hx-target`](@/attributes/hx-target.md) | specifies the target element to be swapped | | |
| | [`hx-trigger`](@/attributes/hx-trigger.md) | specifies the event that triggers the request | | |
| | [`hx-vals`](@/attributes/hx-vals.md) | add values to submit with the request (JSON format) | | |
| </div> | |
| ## Additional Attribute Reference {#attributes-additional} | |
| All other attributes available in htmx. | |
| <div class="info-table"> | |
| | Attribute | Description | | |
| |------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------| | |
| | [`hx-boost`](@/attributes/hx-boost.md) | add [progressive enhancement](https://en.wikipedia.org/wiki/Progressive_enhancement) for links and forms | | |
| | [`hx-confirm`](@/attributes/hx-confirm.md) | shows a `confirm()` dialog before issuing a request | | |
| | [`hx-delete`](@/attributes/hx-delete.md) | issues a `DELETE` to the specified URL | | |
| | [`hx-disable`](@/attributes/hx-disable.md) | disables htmx processing for the given node and any children nodes | | |
| | [`hx-disabled-elt`](@/attributes/hx-disabled-elt.md) | adds the `disabled` attribute to the specified elements while a request is in flight | | |
| | [`hx-disinherit`](@/attributes/hx-disinherit.md) | control and disable automatic attribute inheritance for child nodes | | |
| | [`hx-encoding`](@/attributes/hx-encoding.md) | changes the request encoding type | | |
| | [`hx-ext`](@/attributes/hx-ext.md) | extensions to use for this element | | |
| | [`hx-headers`](@/attributes/hx-headers.md) | adds to the headers that will be submitted with the request | | |
| | [`hx-history`](@/attributes/hx-history.md) | prevent sensitive data being saved to the history cache | | |
| | [`hx-history-elt`](@/attributes/hx-history-elt.md) | the element to snapshot and restore during history navigation | | |
| | [`hx-include`](@/attributes/hx-include.md) | include additional data in requests | | |
| | [`hx-indicator`](@/attributes/hx-indicator.md) | the element to put the `htmx-request` class on during the request | | |
| | [`hx-inherit`](@/attributes/hx-inherit.md) | control and enable automatic attribute inheritance for child nodes if it has been disabled by default | | |
| | [`hx-params`](@/attributes/hx-params.md) | filters the parameters that will be submitted with a request | | |
| | [`hx-patch`](@/attributes/hx-patch.md) | issues a `PATCH` to the specified URL | | |
| | [`hx-preserve`](@/attributes/hx-preserve.md) | specifies elements to keep unchanged between requests | | |
| | [`hx-prompt`](@/attributes/hx-prompt.md) | shows a `prompt()` before submitting a request | | |
| | [`hx-put`](@/attributes/hx-put.md) | issues a `PUT` to the specified URL | | |
| | [`hx-replace-url`](@/attributes/hx-replace-url.md) | replace the URL in the browser location bar | | |
| | [`hx-request`](@/attributes/hx-request.md) | configures various aspects of the request | | |
| | [`hx-sync`](@/attributes/hx-sync.md) | control how requests made by different elements are synchronized | | |
| | [`hx-validate`](@/attributes/hx-validate.md) | force elements to validate themselves before a request | | |
| | [`hx-vars`](@/attributes/hx-vars.md) | adds values dynamically to the parameters to submit with the request (deprecated, please use [`hx-vals`](@/attributes/hx-vals.md)) | | |
| </div> | |
| ## CSS Class Reference {#classes} | |
| <div class="info-table"> | |
| | Class | Description | | |
| |-----------|-------------| | |
| | `htmx-added` | Applied to a new piece of content before it is swapped, removed after it is settled. | |
| | `htmx-indicator` | A dynamically generated class that will toggle visible (opacity:1) when a `htmx-request` class is present | |
| | `htmx-request` | Applied to either the element or the element specified with [`hx-indicator`](@/attributes/hx-indicator.md) while a request is ongoing | |
| | `htmx-settling` | Applied to a target after content is swapped, removed after it is settled. The duration can be modified via [`hx-swap`](@/attributes/hx-swap.md). | |
| | `htmx-swapping` | Applied to a target before any content is swapped, removed after it is swapped. The duration can be modified via [`hx-swap`](@/attributes/hx-swap.md). | |
| </div> | |
| ## HTTP Header Reference {#headers} | |
| ### Request Headers Reference {#request_headers} | |
| <div class="info-table"> | |
| | Header | Description | | |
| |--------|-------------| | |
| | `HX-Boosted` | indicates that the request is via an element using [hx-boost](@/attributes/hx-boost.md) | |
| | `HX-Current-URL` | the current URL of the browser | |
| | `HX-History-Restore-Request` | "true" if the request is for history restoration after a miss in the local history cache | |
| | `HX-Prompt` | the user response to an [hx-prompt](@/attributes/hx-prompt.md) | |
| | `HX-Request` | always "true" | |
| | `HX-Target` | the `id` of the target element if it exists | |
| | `HX-Trigger-Name` | the `name` of the triggered element if it exists | |
| | `HX-Trigger` | the `id` of the triggered element if it exists | |
| </div> | |
| ### Response Headers Reference {#response_headers} | |
| <div class="info-table"> | |
| | Header | Description | | |
| |------------------------------------------------------|-------------| | |
| | [`HX-Location`](@/headers/hx-location.md) | allows you to do a client-side redirect that does not do a full page reload | |
| | [`HX-Push-Url`](@/headers/hx-push-url.md) | pushes a new url into the history stack | |
| | [`HX-Redirect`](@/headers/hx-redirect.md) | can be used to do a client-side redirect to a new location | |
| | `HX-Refresh` | if set to "true" the client-side will do a full refresh of the page | |
| | [`HX-Replace-Url`](@/headers/hx-replace-url.md) | replaces the current URL in the location bar | |
| | `HX-Reswap` | allows you to specify how the response will be swapped. See [hx-swap](@/attributes/hx-swap.md) for possible values | |
| | `HX-Retarget` | a CSS selector that updates the target of the content update to a different element on the page | |
| | `HX-Reselect` | a CSS selector that allows you to choose which part of the response is used to be swapped in. Overrides an existing [`hx-select`](@/attributes/hx-select.md) on the triggering element | |
| | [`HX-Trigger`](@/headers/hx-trigger.md) | allows you to trigger client-side events | |
| | [`HX-Trigger-After-Settle`](@/headers/hx-trigger.md) | allows you to trigger client-side events after the settle step | |
| | [`HX-Trigger-After-Swap`](@/headers/hx-trigger.md) | allows you to trigger client-side events after the swap step | |
| </div> | |
| ## Event Reference {#events} | |
| <div class="info-table"> | |
| | Event | Description | | |
| |-------|-------------| | |
| | [`htmx:abort`](@/events.md#htmx:abort) | send this event to an element to abort a request | |
| | [`htmx:afterOnLoad`](@/events.md#htmx:afterOnLoad) | triggered after an AJAX request has completed processing a successful response | |
| | [`htmx:afterProcessNode`](@/events.md#htmx:afterProcessNode) | triggered after htmx has initialized a node | |
| | [`htmx:afterRequest`](@/events.md#htmx:afterRequest) | triggered after an AJAX request has completed | |
| | [`htmx:afterSettle`](@/events.md#htmx:afterSettle) | triggered after the DOM has settled | |
| | [`htmx:afterSwap`](@/events.md#htmx:afterSwap) | triggered after new content has been swapped in | |
| | [`htmx:beforeCleanupElement`](@/events.md#htmx:beforeCleanupElement) | triggered before htmx [disables](@/attributes/hx-disable.md) an element or removes it from the DOM | |
| | [`htmx:beforeOnLoad`](@/events.md#htmx:beforeOnLoad) | triggered before any response processing occurs | |
| | [`htmx:beforeProcessNode`](@/events.md#htmx:beforeProcessNode) | triggered before htmx initializes a node | |
| | [`htmx:beforeRequest`](@/events.md#htmx:beforeRequest) | triggered before an AJAX request is made | |
| | [`htmx:beforeSwap`](@/events.md#htmx:beforeSwap) | triggered before a swap is done, allows you to configure the swap | |
| | [`htmx:beforeSend`](@/events.md#htmx:beforeSend) | triggered just before an ajax request is sent | |
| | [`htmx:beforeTransition`](@/events.md#htmx:beforeTransition) | triggered before the [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) wrapped swap occurs | |
| | [`htmx:configRequest`](@/events.md#htmx:configRequest) | triggered before the request, allows you to customize parameters, headers | |
| | [`htmx:confirm`](@/events.md#htmx:confirm) | triggered after a trigger occurs on an element, allows you to cancel (or delay) issuing the AJAX request | |
| | [`htmx:historyCacheError`](@/events.md#htmx:historyCacheError) | triggered on an error during cache writing | |
| | [`htmx:historyCacheMiss`](@/events.md#htmx:historyCacheMiss) | triggered on a cache miss in the history subsystem | |
| | [`htmx:historyCacheMissError`](@/events.md#htmx:historyCacheMissError) | triggered on a unsuccessful remote retrieval | |
| | [`htmx:historyCacheMissLoad`](@/events.md#htmx:historyCacheMissLoad) | triggered on a successful remote retrieval | |
| | [`htmx:historyRestore`](@/events.md#htmx:historyRestore) | triggered when htmx handles a history restoration action | |
| | [`htmx:beforeHistorySave`](@/events.md#htmx:beforeHistorySave) | triggered before content is saved to the history cache | |
| | [`htmx:load`](@/events.md#htmx:load) | triggered when new content is added to the DOM | |
| | [`htmx:noSSESourceError`](@/events.md#htmx:noSSESourceError) | triggered when an element refers to a SSE event in its trigger, but no parent SSE source has been defined | |
| | [`htmx:onLoadError`](@/events.md#htmx:onLoadError) | triggered when an exception occurs during the onLoad handling in htmx | |
| | [`htmx:oobAfterSwap`](@/events.md#htmx:oobAfterSwap) | triggered after an out of band element as been swapped in | |
| | [`htmx:oobBeforeSwap`](@/events.md#htmx:oobBeforeSwap) | triggered before an out of band element swap is done, allows you to configure the swap | |
| | [`htmx:oobErrorNoTarget`](@/events.md#htmx:oobErrorNoTarget) | triggered when an out of band element does not have a matching ID in the current DOM | |
| | [`htmx:prompt`](@/events.md#htmx:prompt) | triggered after a prompt is shown | |
| | [`htmx:pushedIntoHistory`](@/events.md#htmx:pushedIntoHistory) | triggered after a url is pushed into history | |
| | [`htmx:replacedInHistory`](@/events.md#htmx:replacedInHistory) | triggered after a url is replaced in history | |
| | [`htmx:responseError`](@/events.md#htmx:responseError) | triggered when an HTTP response error (non-`200` or `300` response code) occurs | |
| | [`htmx:sendAbort`](@/events.md#htmx:sendAbort) | triggered when a request is aborted | |
| | [`htmx:sendError`](@/events.md#htmx:sendError) | triggered when a network error prevents an HTTP request from happening | |
| | [`htmx:sseError`](@/events.md#htmx:sseError) | triggered when an error occurs with a SSE source | |
| | [`htmx:sseOpen`](/events#htmx:sseOpen) | triggered when a SSE source is opened | |
| | [`htmx:swapError`](@/events.md#htmx:swapError) | triggered when an error occurs during the swap phase | |
| | [`htmx:targetError`](@/events.md#htmx:targetError) | triggered when an invalid target is specified | |
| | [`htmx:timeout`](@/events.md#htmx:timeout) | triggered when a request timeout occurs | |
| | [`htmx:validation:validate`](@/events.md#htmx:validation:validate) | triggered before an element is validated | |
| | [`htmx:validation:failed`](@/events.md#htmx:validation:failed) | triggered when an element fails validation | |
| | [`htmx:validation:halted`](@/events.md#htmx:validation:halted) | triggered when a request is halted due to validation errors | |
| | [`htmx:xhr:abort`](@/events.md#htmx:xhr:abort) | triggered when an ajax request aborts | |
| | [`htmx:xhr:loadend`](@/events.md#htmx:xhr:loadend) | triggered when an ajax request ends | |
| | [`htmx:xhr:loadstart`](@/events.md#htmx:xhr:loadstart) | triggered when an ajax request starts | |
| | [`htmx:xhr:progress`](@/events.md#htmx:xhr:progress) | triggered periodically during an ajax request that supports progress events | |
| </div> | |
| ## JavaScript API Reference {#api} | |
| <div class="info-table"> | |
| | Method | Description | | |
| |-------|-------------| | |
| | [`htmx.addClass()`](@/api.md#addClass) | Adds a class to the given element | |
| | [`htmx.ajax()`](@/api.md#ajax) | Issues an htmx-style ajax request | |
| | [`htmx.closest()`](@/api.md#closest) | Finds the closest parent to the given element matching the selector | |
| | [`htmx.config`](@/api.md#config) | A property that holds the current htmx config object | |
| | [`htmx.createEventSource`](@/api.md#createEventSource) | A property holding the function to create SSE EventSource objects for htmx | |
| | [`htmx.createWebSocket`](@/api.md#createWebSocket) | A property holding the function to create WebSocket objects for htmx | |
| | [`htmx.defineExtension()`](@/api.md#defineExtension) | Defines an htmx [extension](https://htmx.org/extensions) | |
| | [`htmx.find()`](@/api.md#find) | Finds a single element matching the selector | |
| | [`htmx.findAll()` `htmx.findAll(elt, selector)`](@/api.md#find) | Finds all elements matching a given selector | |
| | [`htmx.logAll()`](@/api.md#logAll) | Installs a logger that will log all htmx events | |
| | [`htmx.logger`](@/api.md#logger) | A property set to the current logger (default is `null`) | |
| | [`htmx.off()`](@/api.md#off) | Removes an event listener from the given element | |
| | [`htmx.on()`](@/api.md#on) | Creates an event listener on the given element, returning it | |
| | [`htmx.onLoad()`](@/api.md#onLoad) | Adds a callback handler for the `htmx:load` event | |
| | [`htmx.parseInterval()`](@/api.md#parseInterval) | Parses an interval declaration into a millisecond value | |
| | [`htmx.process()`](@/api.md#process) | Processes the given element and its children, hooking up any htmx behavior | |
| | [`htmx.remove()`](@/api.md#remove) | Removes the given element | |
| | [`htmx.removeClass()`](@/api.md#removeClass) | Removes a class from the given element | |
| | [`htmx.removeExtension()`](@/api.md#removeExtension) | Removes an htmx [extension](https://htmx.org/extensions) | |
| | [`htmx.swap()`](@/api.md#swap) | Performs swapping (and settling) of HTML content | |
| | [`htmx.takeClass()`](@/api.md#takeClass) | Takes a class from other elements for the given element | |
| | [`htmx.toggleClass()`](@/api.md#toggleClass) | Toggles a class from the given element | |
| | [`htmx.trigger()`](@/api.md#trigger) | Triggers an event on an element | |
| | [`htmx.values()`](@/api.md#values) | Returns the input values associated with the given element | |
| </div> | |
| ## Configuration Reference {#config} | |
| Htmx has some configuration options that can be accessed either programmatically or declaratively. They are | |
| listed below: | |
| <div class="info-table"> | |
| | Config Variable | Info | | |
| |---------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | |
| | `htmx.config.historyEnabled` | defaults to `true`, really only useful for testing | | |
| | `htmx.config.historyCacheSize` | defaults to 10 | | |
| | `htmx.config.refreshOnHistoryMiss` | defaults to `false`, if set to `true` htmx will issue a full page refresh on history misses rather than use an AJAX request | | |
| | `htmx.config.defaultSwapStyle` | defaults to `innerHTML` | | |
| | `htmx.config.defaultSwapDelay` | defaults to 0 | | |
| | `htmx.config.defaultSettleDelay` | defaults to 20 | | |
| | `htmx.config.includeIndicatorStyles` | defaults to `true` (determines if the indicator styles are loaded) | | |
| | `htmx.config.indicatorClass` | defaults to `htmx-indicator` | | |
| | `htmx.config.requestClass` | defaults to `htmx-request` | | |
| | `htmx.config.addedClass` | defaults to `htmx-added` | | |
| | `htmx.config.settlingClass` | defaults to `htmx-settling` | | |
| | `htmx.config.swappingClass` | defaults to `htmx-swapping` | | |
| | `htmx.config.allowEval` | defaults to `true`, can be used to disable htmx's use of eval for certain features (e.g. trigger filters) | | |
| | `htmx.config.allowScriptTags` | defaults to `true`, determines if htmx will process script tags found in new content | | |
| | `htmx.config.inlineScriptNonce` | defaults to `''`, meaning that no nonce will be added to inline scripts | | |
| | `htmx.config.inlineStyleNonce` | defaults to `''`, meaning that no nonce will be added to inline styles | | |
| | `htmx.config.attributesToSettle` | defaults to `["class", "style", "width", "height"]`, the attributes to settle during the settling phase | | |
| | `htmx.config.wsReconnectDelay` | defaults to `full-jitter` | | |
| | `htmx.config.wsBinaryType` | defaults to `blob`, the [the type of binary data](https://developer.mozilla.org/docs/Web/API/WebSocket/binaryType) being received over the WebSocket connection | | |
| | `htmx.config.disableSelector` | defaults to `[hx-disable], [data-hx-disable]`, htmx will not process elements with this attribute on it or a parent | | |
| | `htmx.config.disableInheritance` | defaults to `false`. If it is set to `true`, the inheritance of attributes is completely disabled and you can explicitly specify the inheritance with the [hx-inherit](@/attributes/hx-inherit.md) attribute. | |
| | `htmx.config.withCredentials` | defaults to `false`, allow cross-site Access-Control requests using credentials such as cookies, authorization headers or TLS client certificates | | |
| | `htmx.config.timeout` | defaults to 0, the number of milliseconds a request can take before automatically being terminated | | |
| | `htmx.config.scrollBehavior` | defaults to 'instant', the scroll behavior when using the [show](@/attributes/hx-swap.md#scrolling-scroll-show) modifier with `hx-swap`. The allowed values are `instant` (scrolling should happen instantly in a single jump), `smooth` (scrolling should animate smoothly) and `auto` (scroll behavior is determined by the computed value of [scroll-behavior](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior)). | | |
| | `htmx.config.defaultFocusScroll` | if the focused element should be scrolled into view, defaults to false and can be overridden using the [focus-scroll](@/attributes/hx-swap.md#focus-scroll) swap modifier. | | |
| | `htmx.config.getCacheBusterParam` | defaults to false, if set to true htmx will append the target element to the `GET` request in the format `org.htmx.cache-buster=targetElementId` | | |
| | `htmx.config.globalViewTransitions` | if set to `true`, htmx will use the [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) API when swapping in new content. | | |
| | `htmx.config.methodsThatUseUrlParams` | defaults to `["get", "delete"]`, htmx will format requests with these methods by encoding their parameters in the URL, not the request body | | |
| | `htmx.config.selfRequestsOnly` | defaults to `true`, whether to only allow AJAX requests to the same domain as the current document | | |
| | `htmx.config.ignoreTitle` | defaults to `false`, if set to `true` htmx will not update the title of the document when a `title` tag is found in new content | | |
| | `htmx.config.scrollIntoViewOnBoost` | defaults to `true`, whether or not the target of a boosted element is scrolled into the viewport. If `hx-target` is omitted on a boosted element, the target defaults to `body`, causing the page to scroll to the top. | | |
| | `htmx.config.triggerSpecsCache` | defaults to `null`, the cache to store evaluated trigger specifications into, improving parsing performance at the cost of more memory usage. You may define a simple object to use a never-clearing cache, or implement your own system using a [proxy object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy) | | |
| | `htmx.config.responseHandling` | the default [Response Handling](@/docs.md#response-handling) behavior for response status codes can be configured here to either swap or error | | |
| | `htmx.config.allowNestedOobSwaps` | defaults to `true`, whether to process OOB swaps on elements that are nested within the main response element. See [Nested OOB Swaps](@/attributes/hx-swap-oob.md#nested-oob-swaps). | | |
| </div> | |
| You can set them directly in javascript, or you can use a `meta` tag: | |
| ```html | |
| <meta name="htmx-config" content='{"defaultSwapStyle":"outerHTML"}'> | |
| ```</doc><doc title="Starlette quick guide" desc="A quick overview of some Starlette features useful to FastHTML devs."># 🌟 Starlette Quick Manual | |
| 2020-02-09 | |
| Starlette is the ASGI web framework used as the foundation of FastHTML. Listed here are some Starlette features FastHTML developers can use directly, since the `FastHTML` class inherits from the `Starlette` class (but note that FastHTML has its own customised `RouteX` and `RouterX` classes for routing, to handle FT element trees etc). | |
| ## Get uploaded file content | |
| ``` | |
| async def handler(request): | |
| inp = await request.form() | |
| uploaded_file = inp["filename"] | |
| filename = uploaded_file.filename # abc.png | |
| content_type = uploaded.content_type # MIME type, e.g. image/png | |
| content = await uploaded_file.read() # image content | |
| ``` | |
| ## Return a customized response (status code and headers) | |
| ``` | |
| import json | |
| from starlette.responses import Response | |
| async def handler(request): | |
| data = { | |
| "name": "Bo" | |
| } | |
| return Response(json.dumps(data), media_type="application/json") | |
| ``` | |
| `Response` takes `status_code`, `headers` and `media_type`, so if we want to change a response's status code, we can do: | |
| ``` | |
| return Response(content, statu_code=404) | |
| ``` | |
| And customized headers: | |
| ``` | |
| headers = { | |
| "x-extra-key": "value" | |
| } | |
| return Response(content, status_code=200, headers=headers) | |
| ``` | |
| ## Redirect | |
| ``` | |
| from starlette.responses import RedirectResponse | |
| async handler(request): | |
| # Customize status_code: | |
| # 301: permanent redirect | |
| # 302: temporary redirect | |
| # 303: see others | |
| # 307: temporary redirect (default) | |
| return RedirectResponse(url=url, status_code=303) | |
| ``` | |
| ## Request context | |
| ### URL Object: `request.url` | |
| * Get request full url: `url = str(request.url)` | |
| * Get scheme: `request.url.scheme` (http, https, ws, wss) | |
| * Get netloc: `request.url.netloc`, e.g.: example.com:8080 | |
| * Get path: `request.url.path`, e.g.: /search | |
| * Get query string: `request.url.query`, e.g.: kw=hello | |
| * Get hostname: `request.url.hostname`, e.g.: example.com | |
| * Get port: `request.url.port`, e.g.: 8080 | |
| * If using secure scheme: `request.url.is_secure`, True is schme is `https` or `wss` | |
| ### Headers: `request.headers` | |
| ``` | |
| { | |
| 'host': 'example.com:8080', | |
| 'connection': 'keep-alive', | |
| 'cache-control': 'max-age=0', | |
| 'sec-ch-ua': 'Google Chrome 80', | |
| 'dnt': '1', | |
| 'upgrade-insecure-requests': '1', | |
| 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) ...', | |
| 'sec-fetch-dest': 'document', | |
| 'accept': 'text/html,image/apng,*/*;q=0.8;v=b3;q=0.9', | |
| 'sec-origin-policy': '0', | |
| 'sec-fetch-site': 'none', | |
| 'sec-fetch-mode': 'navigate', | |
| 'sec-fetch-user': '?1', | |
| 'accept-encoding': 'gzip, deflate, br', | |
| 'accept-language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,zh-TW;q=0.6', | |
| 'cookie': 'session=eyJhZG1pbl91c2_KiQ...' | |
| } | |
| ``` | |
| ### Client: `request.client` | |
| * `request.client.host`: get client sock IP | |
| * `request.client.port`: get client sock port | |
| ### Method: `request.method` | |
| * `request.method`: GET, POST, etc. | |
| ### Get Data | |
| * `await request.body()`: get raw data from body | |
| * `await request.json()`: get passed data and parse it as JSON | |
| * `await request.form()`: get posted data and pass it as dictionary | |
| ### Scope: `request.scope` | |
| ``` | |
| { | |
| 'type': 'http', | |
| 'http_version': '1.1', | |
| 'server': ('127.0.0.1', 9092), | |
| 'client': ('127.0.0.1', 53102), | |
| 'scheme': 'https', | |
| 'method': 'GET', | |
| 'root_path': '', | |
| 'path': '/', | |
| 'raw_path': b'/', | |
| 'query_string': b'kw=hello', | |
| 'headers': [ | |
| (b'host', b'example.com:8080'), | |
| (b'connection', b'keep-alive'), | |
| (b'cache-control', b'max-age=0'), | |
| ... | |
| ], | |
| 'app': <starlette.applications.Starlette object at 0x1081bd650>, | |
| 'session': {'uid': '57ba03ea7333f72a25f837cf'}, | |
| 'router': <starlette.routing.Router object at 0x1081bd6d0>, | |
| 'endpoint': <class 'app.index.Index'>, | |
| 'path_params': {} | |
| } | |
| ``` | |
| ## Put varaible in request & app scope | |
| ``` | |
| app.state.dbconn = get_db_conn() | |
| request.state.start_time = time.time() | |
| # use app-scope state variable in a request | |
| request.app.state.dbconn | |
| ``` | |
| ## Utility functions | |
| ### Use `State` to wrap a dictionary | |
| ``` | |
| from starlette.datastructures import State | |
| data = { | |
| "name": "Bo" | |
| } | |
| print(data["name"]) | |
| # now wrap it with State function | |
| wrapped = State(data) | |
| # You can use the dot syntaxt, but can't use `wrapped["name"]` any more. | |
| print(wrapped.name) | |
| ``` | |
| ### login_required wrapper function | |
| NB: This is easier to do in FastHTML using Beforeware. | |
| ``` | |
| import functools | |
| from starlette.endpoints import HTTPEndpoint | |
| from starlette.responses import Response | |
| def login_required(login_url="/signin"): | |
| def decorator(handler): | |
| @functools.wraps(handler) | |
| async def new_handler(obj, req, *args, **kwargs): | |
| user = req.session.get("login_user") | |
| if user is None: | |
| return seeother(login_url) | |
| return await handler(obj, req, *args, **kwargs) | |
| return new_handler | |
| return decorator | |
| class MyAccount(HTTPEndpiont): | |
| @login_required() | |
| async def get(self, request): | |
| # some logic here | |
| content = "hello" | |
| return Response(content) | |
| ``` | |
| ## Exceptions | |
| Handle exception and customize 403, 404, 503, 500 page: | |
| ``` | |
| from starlette.exceptions import HTTPException | |
| async def exc_handle_403(request, exc): | |
| return HTMLResponse("My 403 page", status_code=exc.status_code) | |
| async def exc_handle_404(request, exc): | |
| return HTMLResponse("My 404 page", status_code=exc.status_code) | |
| async def exc_handle_503(request, exc): | |
| return HTMLResponse("Failed, please try it later", status_code=exc.status_code) | |
| # error is not exception, 500 is server side unexpected error, all other status code will be treated as Exception | |
| async def err_handle_500(request, exc): | |
| import traceback | |
| Log.error(traceback.format_exc()) | |
| return HTMLResponse("My 500 page", status_code=500) | |
| # To add handler, we can add either status_code or Exception itself as key | |
| exception_handlers = { | |
| 403: exc_handle_403, | |
| 404: exc_handle_404, | |
| 503: exc_handle_503, | |
| 500: err_handle_500, | |
| #HTTPException: exc_handle_500, | |
| } | |
| app = Starlette(routes=routes, exception_handlers=exception_handlers) | |
| ``` | |
| ## Background Task | |
| ### Put some async task as background task | |
| ``` | |
| import aiofiles | |
| from starlette.background import BackgroundTask | |
| from starlette.responses import Response | |
| aiofiles_remove = aiofiles.os.wrap(os.remove) | |
| async def del_file(fpath): | |
| await aiofiles_remove(fpath) | |
| async def handler(request): | |
| content = "" | |
| fpath = "/tmp/tmpfile.txt" | |
| task = BackgroundTask(del_file, fpath=fpath) | |
| return Response(content, background=task) | |
| ``` | |
| ### Put multiple tasks as background task | |
| ``` | |
| from starlette.background import BackgroundTasks | |
| async def task1(name): | |
| pass | |
| async def task2(email): | |
| pass | |
| async def handler(request): | |
| tasks = BackgroundTasks() | |
| tasks.add_task(task1, name="John") | |
| tasks.add_task(task2, email="[email protected]") | |
| content = "" | |
| return Response(content, background=tasks) | |
| ``` | |
| ## Write middleware | |
| There are 2 ways to write middleware: | |
| ### Define `__call__` function: | |
| ``` | |
| class MyMiddleware: | |
| def __init__(self, app): | |
| self.app = app | |
| async def __call__(self, scope, receive, send): | |
| # see above scope dictionary as reference | |
| headers = dict(scope["headers"]) | |
| # do something | |
| # pass to next middleware | |
| return await self.app(scope, receive, send) | |
| ``` | |
| ### Use `BaseHTTPMiddleware` | |
| ``` | |
| from starlette.middleware.base import BaseHTTPMiddleware | |
| class CustomHeaderMiddleware(BaseHTTPMiddleware): | |
| async def dispatch(self, request, call_next): | |
| # do something before pass to next middleware | |
| response = await call_next(request) | |
| # do something after next middleware returned | |
| response.headers['X-Author'] = 'John' | |
| return response | |
| ```</doc></docs><api><doc title="API List" desc="A succint list of all functions and methods in fasthtml."># fasthtml Module Documentation | |
| ## fasthtml.authmw | |
| - `class BasicAuthMiddleware` | |
| - `def __init__(self, app, cb, skip)` | |
| - `def __call__(self, scope, receive, send)` | |
| - `def authenticate(self, conn)` | |
| ## fasthtml.cli | |
| - `@call_parse def railway_link()` | |
| Link the current directory to the current project's Railway service | |
| - `@call_parse def railway_deploy(name, mount)` | |
| Deploy a FastHTML app to Railway | |
| ## fasthtml.components | |
| > `ft_html` and `ft_hx` functions to add some conveniences to `ft`, along with a full set of basic HTML components, and functions to work with forms and `FT` conversion | |
| - `def File(fname)` | |
| Use the unescaped text in file `fname` directly | |
| - `def show(ft, *rest)` | |
| Renders FT Components into HTML within a Jupyter notebook. | |
| - `def fill_form(form, obj)` | |
| Fills named items in `form` using attributes in `obj` | |
| - `def fill_dataclass(src, dest)` | |
| Modifies dataclass in-place and returns it | |
| - `def find_inputs(e, tags, **kw)` | |
| Recursively find all elements in `e` with `tags` and attrs matching `kw` | |
| - `def html2ft(html, attr1st)` | |
| Convert HTML to an `ft` expression | |
| - `def sse_message(elm, event)` | |
| Convert element `elm` into a format suitable for SSE streaming | |
| ## fasthtml.core | |
| > The `FastHTML` subclass of `Starlette`, along with the `RouterX` and `RouteX` classes it automatically uses. | |
| - `def parsed_date(s)` | |
| Convert `s` to a datetime | |
| - `def snake2hyphens(s)` | |
| Convert `s` from snake case to hyphenated and capitalised | |
| - `@dataclass class HtmxHeaders` | |
| - `def __bool__(self)` | |
| - `def __init__(self, boosted, current_url, history_restore_request, prompt, request, target, trigger_name, trigger)` | |
| - `@dataclass class HttpHeader` | |
| - `def __init__(self, k, v)` | |
| - `@use_kwargs_dict(**htmx_resps) def HtmxResponseHeaders(**kwargs)` | |
| HTMX response headers | |
| - `def form2dict(form)` | |
| Convert starlette form data to a dict | |
| - `def parse_form(req)` | |
| Starlette errors on empty multipart forms, so this checks for that situation | |
| - `class JSONResponse` | |
| Same as starlette's version, but auto-stringifies non serializable types | |
| - `def render(self, content)` | |
| - `def flat_xt(lst)` | |
| Flatten lists | |
| - `class Beforeware` | |
| - `def __init__(self, f, skip)` | |
| - `def EventStream(s)` | |
| Create a text/event-stream response from `s` | |
| - `def flat_tuple(o)` | |
| Flatten lists | |
| - `def noop_body(c, req)` | |
| Default Body wrap function which just returns the content | |
| - `def respond(req, heads, bdy)` | |
| Default FT response creation function | |
| - `class Redirect` | |
| Use HTMX or Starlette RedirectResponse as required to redirect to `loc` | |
| - `def __init__(self, loc)` | |
| - `def __response__(self, req)` | |
| - `def qp(p, **kw)` | |
| Add parameters kw to path p | |
| - `def def_hdrs(htmx, surreal)` | |
| Default headers for a FastHTML app | |
| - `class FastHTML` | |
| - `def __init__(self, debug, routes, middleware, title, exception_handlers, on_startup, on_shutdown, lifespan, hdrs, ftrs, exts, before, after, surreal, htmx, default_hdrs, sess_cls, secret_key, session_cookie, max_age, sess_path, same_site, sess_https_only, sess_domain, key_fname, body_wrap, htmlkw, nb_hdrs, canonical, **bodykw)` | |
| - `def add_route(self, route)` | |
| - `@patch def ws(self, path, conn, disconn, name, middleware)` | |
| Add a websocket route at `path` | |
| - `def nested_name(f)` | |
| Get name of function `f` using '_' to join nested function names | |
| - `@patch def route(self, path, methods, name, include_in_schema, body_wrap)` | |
| Add a route at `path` | |
| - `def serve(appname, app, host, port, reload, reload_includes, reload_excludes)` | |
| Run the app in an async server, with live reload set as the default. | |
| - `class Client` | |
| A simple httpx ASGI client that doesn't require `async` | |
| - `def __init__(self, app, url)` | |
| - `class RouteFuncs` | |
| - `def __init__(self)` | |
| - `def __setattr__(self, name, value)` | |
| - `def __getattr__(self, name)` | |
| - `def __dir__(self)` | |
| - `class APIRouter` | |
| Add routes to an app | |
| - `def __init__(self, prefix, body_wrap)` | |
| - `def __call__(self, path, methods, name, include_in_schema, body_wrap)` | |
| Add a route at `path` | |
| - `def __getattr__(self, name)` | |
| - `def to_app(self, app)` | |
| Add routes to `app` | |
| - `def ws(self, path, conn, disconn, name, middleware)` | |
| Add a websocket route at `path` | |
| - `def cookie(key, value, max_age, expires, path, domain, secure, httponly, samesite)` | |
| Create a 'set-cookie' `HttpHeader` | |
| - `@patch def static_route_exts(self, prefix, static_path, exts)` | |
| Add a static route at URL path `prefix` with files from `static_path` and `exts` defined by `reg_re_param()` | |
| - `@patch def static_route(self, ext, prefix, static_path)` | |
| Add a static route at URL path `prefix` with files from `static_path` and single `ext` (including the '.') | |
| - `class MiddlewareBase` | |
| - `def __call__(self, scope, receive, send)` | |
| - `class FtResponse` | |
| Wrap an FT response with any Starlette `Response` | |
| - `def __init__(self, content, status_code, headers, cls, media_type, background)` | |
| - `def __response__(self, req)` | |
| ## fasthtml.fastapp | |
| > The `fast_app` convenience wrapper | |
| - `def fast_app(db_file, render, hdrs, ftrs, tbls, before, middleware, live, debug, routes, exception_handlers, on_startup, on_shutdown, lifespan, default_hdrs, pico, surreal, htmx, exts, canonical, secret_key, key_fname, session_cookie, max_age, sess_path, same_site, sess_https_only, sess_domain, htmlkw, bodykw, reload_attempts, reload_interval, static_path, body_wrap, nb_hdrs, **kwargs)` | |
| Create a FastHTML or FastHTMLWithLiveReload app. | |
| ## fasthtml.js | |
| > Basic external Javascript lib wrappers | |
| - `def light_media(css)` | |
| Render light media for day mode views | |
| - `def dark_media(css)` | |
| Render dark media for night mode views | |
| - `def MarkdownJS(sel)` | |
| Implements browser-based markdown rendering. | |
| - `def HighlightJS(sel, langs, light, dark)` | |
| Implements browser-based syntax highlighting. Usage example [here](/tutorials/quickstart_for_web_devs.html#code-highlighting). | |
| - `def MermaidJS(sel, theme)` | |
| Implements browser-based Mermaid diagram rendering. | |
| ## fasthtml.jupyter | |
| > Use FastHTML in Jupyter notebooks | |
| - `def nb_serve(app, log_level, port, host, **kwargs)` | |
| Start a Jupyter compatible uvicorn server with ASGI `app` on `port` with `log_level` | |
| - `def nb_serve_async(app, log_level, port, host, **kwargs)` | |
| Async version of `nb_serve` | |
| - `def is_port_free(port, host)` | |
| Check if `port` is free on `host` | |
| - `def wait_port_free(port, host, max_wait)` | |
| Wait for `port` to be free on `host` | |
| - `class JupyUvi` | |
| Start and stop a Jupyter compatible uvicorn server with ASGI `app` on `port` with `log_level` | |
| - `def __init__(self, app, log_level, host, port, start, **kwargs)` | |
| - `def start(self)` | |
| - `def start_async(self)` | |
| - `def stop(self)` | |
| - `class JupyUviAsync` | |
| Start and stop an async Jupyter compatible uvicorn server with ASGI `app` on `port` with `log_level` | |
| - `def __init__(self, app, log_level, host, port, **kwargs)` | |
| - `def start(self)` | |
| - `def stop(self)` | |
| - `def HTMX(path, app, host, port, height, link, iframe)` | |
| An iframe which displays the HTMX application in a notebook. | |
| ## fasthtml.live_reload | |
| - `class FastHTMLWithLiveReload` | |
| `FastHTMLWithLiveReload` enables live reloading. | |
| This means that any code changes saved on the server will automatically | |
| trigger a reload of both the server and browser window. | |
| How does it work? | |
| - a websocket is created at `/live-reload` | |
| - a small js snippet `LIVE_RELOAD_SCRIPT` is injected into each webpage | |
| - this snippet connects to the websocket at `/live-reload` and listens for an `onclose` event | |
| - when the `onclose` event is detected the browser is reloaded | |
| Why do we listen for an `onclose` event? | |
| When code changes are saved the server automatically reloads if the --reload flag is set. | |
| The server reload kills the websocket connection. The `onclose` event serves as a proxy | |
| for "developer has saved some changes". | |
| Usage | |
| >>> from fasthtml.common import * | |
| >>> app = FastHTMLWithLiveReload() | |
| Run: | |
| serve() | |
| - `def __init__(self, *args, **kwargs)` | |
| ## fasthtml.oauth | |
| > Basic scaffolding for handling OAuth | |
| - `class GoogleAppClient` | |
| A `WebApplicationClient` for Google oauth2 | |
| - `def __init__(self, client_id, client_secret, code, scope, project_id, **kwargs)` | |
| - `@classmethod def from_file(cls, fname, code, scope, **kwargs)` | |
| - `class GitHubAppClient` | |
| A `WebApplicationClient` for GitHub oauth2 | |
| - `def __init__(self, client_id, client_secret, code, scope, **kwargs)` | |
| - `class HuggingFaceClient` | |
| A `WebApplicationClient` for HuggingFace oauth2 | |
| - `def __init__(self, client_id, client_secret, code, scope, state, **kwargs)` | |
| - `class DiscordAppClient` | |
| A `WebApplicationClient` for Discord oauth2 | |
| - `def __init__(self, client_id, client_secret, is_user, perms, scope, **kwargs)` | |
| - `def login_link(self, redirect_uri, scope, state)` | |
| - `def parse_response(self, code, redirect_uri)` | |
| - `class Auth0AppClient` | |
| A `WebApplicationClient` for Auth0 OAuth2 | |
| - `def __init__(self, domain, client_id, client_secret, code, scope, redirect_uri, **kwargs)` | |
| - `def login_link(self, req)` | |
| - `@patch def login_link(self, redirect_uri, scope, state, **kwargs)` | |
| Get a login link for this client | |
| - `def redir_url(request, redir_path, scheme)` | |
| Get the redir url for the host in `request` | |
| - `@patch def parse_response(self, code, redirect_uri)` | |
| Get the token from the oauth2 server response | |
| - `@patch def get_info(self, token)` | |
| Get the info for authenticated user | |
| - `@patch def retr_info(self, code, redirect_uri)` | |
| Combines `parse_response` and `get_info` | |
| - `@patch def retr_id(self, code, redirect_uri)` | |
| Call `retr_info` and then return id/subscriber value | |
| - `class OAuth` | |
| - `def __init__(self, app, cli, skip, redir_path, error_path, logout_path, login_path, https, http_patterns)` | |
| - `def redir_login(self, session)` | |
| - `def redir_url(self, req)` | |
| - `def login_link(self, req, scope, state)` | |
| - `def check_invalid(self, req, session, auth)` | |
| - `def logout(self, session)` | |
| - `def get_auth(self, info, ident, session, state)` | |
| - `@patch() def consent_url(self, proj)` | |
| Get Google OAuth consent screen URL | |
| - `@patch def save(self, fname)` | |
| Save credentials to `fname` | |
| - `def load_creds(fname)` | |
| Load credentials from `fname` | |
| - `@patch def creds(self)` | |
| Create `Credentials` from the client, refreshing if needed | |
| ## fasthtml.pico | |
| > Basic components for generating Pico CSS tags | |
| - `@delegates(ft_hx, keep=True) def Card(*c, **kwargs)` | |
| A PicoCSS Card, implemented as an Article with optional Header and Footer | |
| - `@delegates(ft_hx, keep=True) def Group(*c, **kwargs)` | |
| A PicoCSS Group, implemented as a Fieldset with role 'group' | |
| - `@delegates(ft_hx, keep=True) def Search(*c, **kwargs)` | |
| A PicoCSS Search, implemented as a Form with role 'search' | |
| - `@delegates(ft_hx, keep=True) def Grid(*c, **kwargs)` | |
| A PicoCSS Grid, implemented as child Divs in a Div with class 'grid' | |
| - `@delegates(ft_hx, keep=True) def DialogX(*c, **kwargs)` | |
| A PicoCSS Dialog, with children inside a Card | |
| - `@delegates(ft_hx, keep=True) def Container(*args, **kwargs)` | |
| A PicoCSS Container, implemented as a Main with class 'container' | |
| ## fasthtml.stripe_otp | |
| - `def create_price(app_nm, amt, currency)` | |
| Create a product and bind it to a price object. If product already exist just return the price list. | |
| - `def archive_price(app_nm)` | |
| Archive a price - useful for cleanup if testing. | |
| - `class Payment` | |
| ## fasthtml.svg | |
| > Simple SVG FT elements | |
| - `def Svg(*args, **kwargs)` | |
| An SVG tag; xmlns is added automatically, and viewBox defaults to height and width if not provided | |
| - `@delegates(ft_hx) def ft_svg(tag, *c, **kwargs)` | |
| Create a standard `FT` element with some SVG-specific attrs | |
| - `@delegates(ft_svg) def Rect(width, height, x, y, fill, stroke, stroke_width, rx, ry, **kwargs)` | |
| A standard SVG `rect` element | |
| - `@delegates(ft_svg) def Circle(r, cx, cy, fill, stroke, stroke_width, **kwargs)` | |
| A standard SVG `circle` element | |
| - `@delegates(ft_svg) def Ellipse(rx, ry, cx, cy, fill, stroke, stroke_width, **kwargs)` | |
| A standard SVG `ellipse` element | |
| - `def transformd(translate, scale, rotate, skewX, skewY, matrix)` | |
| Create an SVG `transform` kwarg dict | |
| - `@delegates(ft_svg) def Line(x1, y1, x2, y2, stroke, w, stroke_width, **kwargs)` | |
| A standard SVG `line` element | |
| - `@delegates(ft_svg) def Polyline(*args, **kwargs)` | |
| A standard SVG `polyline` element | |
| - `@delegates(ft_svg) def Polygon(*args, **kwargs)` | |
| A standard SVG `polygon` element | |
| - `@delegates(ft_svg) def Text(*args, **kwargs)` | |
| A standard SVG `text` element | |
| - `class PathFT` | |
| - `def M(self, x, y)` | |
| Move to. | |
| - `def L(self, x, y)` | |
| Line to. | |
| - `def H(self, x)` | |
| Horizontal line to. | |
| - `def V(self, y)` | |
| Vertical line to. | |
| - `def Z(self)` | |
| Close path. | |
| - `def C(self, x1, y1, x2, y2, x, y)` | |
| Cubic Bézier curve. | |
| - `def S(self, x2, y2, x, y)` | |
| Smooth cubic Bézier curve. | |
| - `def Q(self, x1, y1, x, y)` | |
| Quadratic Bézier curve. | |
| - `def T(self, x, y)` | |
| Smooth quadratic Bézier curve. | |
| - `def A(self, rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, x, y)` | |
| Elliptical Arc. | |
| - `def SvgOob(*args, **kwargs)` | |
| Wraps an SVG shape as required for an HTMX OOB swap | |
| - `def SvgInb(*args, **kwargs)` | |
| Wraps an SVG shape as required for an HTMX inband swap | |
| ## fasthtml.xtend | |
| > Simple extensions to standard HTML components, such as adding sensible defaults | |
| - `@delegates(ft_hx, keep=True) def A(*c, **kwargs)` | |
| An A tag; `href` defaults to '#' for more concise use with HTMX | |
| - `@delegates(ft_hx, keep=True) def AX(txt, hx_get, target_id, hx_swap, href, **kwargs)` | |
| An A tag with just one text child, allowing hx_get, target_id, and hx_swap to be positional params | |
| - `@delegates(ft_hx, keep=True) def Form(*c, **kwargs)` | |
| A Form tag; identical to plain `ft_hx` version except default `enctype='multipart/form-data'` | |
| - `@delegates(ft_hx, keep=True) def Hidden(value, id, **kwargs)` | |
| An Input of type 'hidden' | |
| - `@delegates(ft_hx, keep=True) def CheckboxX(checked, label, value, id, name, **kwargs)` | |
| A Checkbox optionally inside a Label, preceded by a `Hidden` with matching name | |
| - `@delegates(ft_html, keep=True) def Script(code, **kwargs)` | |
| A Script tag that doesn't escape its code | |
| - `@delegates(ft_html, keep=True) def Style(*c, **kwargs)` | |
| A Style tag that doesn't escape its code | |
| - `def double_braces(s)` | |
| Convert single braces to double braces if next to special chars or newline | |
| - `def undouble_braces(s)` | |
| Convert double braces to single braces if next to special chars or newline | |
| - `def loose_format(s, **kw)` | |
| String format `s` using `kw`, without being strict about braces outside of template params | |
| - `def ScriptX(fname, src, nomodule, type, _async, defer, charset, crossorigin, integrity, **kw)` | |
| A `script` element with contents read from `fname` | |
| - `def replace_css_vars(css, pre, **kwargs)` | |
| Replace `var(--)` CSS variables with `kwargs` if name prefix matches `pre` | |
| - `def StyleX(fname, **kw)` | |
| A `style` element with contents read from `fname` and variables replaced from `kw` | |
| - `def Nbsp()` | |
| A non-breaking space | |
| - `def Surreal(code)` | |
| Wrap `code` in `domReadyExecute` and set `m=me()` and `p=me('-')` | |
| - `def On(code, event, sel, me)` | |
| An async surreal.js script block event handler for `event` on selector `sel,p`, making available parent `p`, event `ev`, and target `e` | |
| - `def Prev(code, event)` | |
| An async surreal.js script block event handler for `event` on previous sibling, with same vars as `On` | |
| - `def Now(code, sel)` | |
| An async surreal.js script block on selector `me(sel)` | |
| - `def AnyNow(sel, code)` | |
| An async surreal.js script block on selector `any(sel)` | |
| - `def run_js(js, id, **kw)` | |
| Run `js` script, auto-generating `id` based on name of caller if needed, and js-escaping any `kw` params | |
| - `def jsd(org, repo, root, path, prov, typ, ver, esm, **kwargs)` | |
| jsdelivr `Script` or CSS `Link` tag, or URL | |
| - `class Fragment` | |
| An empty tag, used as a container | |
| - `def __init__(self, *c)` | |
| - `@delegates(ft_hx, keep=True) def Titled(title, *args, **kwargs)` | |
| An HTML partial containing a `Title`, and `H1`, and any provided children | |
| - `def Socials(title, site_name, description, image, url, w, h, twitter_site, creator, card)` | |
| OG and Twitter social card headers | |
| - `def YouTubeEmbed(video_id, **kwargs)` | |
| Embed a YouTube video | |
| - `def Favicon(light_icon, dark_icon)` | |
| Light and dark favicon headers | |
| </doc><doc title="MonsterUI API List" desc="Complete API Reference for Monster UI, a component framework similar to shadcn, but for FastHTML"># monsterui Module Documentation | |
| ## monsterui.core | |
| - `class ThemeRadii(Enum)` | |
| Members: none, sm, md, lg | |
| - `class ThemeShadows` | |
| - `class ThemeFont` | |
| - `class Theme(Enum)` | |
| Selector to choose theme and get all headers needed for app. Includes frankenui + tailwind + daisyui + highlight.js options | |
| Members: slate, stone, gray, neutral, red, rose, orange, green, blue, yellow, violet, zinc | |
| - `headers(self, mode, icons, daisy, highlightjs, katex, apex_charts, radii, shadows, font)` | |
| Create frankenui and tailwind cdns | |
| - `local_headers(self, mode, static_dir, icons, daisy, highlightjs, katex, apex_charts, radii, shadows, font)` | |
| Create headers using local files downloaded from CDNs | |
| ## monsterui.daisy | |
| - `class AlertT(Enum)` | |
| Alert styles from DaisyUI | |
| Members: info, success, warning, error | |
| - `def Alert(*c, **kwargs)` | |
| Alert informs users about important events. | |
| - `class StepsT(Enum)` | |
| Options for Steps | |
| Members: vertical, horizonal | |
| - `class StepT(Enum)` | |
| Step styles for LiStep | |
| Members: primary, secondary, accent, info, success, warning, error, neutral | |
| - `def Steps(*li, **kwargs)` | |
| Creates a steps container | |
| - `def LiStep(*c, **kwargs)` | |
| Creates a step list item | |
| - `class LoadingT(Enum)` | |
| Members: spinner, dots, ring, ball, bars, infinity, xs, sm, md, lg | |
| - `def Loading(cls, htmx_indicator, **kwargs)` | |
| Creates a loading animation component | |
| - `class ToastHT(Enum)` | |
| Horizontal position for Toast | |
| Members: start, center, end | |
| - `class ToastVT(Enum)` | |
| Vertical position for Toast | |
| Members: top, middle, bottom | |
| ## monsterui.foundations | |
| > Data Structures and Utilties | |
| - `def stringify(o)` | |
| Converts input types into strings that can be passed to FT components | |
| - `class VEnum(Enum)` | |
| Members: | |
| - `__str__(self)` | |
| - `__add__(self, other)` | |
| - `__radd__(self, other)` | |
| ## monsterui.franken | |
| - `class TextT(Enum)` | |
| Text Styles from https://franken-ui.dev/docs/text | |
| Members: paragraph, lead, meta, gray, italic, xs, sm, lg, xl, light, normal, medium, bold, extrabold, muted, primary, secondary, success, warning, error, info, left, right, center, justify, start, end, top, middle, bottom, truncate, break_, nowrap, underline, highlight | |
| - `class TextPresets(Enum)` | |
| Common Typography Presets | |
| Members: muted_sm, muted_lg, bold_sm, bold_lg, md_weight_sm, md_weight_muted | |
| - `def CodeSpan(*c, **kwargs)` | |
| A CodeSpan with Styling | |
| - `def CodeBlock(*c, **kwargs)` | |
| CodeBlock with Styling | |
| - `def H1(*c, **kwargs)` | |
| H1 with styling and appropriate size | |
| - `def H2(*c, **kwargs)` | |
| H2 with styling and appropriate size | |
| - `def H3(*c, **kwargs)` | |
| H3 with styling and appropriate size | |
| - `def H4(*c, **kwargs)` | |
| H4 with styling and appropriate size | |
| - `def H5(*c, **kwargs)` | |
| H5 with styling and appropriate size | |
| - `def H6(*c, **kwargs)` | |
| H6 with styling and appropriate size | |
| - `def Subtitle(*c, **kwargs)` | |
| Styled muted_sm text designed to go under Headings and Titles | |
| - `def Q(*c, **kwargs)` | |
| Styled quotation mark | |
| - `def Em(*c, **kwargs)` | |
| Styled emphasis text | |
| - `def Strong(*c, **kwargs)` | |
| Styled strong text | |
| - `def I(*c, **kwargs)` | |
| Styled italic text | |
| - `def Small(*c, **kwargs)` | |
| Styled small text | |
| - `def Mark(*c, **kwargs)` | |
| Styled highlighted text | |
| - `def Del(*c, **kwargs)` | |
| Styled deleted text | |
| - `def Ins(*c, **kwargs)` | |
| Styled inserted text | |
| - `def Sub(*c, **kwargs)` | |
| Styled subscript text | |
| - `def Sup(*c, **kwargs)` | |
| Styled superscript text | |
| - `def Blockquote(*c, **kwargs)` | |
| Blockquote with Styling | |
| - `def Caption(*c, **kwargs)` | |
| Styled caption text | |
| - `def Cite(*c, **kwargs)` | |
| Styled citation text | |
| - `def Time(*c, **kwargs)` | |
| Styled time element | |
| - `def Address(*c, **kwargs)` | |
| Styled address element | |
| - `def Abbr(*c, **kwargs)` | |
| Styled abbreviation with dotted underline | |
| - `def Dfn(*c, **kwargs)` | |
| Styled definition term with italic and medium weight | |
| - `def Kbd(*c, **kwargs)` | |
| Styled keyboard input with subtle background | |
| - `def Samp(*c, **kwargs)` | |
| Styled sample output with subtle background | |
| - `def Var(*c, **kwargs)` | |
| Styled variable with italic monospace | |
| - `def Figure(*c, **kwargs)` | |
| Styled figure container with card-like appearance | |
| - `def Details(*c, **kwargs)` | |
| Styled details element | |
| - `def Summary(*c, **kwargs)` | |
| Styled summary element | |
| - `def Data(*c, **kwargs)` | |
| Styled data element | |
| - `def Meter(*c, **kwargs)` | |
| Styled meter element | |
| - `def S(*c, **kwargs)` | |
| Styled strikethrough text (different semantic meaning from Del) | |
| - `def U(*c, **kwargs)` | |
| Styled underline (for proper names in Chinese, proper spelling etc) | |
| - `def Output(*c, **kwargs)` | |
| Styled output element for form results | |
| - `def PicSumImg(h, w, id, grayscale, blur, **kwargs)` | |
| Creates a placeholder image using https://picsum.photos/ | |
| - `def AccordionItem(title, *c)` | |
| Creates a single item for use within an Accordion component, handling title, content, and open state. | |
| - `def Accordion(*c, **kwargs)` | |
| Creates a styled Accordion container using accordion component. | |
| - `class ButtonT(Enum)` | |
| Options for styling Buttons | |
| Members: default, ghost, primary, secondary, destructive, text, link, xs, sm, lg, xl, icon | |
| - `def Button(*c, **kwargs)` | |
| Button with Styling (defaults to `submit` for form submission) | |
| - `class ContainerT(Enum)` | |
| Max width container sizes from https://franken-ui.dev/docs/container | |
| Members: xs, sm, lg, xl, expand | |
| - `class BackgroundT(Enum)` | |
| Members: muted, primary, secondary, default | |
| - `def Container(*c, **kwargs)` | |
| Div to be used as a container that often wraps large sections or a page of content | |
| - `def Titled(title, *c, **kwargs)` | |
| Creates a standard page structure for titled page. Main(Container(title, content)) | |
| - `class DividerT(Enum)` | |
| Divider Styles from https://franken-ui.dev/docs/divider | |
| Members: icon, sm, vertical | |
| - `def Divider(*c, **kwargs)` | |
| Divider with default styling and margin | |
| - `def DividerSplit(*c)` | |
| Creates a simple horizontal line divider with configurable thickness and vertical spacing | |
| - `def Article(*c, **kwargs)` | |
| A styled article container for blog posts or similar content | |
| - `def ArticleTitle(*c, **kwargs)` | |
| A title component for use within an Article | |
| - `def ArticleMeta(*c, **kwargs)` | |
| A metadata component for use within an Article showing things like date, author etc | |
| - `class SectionT(Enum)` | |
| Section styles from https://franken-ui.dev/docs/section | |
| Members: default, muted, primary, secondary, xs, sm, lg, xl, remove_vertical | |
| - `def Section(*c, **kwargs)` | |
| Section with styling and margins | |
| - `def Form(*c, **kwargs)` | |
| A Form with default spacing between form elements | |
| - `def Fieldset(*c, **kwargs)` | |
| A Fieldset with default styling | |
| - `def Legend(*c, **kwargs)` | |
| A Legend with default styling | |
| - `def Input(*c, **kwargs)` | |
| An Input with default styling | |
| - `def Radio(*c, **kwargs)` | |
| A Radio with default styling | |
| - `def CheckboxX(*c, **kwargs)` | |
| A Checkbox with default styling | |
| - `def Range(*c, **kwargs)` | |
| A Range with default styling | |
| - `def TextArea(*c, **kwargs)` | |
| A Textarea with default styling | |
| - `def Switch(*c, **kwargs)` | |
| A Switch with default styling | |
| - `def Upload(*c, **kwargs)` | |
| A file upload component with default styling | |
| - `def UploadZone(*c, **kwargs)` | |
| A file drop zone component with default styling | |
| - `def FormLabel(*c, **kwargs)` | |
| A Label with default styling | |
| - `class LabelT(Enum)` | |
| Members: primary, secondary, destructive | |
| - `def Label(*c, **kwargs)` | |
| FrankenUI labels, which look like pills | |
| - `def UkFormSection(title, description, *c)` | |
| A form section with a title, description and optional button | |
| - `def GenericLabelInput(label, lbl_cls, input_cls, container, cls, id, input_fn, **kwargs)` | |
| `Div(Label,Input)` component with Uk styling injected appropriately. Generally you should higher level API, such as `LabelInput` which is created for you in this library | |
| - `def LabelInput(label, lbl_cls, input_cls, cls, id, **kwargs)` | |
| A `FormLabel` and `Input` pair that provides default spacing and links/names them based on id | |
| - `def LabelRadio(label, lbl_cls, input_cls, container, cls, id, **kwargs)` | |
| A FormLabel and Radio pair that provides default spacing and links/names them based on id | |
| - `def LabelCheckboxX(label, lbl_cls, input_cls, container, cls, id, **kwargs)` | |
| A FormLabel and CheckboxX pair that provides default spacing and links/names them based on id | |
| - `def Options(*c)` | |
| Helper function to wrap things into `Option`s for use in `Select` | |
| - `def Select(*option, **kwargs)` | |
| Creates a select dropdown with uk styling and option for adding a search box | |
| - `def LabelSelect(*option, **kwargs)` | |
| A FormLabel and Select pair that provides default spacing and links/names them based on id | |
| - `@delegates(GenericLabelInput, but=['input_fn', 'cls']) def LabelRange(label, lbl_cls, input_cls, cls, id, value, min, max, step, label_range, **kwargs)` | |
| A FormLabel and Range pair that provides default spacing and links/names them based on id | |
| - `class AT(Enum)` | |
| Link styles from https://franken-ui.dev/docs/link | |
| Members: muted, text, reset, primary, classic | |
| - `class ListT(Enum)` | |
| List styles using Tailwind CSS | |
| Members: disc, circle, square, decimal, hyphen, bullet, divider, striped | |
| - `def ModalContainer(*c, **kwargs)` | |
| Creates a modal container that components go in | |
| - `def ModalDialog(*c, **kwargs)` | |
| Creates a modal dialog | |
| - `def ModalHeader(*c, **kwargs)` | |
| Creates a modal header | |
| - `def ModalBody(*c, **kwargs)` | |
| Creates a modal body | |
| - `def ModalFooter(*c, **kwargs)` | |
| Creates a modal footer | |
| - `def ModalTitle(*c, **kwargs)` | |
| Creates a modal title | |
| - `def ModalCloseButton(*c, **kwargs)` | |
| Creates a button that closes a modal with js | |
| - `def Modal(*c, **kwargs)` | |
| Creates a modal with the appropriate classes to put the boilerplate in the appropriate places for you | |
| - `def Placeholder(*c, **kwargs)` | |
| Creates a placeholder | |
| - `def Progress(*c, **kwargs)` | |
| Creates a progress bar | |
| - `def UkIcon(icon, height, width, stroke_width, cls, **kwargs)` | |
| Creates an icon using lucide icons | |
| - `def UkIconLink(icon, height, width, stroke_width, cls, button, **kwargs)` | |
| Creates an icon link using lucide icons | |
| - `def DiceBearAvatar(seed_name, h, w)` | |
| Creates an Avatar using https://dicebear.com/ | |
| - `def Center(*c, **kwargs)` | |
| Centers contents both vertically and horizontally by default | |
| - `class FlexT(Enum)` | |
| Flexbox modifiers using Tailwind CSS | |
| Members: block, inline, left, center, right, between, around, stretch, top, middle, bottom, row, row_reverse, column, column_reverse, nowrap, wrap, wrap_reverse | |
| - `def Grid(*div, **kwargs)` | |
| Creates a responsive grid layout with smart defaults based on content | |
| - `def DivFullySpaced(*c, **kwargs)` | |
| Creates a flex div with it's components having as much space between them as possible | |
| - `def DivCentered(*c, **kwargs)` | |
| Creates a flex div with it's components centered in it | |
| - `def DivLAligned(*c, **kwargs)` | |
| Creates a flex div with it's components aligned to the left | |
| - `def DivRAligned(*c, **kwargs)` | |
| Creates a flex div with it's components aligned to the right | |
| - `def DivVStacked(*c, **kwargs)` | |
| Creates a flex div with it's components stacked vertically | |
| - `def DivHStacked(*c, **kwargs)` | |
| Creates a flex div with it's components stacked horizontally | |
| - `class NavT(Enum)` | |
| Members: default, primary, secondary | |
| - `def NavContainer(*li, **kwargs)` | |
| Creates a navigation container (useful for creating a sidebar navigation). A Nav is a list (NavBar is something different) | |
| - `def NavParentLi(*nav_container, **kwargs)` | |
| Creates a navigation list item with a parent nav for nesting | |
| - `def NavDividerLi(*c, **kwargs)` | |
| Creates a navigation list item with a divider | |
| - `def NavHeaderLi(*c, **kwargs)` | |
| Creates a navigation list item with a header | |
| - `def NavSubtitle(*c, **kwargs)` | |
| Creates a navigation subtitle | |
| - `def NavCloseLi(*c, **kwargs)` | |
| Creates a navigation list item with a close button | |
| - `class ScrollspyT(Enum)` | |
| Members: underline, bold | |
| - `def NavBar(*c)` | |
| Creates a responsive navigation bar with mobile menu support | |
| - `def SliderContainer(*c, **kwargs)` | |
| Creates a slider container | |
| - `def SliderItems(*c, **kwargs)` | |
| Creates a slider items container | |
| - `def SliderNav(cls, prev_cls, next_cls, **kwargs)` | |
| Navigation arrows for Slider component | |
| - `def Slider(*c, **kwargs)` | |
| Creates a slider with optional navigation arrows | |
| - `def DropDownNavContainer(*li, **kwargs)` | |
| A Nav that is part of a DropDown | |
| - `def TabContainer(*li, **kwargs)` | |
| A TabContainer where children will be different tabs | |
| - `class CardT(Enum)` | |
| Card styles from UIkit | |
| Members: default, primary, secondary, destructive, hover | |
| - `def CardTitle(*c, **kwargs)` | |
| Creates a card title | |
| - `def CardHeader(*c, **kwargs)` | |
| Creates a card header | |
| - `def CardBody(*c, **kwargs)` | |
| Creates a card body | |
| - `def CardFooter(*c, **kwargs)` | |
| Creates a card footer | |
| - `def CardContainer(*c, **kwargs)` | |
| Creates a card container | |
| - `def Card(*c, **kwargs)` | |
| Creates a Card with a header, body, and footer | |
| - `class TableT(Enum)` | |
| Members: divider, striped, hover, sm, lg, justify, middle, responsive | |
| - `def Table(*c, **kwargs)` | |
| Creates a table | |
| - `def TableFromLists(header_data, body_data, footer_data, header_cell_render, body_cell_render, footer_cell_render, cls, sortable, **kwargs)` | |
| Creates a Table from a list of header data and a list of lists of body data | |
| - `def TableFromDicts(header_data, body_data, footer_data, header_cell_render, body_cell_render, footer_cell_render, cls, sortable, **kwargs)` | |
| Creates a Table from a list of header data and a list of dicts of body data | |
| - `def apply_classes(html_str, class_map, class_map_mods)` | |
| Apply classes to html string | |
| - `def render_md(md_content, class_map, class_map_mods)` | |
| Renders markdown using mistletoe and lxml | |
| - `def get_franken_renderer(img_dir)` | |
| Create a renderer class with the specified img_dir | |
| - `def ThemePicker(color, radii, shadows, font, mode, cls, custom_themes)` | |
| Theme picker component with configurable sections | |
| - `def LightboxContainer(*lightboxitem, **kwargs)` | |
| Lightbox container that will hold `LightboxItems` | |
| - `def LightboxItem(*c, **kwargs)` | |
| Anchor tag with appropriate structure to go inside a `LightBoxContainer` | |
| - `def ApexChart(**kws)` | |
| Apex chart component | |
| </doc></api><examples><doc title="Websockets application" desc="Very brief example of using websockets with HTMX and FastHTML">from asyncio import sleep | |
| from fasthtml.common import * | |
| app = FastHTML(exts='ws') | |
| rt = app.route | |
| def mk_inp(): return Input(id='msg') | |
| nid = 'notifications' | |
| @rt('/') | |
| async def get(): | |
| cts = Div( | |
| Div(id=nid), | |
| 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=nid)) | |
| async def on_disconnect( ): print('Disconnected!') | |
| @app.ws('/ws', conn=on_connect, disconn=on_disconnect) | |
| async def ws(msg:str, send): | |
| await send(Div('Hello ' + msg, id=nid)) | |
| await sleep(2) | |
| return Div('Goodbye ' + msg, id=nid), mk_inp() | |
| serve() | |
| </doc><doc title="Todo list application" desc="Detailed walk-thru of a complete CRUD app in FastHTML showing idiomatic use of FastHTML and HTMX patterns.">### | |
| # Walkthrough of an idiomatic fasthtml app | |
| ### | |
| # This fasthtml app includes functionality from fastcore, starlette, fastlite, and fasthtml itself. | |
| # Run with: `python adv_app.py` | |
| # Importing from `fasthtml.common` brings the key parts of all of these together. | |
| # For simplicity, you can just `from fasthtml.common import *`: | |
| from fasthtml.common import * | |
| # ...or you can import everything into a namespace: | |
| # from fasthtml import common as fh | |
| # ...or you can import each symbol explicitly (which we're commenting out here but including for completeness): | |
| """ | |
| from fasthtml.common import ( | |
| # These are the HTML components we use in this app | |
| A, AX, Button, Card, CheckboxX, Container, Div, Form, Grid, Group, H1, H2, Hidden, Input, Li, Main, Script, Style, Textarea, Title, Titled, Ul, | |
| # These are FastHTML symbols we'll use | |
| Beforeware, FastHTML, fast_app, SortableJS, fill_form, picolink, serve, | |
| # These are from Starlette, Fastlite, fastcore, and the Python stdlib | |
| FileResponse, NotFoundError, RedirectResponse, database, patch, dataclass | |
| ) | |
| """ | |
| from hmac import compare_digest | |
| # You can use any database you want; it'll be easier if you pick a lib that supports the MiniDataAPI spec. | |
| # Here we are using SQLite, with the FastLite library, which supports the MiniDataAPI spec. | |
| db = database('data/utodos.db') | |
| # The `t` attribute is the table collection. The `todos` and `users` tables are not created if they don't exist. | |
| # Instead, you can use the `create` method to create them if needed. | |
| todos,users = db.t.todos,db.t.users | |
| if todos not in db.t: | |
| # You can pass a dict, or kwargs, to most MiniDataAPI methods. | |
| users.create(dict(name=str, pwd=str), pk='name') | |
| todos.create(id=int, title=str, done=bool, name=str, details=str, priority=int, pk='id') | |
| # Although you can just use dicts, it can be helpful to have types for your DB objects. | |
| # The `dataclass` method creates that type, and stores it in the object, so it will use it for any returned items. | |
| Todo,User = todos.dataclass(),users.dataclass() | |
| # Any Starlette response class can be returned by a FastHTML route handler. | |
| # In that case, FastHTML won't change it at all. | |
| # Status code 303 is a redirect that can change POST to GET, so it's appropriate for a login page. | |
| login_redir = RedirectResponse('/login', status_code=303) | |
| # The `before` function is a *Beforeware* function. These are functions that run before a route handler is called. | |
| def before(req, sess): | |
| # This sets the `auth` attribute in the request scope, and gets it from the session. | |
| # The session is a Starlette session, which is a dict-like object which is cryptographically signed, | |
| # so it can't be tampered with. | |
| # The `auth` key in the scope is automatically provided to any handler which requests it, and can not | |
| # be injected by the user using query params, cookies, etc, so it should be secure to use. | |
| auth = req.scope['auth'] = sess.get('auth', None) | |
| # If the session key is not there, it redirects to the login page. | |
| if not auth: return login_redir | |
| # `xtra` is part of the MiniDataAPI spec. It adds a filter to queries and DDL statements, | |
| # to ensure that the user can only see/edit their own todos. | |
| todos.xtra(name=auth) | |
| markdown_js = """ | |
| import { marked } from "https://cdn.jsdelivr.net/npm/marked/lib/marked.esm.js"; | |
| proc_htmx('.markdown', e => e.innerHTML = marked.parse(e.textContent)); | |
| """ | |
| # We will use this in our `exception_handlers` dict | |
| def _not_found(req, exc): return Titled('Oh no!', Div('We could not find that page :(')) | |
| # To create a Beforeware object, we pass the function itself, and optionally a list of regexes to skip. | |
| bware = Beforeware(before, skip=[r'/favicon\.ico', r'/static/.*', r'.*\.css', '/login']) | |
| # The `FastHTML` class is a subclass of `Starlette`, so you can use any parameters that `Starlette` accepts. | |
| # In addition, you can add your Beforeware here, and any headers you want included in HTML responses. | |
| # FastHTML includes the "HTMX" and "Surreal" libraries in headers, unless you pass `default_hdrs=False`. | |
| app = FastHTML(before=bware, | |
| # These are the same as Starlette exception_handlers, except they also support `FT` results | |
| exception_handlers={404: _not_found}, | |
| # PicoCSS is a particularly simple CSS framework, with some basic integration built in to FastHTML. | |
| # `picolink` is pre-defined with the header for the PicoCSS stylesheet. | |
| # You can use any CSS framework you want, or none at all. | |
| hdrs=(picolink, | |
| # `Style` is an `FT` object, which are 3-element lists consisting of: | |
| # (tag_name, children_list, attrs_dict). | |
| # FastHTML composes them from trees and auto-converts them to HTML when needed. | |
| # You can also use plain HTML strings in handlers and headers, | |
| # which will be auto-escaped, unless you use `NotStr(...string...)`. | |
| Style(':root { --pico-font-size: 100%; }'), | |
| # Have a look at fasthtml/js.py to see how these Javascript libraries are added to FastHTML. | |
| # They are only 5-10 lines of code each, and you can add your own too. | |
| SortableJS('.sortable'), | |
| # MarkdownJS is actually provided as part of FastHTML, but we've included the js code here | |
| # so that you can see how it works. | |
| Script(markdown_js, type='module')) | |
| ) | |
| # We add `rt` as a shortcut for `app.route`, which is what we'll use to decorate our route handlers. | |
| # When using `app.route` (or this shortcut), the only required argument is the path. | |
| # The name of the decorated function (eg `get`, `post`, etc) is used as the HTTP verb for the handler. | |
| rt = app.route | |
| # For instance, this function handles GET requests to the `/login` path. | |
| @rt("/login") | |
| def get(): | |
| # This creates a form with two input fields, and a submit button. | |
| # All of these components are `FT` objects. All HTML tags are provided in this form by FastHTML. | |
| # If you want other custom tags (e.g. `MyTag`), they can be auto-generated by e.g | |
| # `from fasthtml.components import MyTag`. | |
| # Alternatively, manually call e.g `ft(tag_name, *children, **attrs)`. | |
| frm = Form( | |
| # Tags with a `name` attr will have `name` auto-set to the same as `id` if not provided | |
| Input(id='name', placeholder='Name'), | |
| Input(id='pwd', type='password', placeholder='Password'), | |
| Button('login'), | |
| action='/login', method='post') | |
| # If a user visits the URL directly, FastHTML auto-generates a full HTML page. | |
| # However, if the URL is accessed by HTMX, then one HTML partial is created for each element of the tuple. | |
| # To avoid this auto-generation of a full page, return a `HTML` object, or a Starlette `Response`. | |
| # `Titled` returns a tuple of a `Title` with the first arg and a `Container` with the rest. | |
| # See the comments for `Title` later for details. | |
| return Titled("Login", frm) | |
| # Handlers are passed whatever information they "request" in the URL, as keyword arguments. | |
| # Dataclasses, dicts, namedtuples, TypedDicts, and custom classes are automatically instantiated | |
| # from form data. | |
| # In this case, the `Login` class is a dataclass, so the handler will be passed `name` and `pwd`. | |
| @dataclass | |
| class Login: name:str; pwd:str | |
| # This handler is called when a POST request is made to the `/login` path. | |
| # The `login` argument is an instance of the `Login` class, which has been auto-instantiated from the form data. | |
| # There are a number of special parameter names, which will be passed useful information about the request: | |
| # `session`: the Starlette session; `request`: the Starlette request; `auth`: the value of `scope['auth']`, | |
| # `htmx`: the HTMX headers, if any; `app`: the FastHTML app object. | |
| # You can also pass any string prefix of `request` or `session`. | |
| @rt("/login") | |
| def post(login:Login, sess): | |
| if not login.name or not login.pwd: return login_redir | |
| # Indexing into a MiniDataAPI table queries by primary key, which is `name` here. | |
| # It returns a dataclass object, if `dataclass()` has been called at some point, or a dict otherwise. | |
| try: u = users[login.name] | |
| # If the primary key does not exist, the method raises a `NotFoundError`. | |
| # Here we use this to just generate a user -- in practice you'd probably to redirect to a signup page. | |
| except NotFoundError: u = users.insert(login) | |
| # This compares the passwords using a constant time string comparison | |
| # https://sqreen.github.io/DevelopersSecurityBestPractices/timing-attack/python | |
| if not compare_digest(u.pwd.encode("utf-8"), login.pwd.encode("utf-8")): return login_redir | |
| # Because the session is signed, we can securely add information to it. It's stored in the browser cookies. | |
| # If you don't pass a secret signing key to `FastHTML`, it will auto-generate one and store it in a file `./sesskey`. | |
| sess['auth'] = u.name | |
| return RedirectResponse('/', status_code=303) | |
| # Instead of using `app.route` (or the `rt` shortcut), you can also use `app.get`, `app.post`, etc. | |
| # In this case, the function name is not used to determine the HTTP verb. | |
| @app.get("/logout") | |
| def logout(sess): | |
| del sess['auth'] | |
| return login_redir | |
| # FastHTML uses Starlette's path syntax, and adds a `static` type which matches standard static file extensions. | |
| # You can define your own regex path specifiers -- for instance this is how `static` is defined in FastHTML | |
| # `reg_re_param("static", "ico|gif|jpg|jpeg|webm|css|js|woff|png|svg|mp4|webp|ttf|otf|eot|woff2|txt|xml|html")` | |
| # In this app, we only actually have one static file, which is `favicon.ico`. But it would also be needed if | |
| # we were referencing images, CSS/JS files, etc. | |
| # Note, this function is unnecessary, as the `fast_app()` call already includes this functionality. | |
| # However, it's included here to show how you can define your own static file handler. | |
| @rt("/{fname:path}.{ext:static}") | |
| def get(fname:str, ext:str): return FileResponse(f'{fname}.{ext}') | |
| # The `patch` decorator, which is defined in `fastcore`, adds a method to an existing class. | |
| # Here we are adding a method to the `Todo` class, which is returned by the `todos` table. | |
| # The `__ft__` method is a special method that FastHTML uses to convert the object into an `FT` object, | |
| # so that it can be composed into an FT tree, and later rendered into HTML. | |
| @patch | |
| def __ft__(self:Todo): | |
| # Some FastHTML tags have an 'X' suffix, which means they're "extended" in some way. | |
| # For instance, here `AX` is an extended `A` tag, which takes 3 positional arguments: | |
| # `(text, hx_get, target_id)`. | |
| # All underscores in FT attrs are replaced with hyphens, so this will create an `hx-get` attr, | |
| # which HTMX uses to trigger a GET request. | |
| # Generally, most of your route handlers in practice (as in this demo app) are likely to be HTMX handlers. | |
| # For instance, for this demo, we only have two full-page handlers: the '/login' and '/' GET handlers. | |
| show = AX(self.title, f'/todos/{self.id}', 'current-todo') | |
| edit = AX('edit', f'/edit/{self.id}' , 'current-todo') | |
| dt = '✅ ' if self.done else '' | |
| # FastHTML provides some shortcuts. For instance, `Hidden` is defined as simply: | |
| # `return Input(type="hidden", value=value, **kwargs)` | |
| cts = (dt, show, ' | ', edit, Hidden(id="id", value=self.id), Hidden(id="priority", value="0")) | |
| # Any FT object can take a list of children as positional args, and a dict of attrs as keyword args. | |
| return Li(*cts, id=f'todo-{self.id}') | |
| # This is the handler for the main todo list application. | |
| # By including the `auth` parameter, it gets passed the current username, for displaying in the title. | |
| @rt("/") | |
| def get(auth): | |
| title = f"{auth}'s Todo list" | |
| top = Grid(H1(title), Div(A('logout', href='/logout'), style='text-align: right')) | |
| # We don't normally need separate "screens" for adding or editing data. Here for instance, | |
| # we're using an `hx-post` to add a new todo, which is added to the start of the list (using 'afterbegin'). | |
| new_inp = Input(id="new-title", name="title", placeholder="New Todo") | |
| add = Form(Group(new_inp, Button("Add")), | |
| hx_post="/", target_id='todo-list', hx_swap="afterbegin") | |
| # In the MiniDataAPI spec, treating a table as a callable (i.e with `todos(...)` here) queries the table. | |
| # Because we called `xtra` in our Beforeware, this queries the todos for the current user only. | |
| # We can include the todo objects directly as children of the `Form`, because the `Todo` class has `__ft__` defined. | |
| # This is automatically called by FastHTML to convert the `Todo` objects into `FT` objects when needed. | |
| # The reason we put the todo list inside a form is so that we can use the 'sortable' js library to reorder them. | |
| # That library calls the js `end` event when dragging is complete, so our trigger here causes our `/reorder` | |
| # handler to be called. | |
| frm = Form(*todos(order_by='priority'), | |
| id='todo-list', cls='sortable', hx_post="/reorder", hx_trigger="end") | |
| # We create an empty 'current-todo' Div at the bottom of our page, as a target for the details and editing views. | |
| card = Card(Ul(frm), header=add, footer=Div(id='current-todo')) | |
| # PicoCSS uses `<Main class='container'>` page content; `Container` is a tiny function that generates that. | |
| # A handler can return either a single `FT` object or string, or a tuple of them. | |
| # In the case of a tuple, the stringified objects are concatenated and returned to the browser. | |
| # The `Title` tag has a special purpose: it sets the title of the page. | |
| return Title(title), Container(top, card) | |
| # This is the handler for the reordering of todos. | |
| # It's a POST request, which is used by the 'sortable' js library. | |
| # Because the todo list form created earlier included hidden inputs with the todo IDs, | |
| # they are passed as form data. By using a parameter called (e.g) "id", FastHTML will try to find | |
| # something suitable in the request with this name. In order, it searches as follows: | |
| # path; query; cookies; headers; session keys; form data. | |
| # Although all these are provided in the request as strings, FastHTML will use your parameter's type | |
| # annotation to try to cast the value to the requested type. | |
| # In the case of form data, there can be multiple values with the same key. So in this case, | |
| # the parameter is a list of ints. | |
| @rt("/reorder") | |
| def post(id:list[int]): | |
| for i,id_ in enumerate(id): todos.update({'priority':i}, id_) | |
| # HTMX by default replaces the inner HTML of the calling element, which in this case is the todo list form. | |
| # Therefore, we return the list of todos, now in the correct order, which will be auto-converted to FT for us. | |
| # In this case, it's not strictly necessary, because sortable.js has already reorder the DOM elements. | |
| # However, by returning the updated data, we can be assured that there aren't sync issues between the DOM | |
| # and the server. | |
| return tuple(todos(order_by='priority')) | |
| # Refactoring components in FastHTML is as simple as creating Python functions. | |
| # The `clr_details` function creates a Div with specific HTMX attributes. | |
| # `hx_swap_oob='innerHTML'` tells HTMX to swap the inner HTML of the target element out-of-band, | |
| # meaning it will update this element regardless of where the HTMX request originated from. | |
| def clr_details(): return Div(hx_swap_oob='innerHTML', id='current-todo') | |
| # This route handler uses a path parameter `{id}` which is automatically parsed and passed as an int. | |
| @rt("/todos/{id}") | |
| def delete(id:int): | |
| # The `delete` method is part of the MiniDataAPI spec, removing the item with the given primary key. | |
| todos.delete(id) | |
| # Returning `clr_details()` ensures the details view is cleared after deletion, | |
| # leveraging HTMX's out-of-band swap feature. | |
| # Note that we are not returning *any* FT component that doesn't have an "OOB" swap, so the target element | |
| # inner HTML is simply deleted. That's why the deleted todo is removed from the list. | |
| return clr_details() | |
| @rt("/edit/{id}") | |
| def get(id:int): | |
| # The `hx_put` attribute tells HTMX to send a PUT request when the form is submitted. | |
| # `target_id` specifies which element will be updated with the server's response. | |
| res = Form(Group(Input(id="title"), Button("Save")), | |
| Hidden(id="id"), CheckboxX(id="done", label='Done'), | |
| Textarea(id="details", name="details", rows=10), | |
| hx_put="/", target_id=f'todo-{id}', id="edit") | |
| # `fill_form` populates the form with existing todo data, and returns the result. | |
| # Indexing into a table (`todos`) queries by primary key, which is `id` here. It also includes | |
| # `xtra`, so this will only return the id if it belongs to the current user. | |
| return fill_form(res, todos[id]) | |
| @rt("/") | |
| def put(todo: Todo): | |
| # `update` is part of the MiniDataAPI spec. | |
| # Note that the updated todo is returned. By returning the updated todo, we can update the list directly. | |
| # Because we return a tuple with `clr_details()`, the details view is also cleared. | |
| return todos.update(todo), clr_details() | |
| @rt("/") | |
| def post(todo:Todo): | |
| # `hx_swap_oob='true'` tells HTMX to perform an out-of-band swap, updating this element wherever it appears. | |
| # This is used to clear the input field after adding the new todo. | |
| new_inp = Input(id="new-title", name="title", placeholder="New Todo", hx_swap_oob='true') | |
| # `insert` returns the inserted todo, which is appended to the start of the list, because we used | |
| # `hx_swap='afterbegin'` when creating the todo list form. | |
| return todos.insert(todo), new_inp | |
| @rt("/todos/{id}") | |
| def get(id:int): | |
| todo = todos[id] | |
| # `hx_swap` determines how the update should occur. We use "outerHTML" to replace the entire todo `Li` element. | |
| btn = Button('delete', hx_delete=f'/todos/{todo.id}', | |
| target_id=f'todo-{todo.id}', hx_swap="outerHTML") | |
| # The "markdown" class is used here because that's the CSS selector we used in the JS earlier. | |
| # Therefore this will trigger the JS to parse the markdown in the details field. | |
| # Because `class` is a reserved keyword in Python, we use `cls` instead, which FastHTML auto-converts. | |
| return Div(H2(todo.title), Div(todo.details, cls="markdown"), btn) | |
| serve()</doc></examples></project> | |