pyweft 0.2.0__tar.gz → 0.3.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.
Files changed (63) hide show
  1. {pyweft-0.2.0 → pyweft-0.3.0}/.gitignore +1 -0
  2. {pyweft-0.2.0 → pyweft-0.3.0}/CHANGELOG.md +21 -0
  3. {pyweft-0.2.0 → pyweft-0.3.0}/PKG-INFO +47 -27
  4. {pyweft-0.2.0 → pyweft-0.3.0}/README.md +45 -25
  5. {pyweft-0.2.0 → pyweft-0.3.0}/docs/getting-started.md +119 -10
  6. {pyweft-0.2.0 → pyweft-0.3.0}/pyproject.toml +9 -1
  7. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/__init__.py +4 -2
  8. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/cli.py +21 -0
  9. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/desktop.py +36 -7
  10. pyweft-0.3.0/src/pyweft/http.py +225 -0
  11. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/static/shell.html +6 -0
  12. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/static/worker.js +3 -0
  13. pyweft-0.3.0/src/pyweft/storage.py +164 -0
  14. pyweft-0.3.0/src/pyweft/webhost.py +88 -0
  15. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_desktop.py +45 -0
  16. pyweft-0.3.0/tests/test_http.py +185 -0
  17. pyweft-0.3.0/tests/test_storage.py +125 -0
  18. {pyweft-0.2.0 → pyweft-0.3.0}/LICENSE +0 -0
  19. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/__main__.py +0 -0
  20. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/background.py +0 -0
  21. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/binding.py +0 -0
  22. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/component.py +0 -0
  23. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/dialogs.py +0 -0
  24. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/differ.py +0 -0
  25. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/dommodel.py +0 -0
  26. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/effects.py +0 -0
  27. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/events.py +0 -0
  28. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/expressions.py +0 -0
  29. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/hotreload.py +0 -0
  30. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/packaging.py +0 -0
  31. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/parser.py +0 -0
  32. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/prerender.py +0 -0
  33. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/renderer.py +0 -0
  34. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/routing.py +0 -0
  35. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/static/loading.html +0 -0
  36. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/static/runtime.js +0 -0
  37. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/store.py +0 -0
  38. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/styles.py +0 -0
  39. {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/vdom.py +0 -0
  40. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_app_settings.py +0 -0
  41. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_async.py +0 -0
  42. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_background.py +0 -0
  43. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_cli.py +0 -0
  44. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_components_kit.py +0 -0
  45. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_compose.py +0 -0
  46. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_dialogs.py +0 -0
  47. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_differ.py +0 -0
  48. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_dynamic.py +0 -0
  49. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_effects.py +0 -0
  50. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_events.py +0 -0
  51. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_focus.py +0 -0
  52. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_folders.py +0 -0
  53. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_hotreload.py +0 -0
  54. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_inline_handlers.py +0 -0
  55. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_invalidate.py +0 -0
  56. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_libraries.py +0 -0
  57. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_packaging.py +0 -0
  58. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_parser.py +0 -0
  59. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_prerender.py +0 -0
  60. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_renderer.py +0 -0
  61. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_routing.py +0 -0
  62. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_store.py +0 -0
  63. {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_styles.py +0 -0
@@ -8,3 +8,4 @@ dist-desktop/
8
8
  .venv/
9
9
  CLAUDE.md
10
10
  PROMPTS.md
11
+ website/_build/
@@ -8,6 +8,27 @@ releasing moves them under the new version (see [RELEASING.md](RELEASING.md)).
8
8
 
9
9
  ## Unreleased
10
10
 
11
+ ## 0.3.0
12
+
13
+ - **`pyweft.http`**: call web APIs and load the app's data files, with the
14
+ same code in the browser (the page's fetch) and on the desktop (the
15
+ standard library). `get_json`, `get`/`post`/`put`/`patch`/`delete`,
16
+ `request`; JSON, form and text bodies, query parameters, timeouts. A
17
+ relative URL is a file the app ships (`assets/prices.json`).
18
+ - **`pyweft.storage`**: keep data between visits: `get`, `set`, `delete`,
19
+ `has`, `keys`, `clear`, for any JSON value. The browser's local storage,
20
+ kept apart per app; on the desktop, a JSON file in the user's data
21
+ folder; in tests, memory.
22
+ - The todo example remembers its list, and the tutorial has a step for it.
23
+ - The todo and shop examples are restyled with Tailwind CSS (built with
24
+ its CLI), in light and dark, and work at phone width. The shop has a
25
+ dashboard, an orders table and a reusable status badge.
26
+
27
+ ## 0.2.1
28
+
29
+ - The PyPI page installs from PyPI, and its links to the guide and the
30
+ toolkit work there.
31
+
11
32
  ## 0.2.0
12
33
 
13
34
  The first release. (It starts at 0.2.0 because an earlier, unrelated
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pyweft
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: A UI framework for Python: XML templates, Python code-behind, one render core for web, desktop and mobile.
5
5
  Project-URL: Homepage, https://github.com/prassanthn/PyWeft
6
- Project-URL: Documentation, https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md
6
+ Project-URL: Documentation, https://prassanthn.github.io/PyWeft/
7
7
  Project-URL: Issues, https://github.com/prassanthn/PyWeft/issues
8
8
  Author: Prassanth Nicholas
9
9
  License-Expression: MIT
@@ -36,6 +36,12 @@ running on your machine. No JavaScript to write, no build step.
36
36
 
37
37
  *PyWeft: the weft is the thread woven through the warp to make fabric.*
38
38
 
39
+ **[Website](https://prassanthn.github.io/PyWeft/)**: a step-by-step
40
+ [tutorial](https://prassanthn.github.io/PyWeft/tutorial/), the
41
+ [guide](https://prassanthn.github.io/PyWeft/guide/), and the
42
+ [controls](https://prassanthn.github.io/PyWeft/live/gallery/) and
43
+ [examples](https://prassanthn.github.io/PyWeft/examples/) running live.
44
+
39
45
  ```xml
40
46
  <template>
41
47
  <div>
@@ -56,7 +62,7 @@ is plain Python fields, and the page updates itself after every event.
56
62
 
57
63
  > **Status:** a working proof of concept. Web apps and desktop apps (run
58
64
  > locally or built into a program to hand out) work today. APIs may still
59
- > change. Not on PyPI yet, so you install from GitHub (below).
65
+ > change.
60
66
 
61
67
  ## Quick start
62
68
 
@@ -64,7 +70,7 @@ You need Python 3.10 or newer. With [uv](https://docs.astral.sh/uv/):
64
70
 
65
71
  ```bash
66
72
  uv init myproject && cd myproject
67
- uv add git+https://github.com/prassanthn/PyWeft.git
73
+ uv add pyweft
68
74
  uv run pyweft new myapp
69
75
  uv run pyweft run myapp
70
76
  ```
@@ -76,7 +82,7 @@ time, cached after that); then it comes alive.
76
82
  With pip instead:
77
83
 
78
84
  ```bash
79
- pip install git+https://github.com/prassanthn/PyWeft.git
85
+ pip install pyweft
80
86
  pyweft new myapp
81
87
  pyweft run myapp
82
88
  ```
@@ -130,13 +136,13 @@ height = 800
130
136
 
131
137
  ## Ready-made controls (no HTML or CSS needed)
132
138
 
133
- [`pyweft-ui`](packages/pyweft-ui/README.md) gives you styled, accessible
139
+ [`pyweft-ui`](https://github.com/prassanthn/PyWeft/blob/main/packages/pyweft-ui/README.md) gives you styled, accessible
134
140
  controls as components: buttons, text fields, selects, checkboxes, switches,
135
141
  cards, tabs, sortable tables, dialogs, alerts, toasts, layout and light/dark
136
142
  themes.
137
143
 
138
144
  ```bash
139
- uv add "pyweft-ui @ git+https://github.com/prassanthn/PyWeft.git#subdirectory=packages/pyweft-ui"
145
+ uv add pyweft-ui
140
146
  ```
141
147
 
142
148
  Add `"pyweft-ui"` to the `dependencies` list in `myapp/pyproject.toml`, then
@@ -208,6 +214,9 @@ To see every control live, clone this repository and run
208
214
  - **Files and dialogs:** `pyweft.dialogs` opens and saves files and asks for
209
215
  confirmation with the same code on both targets: the browser's picker and
210
216
  downloads on the web, the system's own dialogs on the desktop.
217
+ - **Data in and out:** `pyweft.http` calls web APIs and loads your app's
218
+ data files, and `pyweft.storage` keeps settings and lists between visits,
219
+ with the same code in the browser and on the desktop.
211
220
  - **A fast dev loop:** hot reload that keeps your app's state, and readable
212
221
  errors that point to your `.weft` file and line.
213
222
  - **Fast first load:** your app's first screen is rendered ahead of time and
@@ -217,20 +226,21 @@ To see every control live, clone this repository and run
217
226
 
218
227
  | You want to | Read |
219
228
  |---|---|
220
- | Learn the basics: state, templates, events | [Getting started](docs/getting-started.md), sections 1–3 |
221
- | Run code when a component appears, updates or goes away | [Lifecycle](docs/getting-started.md#lifecycle-when-your-code-runs) |
222
- | Split an app into components, use slots | [Composing components](docs/getting-started.md#4-composing-components) |
223
- | Build an app with several pages | [Pages and navigation](docs/getting-started.md#pages-and-navigation) |
224
- | Handle forms and keyboard input | [Binding form controls](docs/getting-started.md#binding-form-controls), [event modifiers](docs/getting-started.md#event-modifiers) |
225
- | Load data, run async code, do heavy work | [Async handlers](docs/getting-started.md#async-handlers-and-background-work), [`run_in_background`](docs/getting-started.md#heavy-work-run_in_background) |
226
- | Style with Tailwind or Bootstrap | [Using CSS frameworks](docs/getting-started.md#using-css-frameworks) |
227
- | Build screens with the ready-made controls | [pyweft-ui guide](packages/pyweft-ui/docs/guide.md), then the [reference](packages/pyweft-ui/README.md) |
228
- | Deploy your app | [Building for deployment](docs/getting-started.md#building-for-deployment) |
229
- | Run it as a desktop app | [A desktop app](docs/getting-started.md#a-desktop-app-pyweft-run---desktop) |
230
- | Hand out a desktop app (`.exe`, `.app`) | [Handing out a desktop app](docs/getting-started.md#handing-out-a-desktop-app-pyweft-build---desktop) |
231
- | Open and save files, ask for confirmation | [Files and dialogs](docs/getting-started.md#files-and-dialogs) |
232
- | Name the app, give it an icon | [The app's title and icon](docs/getting-started.md#the-apps-title-and-icon) |
233
- | Test components without a browser | [Using the engine from plain Python](docs/getting-started.md#6-using-the-engine-from-plain-python) |
229
+ | Learn the basics: state, templates, events | [Getting started](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md), sections 1–3 |
230
+ | Run code when a component appears, updates or goes away | [Lifecycle](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#lifecycle-when-your-code-runs) |
231
+ | Split an app into components, use slots | [Composing components](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#4-composing-components) |
232
+ | Build an app with several pages | [Pages and navigation](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#pages-and-navigation) |
233
+ | Handle forms and keyboard input | [Binding form controls](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#binding-form-controls), [event modifiers](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#event-modifiers) |
234
+ | Load data, run async code, do heavy work | [Async handlers](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#async-handlers-and-background-work), [`run_in_background`](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#heavy-work-run_in_background) |
235
+ | Style with Tailwind or Bootstrap | [Using CSS frameworks](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#using-css-frameworks) |
236
+ | Build screens with the ready-made controls | [pyweft-ui guide](https://github.com/prassanthn/PyWeft/blob/main/packages/pyweft-ui/docs/guide.md), then the [reference](https://github.com/prassanthn/PyWeft/blob/main/packages/pyweft-ui/README.md) |
237
+ | Deploy your app | [Building for deployment](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#building-for-deployment) |
238
+ | Run it as a desktop app | [A desktop app](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#a-desktop-app-pyweft-run---desktop) |
239
+ | Hand out a desktop app (`.exe`, `.app`) | [Handing out a desktop app](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#handing-out-a-desktop-app-pyweft-build---desktop) |
240
+ | Open and save files, ask for confirmation | [Files and dialogs](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#files-and-dialogs) |
241
+ | Call web APIs, save data between visits | [Loading data](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#loading-data-pywefthttp), [saving data](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#saving-data-between-visits-pyweftstorage) |
242
+ | Name the app, give it an icon | [The app's title and icon](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#the-apps-title-and-icon) |
243
+ | Test components without a browser | [Using the engine from plain Python](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#6-using-the-engine-from-plain-python) |
234
244
 
235
245
  `examples/todo/` is a small complete app, and `examples/shop/` one with
236
246
  pages; run them from a clone with `uv run pyweft run examples/todo`.
@@ -279,15 +289,25 @@ uv run pyweft run packages/pyweft-ui/gallery # every control
279
289
 
280
290
  With pip: `pip install -e ".[dev]"` and `pip install -e packages/pyweft-ui`.
281
291
 
282
- Releases go to PyPI by pushing a version tag; [RELEASING.md](RELEASING.md)
283
- has the steps, and [CHANGELOG.md](CHANGELOG.md) what each release changed.
292
+ Releases go to PyPI by pushing a version tag; [RELEASING.md](https://github.com/prassanthn/PyWeft/blob/main/RELEASING.md)
293
+ has the steps, and [CHANGELOG.md](https://github.com/prassanthn/PyWeft/blob/main/CHANGELOG.md) what each release changed.
294
+
295
+ The website is rebuilt and published on every push to `main`. To build it
296
+ locally:
297
+
298
+ ```bash
299
+ uv sync --group website
300
+ uv run python website/build.py
301
+ uv run python -m http.server -d website/_build/site # http://127.0.0.1:8000/
302
+ ```
284
303
 
285
304
  ```
286
305
  src/pyweft/ the framework (zero runtime dependencies)
287
306
  static/ the browser side: runtime.js, the page shell, the worker
288
307
  packages/pyweft-ui/ the control toolkit, released as its own package
289
- docs/ the guide
308
+ docs/ the guide (the website splits it into pages)
290
309
  examples/ a headless demo, the todo app and the shop (pages)
310
+ website/ the website: home page, tutorial, build script
291
311
  tests/ the framework's tests
292
312
  ```
293
313
 
@@ -311,9 +331,9 @@ handlers and Web Worker background work; file and confirmation dialogs on
311
331
  both targets; the app's title and icon, and window settings; scoped styles
312
332
  and CSS framework support; the `pyweft` CLI with hot reload, static builds
313
333
  and pre-rendered first screens; `pyweft-ui` with 22 keyboard-accessible
314
- controls and a gallery.
334
+ controls and a gallery; both released on PyPI.
315
335
 
316
- **Next:** publishing `pyweft` and `pyweft-ui` to PyPI; more controls (menus,
336
+ **Next:** more controls (menus,
317
337
  a date picker, form validation); installers (`.msi`, `.dmg`) and code
318
338
  signing for desktop apps.
319
339
 
@@ -321,4 +341,4 @@ signing for desktop apps.
321
341
 
322
342
  ## License
323
343
 
324
- MIT. See [LICENSE](LICENSE).
344
+ MIT. See [LICENSE](https://github.com/prassanthn/PyWeft/blob/main/LICENSE).
@@ -7,6 +7,12 @@ running on your machine. No JavaScript to write, no build step.
7
7
 
8
8
  *PyWeft: the weft is the thread woven through the warp to make fabric.*
9
9
 
10
+ **[Website](https://prassanthn.github.io/PyWeft/)**: a step-by-step
11
+ [tutorial](https://prassanthn.github.io/PyWeft/tutorial/), the
12
+ [guide](https://prassanthn.github.io/PyWeft/guide/), and the
13
+ [controls](https://prassanthn.github.io/PyWeft/live/gallery/) and
14
+ [examples](https://prassanthn.github.io/PyWeft/examples/) running live.
15
+
10
16
  ```xml
11
17
  <template>
12
18
  <div>
@@ -27,7 +33,7 @@ is plain Python fields, and the page updates itself after every event.
27
33
 
28
34
  > **Status:** a working proof of concept. Web apps and desktop apps (run
29
35
  > locally or built into a program to hand out) work today. APIs may still
30
- > change. Not on PyPI yet, so you install from GitHub (below).
36
+ > change.
31
37
 
32
38
  ## Quick start
33
39
 
@@ -35,7 +41,7 @@ You need Python 3.10 or newer. With [uv](https://docs.astral.sh/uv/):
35
41
 
36
42
  ```bash
37
43
  uv init myproject && cd myproject
38
- uv add git+https://github.com/prassanthn/PyWeft.git
44
+ uv add pyweft
39
45
  uv run pyweft new myapp
40
46
  uv run pyweft run myapp
41
47
  ```
@@ -47,7 +53,7 @@ time, cached after that); then it comes alive.
47
53
  With pip instead:
48
54
 
49
55
  ```bash
50
- pip install git+https://github.com/prassanthn/PyWeft.git
56
+ pip install pyweft
51
57
  pyweft new myapp
52
58
  pyweft run myapp
53
59
  ```
@@ -101,13 +107,13 @@ height = 800
101
107
 
102
108
  ## Ready-made controls (no HTML or CSS needed)
103
109
 
104
- [`pyweft-ui`](packages/pyweft-ui/README.md) gives you styled, accessible
110
+ [`pyweft-ui`](https://github.com/prassanthn/PyWeft/blob/main/packages/pyweft-ui/README.md) gives you styled, accessible
105
111
  controls as components: buttons, text fields, selects, checkboxes, switches,
106
112
  cards, tabs, sortable tables, dialogs, alerts, toasts, layout and light/dark
107
113
  themes.
108
114
 
109
115
  ```bash
110
- uv add "pyweft-ui @ git+https://github.com/prassanthn/PyWeft.git#subdirectory=packages/pyweft-ui"
116
+ uv add pyweft-ui
111
117
  ```
112
118
 
113
119
  Add `"pyweft-ui"` to the `dependencies` list in `myapp/pyproject.toml`, then
@@ -179,6 +185,9 @@ To see every control live, clone this repository and run
179
185
  - **Files and dialogs:** `pyweft.dialogs` opens and saves files and asks for
180
186
  confirmation with the same code on both targets: the browser's picker and
181
187
  downloads on the web, the system's own dialogs on the desktop.
188
+ - **Data in and out:** `pyweft.http` calls web APIs and loads your app's
189
+ data files, and `pyweft.storage` keeps settings and lists between visits,
190
+ with the same code in the browser and on the desktop.
182
191
  - **A fast dev loop:** hot reload that keeps your app's state, and readable
183
192
  errors that point to your `.weft` file and line.
184
193
  - **Fast first load:** your app's first screen is rendered ahead of time and
@@ -188,20 +197,21 @@ To see every control live, clone this repository and run
188
197
 
189
198
  | You want to | Read |
190
199
  |---|---|
191
- | Learn the basics: state, templates, events | [Getting started](docs/getting-started.md), sections 1–3 |
192
- | Run code when a component appears, updates or goes away | [Lifecycle](docs/getting-started.md#lifecycle-when-your-code-runs) |
193
- | Split an app into components, use slots | [Composing components](docs/getting-started.md#4-composing-components) |
194
- | Build an app with several pages | [Pages and navigation](docs/getting-started.md#pages-and-navigation) |
195
- | Handle forms and keyboard input | [Binding form controls](docs/getting-started.md#binding-form-controls), [event modifiers](docs/getting-started.md#event-modifiers) |
196
- | Load data, run async code, do heavy work | [Async handlers](docs/getting-started.md#async-handlers-and-background-work), [`run_in_background`](docs/getting-started.md#heavy-work-run_in_background) |
197
- | Style with Tailwind or Bootstrap | [Using CSS frameworks](docs/getting-started.md#using-css-frameworks) |
198
- | Build screens with the ready-made controls | [pyweft-ui guide](packages/pyweft-ui/docs/guide.md), then the [reference](packages/pyweft-ui/README.md) |
199
- | Deploy your app | [Building for deployment](docs/getting-started.md#building-for-deployment) |
200
- | Run it as a desktop app | [A desktop app](docs/getting-started.md#a-desktop-app-pyweft-run---desktop) |
201
- | Hand out a desktop app (`.exe`, `.app`) | [Handing out a desktop app](docs/getting-started.md#handing-out-a-desktop-app-pyweft-build---desktop) |
202
- | Open and save files, ask for confirmation | [Files and dialogs](docs/getting-started.md#files-and-dialogs) |
203
- | Name the app, give it an icon | [The app's title and icon](docs/getting-started.md#the-apps-title-and-icon) |
204
- | Test components without a browser | [Using the engine from plain Python](docs/getting-started.md#6-using-the-engine-from-plain-python) |
200
+ | Learn the basics: state, templates, events | [Getting started](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md), sections 1–3 |
201
+ | Run code when a component appears, updates or goes away | [Lifecycle](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#lifecycle-when-your-code-runs) |
202
+ | Split an app into components, use slots | [Composing components](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#4-composing-components) |
203
+ | Build an app with several pages | [Pages and navigation](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#pages-and-navigation) |
204
+ | Handle forms and keyboard input | [Binding form controls](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#binding-form-controls), [event modifiers](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#event-modifiers) |
205
+ | Load data, run async code, do heavy work | [Async handlers](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#async-handlers-and-background-work), [`run_in_background`](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#heavy-work-run_in_background) |
206
+ | Style with Tailwind or Bootstrap | [Using CSS frameworks](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#using-css-frameworks) |
207
+ | Build screens with the ready-made controls | [pyweft-ui guide](https://github.com/prassanthn/PyWeft/blob/main/packages/pyweft-ui/docs/guide.md), then the [reference](https://github.com/prassanthn/PyWeft/blob/main/packages/pyweft-ui/README.md) |
208
+ | Deploy your app | [Building for deployment](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#building-for-deployment) |
209
+ | Run it as a desktop app | [A desktop app](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#a-desktop-app-pyweft-run---desktop) |
210
+ | Hand out a desktop app (`.exe`, `.app`) | [Handing out a desktop app](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#handing-out-a-desktop-app-pyweft-build---desktop) |
211
+ | Open and save files, ask for confirmation | [Files and dialogs](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#files-and-dialogs) |
212
+ | Call web APIs, save data between visits | [Loading data](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#loading-data-pywefthttp), [saving data](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#saving-data-between-visits-pyweftstorage) |
213
+ | Name the app, give it an icon | [The app's title and icon](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#the-apps-title-and-icon) |
214
+ | Test components without a browser | [Using the engine from plain Python](https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md#6-using-the-engine-from-plain-python) |
205
215
 
206
216
  `examples/todo/` is a small complete app, and `examples/shop/` one with
207
217
  pages; run them from a clone with `uv run pyweft run examples/todo`.
@@ -250,15 +260,25 @@ uv run pyweft run packages/pyweft-ui/gallery # every control
250
260
 
251
261
  With pip: `pip install -e ".[dev]"` and `pip install -e packages/pyweft-ui`.
252
262
 
253
- Releases go to PyPI by pushing a version tag; [RELEASING.md](RELEASING.md)
254
- has the steps, and [CHANGELOG.md](CHANGELOG.md) what each release changed.
263
+ Releases go to PyPI by pushing a version tag; [RELEASING.md](https://github.com/prassanthn/PyWeft/blob/main/RELEASING.md)
264
+ has the steps, and [CHANGELOG.md](https://github.com/prassanthn/PyWeft/blob/main/CHANGELOG.md) what each release changed.
265
+
266
+ The website is rebuilt and published on every push to `main`. To build it
267
+ locally:
268
+
269
+ ```bash
270
+ uv sync --group website
271
+ uv run python website/build.py
272
+ uv run python -m http.server -d website/_build/site # http://127.0.0.1:8000/
273
+ ```
255
274
 
256
275
  ```
257
276
  src/pyweft/ the framework (zero runtime dependencies)
258
277
  static/ the browser side: runtime.js, the page shell, the worker
259
278
  packages/pyweft-ui/ the control toolkit, released as its own package
260
- docs/ the guide
279
+ docs/ the guide (the website splits it into pages)
261
280
  examples/ a headless demo, the todo app and the shop (pages)
281
+ website/ the website: home page, tutorial, build script
262
282
  tests/ the framework's tests
263
283
  ```
264
284
 
@@ -282,9 +302,9 @@ handlers and Web Worker background work; file and confirmation dialogs on
282
302
  both targets; the app's title and icon, and window settings; scoped styles
283
303
  and CSS framework support; the `pyweft` CLI with hot reload, static builds
284
304
  and pre-rendered first screens; `pyweft-ui` with 22 keyboard-accessible
285
- controls and a gallery.
305
+ controls and a gallery; both released on PyPI.
286
306
 
287
- **Next:** publishing `pyweft` and `pyweft-ui` to PyPI; more controls (menus,
307
+ **Next:** more controls (menus,
288
308
  a date picker, form validation); installers (`.msi`, `.dmg`) and code
289
309
  signing for desktop apps.
290
310
 
@@ -292,4 +312,4 @@ signing for desktop apps.
292
312
 
293
313
  ## License
294
314
 
295
- MIT. See [LICENSE](LICENSE).
315
+ MIT. See [LICENSE](https://github.com/prassanthn/PyWeft/blob/main/LICENSE).
@@ -51,6 +51,8 @@ later mobile: only the transport differs.
51
51
  [PyPI packages](#using-pypi-packages),
52
52
  [calling JavaScript](#calling-javascript),
53
53
  [files and dialogs](#files-and-dialogs),
54
+ [loading data (http)](#loading-data-pywefthttp),
55
+ [saving data between visits (storage)](#saving-data-between-visits-pyweftstorage),
54
56
  [building for deployment](#building-for-deployment),
55
57
  [a desktop app](#a-desktop-app-pyweft-run---desktop),
56
58
  [handing it out](#handing-out-a-desktop-app-pyweft-build---desktop)
@@ -62,18 +64,17 @@ later mobile: only the transport differs.
62
64
 
63
65
  ## 1. Install
64
66
 
65
- PyWeft is not on PyPI yet, so install it straight from GitHub. With uv:
67
+ With uv:
66
68
 
67
69
  ```bash
68
70
  uv init myproject && cd myproject
69
- uv add git+https://github.com/prassanthn/PyWeft.git
71
+ uv add pyweft
70
72
  uv run pyweft new myapp
71
73
  uv run pyweft run myapp
72
74
  ```
73
75
 
74
- With pip, the equivalent is
75
- `pip install git+https://github.com/prassanthn/PyWeft.git`, then `pyweft new`
76
- and `pyweft run` directly.
76
+ With pip, the equivalent is `pip install pyweft`, then `pyweft new` and
77
+ `pyweft run` directly.
77
78
 
78
79
  To hack on the framework itself, clone it and install in editable mode:
79
80
 
@@ -1695,6 +1696,21 @@ page:
1695
1696
  `@apply` works in `_tailwind/input.css`, but not in a component's `<style>`
1696
1697
  block, which Tailwind never reads.
1697
1698
 
1699
+ **Classes that depend on state.** A `:class` sets the whole attribute, so
1700
+ an element with both `class` and `:class` gets only the `:class` one. Put
1701
+ the fixed classes in the expression too:
1702
+
1703
+ ```xml
1704
+ <button @click="show = name"
1705
+ :class="'rounded-lg px-3 py-1.5 ' + ('bg-slate-900 text-white' if name == show else 'text-slate-500')">
1706
+ {{ name }}
1707
+ </button>
1708
+ ```
1709
+
1710
+ The todo and shop examples (`examples/todo`, `examples/shop`) are styled
1711
+ with Tailwind's CLI exactly as above: see each one's `_tailwind/input.css`
1712
+ for the setup and the command that rebuilds its `tailwind.css`.
1713
+
1698
1714
  ### Ready-made controls: pyweft-ui
1699
1715
 
1700
1716
  If you'd rather not write HTML and CSS at all, `pyweft-ui` gives you styled,
@@ -1729,15 +1745,13 @@ reference; and the gallery shows every control live:
1729
1745
  uv run pyweft run packages/pyweft-ui/gallery
1730
1746
  ```
1731
1747
 
1732
- It is not on PyPI yet. Install it from GitHub into the same environment as
1733
- PyWeft:
1748
+ Install it into the same environment as PyWeft:
1734
1749
 
1735
1750
  ```bash
1736
- uv add "pyweft-ui @ git+https://github.com/prassanthn/PyWeft.git#subdirectory=packages/pyweft-ui"
1751
+ uv add pyweft-ui
1737
1752
  ```
1738
1753
 
1739
- (with pip: `pip install "git+https://github.com/prassanthn/PyWeft.git#subdirectory=packages/pyweft-ui"`),
1740
- then add `"pyweft-ui"` to the `dependencies` in your app's own
1754
+ (with pip: `pip install pyweft-ui`), then add `"pyweft-ui"` to the `dependencies` in your app's own
1741
1755
  `pyproject.toml`. PyWeft ships it to the browser for you (see "Components
1742
1756
  from packages"). Inside a clone of this repository, `uv sync` already
1743
1757
  installs it.
@@ -1872,6 +1886,101 @@ async def clear(self):
1872
1886
  - **In tests**, `dialogs.set_host()` installs a stand-in that answers
1873
1887
  instead of the user; without one, the calls raise an error saying so.
1874
1888
 
1889
+ ### Loading data: pyweft.http
1890
+
1891
+ `pyweft.http` calls web APIs and loads files, the same way in the browser
1892
+ and in a [desktop app](#a-desktop-app-pyweft-run---desktop). Requests are
1893
+ `async`, so make them in `async` handlers or an `async def on_init`:
1894
+
1895
+ ```python
1896
+ from pyweft import http
1897
+
1898
+ orders: list = []
1899
+ error: str = ""
1900
+
1901
+ async def on_init(self):
1902
+ self.orders = await http.get_json("https://api.example.com/orders")
1903
+
1904
+ async def save(self, order):
1905
+ response = await http.post("https://api.example.com/orders", json=order)
1906
+ if not response.ok:
1907
+ self.error = f"Couldn't save the order ({response.status})"
1908
+ ```
1909
+
1910
+ | Call | Returns |
1911
+ |---|---|
1912
+ | `get_json(url)` | the body parsed as JSON; `http.HTTPError` unless the status is 2xx |
1913
+ | `get(url)`, `post(url)`, `put(url)`, `patch(url)`, `delete(url)` | a `Response`, whatever its status |
1914
+ | `request(method, url)` | the same, for any method |
1915
+
1916
+ Each takes `params=` (added to the URL's query), `headers=`, `timeout=`
1917
+ (seconds, 30 by default) and a body: `json=` (any JSON value) or `data=`
1918
+ (bytes, text, or a dict sent as a form).
1919
+
1920
+ - **A `Response`** has `status`, `ok` (a 2xx status), `headers` (names in
1921
+ lower case), `data` (the bytes), `text` and `json()`.
1922
+ `raise_for_status()` raises `http.HTTPError` unless it's 2xx.
1923
+ - **A relative URL is a file your app ships.** Put data files in
1924
+ `assets/`, and `await http.get_json("assets/prices.json")` reads them in
1925
+ the browser and in a desktop window alike.
1926
+ - **Failures:** no answer at all (offline, a mistyped address) raises
1927
+ `http.NetworkError`; a slow server raises `TimeoutError`.
1928
+ - **In the browser, the browser's rules apply.** A request to another site
1929
+ only works if that server allows it (it must send CORS headers; public
1930
+ APIs usually do). There is no such rule in a desktop app.
1931
+ - **Everyone can see the requests a web app makes**, as with any web page,
1932
+ so an API key in your code isn't secret. Keep secrets on a server of
1933
+ your own and call that.
1934
+ - **In tests**, requests go out for real through the standard library.
1935
+ `http.set_host()` installs a stand-in instead: an object with `async
1936
+ request(method, url, headers, body, timeout)` returning `(status,
1937
+ headers, data, url)`.
1938
+
1939
+ ### Saving data between visits: pyweft.storage
1940
+
1941
+ `pyweft.storage` keeps small amounts of data, such as settings, a draft or
1942
+ a list, until the next visit:
1943
+
1944
+ ```python
1945
+ from pyweft import effect, storage
1946
+
1947
+ items: list = []
1948
+
1949
+ def on_init(self):
1950
+ self.items = storage.get("todos", []) # [] the first time
1951
+
1952
+ @effect
1953
+ def keep(self):
1954
+ storage.set("todos", self.items) # runs again whenever items changes
1955
+ ```
1956
+
1957
+ | Call | Does |
1958
+ |---|---|
1959
+ | `get(key, default=None)` | the stored value, or `default` |
1960
+ | `set(key, value)` | stores `value`, replacing what was there |
1961
+ | `delete(key)`, `clear()` | removes one key, or everything this app stored |
1962
+ | `has(key)`, `keys()` | whether a key has a value; every key, sorted |
1963
+
1964
+ - **Values are anything JSON can hold:** numbers, text, `True`/`False`/
1965
+ `None`, and lists and dicts of those. Anything else raises `TypeError`;
1966
+ turn dates into text first, for example.
1967
+ - **`get` returns a copy.** Changing a list you got back doesn't change
1968
+ what's stored until you `set` it again, which is what the effect above
1969
+ does.
1970
+ - **The calls aren't `async`**, so they work anywhere: in `on_init`, an
1971
+ effect, a property, or a module of your app.
1972
+ - **Where it lives:** in the browser, the browser's local storage for your
1973
+ site (about 5 MB in all, cleared if the visitor clears site data), kept
1974
+ apart per app by the `[project]` name in its `pyproject.toml`. In a
1975
+ desktop app, a JSON file in the user's data folder:
1976
+ `%APPDATA%\<name>\storage.json` on Windows,
1977
+ `~/Library/Application Support/<name>/` on macOS,
1978
+ `~/.local/share/<name>/` on Linux.
1979
+ - **Not in `run_in_background`:** the worker can't reach the page's
1980
+ storage, so read what the function needs first and pass it in.
1981
+ - **In tests** it lives in memory. `storage.set_host(storage.MemoryStorage())`
1982
+ starts a test empty; `storage.FileStorage(path)` keeps it in a file.
1983
+
1875
1984
  ### Building for deployment
1876
1985
 
1877
1986
  ```bash
@@ -35,7 +35,7 @@ package = ["pywebview>=5", "pyinstaller>=6"]
35
35
 
36
36
  [project.urls]
37
37
  Homepage = "https://github.com/prassanthn/PyWeft"
38
- Documentation = "https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md"
38
+ Documentation = "https://prassanthn.github.io/PyWeft/"
39
39
  Issues = "https://github.com/prassanthn/PyWeft/issues"
40
40
 
41
41
  [project.scripts]
@@ -70,3 +70,11 @@ pyweft-ui = { workspace = true }
70
70
 
71
71
  [dependency-groups]
72
72
  dev = ["pyweft-ui"]
73
+ # The website (website/build.py): uv sync --group website
74
+ # and the examples' Tailwind stylesheets (examples/*/_tailwind/input.css)
75
+ website = [
76
+ "mkdocs-material>=9.8,<10; python_version >= '3.11'",
77
+ "pytailwindcss>=0.4; python_version >= '3.11'",
78
+ # the todo example's own dependency, so its first screen can be pre-rendered
79
+ "humanize",
80
+ ]
@@ -4,7 +4,7 @@ Markup in XML, logic in Python, one render core that emits transport-agnostic
4
4
  patches so desktop (webview), web (Pyodide) and mobile can share a codebase.
5
5
  """
6
6
 
7
- from . import dialogs
7
+ from . import dialogs, http, storage
8
8
  from .background import BackgroundError, run_in_background
9
9
  from .component import (
10
10
  Component,
@@ -24,7 +24,7 @@ from .store import Store
24
24
  from .styles import collect_styles, scope_css
25
25
  from .vdom import Element, Node, Text, to_dicts
26
26
 
27
- __version__ = "0.2.0"
27
+ __version__ = "0.3.0"
28
28
 
29
29
  __all__ = [
30
30
  "Component",
@@ -43,6 +43,8 @@ __all__ = [
43
43
  "Store",
44
44
  "set_title",
45
45
  "dialogs",
46
+ "http",
47
+ "storage",
46
48
  "collect_styles",
47
49
  "scope_css",
48
50
  "diff",
@@ -234,6 +234,26 @@ def app_identity(app_dir: Path) -> AppIdentity:
234
234
  return AppIdentity(title=title.strip(), icon=icon)
235
235
 
236
236
 
237
+ def app_id(app_dir: Path) -> str:
238
+ """A stable name for keeping the app's data apart (pyweft.storage): the
239
+ ``[project]`` name from its pyproject.toml, else the folder's name.
240
+ Renaming the project starts its saved data afresh."""
241
+ name = ""
242
+ pyproject = app_dir / "pyproject.toml"
243
+ if pyproject.exists():
244
+ text = pyproject.read_text(encoding="utf-8")
245
+ if tomllib is not None:
246
+ try:
247
+ name = tomllib.loads(text).get("project", {}).get("name", "")
248
+ except tomllib.TOMLDecodeError as exc:
249
+ raise CliError(f"{pyproject}: {exc}") from None
250
+ else:
251
+ name = _flat_table(text, "project").get("name", "")
252
+ if not isinstance(name, str) or not name.strip():
253
+ name = app_dir.resolve().name
254
+ return re.sub(r"[^a-z0-9._-]+", "-", name.strip().lower()).strip("-.") or "app"
255
+
256
+
237
257
  # mimetypes reads the Windows registry, which can be wrong or missing these.
238
258
  _CONTENT_TYPES = {
239
259
  ".css": "text/css",
@@ -515,6 +535,7 @@ def render_shell(
515
535
  "__PYWEFT_LIBRARIES__",
516
536
  json.dumps([{"name": lib.name, "files": lib.files} for lib in app_libraries(app_dir)]),
517
537
  )
538
+ .replace("__PYWEFT_APP_ID__", json.dumps(app_id(app_dir)))
518
539
  .replace("__PYWEFT_DEV__", "true" if dev else "false")
519
540
  .replace("__PYWEFT_SINCE__", str(since))
520
541
  .replace("__PYWEFT_CSS__", css)