glue-ui 0.6.6__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.
glue_ui-0.6.6/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Chris Knott
4
+ Copyright (c) 2026 alepodj
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ include README.md
glue_ui-0.6.6/PKG-INFO ADDED
@@ -0,0 +1,450 @@
1
+ Metadata-Version: 2.4
2
+ Name: glue-ui
3
+ Version: 0.6.6
4
+ Summary: For little HTML GUI applications, with easy Python/JS interop
5
+ Home-page: https://github.com/alepodj/Glue
6
+ Author: alepodj
7
+ Keywords: gui,html,javascript,desktop,pywebview,chrome,edge
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Natural Language :: English
10
+ Classifier: Operating System :: MacOS
11
+ Classifier: Operating System :: POSIX
12
+ Classifier: Operating System :: Microsoft :: Windows :: Windows 10
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Programming Language :: Python :: Implementation :: CPython
20
+ Classifier: License :: OSI Approved :: MIT License
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: bottle<1.0.0
25
+ Requires-Dist: bottle-websocket<1.0.0
26
+ Requires-Dist: gevent
27
+ Requires-Dist: gevent-websocket<1.0.0
28
+ Requires-Dist: pyparsing<4.0.0,>=3.0.0
29
+ Requires-Dist: pywebview>=6.0
30
+ Provides-Extra: jinja2
31
+ Requires-Dist: jinja2>=2.10; extra == "jinja2"
32
+ Provides-Extra: build
33
+ Requires-Dist: pyinstaller; extra == "build"
34
+ Dynamic: author
35
+ Dynamic: classifier
36
+ Dynamic: description
37
+ Dynamic: description-content-type
38
+ Dynamic: home-page
39
+ Dynamic: keywords
40
+ Dynamic: license-file
41
+ Dynamic: provides-extra
42
+ Dynamic: requires-dist
43
+ Dynamic: requires-python
44
+ Dynamic: summary
45
+
46
+ # Glue
47
+ <sub>Glue is a fork of [Eel](https://github.com/python-eel/Eel) by Chris Knott and contributors.</sub>
48
+
49
+ A little and opinionated Python library for making **desktop apps with HTML, CSS, and JavaScript** — plus full access to Python. It hosts a local window, then lets Python and JavaScript call each other. No new UI framework.
50
+
51
+ ![Presentation — cover](assets/readme/01-cover.png)
52
+ ![Presentation — pitch and simplest app](assets/readme/02-pitch.png)
53
+ ![Presentation — familiar window](assets/readme/03-window.png)
54
+ ![Presentation — Frontend ↔ Backend](assets/readme/04-front-to-back.png)
55
+ ![Presentation — why it feels easy](assets/readme/05-why.png)
56
+
57
+ ## Contents
58
+
59
+ - [Install](#install)
60
+ - [Quick start](#quick-start)
61
+ - [Demo](#demo)
62
+ - [Call both ways](#call-both-ways)
63
+ - [Hello, World!](#hello-world)
64
+ - [Return values](#return-values)
65
+ - [Settings](#settings)
66
+ - [App options](#app-options)
67
+ - [Hosts](#hosts)
68
+ - [Async Python](#async-python)
69
+ - [Package with PyInstaller](#package-with-pyinstaller)
70
+ - [Examples](#examples)
71
+ - [Project layout](#project-layout)
72
+
73
+ ---
74
+
75
+ ## Install
76
+
77
+ ```shell
78
+ pip install glue-ui
79
+ ```
80
+
81
+ That installs the **PyPI** project `glue-ui`. You still `import glue` in Python (the import name is unchanged).
82
+
83
+ From a clone of this repo:
84
+
85
+ ```shell
86
+ pip install .
87
+ ```
88
+
89
+ Editable / development (code changes apply without reinstalling):
90
+
91
+ ```shell
92
+ pip install -e .
93
+ ```
94
+
95
+ Directly from GitHub (no local clone required):
96
+
97
+ ```shell
98
+ pip install "git+https://github.com/alepodj/Glue.git@main"
99
+ ```
100
+
101
+ Optional extras:
102
+
103
+ ```shell
104
+ pip install "glue-ui[jinja2]" # Jinja2 templates
105
+ pip install "glue-ui[build]" # PyInstaller for packaging
106
+ # From a clone: pip install ".[jinja2]" / pip install ".[build]"
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Quick start
112
+
113
+ Put your frontend in a folder named `ui/` and `index.html` (the defaults), then:
114
+
115
+ ```python
116
+ import glue
117
+
118
+ glue.init() # Override ui/ with any path: glue.init('frontend')
119
+ glue.start() # Override index.html: glue.start('main.html')
120
+ ```
121
+
122
+ By default Glue opens a **PyWebView** native window. If that isn’t available, it falls back to **Chrome/Chromium** in app mode (`--app`), then **Edge** on Windows only.
123
+
124
+ Include the bridge on every html page:
125
+
126
+ ```html
127
+ <script type="text/javascript" src="/glue.js"></script>
128
+ ```
129
+
130
+ ***Put your backend logic in one Python file or split it across any folders you like — Glue does not require a fixed backend layout. You own the imports and paths; your start point is the Python script you run (often at the project root).***
131
+
132
+ ### Security note
133
+
134
+ By default Glue binds to **localhost** and only accepts Glue WebSocket clients from the loopback interface. Every `@glue.expose`d Python function is callable by any page that can open that socket — treat exposed functions as your trust boundary. Do not put secrets under the frontend folder (`ui/`). Use `all_interfaces=True` only on trusted networks (it allows remote clients to call exposed functions).
135
+
136
+ ---
137
+
138
+ ## Demo
139
+
140
+ The [`examples/00 - presentation`](examples/00%20-%20presentation) app is a full walkthrough of the idea. Run it with:
141
+
142
+ ```shell
143
+ cd "examples/00 - presentation"
144
+ python presentation.py
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Call both ways
150
+
151
+ ### Python → available in JavaScript
152
+
153
+ ```python
154
+ #python
155
+ @glue.expose
156
+ def add(a, b):
157
+ return a + b
158
+ ```
159
+
160
+ ```javascript
161
+ //javascript
162
+ let sum = await glue.add(1, 2)();
163
+ ```
164
+
165
+ ### JavaScript → available in Python
166
+
167
+ ```javascript
168
+ //javascript
169
+ glue.expose(js_random);
170
+ function js_random() {
171
+ return Math.random();
172
+ }
173
+ ```
174
+
175
+ ```python
176
+ #python
177
+ n = glue.js_random()() # wait for the value
178
+ glue.js_random()(print) # or use a callback
179
+ ```
180
+
181
+ Complex values travel as JSON over a WebSocket.
182
+
183
+ If a JS bundler renames functions, expose them with an explicit name:
184
+
185
+ ```javascript
186
+ glue.expose(someFunction, "my_javascript_function");
187
+ ```
188
+
189
+ ---
190
+
191
+ ## Hello, World!
192
+
193
+ Full example: [`examples/01 - hello_world`](examples/01%20-%20hello_world)
194
+
195
+ **`ui/hello.html`**
196
+
197
+ ```html
198
+ <!DOCTYPE html>
199
+ <html>
200
+ <head>
201
+ <title>Hello, World!</title>
202
+ <script type="text/javascript" src="/glue.js"></script>
203
+ <script type="text/javascript">
204
+ glue.expose(say_hello_js);
205
+ function say_hello_js(x) {
206
+ console.log("Hello from " + x);
207
+ }
208
+
209
+ say_hello_js("Javascript World!");
210
+ glue.say_hello_py("Javascript World!");
211
+ </script>
212
+ </head>
213
+ <body>Hello, World!</body>
214
+ </html>
215
+ ```
216
+
217
+ **`hello.py`**
218
+
219
+ ```python
220
+ import glue
221
+
222
+ glue.init()
223
+
224
+ @glue.expose
225
+ def say_hello_py(x):
226
+ print('Hello from %s' % x)
227
+
228
+ say_hello_py('Python World!')
229
+ glue.say_hello_js('Python World!')
230
+
231
+ glue.start('hello.html')
232
+ ```
233
+
234
+ Run `python hello.py`. Python→JS calls made before the page WebSocket connects are queued and flushed when the bridge is ready (the HTTP server is already up before the window opens).
235
+
236
+ ---
237
+
238
+ ## Return values
239
+
240
+ Python and the UI talk over a WebSocket. Glue gives you two ways to get a result back.
241
+
242
+ **Callback**
243
+
244
+ ```python
245
+ glue.js_random()(lambda n: print('Got', n))
246
+ ```
247
+
248
+ **Synchronous wait** (after `glue.start()`, or with `block=False`)
249
+
250
+ ```python
251
+ n = glue.js_random()()
252
+ ```
253
+
254
+ In JavaScript, use `async` / `await`:
255
+
256
+ ```javascript
257
+ let n = await glue.py_random()();
258
+ ```
259
+
260
+ Python waits up to 10 seconds for a JS result by default (`js_result_timeout` on `glue.init`).
261
+
262
+ ---
263
+
264
+ ## Settings
265
+
266
+ Optional durable JSON for themes, defaults, and other app state that should survive restarts. **Python only** — expose your own wrappers with `@glue.expose` if the UI needs it.
267
+
268
+ The default location is opt-in via `app_name`. Without it, Glue does not invent a path; you can still load/save any file with an explicit path.
269
+
270
+ ```python
271
+ import glue
272
+
273
+ glue.init(app_name='myapp') # unlocks ~/.myapp/myapp.json (all OS)
274
+
275
+ data = glue.settings() # {} if the file does not exist yet — nothing created on disk
276
+ data['theme'] = 'dark'
277
+ glue.save_settings(data) # first save creates ~/.myapp/ and writes myapp.json
278
+ ```
279
+
280
+ ```python
281
+ # Custom file — works with or without app_name
282
+ data = glue.settings('/path/to/prefs.json')
283
+ glue.save_settings(data, '/path/to/prefs.json')
284
+ # or after settings(path), save_settings(data) reuses that path
285
+ ```
286
+
287
+ - Structure of the JSON object is yours.
288
+ - `glue.settings_path()` returns the default path when `app_name` is set.
289
+ - Passing `app_name` alone does not create a folder or an empty file.
290
+
291
+ ---
292
+
293
+ ## App options
294
+
295
+ Pass keyword arguments to `glue.start()`:
296
+
297
+ | Option | Default | Source | Hosts | Notes |
298
+ |--------|---------|--------|-------|--------|
299
+ | *(pages)* | `'index.html'` | Glue | all | Which HTML file(s) to open first (positional args), they must come before keyword args |
300
+ | `mode` | `'auto'` | Glue | all | How to show the UI — see [Hosts](#hosts) |
301
+ | `host` | `'localhost'` | Glue | all | Hostname the local HTTP server binds to |
302
+ | `port` | `8000` | Glue | all | Server port (`0` = pick a free port) |
303
+ | `block` | `True` | Glue | all | Keep `start()` running until the app exits. `False` returns so you can run your own loop (skips PyWebView in `auto`; PyWebView needs `True`) |
304
+ | `jinja_templates` | `None` | Glue | all | Subfolder of the UI root for Jinja2 templates |
305
+ | `close_callback` | `None` | Glue | all | Called when a window/WebSocket closes (cleanup / last-window logic) |
306
+ | `all_interfaces` | `False` | Glue | all | Listen on `0.0.0.0` and allow remote WebSocket clients (trusted networks only) |
307
+ | `disable_cache` | `True` | Glue | all | Send `no-store` so the browser doesn’t cache UI assets during development |
308
+ | `default_path` | `'index.html'` | Glue | all | Page served for the root URL `/` (homepage) |
309
+ | `app` | new Bottle | Glue | all | Optional custom [Bottle](https://bottlepy.org/) app (e.g. add auth/session middleware). Omit to let Glue create one |
310
+ | `shutdown_delay` | `1.0` | Glue | all | Seconds to wait after the last window closes before the Python process exits |
311
+ | `geometry` | `{}` | Glue | webview / chrome / edge | Per-page size/position when opening multiple files. Keys = page paths; values = `{'size': (w, h), 'position': (x, y)}` (either key optional). Overrides global `size` / `position` for that page |
312
+ | `app_mode` | `True` | Glue | chrome/edge | Open Chromium in `--app` (desktop-like, less browser chrome) |
313
+ | `cmdline_args` | `['--disable-http-cache']` | Glue | chrome/edge/custom | **Chrome/Edge:** extra flags appended to the browser command. **`custom`:** the full `Popen` argv (executable + args) |
314
+ | `frameless` | `True` | PyWebView | webview | Hide the OS title bar; Glue draws its in-page title bar (`False` = stock OS chrome) |
315
+ | `easy_drag` | `False` | PyWebView | webview | Allow dragging a frameless window from any empty area of the page |
316
+ | `shadow` | `True` on Windows | PyWebView | webview | Draw a drop shadow around the window |
317
+ | `debug` | `False` | PyWebView | webview | Enable DevTools / shortcuts (F12, F5, context menu) for debugging |
318
+ | `confirm_close` | `False` | PyWebView | webview | Ask “close this window?” before quitting |
319
+ | `fullscreen` | `False` | PyWebView | webview | Start covering the whole screen |
320
+ | `minimized` | `False` | PyWebView | webview | Start in the taskbar/dock (hidden until restored) |
321
+ | `maximized` | `False` | PyWebView | webview | Start maximized to the work area |
322
+ | `on_top` | `False` | PyWebView | webview | Keep the window above other windows |
323
+ | `min_size` | `(200, 100)` | PyWebView | webview | Smallest `(width, height)` the user can resize to |
324
+ | `icon` | `ui/favicon.ico` if present | PyWebView | webview / chrome / edge | **PyWebView:** app icon in the taskbar/dock (`.ico` / `.icns`). **Chrome/Edge:** caption icon comes from the page favicon — Glue injects `<link rel="icon" href="/favicon.ico?v=…">` into served HTML when `ui/favicon.ico` exists |
325
+ | `gui` | auto | PyWebView | webview | Force a specific web engine backend (`'edgechromium'`, `'qt'`, `'gtk'`, …) |
326
+ | `menu` | `[]` | PyWebView | webview | Native menu bar (`Menu` / `MenuAction` from PyWebView); empty = no menus |
327
+ | `webview_options` | `{}` | PyWebView | webview | Escape hatch for other [PyWebView](https://pywebview.flowrl.com/api/) kwargs; first-class kwargs win on conflict |
328
+ | `size` | `None` → **1280×720** | both | webview / chrome / edge | Initial content size as `(width, height)` pixels |
329
+ | `position` | `None` | both | webview / chrome / edge | Initial screen position as `(left, top)`; omit to leave placement to the host (usually centered) |
330
+ | `title` | `'Glue'` | both | webview | Text in the native/in-page title bar (Chrome/Edge use the page `<title>`) |
331
+ | `resizable` | `True` | both | webview | Allow the user to resize; on Windows frameless, Glue adds edge grips |
332
+
333
+ **Source Annotations:**
334
+ - `Glue` = Glue’s own API
335
+ - `PyWebView` = A PyWebView window option that Glue exposes on glue.start() for convenience
336
+ - `both` = Glue has always offered this as a first-class glue.start() option, and when the host is PyWebView, Glue also maps it into PyWebView’s kwargs
337
+
338
+ **Hosts Annotations:**
339
+ - Which window host honors the option (`all`, `webview`, `chrome/edge`, …).
340
+
341
+ **Defaults**:
342
+ - What you get when you omit the argument (Glue’s opinionated window setup). Pass an explicit value to override; that also wins over the same key in `webview_options`. Glue sets `SHOW_DEFAULT_MENUS=False` so stock Edit menus stay off unless you build your own. Advanced window control: `glue.get_webview_windows()`.
343
+
344
+ ---
345
+
346
+ ## Hosts
347
+
348
+ **How Glue opens your UI** on any OS.
349
+
350
+ Default order for `mode='auto'`:
351
+
352
+ 1. **PyWebView** — native window (Windows / macOS / Linux); full control via PyWebView APIs / `glue.get_webview_windows()`
353
+ 2. **Chrome/Chromium** — app mode (`--app`) on all platforms
354
+ 3. **Edge** — Windows only, if Chrome is missing
355
+
356
+ `size` / `position` / `geometry` apply on PyWebView as window kwargs, and on Chrome/Edge via `/glue.js` (`resizeTo` / `moveTo` on page load). When `ui/favicon.ico` is present, Glue injects a favicon `<link>` into served HTML so Chrome/Edge `--app` windows can show it in the caption.
357
+
358
+ PyWebView window options are first-class on [`glue.start()`](#app-options) (plus `webview_options` escape hatch).
359
+
360
+ | `mode` | Behavior |
361
+ |--------|----------|
362
+ | `'auto'` | PyWebView → Chrome → Edge (Windows); see above |
363
+ | `'webview'` | PyWebView only |
364
+ | `'chrome'` / `'edge'` | Force that Chromium browser |
365
+ | `None` | Server only (tests, custom frontends) |
366
+ | `'custom'` | Raw `Popen(cmdline_args)` — the full process argv (executable + args) |
367
+
368
+ Try [`01 - hello_world`](examples/01%20-%20hello_world) for auto launch, or [`02 - hello_world_chrome`](examples/02%20-%20hello_world_chrome) to force Chrome.
369
+
370
+ ---
371
+
372
+ ## Async Python
373
+
374
+ Glue uses Bottle + Gevent. Prefer `glue.sleep()` and `glue.spawn()` over `time.sleep()`.
375
+
376
+ If you need Gevent monkey-patching, do it **before** importing Glue:
377
+
378
+ ```python
379
+ from gevent import monkey
380
+ monkey.patch_all()
381
+ import glue
382
+ ```
383
+
384
+ ```python
385
+ import glue
386
+ glue.init()
387
+
388
+ def ticker():
389
+ while True:
390
+ print("tick")
391
+ glue.sleep(1.0)
392
+
393
+ glue.spawn(ticker)
394
+ glue.start(block=False)
395
+
396
+ while True:
397
+ print("main")
398
+ glue.sleep(1.0)
399
+ ```
400
+
401
+ With `block=False`, `mode='auto'` skips PyWebView (its GUI loop needs the main thread) and uses Chrome/Edge instead.
402
+
403
+ ---
404
+
405
+ ## Package with PyInstaller
406
+
407
+ 1. Use a clean virtualenv with only what you need
408
+ 2. `pip install "glue-ui[build]"` (or `pip install ".[build]"` from a clone)
409
+ 3. From your app folder: `python -m glue your_script.py ui`
410
+ 4. Check `dist/`, then ship with `--onefile --noconsole` when ready
411
+
412
+ Extra PyInstaller flags pass through, for example:
413
+
414
+ ```shell
415
+ python -m glue file_access.py ui --exclude numpy --onefile --noconsole
416
+ ```
417
+
418
+ See the [PyInstaller docs](https://pyinstaller.readthedocs.io/) for more.
419
+
420
+ ---
421
+
422
+ ## Examples
423
+
424
+ | Example | What it shows |
425
+ |---------|----------------|
426
+ | [`00 - presentation`](examples/00%20-%20presentation) | Full product story (screenshots above) |
427
+ | [`01 - hello_world`](examples/01%20-%20hello_world) | Minimal two-way hello |
428
+ | [`02 - hello_world_chrome`](examples/02%20-%20hello_world_chrome) | Force Chrome |
429
+ | [`03 - callbacks`](examples/03%20-%20callbacks) | Async callbacks |
430
+ | [`04 - sync_callbacks`](examples/04%20-%20sync_callbacks) | Blocking returns |
431
+ | [`05 - file_access`](examples/05%20-%20file_access) | Python file access from the UI |
432
+ | [`06 - input`](examples/06%20-%20input) | Live input bridge |
433
+ | [`07 - jinja_templates`](examples/07%20-%20jinja_templates) | Jinja2 pages |
434
+ | [`08 - createreactapp`](examples/08%20-%20createreactapp) | React + TypeScript |
435
+ | [`09 - disable_cache`](examples/09%20-%20disable_cache) | Cache control |
436
+ | [`10 - custom_app_routes`](examples/10%20-%20custom_app_routes) | Custom Bottle routes |
437
+
438
+ ---
439
+
440
+ ## Project layout
441
+
442
+ ```
443
+ my_app.py
444
+ ui/
445
+ index.html
446
+ css/
447
+ js/
448
+ ```
449
+
450
+ Frontend lives under one `ui/` root (by default). Backend is normal Python. Glue glues them together.