swl 5.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- swl-5.0.0/.flake8 +23 -0
- swl-5.0.0/.github/workflows/publish.yml +39 -0
- swl-5.0.0/.github/workflows/test.yml +22 -0
- swl-5.0.0/.gitignore +8 -0
- swl-5.0.0/CHANGELOG.md +90 -0
- swl-5.0.0/CLAUDE.md +72 -0
- swl-5.0.0/LICENSE +21 -0
- swl-5.0.0/PKG-INFO +146 -0
- swl-5.0.0/README.md +118 -0
- swl-5.0.0/doc/htmx-migration.md +138 -0
- swl-5.0.0/doc/license.conf +8 -0
- swl-5.0.0/doc/reference.md +610 -0
- swl-5.0.0/example/README.md +309 -0
- swl-5.0.0/example/pyproject.toml +25 -0
- swl-5.0.0/example/swl_tasks/__init__.py +18 -0
- swl-5.0.0/example/swl_tasks/__main__.py +37 -0
- swl-5.0.0/example/swl_tasks/app.py +138 -0
- swl-5.0.0/example/swl_tasks/app_test.py +240 -0
- swl-5.0.0/example/swl_tasks/static/tasks.css +46 -0
- swl-5.0.0/example/swl_tasks/static/tasks.js +69 -0
- swl-5.0.0/example/swl_tasks/store.py +100 -0
- swl-5.0.0/example/swl_tasks/templates/tasks/detail.j2 +39 -0
- swl-5.0.0/example/swl_tasks/templates/tasks/form.j2 +37 -0
- swl-5.0.0/example/swl_tasks/templates/tasks/list.j2 +53 -0
- swl-5.0.0/example/swl_tasks/templates/tasks/login.j2 +40 -0
- swl-5.0.0/example/swl_tasks/templates/tasks/menu.j2 +26 -0
- swl-5.0.0/example/swl_tasks/templates/tasks/page.j2 +102 -0
- swl-5.0.0/example/swl_tasks/templates/tasks/style_switch.j2 +30 -0
- swl-5.0.0/example/swl_tasks/templates/tasks/tab_details.j2 +32 -0
- swl-5.0.0/example/swl_tasks/templates/tasks/tab_history.j2 +25 -0
- swl-5.0.0/example/swl_tasks/views.py +238 -0
- swl-5.0.0/pyproject.toml +59 -0
- swl-5.0.0/setup.cfg +4 -0
- swl-5.0.0/swl/.gitignore +1 -0
- swl-5.0.0/swl/__init__.py +26 -0
- swl-5.0.0/swl/_version.py +24 -0
- swl-5.0.0/swl/actions.py +150 -0
- swl-5.0.0/swl/actions_test.py +245 -0
- swl-5.0.0/swl/auth_test.py +121 -0
- swl-5.0.0/swl/breadcrumbs.py +52 -0
- swl-5.0.0/swl/config.py +66 -0
- swl-5.0.0/swl/dispatcher.py +46 -0
- swl-5.0.0/swl/dispatcher_test.py +45 -0
- swl-5.0.0/swl/frameworks.py +179 -0
- swl-5.0.0/swl/frameworks_test.py +122 -0
- swl-5.0.0/swl/passwd.py +186 -0
- swl-5.0.0/swl/passwd_test.py +208 -0
- swl-5.0.0/swl/resources/__init__.py +14 -0
- swl-5.0.0/swl/resources/bootstrap-web-resources.json +22 -0
- swl-5.0.0/swl/resources/bulma-web-resources.json +18 -0
- swl-5.0.0/swl/resources/swl-web-resources.json +17 -0
- swl-5.0.0/swl/resources/tabler-web-resources.json +22 -0
- swl-5.0.0/swl/resources/web_resources.py +64 -0
- swl-5.0.0/swl/resources/web_resources_test.py +81 -0
- swl-5.0.0/swl/responses.py +212 -0
- swl-5.0.0/swl/responses_test.py +258 -0
- swl-5.0.0/swl/routes_endpoint.py +135 -0
- swl-5.0.0/swl/routes_endpoint_test.py +123 -0
- swl-5.0.0/swl/session/__init__.py +15 -0
- swl-5.0.0/swl/session/sessionauthbackend.py +30 -0
- swl-5.0.0/swl/session/sessionsecretkey.py +28 -0
- swl-5.0.0/swl/session/sessionsecretkey_test.py +23 -0
- swl-5.0.0/swl/shutdown.py +40 -0
- swl-5.0.0/swl/sort/__init__.py +14 -0
- swl-5.0.0/swl/sort/info.py +76 -0
- swl-5.0.0/swl/sort/info_test.py +81 -0
- swl-5.0.0/swl/static/swl/lang/de_DE.json +16 -0
- swl-5.0.0/swl/static/swl/lang/en_US.json +16 -0
- swl-5.0.0/swl/static/swl/lang/fr_FR.json +16 -0
- swl-5.0.0/swl/static/swl/lang/nl_NL.json +16 -0
- swl-5.0.0/swl/static/swl/lang.js +37 -0
- swl-5.0.0/swl/static/swl/seecr-logo-zonder.png +0 -0
- swl-5.0.0/swl/static/swl/swl-base.css +145 -0
- swl-5.0.0/swl/static/swl/swl-run.js +21 -0
- swl-5.0.0/swl/static/swl/swl.css +412 -0
- swl-5.0.0/swl/static/swl/swl.js +535 -0
- swl-5.0.0/swl/static/swl/theme.js +63 -0
- swl-5.0.0/swl/static/swl/websocket.js +62 -0
- swl-5.0.0/swl/swl.py +171 -0
- swl-5.0.0/swl/swl_test.py +100 -0
- swl-5.0.0/swl/templates/swl/account.j2 +22 -0
- swl-5.0.0/swl/templates/swl/account_password.j2 +42 -0
- swl-5.0.0/swl/templates/swl/alert.j2 +16 -0
- swl-5.0.0/swl/templates/swl/base.j2 +59 -0
- swl-5.0.0/swl/templates/swl/breadcrumbs.j2 +33 -0
- swl-5.0.0/swl/templates/swl/card.j2 +34 -0
- swl-5.0.0/swl/templates/swl/contextmenu.j2 +5 -0
- swl-5.0.0/swl/templates/swl/dialog.j2 +29 -0
- swl-5.0.0/swl/templates/swl/footer.j2 +25 -0
- swl-5.0.0/swl/templates/swl/inputs.j2 +60 -0
- swl-5.0.0/swl/templates/swl/kickstart.j2 +13 -0
- swl-5.0.0/swl/templates/swl/modal-content.j2 +39 -0
- swl-5.0.0/swl/templates/swl/modal.j2 +20 -0
- swl-5.0.0/swl/templates/swl/placeholder.j2 +35 -0
- swl-5.0.0/swl/templates/swl/toast.j2 +4 -0
- swl-5.0.0/swl/templates/swl/users.j2 +56 -0
- swl-5.0.0/swl/templates/swl/users_list.j2 +59 -0
- swl-5.0.0/swl/test/__init__.py +12 -0
- swl-5.0.0/swl/test/js-test/swl.test.js +509 -0
- swl-5.0.0/swl/test/server.py +96 -0
- swl-5.0.0/swl/test/views/__init__.py +16 -0
- swl-5.0.0/swl/test/views/change.py +26 -0
- swl-5.0.0/swl/test/views/misc.py +120 -0
- swl-5.0.0/swl/test/views/modal.py +38 -0
- swl-5.0.0/swl/test/views/rehydrate.py +54 -0
- swl-5.0.0/swl/test_utils.py +117 -0
- swl-5.0.0/swl/userbase.py +233 -0
- swl-5.0.0/swl/userbase_test.py +213 -0
- swl-5.0.0/swl/utils.py +36 -0
- swl-5.0.0/swl/utils_test.py +36 -0
- swl-5.0.0/swl/uvicorn_app.py +41 -0
- swl-5.0.0/swl/views/__init__.py +16 -0
- swl-5.0.0/swl/views/account.py +178 -0
- swl-5.0.0/swl/views/account_test.py +289 -0
- swl-5.0.0/swl.egg-info/PKG-INFO +146 -0
- swl-5.0.0/swl.egg-info/SOURCES.txt +119 -0
- swl-5.0.0/swl.egg-info/dependency_links.txt +1 -0
- swl-5.0.0/swl.egg-info/requires.txt +12 -0
- swl-5.0.0/swl.egg-info/scm_file_list.json +115 -0
- swl-5.0.0/swl.egg-info/scm_version.json +8 -0
- swl-5.0.0/swl.egg-info/top_level.txt +1 -0
swl-5.0.0/.flake8
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[flake8]
|
|
2
|
+
exclude =
|
|
3
|
+
# No need to traverse our git directory
|
|
4
|
+
.git,
|
|
5
|
+
# There's no value in checking cache directories
|
|
6
|
+
__pycache__,
|
|
7
|
+
# This contains our built documentation
|
|
8
|
+
build,
|
|
9
|
+
# This contains builds of flake8 that we don't want to check
|
|
10
|
+
dist,
|
|
11
|
+
# This contains the virtual environment
|
|
12
|
+
env,
|
|
13
|
+
extend-ignore =
|
|
14
|
+
# E266 too many leading '#' for block comment
|
|
15
|
+
E266,
|
|
16
|
+
# docstring
|
|
17
|
+
D10
|
|
18
|
+
per-file-ignores =
|
|
19
|
+
swl/*_test.py:F401,F811
|
|
20
|
+
# Ignore unused imports in __init__
|
|
21
|
+
swl/*/__init__.py:F401,F403
|
|
22
|
+
swl/__init__.py:F401,F403
|
|
23
|
+
max-line-length = 90
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Publishes when a MAJOR.MINOR.PATCH tag (e.g. 0.6.4) is pushed. The tag
|
|
4
|
+
# becomes the package version via setuptools_scm.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
tags: ["[0-9]+.[0-9]+.[0-9]+"]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v5
|
|
14
|
+
with:
|
|
15
|
+
fetch-depth: 0
|
|
16
|
+
- uses: actions/setup-python@v6
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.13"
|
|
19
|
+
- run: pip install build
|
|
20
|
+
- run: python -m build
|
|
21
|
+
- uses: actions/upload-artifact@v4
|
|
22
|
+
with:
|
|
23
|
+
name: dist
|
|
24
|
+
path: dist/
|
|
25
|
+
|
|
26
|
+
publish:
|
|
27
|
+
needs: build
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
environment:
|
|
30
|
+
name: pypi
|
|
31
|
+
url: https://pypi.org/project/swl/
|
|
32
|
+
permissions:
|
|
33
|
+
id-token: write # PyPI trusted publishing, no API token needed
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/download-artifact@v4
|
|
36
|
+
with:
|
|
37
|
+
name: dist
|
|
38
|
+
path: dist/
|
|
39
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v5
|
|
16
|
+
with:
|
|
17
|
+
fetch-depth: 0 # setuptools_scm needs the tags
|
|
18
|
+
- uses: actions/setup-python@v6
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python-version }}
|
|
21
|
+
- run: pip install -e . -e example/
|
|
22
|
+
- run: python -m pytest swl example/swl_tasks
|
swl-5.0.0/.gitignore
ADDED
swl-5.0.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 5.0.0 (2026-10-29)
|
|
4
|
+
|
|
5
|
+
SWL moves its browser side to [htmx 4](https://four.htmx.org). The Python API (actions,
|
|
6
|
+
`register()`, `swl.success()` / `swl.fail()`) stays the same, but templates and JavaScript have
|
|
7
|
+
to be converted: `doc/htmx-migration.md` lists every change.
|
|
8
|
+
|
|
9
|
+
SWL is now released under the MIT License (it was GPL v2 or later), and is published on
|
|
10
|
+
[GitHub](https://github.com/seecr/swl) and [PyPI](https://pypi.org/project/swl/).
|
|
11
|
+
|
|
12
|
+
### Breaking
|
|
13
|
+
|
|
14
|
+
- **htmx instead of SWL's own jQuery code.**
|
|
15
|
+
- The `data-swl-*` attributes are replaced by `hx-*` attributes; `data-swl-validate` and
|
|
16
|
+
`data-swl-on-changed` stay. See the table in `doc/htmx-migration.md`.
|
|
17
|
+
- jQuery is no longer loaded.
|
|
18
|
+
- `rehydrate.js` is gone, and `swl.js` no longer exports `hydrate`, `start_hydrating`,
|
|
19
|
+
`rehydrate_placeholder`, `arguments_from_elem` and `swl_arguments_from_elem`.
|
|
20
|
+
- **The response is HTML plus headers instead of JSON.**
|
|
21
|
+
- Hydrated content arrives as `<hx-partial>` elements, and the other instructions as an
|
|
22
|
+
`HX-Trigger` header.
|
|
23
|
+
- An empty response is `204`; `swl.fail()` is `422`.
|
|
24
|
+
- Tests use `swl.test_utils.assert_success()` and `assert_fail()` instead of
|
|
25
|
+
`response.json()`.
|
|
26
|
+
- **`hydrate()` data can no longer be an action url** that is fetched next. Return a placeholder
|
|
27
|
+
in the HTML instead.
|
|
28
|
+
- **Placeholders post only their own values** (`hx-vals` and `hx-include`), not the fields inside
|
|
29
|
+
or around them.
|
|
30
|
+
- **The modal, message dialog, toast and context menu are native HTML.** The modal and dialog
|
|
31
|
+
are `<dialog>` elements; the toast and context menu are popovers. They no longer need
|
|
32
|
+
Bootstrap's JavaScript.
|
|
33
|
+
- `swl/modal-content.j2` uses `swl-modal-*` classes instead of Bootstrap's `modal-header` /
|
|
34
|
+
`modal-body`.
|
|
35
|
+
- JavaScript that listened to `shown.bs.modal` / `hidden.bs.modal` uses `swl-modal-shown` /
|
|
36
|
+
the dialog's `close` event.
|
|
37
|
+
- Put context menu items in a `<div class="swl-menu">`.
|
|
38
|
+
- A page with its own Bootstrap modal markup (`<div id="swl-modal" class="modal">`) still
|
|
39
|
+
works.
|
|
40
|
+
- **Web resources come from cdn.jsdelivr.net**, with integrity hashes: htmx 4.0.0, Bootstrap
|
|
41
|
+
5.3.8 (was 5.3.0), Bootstrap Icons 1.13.1 (was 1.11.3), Tabler 1.6.1 and Bulma 1.0.4.
|
|
42
|
+
- `urls.cdn` now defaults to `https://cdn.jsdelivr.net` (was `https://cdn.seecr.nl`).
|
|
43
|
+
- `swl/resources/default-web-resources.json` is renamed to `bootstrap-web-resources.json`.
|
|
44
|
+
- **The account and user screens have a new layout.** They have captions above the fields, an
|
|
45
|
+
avatar, and toasts after a change. An application that styled their old Bootstrap markup should
|
|
46
|
+
check them.
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- **Choice of CSS framework**: `ui.framework` is `"bootstrap"` (the default), `"tabler"`,
|
|
51
|
+
`"bulma"` or `"swl"`.
|
|
52
|
+
- `"swl"` uses SWL's own `swl-base.css`, with no framework at all.
|
|
53
|
+
- The choice sets the web resources, and `SWL.ui`: CSS classes for inputs, buttons and tables
|
|
54
|
+
in that framework.
|
|
55
|
+
- `paths.resources` still replaces the resources with your own file.
|
|
56
|
+
- **Theme variables** (`--swl-accent`, `--swl-bg`, ...) for SWL's own parts. They follow
|
|
57
|
+
Bootstrap's, Tabler's or Bulma's colours.
|
|
58
|
+
- **Layout classes in `swl.css`** that work with every framework: `swl-container`, `swl-nav`,
|
|
59
|
+
`swl-columns`, `swl-card`, `swl-tabs`, `swl-stack`, `swl-row`, `swl-field`, `swl-list`,
|
|
60
|
+
`swl-link`, `swl-menu` and more.
|
|
61
|
+
- **Light and dark colours**: `/static/swl/theme.js` with `swl_theme.set("light" | "dark" |
|
|
62
|
+
"auto")`, for every framework.
|
|
63
|
+
- **`FrameworkCookieMiddleware`**: lets a visitor choose the framework with a cookie, for demos
|
|
64
|
+
and style guides.
|
|
65
|
+
- **`placeholder()` macro** (`swl/placeholder.j2`) for elements that load and reload
|
|
66
|
+
themselves.
|
|
67
|
+
- **`swl.success().trigger(event, **detail)`**: fire your own JavaScript event.
|
|
68
|
+
- **`data-swl-close`** on a button closes the modal. `swl/modal-content.j2` has an optional
|
|
69
|
+
`modal_footer` block.
|
|
70
|
+
- **Deep links.**
|
|
71
|
+
- `kickstart` now waits until the page's placeholders have loaded, so it can rebuild nested
|
|
72
|
+
screens.
|
|
73
|
+
- Its `kwargs` may be a dict and are escaped safely.
|
|
74
|
+
- `doc/reference.md` explains deep links: `set_url` plus a page route.
|
|
75
|
+
- **An example application** (`example/`, a task list) with a guided walkthrough. Run it with
|
|
76
|
+
`--framework swl | bootstrap | tabler | bulma`.
|
|
77
|
+
- **Documentation**: `README.md`, `doc/reference.md` (with debugging and troubleshooting) and
|
|
78
|
+
`doc/htmx-migration.md`.
|
|
79
|
+
|
|
80
|
+
### Fixed
|
|
81
|
+
|
|
82
|
+
- Failing requests (HTTP errors, network errors) are reported in SWL's dialog, never swapped into
|
|
83
|
+
the page. A request that ends on a redirect, for example to the login page after the session
|
|
84
|
+
expired, becomes a page load.
|
|
85
|
+
- A function called by name (`call`, `data-swl-validate`, the modal's `call`) that is missing or
|
|
86
|
+
fails is reported, and does not stop the rest of the response.
|
|
87
|
+
- The context menu posts the element's values and the click position correctly.
|
|
88
|
+
- `open_modal(call=...)` runs the function once per opening.
|
|
89
|
+
- A second toast while the first is still showing no longer causes an error.
|
|
90
|
+
- `SWL.globals` referred to an attribute that does not exist.
|
swl-5.0.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
# SWL (Seecr Web Lib)
|
|
6
|
+
|
|
7
|
+
Python library for interactive web apps: Starlette on the server, Jinja templates, htmx 4 in the
|
|
8
|
+
browser. MIT license. Maintained by Seecr (Johan Jonkers, Thijs Janssen).
|
|
9
|
+
|
|
10
|
+
## State of the project
|
|
11
|
+
|
|
12
|
+
- The browser side is htmx 4 (it was jQuery with `data-swl-*` attributes up to 4.3.0; a breaking
|
|
13
|
+
change). `doc/htmx-migration.md` describes converting an application.
|
|
14
|
+
- SWL's modal/dialog are native `<dialog>`, toast and context menu are popovers, styled by
|
|
15
|
+
`swl.css` with `--swl-*` variables; SWL needs no framework JavaScript. `"ui.framework"`:
|
|
16
|
+
"bootstrap" (default), "tabler" (Bootstrap-based), "bulma" or "swl" (SWL's own `swl-base.css`,
|
|
17
|
+
no framework) picks the web resources and `SWL.ui` (classes for inputs/buttons, see
|
|
18
|
+
`swl/frameworks.py`). Layout classes (swl-nav, swl-columns, swl-card, swl-tabs) live in swl.css.
|
|
19
|
+
The example runs with each: `python -m swl_tasks --framework bulma`.
|
|
20
|
+
Pico CSS was considered and rejected (no longer maintained).
|
|
21
|
+
|
|
22
|
+
## Where things are
|
|
23
|
+
|
|
24
|
+
- `swl/swl.py`, `swl/actions.py`: app setup, action registration (`__actions__`, `register(name)`
|
|
25
|
+
gives `/action/<md5>` urls; actions need a logged-in user unless `authenticated=False`).
|
|
26
|
+
- `swl/responses.py`: `swl.success()` / `swl.fail()` builder. Output = HTML with `<hx-partial>`
|
|
27
|
+
elements (from `hydrate`) + an `HX-Trigger` header with SWL events (`swl-call`, `swl-rehydrate`,
|
|
28
|
+
`swl-modal-open/-close`, `swl-contextmenu`, `swl-toast`, `swl-reset-form`, `swl-error`, ...),
|
|
29
|
+
`HX-Redirect`, `HX-Replace-Url`. Empty response = 204, fail = 422 (+ `HX-Reswap: none`).
|
|
30
|
+
- `swl/static/swl/swl.js`: browser side (event handlers, dialogs via `swl.ui`, `swl_action`,
|
|
31
|
+
`swl.rehydrate`, `swl.kickstart`, toasts); `swl-run.js` boots it; `websocket.js` = live updates
|
|
32
|
+
(`data-swl-on-changed` placeholders reload when the dispatcher signals a change).
|
|
33
|
+
- `swl/templates/swl/`: `base.j2` (loads htmx + htmx-config meta), `placeholder.j2` (macro),
|
|
34
|
+
`kickstart.j2`, `modal-content.j2`, account/users views (`swl/views/account.py`).
|
|
35
|
+
- `swl/resources/<framework>-web-resources.json`: CDN scripts/styles per framework, all from
|
|
36
|
+
cdn.jsdelivr.net with SRI hashes (htmx is `htmx.org@4.0.0`); `web_resources.py` loads them.
|
|
37
|
+
- `swl/test_utils.py`: `assert_success(response)` (returns the events), `assert_fail(response, msg)`.
|
|
38
|
+
- `doc/reference.md`: full reference (deep links, tabs, debugging); `doc/htmx-migration.md`.
|
|
39
|
+
- `example/`: task-list example app (`swl_tasks`), README walks one click end to end.
|
|
40
|
+
Run: `pip install -e example && python -m swl_tasks`, login demo / demo-password.
|
|
41
|
+
|
|
42
|
+
## Key conventions (htmx 4 specifics that bite)
|
|
43
|
+
|
|
44
|
+
- Attributes only inherit when written as `hx-vals:inherited` / `hx-confirm:inherited`.
|
|
45
|
+
- Non-form elements (placeholders, links) post only `hx-vals` + `hx-include`, plus `placeholder=<id>`.
|
|
46
|
+
- `HX-Trigger` events fire after the swap; the source may be detached, so listen on `document`.
|
|
47
|
+
- Recipes for rehydrate live on the element (`hx-post` + `hx-vals`), not in sessionStorage.
|
|
48
|
+
- Deep links: actions call `set_url(...)`, the page route renders that screen; `kickstart` only for
|
|
49
|
+
state only an action can produce (it waits until placeholders settle: 100 ms quiet, max 5 s).
|
|
50
|
+
- Values from users/urls into `hx-vals` or kickstart: always `|tojson`.
|
|
51
|
+
- Bootstrap tabs: `hx-trigger="show.bs.tab"`, plus `load, ` on the initially active tab. Without
|
|
52
|
+
Bootstrap: `<nav class="swl-tabs">` with `aria-selected` buttons (see `doc/reference.md`).
|
|
53
|
+
|
|
54
|
+
## Testing
|
|
55
|
+
|
|
56
|
+
- Python: `pytest swl example/swl_tasks` (tests are `*_test.py` next to the code; needs
|
|
57
|
+
`pip install -e . -e example/`). pytest runs with `-n5` (xdist) and `asyncio_mode = auto` from
|
|
58
|
+
`pyproject.toml`; for one test use `pytest -n0 swl/responses_test.py::test_name`.
|
|
59
|
+
- JavaScript: QUnit tests in `swl/test/js-test/swl.test.js`, served by `python -m swl.test.server`
|
|
60
|
+
(open http://localhost:8000/). Headless: run that page in Playwright.
|
|
61
|
+
- Style: flake8 (`.flake8`, max line length 90), isort with the black profile.
|
|
62
|
+
|
|
63
|
+
## Working agreements
|
|
64
|
+
|
|
65
|
+
- Python >= 3.11; all dependencies come from PyPI. `env/` is a local virtualenv, ignored.
|
|
66
|
+
- Versions come from git tags via `setuptools_scm` (tags like `5.0.0`); `swl/_version.py` is
|
|
67
|
+
generated and git-ignored. Pushing a version tag to GitHub publishes that version to PyPI
|
|
68
|
+
(`.github/workflows/publish.yml`), and PyPI never accepts a version twice. Don't push tags
|
|
69
|
+
unless asked. `.github/workflows/test.yml` runs the tests on every push.
|
|
70
|
+
- Never push or publish to PyPI; that is the maintainers' decision. Commit messages are prefixed
|
|
71
|
+
`JJ: `. No `Claude-Session:` lines in commit messages.
|
|
72
|
+
- Keep changes on a branch; report behaviour changes and anything left undone.
|
swl-5.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 Seecr (Seek You Too B.V.) https://seecr.nl
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
swl-5.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: swl
|
|
3
|
+
Version: 5.0.0
|
|
4
|
+
Summary: Seecr Web Lib makes developing webbased apps easier
|
|
5
|
+
Author-email: Thijs Janssen <thijs@seecr.nl>, Johan Jonkers <johan@seecr.nl>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/seecr/swl
|
|
8
|
+
Project-URL: Source, https://github.com/seecr/swl
|
|
9
|
+
Project-URL: Changelog, https://github.com/seecr/swl/blob/main/CHANGELOG.md
|
|
10
|
+
Keywords: swl
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: argon2-cffi<26,>=25.1.0
|
|
16
|
+
Requires-Dist: httpx2<3,>=2.16.0
|
|
17
|
+
Requires-Dist: pytest<10,>=9.2.0
|
|
18
|
+
Requires-Dist: pytest-asyncio<2,>=1.4.1
|
|
19
|
+
Requires-Dist: pytest-xdist<4,>=3.8.0
|
|
20
|
+
Requires-Dist: seecr-stdlib<1,>=0.6.5
|
|
21
|
+
Requires-Dist: seecr-testtools<1,>=0.4.3
|
|
22
|
+
Requires-Dist: setuptools-scm<11,>=10.3.4
|
|
23
|
+
Requires-Dist: starlette<2,>=1.10.0
|
|
24
|
+
Requires-Dist: starlette[full]
|
|
25
|
+
Requires-Dist: uvicorn<1,>=0.54.0
|
|
26
|
+
Requires-Dist: uvicorn[standard]
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# SWL (Seecr Web Lib)
|
|
30
|
+
|
|
31
|
+
[](https://github.com/seecr/swl/actions/workflows/test.yml)
|
|
32
|
+
[](https://pypi.org/project/swl/)
|
|
33
|
+
|
|
34
|
+
Interactive web applications in Python, without a JavaScript frontend.
|
|
35
|
+
|
|
36
|
+
SWL is a small layer on top of [Starlette](https://www.starlette.io),
|
|
37
|
+
[Jinja](https://jinja.palletsprojects.com) and [htmx 4](https://four.htmx.org). The server renders
|
|
38
|
+
HTML; an action answers with a short list of instructions ("put this HTML there, reload that list,
|
|
39
|
+
close the dialog, show a toast") that SWL carries out in the browser. You write Python and
|
|
40
|
+
templates. There is no build step, and no second application to keep in sync.
|
|
41
|
+
|
|
42
|
+
It is made for the software around data: admin screens, editors, review tools, form builders and
|
|
43
|
+
dashboards. These are lots of forms, lists, dialogs and tabs, used every day by people who need
|
|
44
|
+
them to work.
|
|
45
|
+
|
|
46
|
+
## A taste
|
|
47
|
+
|
|
48
|
+
```jinja
|
|
49
|
+
{% from "swl/placeholder.j2" import placeholder %}
|
|
50
|
+
|
|
51
|
+
<form hx-post="{{ register('note_add') }}">
|
|
52
|
+
<input name="text" class="{{ SWL.ui.input }}">
|
|
53
|
+
<button type="submit" class="{{ SWL.ui.button_primary }}">Add</button>
|
|
54
|
+
</form>
|
|
55
|
+
|
|
56
|
+
{{ placeholder("notes", register('notes_list')) }}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
async def notes_list(request, swl, db, **kwargs):
|
|
61
|
+
html = swl.template("notes/list.j2").render(notes=db.notes())
|
|
62
|
+
return swl.success().hydrate("notes", html)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def note_add(request, swl, db, **kwargs):
|
|
66
|
+
async with request.form() as form:
|
|
67
|
+
text = form.get("text", "").strip()
|
|
68
|
+
if not text:
|
|
69
|
+
return swl.fail().message("A note needs some text")
|
|
70
|
+
db.add_note(text)
|
|
71
|
+
return swl.success().reset_form().rehydrate("notes").toast("Note added")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
__actions__ = [notes_list, note_add]
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`note_add` doesn't render the list. It asks the list to reload itself, so `notes_list` stays the
|
|
78
|
+
only place that knows how the list looks. And because a response is data, you test an action
|
|
79
|
+
without a browser.
|
|
80
|
+
|
|
81
|
+
## What you get
|
|
82
|
+
|
|
83
|
+
- **Actions**: async Python functions, registered by module, with login and authorisation checks.
|
|
84
|
+
A template gets an action's url with `register("name")`.
|
|
85
|
+
- **A response builder**: `hydrate`, `rehydrate`, `open_modal`, `close_modal`,
|
|
86
|
+
`open_contextmenu`, `toast`, `call` (a JavaScript function), `set_url`, `redirect`,
|
|
87
|
+
`trigger`, and `swl.fail().message(...)` for errors.
|
|
88
|
+
- **Placeholders and rehydrate**: parts of the page that load themselves and can be reloaded by
|
|
89
|
+
name from any action.
|
|
90
|
+
- **Live updates**: a placeholder can reload when the data it shows changes, also in other
|
|
91
|
+
browser tabs and for other users (over a websocket).
|
|
92
|
+
- **Screens you can reload and link to**: actions keep the url up to date, and a page route
|
|
93
|
+
renders the same screen from it.
|
|
94
|
+
- **Ready-made parts**: a modal, confirm and error dialogs, toasts and context menus, all
|
|
95
|
+
plain HTML (`<dialog>`, popover). There are also login, "my account" and user management
|
|
96
|
+
screens.
|
|
97
|
+
- **Your choice of CSS framework**: `ui.framework` is `"bootstrap"` (the default), `"tabler"`,
|
|
98
|
+
`"bulma"` or `"swl"`, SWL's own small stylesheet with no framework at all. Light and dark
|
|
99
|
+
colours work with each of them.
|
|
100
|
+
|
|
101
|
+
## Install
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
pip install swl
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Or from a checkout: `pip install -e .`. SWL needs Python 3.11 or newer. Its dependencies, including `seecr-stdlib`, come from PyPI.
|
|
108
|
+
|
|
109
|
+
## Try the example
|
|
110
|
+
|
|
111
|
+
The example application is a small task list. It uses nearly everything: dialogs, search as you
|
|
112
|
+
type, tabs, a context menu, live updates between browser tabs, screens that survive a reload,
|
|
113
|
+
and switches for the CSS framework and dark mode.
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
pip install -e .
|
|
117
|
+
pip install -e example/
|
|
118
|
+
python -m swl_tasks
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Open <http://127.0.0.1:8000/> and log in as **demo** / **demo-password**.
|
|
122
|
+
|
|
123
|
+
## Documentation
|
|
124
|
+
|
|
125
|
+
- [`example/README.md`](example/README.md): a guided walk through the example, from one click to
|
|
126
|
+
the updated list. Start here.
|
|
127
|
+
- [`doc/reference.md`](doc/reference.md): everything SWL provides, for looking things up. Also
|
|
128
|
+
covers debugging and troubleshooting.
|
|
129
|
+
- [`doc/htmx-migration.md`](doc/htmx-migration.md): upgrading an application from SWL 4 (jQuery,
|
|
130
|
+
`data-swl-*` attributes) to the htmx version.
|
|
131
|
+
- [`CHANGELOG.md`](CHANGELOG.md): what changed.
|
|
132
|
+
|
|
133
|
+
## Tests
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
pytest swl example/swl_tasks
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The JavaScript tests (QUnit) run in a browser: `python -m swl.test.server`, then open
|
|
140
|
+
<http://localhost:8000/>.
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
SWL is released under the MIT License; see [LICENSE](LICENSE).
|
|
145
|
+
|
|
146
|
+
Copyright © Seecr (Seek You Too B.V.), <https://seecr.nl>.
|
swl-5.0.0/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# SWL (Seecr Web Lib)
|
|
2
|
+
|
|
3
|
+
[](https://github.com/seecr/swl/actions/workflows/test.yml)
|
|
4
|
+
[](https://pypi.org/project/swl/)
|
|
5
|
+
|
|
6
|
+
Interactive web applications in Python, without a JavaScript frontend.
|
|
7
|
+
|
|
8
|
+
SWL is a small layer on top of [Starlette](https://www.starlette.io),
|
|
9
|
+
[Jinja](https://jinja.palletsprojects.com) and [htmx 4](https://four.htmx.org). The server renders
|
|
10
|
+
HTML; an action answers with a short list of instructions ("put this HTML there, reload that list,
|
|
11
|
+
close the dialog, show a toast") that SWL carries out in the browser. You write Python and
|
|
12
|
+
templates. There is no build step, and no second application to keep in sync.
|
|
13
|
+
|
|
14
|
+
It is made for the software around data: admin screens, editors, review tools, form builders and
|
|
15
|
+
dashboards. These are lots of forms, lists, dialogs and tabs, used every day by people who need
|
|
16
|
+
them to work.
|
|
17
|
+
|
|
18
|
+
## A taste
|
|
19
|
+
|
|
20
|
+
```jinja
|
|
21
|
+
{% from "swl/placeholder.j2" import placeholder %}
|
|
22
|
+
|
|
23
|
+
<form hx-post="{{ register('note_add') }}">
|
|
24
|
+
<input name="text" class="{{ SWL.ui.input }}">
|
|
25
|
+
<button type="submit" class="{{ SWL.ui.button_primary }}">Add</button>
|
|
26
|
+
</form>
|
|
27
|
+
|
|
28
|
+
{{ placeholder("notes", register('notes_list')) }}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
async def notes_list(request, swl, db, **kwargs):
|
|
33
|
+
html = swl.template("notes/list.j2").render(notes=db.notes())
|
|
34
|
+
return swl.success().hydrate("notes", html)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def note_add(request, swl, db, **kwargs):
|
|
38
|
+
async with request.form() as form:
|
|
39
|
+
text = form.get("text", "").strip()
|
|
40
|
+
if not text:
|
|
41
|
+
return swl.fail().message("A note needs some text")
|
|
42
|
+
db.add_note(text)
|
|
43
|
+
return swl.success().reset_form().rehydrate("notes").toast("Note added")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
__actions__ = [notes_list, note_add]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`note_add` doesn't render the list. It asks the list to reload itself, so `notes_list` stays the
|
|
50
|
+
only place that knows how the list looks. And because a response is data, you test an action
|
|
51
|
+
without a browser.
|
|
52
|
+
|
|
53
|
+
## What you get
|
|
54
|
+
|
|
55
|
+
- **Actions**: async Python functions, registered by module, with login and authorisation checks.
|
|
56
|
+
A template gets an action's url with `register("name")`.
|
|
57
|
+
- **A response builder**: `hydrate`, `rehydrate`, `open_modal`, `close_modal`,
|
|
58
|
+
`open_contextmenu`, `toast`, `call` (a JavaScript function), `set_url`, `redirect`,
|
|
59
|
+
`trigger`, and `swl.fail().message(...)` for errors.
|
|
60
|
+
- **Placeholders and rehydrate**: parts of the page that load themselves and can be reloaded by
|
|
61
|
+
name from any action.
|
|
62
|
+
- **Live updates**: a placeholder can reload when the data it shows changes, also in other
|
|
63
|
+
browser tabs and for other users (over a websocket).
|
|
64
|
+
- **Screens you can reload and link to**: actions keep the url up to date, and a page route
|
|
65
|
+
renders the same screen from it.
|
|
66
|
+
- **Ready-made parts**: a modal, confirm and error dialogs, toasts and context menus, all
|
|
67
|
+
plain HTML (`<dialog>`, popover). There are also login, "my account" and user management
|
|
68
|
+
screens.
|
|
69
|
+
- **Your choice of CSS framework**: `ui.framework` is `"bootstrap"` (the default), `"tabler"`,
|
|
70
|
+
`"bulma"` or `"swl"`, SWL's own small stylesheet with no framework at all. Light and dark
|
|
71
|
+
colours work with each of them.
|
|
72
|
+
|
|
73
|
+
## Install
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
pip install swl
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Or from a checkout: `pip install -e .`. SWL needs Python 3.11 or newer. Its dependencies, including `seecr-stdlib`, come from PyPI.
|
|
80
|
+
|
|
81
|
+
## Try the example
|
|
82
|
+
|
|
83
|
+
The example application is a small task list. It uses nearly everything: dialogs, search as you
|
|
84
|
+
type, tabs, a context menu, live updates between browser tabs, screens that survive a reload,
|
|
85
|
+
and switches for the CSS framework and dark mode.
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
pip install -e .
|
|
89
|
+
pip install -e example/
|
|
90
|
+
python -m swl_tasks
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Open <http://127.0.0.1:8000/> and log in as **demo** / **demo-password**.
|
|
94
|
+
|
|
95
|
+
## Documentation
|
|
96
|
+
|
|
97
|
+
- [`example/README.md`](example/README.md): a guided walk through the example, from one click to
|
|
98
|
+
the updated list. Start here.
|
|
99
|
+
- [`doc/reference.md`](doc/reference.md): everything SWL provides, for looking things up. Also
|
|
100
|
+
covers debugging and troubleshooting.
|
|
101
|
+
- [`doc/htmx-migration.md`](doc/htmx-migration.md): upgrading an application from SWL 4 (jQuery,
|
|
102
|
+
`data-swl-*` attributes) to the htmx version.
|
|
103
|
+
- [`CHANGELOG.md`](CHANGELOG.md): what changed.
|
|
104
|
+
|
|
105
|
+
## Tests
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
pytest swl example/swl_tasks
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The JavaScript tests (QUnit) run in a browser: `python -m swl.test.server`, then open
|
|
112
|
+
<http://localhost:8000/>.
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
SWL is released under the MIT License; see [LICENSE](LICENSE).
|
|
117
|
+
|
|
118
|
+
Copyright © Seecr (Seek You Too B.V.), <https://seecr.nl>.
|