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.
- {pyweft-0.2.0 → pyweft-0.3.0}/.gitignore +1 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/CHANGELOG.md +21 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/PKG-INFO +47 -27
- {pyweft-0.2.0 → pyweft-0.3.0}/README.md +45 -25
- {pyweft-0.2.0 → pyweft-0.3.0}/docs/getting-started.md +119 -10
- {pyweft-0.2.0 → pyweft-0.3.0}/pyproject.toml +9 -1
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/__init__.py +4 -2
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/cli.py +21 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/desktop.py +36 -7
- pyweft-0.3.0/src/pyweft/http.py +225 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/static/shell.html +6 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/static/worker.js +3 -0
- pyweft-0.3.0/src/pyweft/storage.py +164 -0
- pyweft-0.3.0/src/pyweft/webhost.py +88 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_desktop.py +45 -0
- pyweft-0.3.0/tests/test_http.py +185 -0
- pyweft-0.3.0/tests/test_storage.py +125 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/LICENSE +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/__main__.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/background.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/binding.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/component.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/dialogs.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/differ.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/dommodel.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/effects.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/events.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/expressions.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/hotreload.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/packaging.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/parser.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/prerender.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/renderer.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/routing.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/static/loading.html +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/static/runtime.js +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/store.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/styles.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/src/pyweft/vdom.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_app_settings.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_async.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_background.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_cli.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_components_kit.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_compose.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_dialogs.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_differ.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_dynamic.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_effects.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_events.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_focus.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_folders.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_hotreload.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_inline_handlers.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_invalidate.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_libraries.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_packaging.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_parser.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_prerender.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_renderer.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_routing.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_store.py +0 -0
- {pyweft-0.2.0 → pyweft-0.3.0}/tests/test_styles.py +0 -0
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
|
233
|
-
|
|
|
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:**
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
|
204
|
-
|
|
|
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:**
|
|
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
|
-
|
|
67
|
+
With uv:
|
|
66
68
|
|
|
67
69
|
```bash
|
|
68
70
|
uv init myproject && cd myproject
|
|
69
|
-
uv add
|
|
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
|
-
`
|
|
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
|
-
|
|
1733
|
-
PyWeft:
|
|
1748
|
+
Install it into the same environment as PyWeft:
|
|
1734
1749
|
|
|
1735
1750
|
```bash
|
|
1736
|
-
uv add
|
|
1751
|
+
uv add pyweft-ui
|
|
1737
1752
|
```
|
|
1738
1753
|
|
|
1739
|
-
(with pip: `pip install "
|
|
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.
|
|
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.
|
|
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)
|