introduction

SWL (Seecr Web Lib) lets you build interactive web applications in Python, without a JavaScript frontend.

It is a small layer on top of Starlette, Jinja and htmx 4. The server renders HTML. An action answers with a short list of instructions: put this HTML there, reload that list, close the dialog, show a toast. SWL carries them out in the browser.

You write Python and templates. There is no build step, and no second application to keep in sync.

SWL is made for the software around data: admin screens, editors, review tools, form builders and dashboards. Lots of forms, lists, dialogs and tabs, used every day by people who need them to work.

motivation

Keep the server in charge of the HTML, let it say what should happen next, and let the browser do it. That is SWL.

quick start

pip install swl

A form that adds a note, and a list that loads itself:

{% from "swl/placeholder.j2" import placeholder %}

<form hx-post="{{ register('note_add') }}">
    <input name="text" class="{{ SWL.ui.input }}">
    <button type="submit" class="{{ SWL.ui.button_primary }}">Add</button>
</form>

{{ placeholder("notes", register('notes_list')) }}
async def notes_list(request, swl, db, **kwargs):
    html = swl.template("notes/list.j2").render(notes=db.notes())
    return swl.success().hydrate("notes", html)


async def note_add(request, swl, db, **kwargs):
    async with request.form() as form:
        text = form.get("text", "").strip()
    if not text:
        return swl.fail().message("A note needs some text")
    db.add_note(text)
    return swl.success().reset_form().rehydrate("notes").toast("Note added")


__actions__ = [notes_list, note_add]

note_add doesn’t render the list. It asks the list to reload itself, so notes_list stays the only place that knows how the list looks. And because a response is data, you test an action without a browser.

SWL needs Python 3.11 or newer.

what you get

actions
Async Python functions, registered by module, with login and authorisation checks. A template gets an action's url with register("name").
a response builder
hydrate, rehydrate, open_modal, close_modal, open_contextmenu, toast, call, set_url, redirect, trigger, and swl.fail().message(...) for errors.
placeholders
Parts of the page that load themselves and can be reloaded by name from any action.
live updates
A placeholder can reload when the data it shows changes, also in other browser tabs and for other users, over a websocket. A change goes to everyone or only to the users it concerns, so private things stay private. Pages catch up after a lost connection.
translations
Mark texts with _() and SWL shows them in the visitor's language: their own choice or their browser's. Dates and numbers follow. SWL's own screens come in English, Dutch, German, French and Spanish.
deep links
Actions keep the url up to date, and a page route renders the same screen from it. Screens survive a reload and can be shared.
ready-made parts
A modal, confirm and error dialogs, toasts and context menus, all plain HTML (<dialog>, popover). Login, "my account" and user management screens.
your css framework
Bootstrap (the default), Tabler, Bulma, or SWL's own small stylesheet with no framework at all. Light and dark work with each of them.

try the example

The example application is a small task list. It uses nearly everything: dialogs, search as you type, tabs, a context menu, live updates between browser tabs, private tasks that only their owner hears about, screens that survive a reload, and switches for the language, the CSS framework and dark mode.

git clone https://github.com/seecr/swl.git
cd swl
pip install -e . -e example/
python -m swl_tasks

Open http://127.0.0.1:8000/ and log in as demo / demo-password.

docs

sources

SWL is open source under the MIT License.

SWL is made and maintained by Seecr, who use it for their own applications.

Simplicity is prerequisite for reliability.
Edsger W. Dijkstra, EWD 498 (1975)