pyweft 0.2.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/.gitignore +10 -0
- pyweft-0.2.0/CHANGELOG.md +40 -0
- pyweft-0.2.0/LICENSE +21 -0
- pyweft-0.2.0/PKG-INFO +324 -0
- pyweft-0.2.0/README.md +295 -0
- pyweft-0.2.0/docs/getting-started.md +2172 -0
- pyweft-0.2.0/pyproject.toml +72 -0
- pyweft-0.2.0/src/pyweft/__init__.py +60 -0
- pyweft-0.2.0/src/pyweft/__main__.py +4 -0
- pyweft-0.2.0/src/pyweft/background.py +203 -0
- pyweft-0.2.0/src/pyweft/binding.py +118 -0
- pyweft-0.2.0/src/pyweft/cli.py +1006 -0
- pyweft-0.2.0/src/pyweft/component.py +487 -0
- pyweft-0.2.0/src/pyweft/desktop.py +606 -0
- pyweft-0.2.0/src/pyweft/dialogs.py +123 -0
- pyweft-0.2.0/src/pyweft/differ.py +195 -0
- pyweft-0.2.0/src/pyweft/dommodel.py +63 -0
- pyweft-0.2.0/src/pyweft/effects.py +191 -0
- pyweft-0.2.0/src/pyweft/events.py +102 -0
- pyweft-0.2.0/src/pyweft/expressions.py +184 -0
- pyweft-0.2.0/src/pyweft/hotreload.py +122 -0
- pyweft-0.2.0/src/pyweft/packaging.py +186 -0
- pyweft-0.2.0/src/pyweft/parser.py +426 -0
- pyweft-0.2.0/src/pyweft/prerender.py +121 -0
- pyweft-0.2.0/src/pyweft/renderer.py +802 -0
- pyweft-0.2.0/src/pyweft/routing.py +364 -0
- pyweft-0.2.0/src/pyweft/static/loading.html +102 -0
- pyweft-0.2.0/src/pyweft/static/runtime.js +341 -0
- pyweft-0.2.0/src/pyweft/static/shell.html +693 -0
- pyweft-0.2.0/src/pyweft/static/worker.js +89 -0
- pyweft-0.2.0/src/pyweft/store.py +118 -0
- pyweft-0.2.0/src/pyweft/styles.py +171 -0
- pyweft-0.2.0/src/pyweft/vdom.py +57 -0
- pyweft-0.2.0/tests/test_app_settings.py +183 -0
- pyweft-0.2.0/tests/test_async.py +587 -0
- pyweft-0.2.0/tests/test_background.py +245 -0
- pyweft-0.2.0/tests/test_cli.py +345 -0
- pyweft-0.2.0/tests/test_components_kit.py +414 -0
- pyweft-0.2.0/tests/test_compose.py +320 -0
- pyweft-0.2.0/tests/test_desktop.py +203 -0
- pyweft-0.2.0/tests/test_dialogs.py +136 -0
- pyweft-0.2.0/tests/test_differ.py +205 -0
- pyweft-0.2.0/tests/test_dynamic.py +127 -0
- pyweft-0.2.0/tests/test_effects.py +433 -0
- pyweft-0.2.0/tests/test_events.py +294 -0
- pyweft-0.2.0/tests/test_focus.py +98 -0
- pyweft-0.2.0/tests/test_folders.py +232 -0
- pyweft-0.2.0/tests/test_hotreload.py +509 -0
- pyweft-0.2.0/tests/test_inline_handlers.py +142 -0
- pyweft-0.2.0/tests/test_invalidate.py +117 -0
- pyweft-0.2.0/tests/test_libraries.py +121 -0
- pyweft-0.2.0/tests/test_packaging.py +136 -0
- pyweft-0.2.0/tests/test_parser.py +156 -0
- pyweft-0.2.0/tests/test_prerender.py +147 -0
- pyweft-0.2.0/tests/test_renderer.py +205 -0
- pyweft-0.2.0/tests/test_routing.py +249 -0
- pyweft-0.2.0/tests/test_store.py +183 -0
- pyweft-0.2.0/tests/test_styles.py +194 -0
pyweft-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
What changed in each release of `pyweft`. Newest first. The toolkit,
|
|
4
|
+
`pyweft-ui`, has [its own](packages/pyweft-ui/CHANGELOG.md).
|
|
5
|
+
|
|
6
|
+
Add a line under **Unreleased** with each change worth knowing about;
|
|
7
|
+
releasing moves them under the new version (see [RELEASING.md](RELEASING.md)).
|
|
8
|
+
|
|
9
|
+
## Unreleased
|
|
10
|
+
|
|
11
|
+
## 0.2.0
|
|
12
|
+
|
|
13
|
+
The first release. (It starts at 0.2.0 because an earlier, unrelated
|
|
14
|
+
project named `pyweft` used 0.1.0 to 0.1.3 and deleted them, and PyPI never
|
|
15
|
+
lets a deleted file name be used again.)
|
|
16
|
+
|
|
17
|
+
- **Components** in `.weft` files: an XML template, a Python code block and
|
|
18
|
+
a scoped `<style>` block. `{{ }}`, `:attr`, `@event` handlers with
|
|
19
|
+
modifiers and a detailed event object, `@bind` for every form control,
|
|
20
|
+
`<If>`/`<ElseIf>`/`<Else>`, keyed `<For>`, `@property` values.
|
|
21
|
+
- **Composition**: props, `emit`, slots (named, with fallbacks and slot
|
|
22
|
+
props), `@bind` on components, attributes passed to the root element,
|
|
23
|
+
components in folders, component libraries from packages, `<Dynamic>`.
|
|
24
|
+
- **Pages**: `router("pages")`, `<Link>`, `navigate()`, `back()`, typed
|
|
25
|
+
values from the address and query, a not-found page.
|
|
26
|
+
- **State**: `Store` for state shared across components, `@effect`,
|
|
27
|
+
lifecycle hooks, `state_has_changed()`.
|
|
28
|
+
- **Async**: async handlers and `on_init`, `start_task()`, and
|
|
29
|
+
`run_in_background()` (a Web Worker in the browser, a thread elsewhere).
|
|
30
|
+
- **Focus and the page**: `ref=` with `focus()`, `scroll_to()` and friends;
|
|
31
|
+
`set_title()`; `pyweft.dialogs` for opening and saving files and asking
|
|
32
|
+
for confirmation.
|
|
33
|
+
- **Browser target**: Pyodide, with the first screen rendered ahead of time
|
|
34
|
+
and a loading screen that shows real progress.
|
|
35
|
+
- **Desktop target**: `pyweft run --desktop` (pywebview, the `desktop` extra)
|
|
36
|
+
and `pyweft build --desktop` for a standalone program (PyInstaller, the
|
|
37
|
+
`package` extra); title, icon and window settings in `[tool.pyweft]`.
|
|
38
|
+
- **CLI**: `pyweft new`, `run` (hot reload that keeps state, error overlay)
|
|
39
|
+
and `build` (a static site); PyPI dependencies installed in the browser;
|
|
40
|
+
`head.html`, `assets/` and CSS frameworks.
|
pyweft-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Prassanth Nicholas
|
|
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.
|
pyweft-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pyweft
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A UI framework for Python: XML templates, Python code-behind, one render core for web, desktop and mobile.
|
|
5
|
+
Project-URL: Homepage, https://github.com/prassanthn/PyWeft
|
|
6
|
+
Project-URL: Documentation, https://github.com/prassanthn/PyWeft/blob/main/docs/getting-started.md
|
|
7
|
+
Project-URL: Issues, https://github.com/prassanthn/PyWeft/issues
|
|
8
|
+
Author: Prassanth Nicholas
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: components,framework,pyodide,ui,webassembly,webview
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Provides-Extra: desktop
|
|
22
|
+
Requires-Dist: pywebview>=5; extra == 'desktop'
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
25
|
+
Provides-Extra: package
|
|
26
|
+
Requires-Dist: pyinstaller>=6; extra == 'package'
|
|
27
|
+
Requires-Dist: pywebview>=5; extra == 'package'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# PyWeft
|
|
31
|
+
|
|
32
|
+
**Build web and desktop apps in Python.** Write components as `.weft` files,
|
|
33
|
+
an XML template plus a Python code block, and PyWeft runs them in the browser
|
|
34
|
+
with real CPython compiled to WebAssembly, or in a native window with Python
|
|
35
|
+
running on your machine. No JavaScript to write, no build step.
|
|
36
|
+
|
|
37
|
+
*PyWeft: the weft is the thread woven through the warp to make fabric.*
|
|
38
|
+
|
|
39
|
+
```xml
|
|
40
|
+
<template>
|
|
41
|
+
<div>
|
|
42
|
+
<h1>Hello, {{ name or "stranger" }}!</h1>
|
|
43
|
+
<input @bind="name" placeholder="your name" />
|
|
44
|
+
<button @click="count += 1">Clicked {{ count }} times</button>
|
|
45
|
+
</div>
|
|
46
|
+
</template>
|
|
47
|
+
|
|
48
|
+
<code>
|
|
49
|
+
name: str = ""
|
|
50
|
+
count: int = 0
|
|
51
|
+
</code>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Type in the box and the heading follows; click and the count goes up. State
|
|
55
|
+
is plain Python fields, and the page updates itself after every event.
|
|
56
|
+
|
|
57
|
+
> **Status:** a working proof of concept. Web apps and desktop apps (run
|
|
58
|
+
> 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).
|
|
60
|
+
|
|
61
|
+
## Quick start
|
|
62
|
+
|
|
63
|
+
You need Python 3.10 or newer. With [uv](https://docs.astral.sh/uv/):
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
uv init myproject && cd myproject
|
|
67
|
+
uv add git+https://github.com/prassanthn/PyWeft.git
|
|
68
|
+
uv run pyweft new myapp
|
|
69
|
+
uv run pyweft run myapp
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Open <http://127.0.0.1:8642/>. Your page shows straight away, dimmed, while
|
|
73
|
+
the browser downloads Python in the background (a few seconds the first
|
|
74
|
+
time, cached after that); then it comes alive.
|
|
75
|
+
|
|
76
|
+
With pip instead:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install git+https://github.com/prassanthn/PyWeft.git
|
|
80
|
+
pyweft new myapp
|
|
81
|
+
pyweft run myapp
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Now open `myapp/app.weft` in your editor, change something and save: the page
|
|
85
|
+
updates in place and keeps what you'd typed. Make a mistake and an overlay
|
|
86
|
+
shows the file and line; fix it and save again.
|
|
87
|
+
|
|
88
|
+
`myapp/` is your whole app:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
myapp/
|
|
92
|
+
app.weft the root component
|
|
93
|
+
styles.css page-wide CSS (optional)
|
|
94
|
+
pyproject.toml your app's dependencies, installed in the browser
|
|
95
|
+
head.html extra <head> tags: fonts, CDN stylesheets (optional)
|
|
96
|
+
loading.html what visitors see while Python starts (optional)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
When you're ready to publish, `uv run pyweft build myapp` writes a static site
|
|
100
|
+
to `myapp/dist/` that any web host can serve, such as GitHub Pages or Netlify.
|
|
101
|
+
|
|
102
|
+
The same app also runs as a desktop app, in a native window with Python on
|
|
103
|
+
your machine:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
uv add "pyweft[desktop]"
|
|
107
|
+
uv run pyweft run myapp --desktop
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
To hand it to people without Python, build it into one program (an `.exe`
|
|
111
|
+
on Windows, a `.app` on macOS) in `myapp/dist-desktop/`:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
uv add --dev "pyweft[package]"
|
|
115
|
+
uv run pyweft build myapp --desktop
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Name the app and give it an icon in `myapp/pyproject.toml`, for the browser
|
|
119
|
+
tab and the window alike, and size the window there too:
|
|
120
|
+
|
|
121
|
+
```toml
|
|
122
|
+
[tool.pyweft]
|
|
123
|
+
title = "Acme Orders"
|
|
124
|
+
icon = "assets/icon.ico"
|
|
125
|
+
|
|
126
|
+
[tool.pyweft.desktop]
|
|
127
|
+
width = 1200
|
|
128
|
+
height = 800
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Ready-made controls (no HTML or CSS needed)
|
|
132
|
+
|
|
133
|
+
[`pyweft-ui`](packages/pyweft-ui/README.md) gives you styled, accessible
|
|
134
|
+
controls as components: buttons, text fields, selects, checkboxes, switches,
|
|
135
|
+
cards, tabs, sortable tables, dialogs, alerts, toasts, layout and light/dark
|
|
136
|
+
themes.
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
uv add "pyweft-ui @ git+https://github.com/prassanthn/PyWeft.git#subdirectory=packages/pyweft-ui"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Add `"pyweft-ui"` to the `dependencies` list in `myapp/pyproject.toml`, then
|
|
143
|
+
use the controls:
|
|
144
|
+
|
|
145
|
+
```xml
|
|
146
|
+
<template>
|
|
147
|
+
<ui.Theme mode="auto">
|
|
148
|
+
<ui.AppShell title="Orders">
|
|
149
|
+
<ui.Card title="New order">
|
|
150
|
+
<ui.TextField label="Customer" @bind="customer" required="true" />
|
|
151
|
+
<ui.Select label="Size" options="Small, Medium, Large" @bind="size" />
|
|
152
|
+
<ui.Switch label="Express delivery" @bind="express" />
|
|
153
|
+
<Fill name="footer">
|
|
154
|
+
<ui.Button variant="primary" icon="send" @click="place_order">Place order</ui.Button>
|
|
155
|
+
</Fill>
|
|
156
|
+
</ui.Card>
|
|
157
|
+
</ui.AppShell>
|
|
158
|
+
</ui.Theme>
|
|
159
|
+
</template>
|
|
160
|
+
|
|
161
|
+
<code>
|
|
162
|
+
from pyweft_ui import ui
|
|
163
|
+
|
|
164
|
+
customer: str = ""
|
|
165
|
+
size: str = "Medium"
|
|
166
|
+
express: bool = False
|
|
167
|
+
|
|
168
|
+
def place_order(self):
|
|
169
|
+
...
|
|
170
|
+
</code>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
PyWeft notices the toolkit is installed and sends it to the browser for you.
|
|
174
|
+
To see every control live, clone this repository and run
|
|
175
|
+
`uv run pyweft run packages/pyweft-ui/gallery`.
|
|
176
|
+
|
|
177
|
+
## What you get
|
|
178
|
+
|
|
179
|
+
- **Components in one file:** template, Python code and a `<style>` block
|
|
180
|
+
that only applies to that component.
|
|
181
|
+
- **Template syntax:**
|
|
182
|
+
- **Values:** `{{ expression }}` shows a value, `:attr="expression"` sets an
|
|
183
|
+
attribute from Python, `@bind="field"` keeps a form control and a field
|
|
184
|
+
in sync.
|
|
185
|
+
- **Events:** `@click="method"` handles an event, with modifiers like
|
|
186
|
+
`@submit.prevent` and `@keydown.enter`.
|
|
187
|
+
- **Focus:** `ref="name"` marks an element; `self.focus("name")` and
|
|
188
|
+
`self.scroll_to("name")` move the keyboard focus or the page to it.
|
|
189
|
+
- **Control flow:** `<If>`, `<ElseIf>`, `<Else>` and `<For>`.
|
|
190
|
+
- **Composition:** components inside components, with props, events, slots
|
|
191
|
+
for passing content, and `@bind` on your own components. Organise them in
|
|
192
|
+
folders, or install them as packages.
|
|
193
|
+
- **Shared state:** a `Store` holds what many components use, like a cart
|
|
194
|
+
or the signed-in user; change it from anywhere and every page showing it
|
|
195
|
+
updates.
|
|
196
|
+
- **Pages:** a `pages/` folder becomes your app's screens, with links, Back
|
|
197
|
+
and Forward, typed values from the address (`/orders/{id}`), and a
|
|
198
|
+
not-found page.
|
|
199
|
+
- **Lifecycle and effects:** `on_init`, `on_after_render` and `on_dispose`
|
|
200
|
+
run as a component appears, updates and goes away; `@effect` methods
|
|
201
|
+
re-run when the state they read changes.
|
|
202
|
+
- **Async:** `async def` handlers, plus `run_in_background()` to run heavy
|
|
203
|
+
Python in a Web Worker so the page never freezes.
|
|
204
|
+
- **Styling your way:** scoped styles, plain CSS files, or frameworks such as
|
|
205
|
+
Tailwind and Bootstrap through `head.html`.
|
|
206
|
+
- **Packages:** any pure-Python package from PyPI, declared in
|
|
207
|
+
`pyproject.toml`, works in the browser.
|
|
208
|
+
- **Files and dialogs:** `pyweft.dialogs` opens and saves files and asks for
|
|
209
|
+
confirmation with the same code on both targets: the browser's picker and
|
|
210
|
+
downloads on the web, the system's own dialogs on the desktop.
|
|
211
|
+
- **A fast dev loop:** hot reload that keeps your app's state, and readable
|
|
212
|
+
errors that point to your `.weft` file and line.
|
|
213
|
+
- **Fast first load:** your app's first screen is rendered ahead of time and
|
|
214
|
+
shows at once, while Python starts in the background.
|
|
215
|
+
|
|
216
|
+
## Learn more
|
|
217
|
+
|
|
218
|
+
| You want to | Read |
|
|
219
|
+
|---|---|
|
|
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) |
|
|
234
|
+
|
|
235
|
+
`examples/todo/` is a small complete app, and `examples/shop/` one with
|
|
236
|
+
pages; run them from a clone with `uv run pyweft run examples/todo`.
|
|
237
|
+
|
|
238
|
+
## Good to know
|
|
239
|
+
|
|
240
|
+
- **Templates must be well-formed XML:** close every tag (`<br/>`, `<input />`)
|
|
241
|
+
and quote every attribute.
|
|
242
|
+
- **No threads in the browser.** Use `async`/`await` for waiting and
|
|
243
|
+
`run_in_background` for heavy computation.
|
|
244
|
+
- **Code runs in the visitor's browser.** Your `.weft` files are visible to
|
|
245
|
+
them, as with any website's JavaScript, so keep secrets on a server.
|
|
246
|
+
- **Not every package works in a browser:** pure-Python packages do, and so
|
|
247
|
+
do the ~250 that Pyodide ships prebuilt (NumPy, pandas, …). Anything that
|
|
248
|
+
needs raw sockets or subprocesses doesn't.
|
|
249
|
+
|
|
250
|
+
## How it works
|
|
251
|
+
|
|
252
|
+
PyWeft renders HTML rather than native widgets. A component renders to a
|
|
253
|
+
lightweight tree of elements; after every event PyWeft renders again, compares
|
|
254
|
+
the new tree with the old one and sends only the differences to the page as
|
|
255
|
+
small JSON instructions. The core never touches the page itself. That is what
|
|
256
|
+
makes other targets cheap: a desktop app is a webview showing the same page,
|
|
257
|
+
with Python running natively and the same instructions crossing a bridge.
|
|
258
|
+
|
|
259
|
+
| Target | Where Python runs | What draws the page |
|
|
260
|
+
|---|---|---|
|
|
261
|
+
| Web | Pyodide, in the browser | the browser |
|
|
262
|
+
| Desktop (`pyweft run --desktop`) | CPython on the machine | a webview (WebView2, WKWebView, WebKitGTK) |
|
|
263
|
+
| Mobile (later) | CPython via Briefcase | a webview |
|
|
264
|
+
|
|
265
|
+
There is no reactive state system to learn: a handler runs, the component
|
|
266
|
+
renders again, and the comparison finds what changed. `@effect` is the one
|
|
267
|
+
place where PyWeft tracks which fields you read.
|
|
268
|
+
|
|
269
|
+
## Working on PyWeft
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
git clone https://github.com/prassanthn/PyWeft.git && cd PyWeft
|
|
273
|
+
uv sync --extra dev # also installs packages/pyweft-ui, editable
|
|
274
|
+
uv run pytest # 920 tests, including pyweft-ui's
|
|
275
|
+
uv run python examples/demo.py # render -> event -> diff -> apply, no browser
|
|
276
|
+
uv run pyweft run examples/todo # the todo app
|
|
277
|
+
uv run pyweft run packages/pyweft-ui/gallery # every control
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
With pip: `pip install -e ".[dev]"` and `pip install -e packages/pyweft-ui`.
|
|
281
|
+
|
|
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.
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
src/pyweft/ the framework (zero runtime dependencies)
|
|
287
|
+
static/ the browser side: runtime.js, the page shell, the worker
|
|
288
|
+
packages/pyweft-ui/ the control toolkit, released as its own package
|
|
289
|
+
docs/ the guide
|
|
290
|
+
examples/ a headless demo, the todo app and the shop (pages)
|
|
291
|
+
tests/ the framework's tests
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Under the hood:
|
|
295
|
+
- **Patches:** each patch instruction says where to insert, remove, move or
|
|
296
|
+
change something. They're ordered so the page can apply them front to back
|
|
297
|
+
with no bookkeeping.
|
|
298
|
+
- **Testing the differ:** tests apply the patches to the old tree and check
|
|
299
|
+
the result equals the new one, rather than checking the exact instructions.
|
|
300
|
+
400 of the tests do this on randomly generated trees.
|
|
301
|
+
- **Handler ids** are built from list keys where available, so a row keeps
|
|
302
|
+
its handlers when a list is reordered.
|
|
303
|
+
|
|
304
|
+
## Roadmap
|
|
305
|
+
|
|
306
|
+
**Done:** the render core and differ; the browser target; the desktop target
|
|
307
|
+
(pywebview), and standalone desktop programs (PyInstaller); components with props, events, slots and component libraries;
|
|
308
|
+
pages and navigation; shared state with stores; events with modifiers and
|
|
309
|
+
`@bind` for every form control; moving focus from Python; effects; async
|
|
310
|
+
handlers and Web Worker background work; file and confirmation dialogs on
|
|
311
|
+
both targets; the app's title and icon, and window settings; scoped styles
|
|
312
|
+
and CSS framework support; the `pyweft` CLI with hot reload, static builds
|
|
313
|
+
and pre-rendered first screens; `pyweft-ui` with 22 keyboard-accessible
|
|
314
|
+
controls and a gallery.
|
|
315
|
+
|
|
316
|
+
**Next:** publishing `pyweft` and `pyweft-ui` to PyPI; more controls (menus,
|
|
317
|
+
a date picker, form validation); installers (`.msi`, `.dmg`) and code
|
|
318
|
+
signing for desktop apps.
|
|
319
|
+
|
|
320
|
+
**Later:** mobile packaging with Briefcase, developer tools.
|
|
321
|
+
|
|
322
|
+
## License
|
|
323
|
+
|
|
324
|
+
MIT. See [LICENSE](LICENSE).
|