pgwidgets-python 0.1.3__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.
- pgwidgets_python-0.1.3/.flake8 +3 -0
- pgwidgets_python-0.1.3/.gitignore +12 -0
- pgwidgets_python-0.1.3/.readthedocs.yaml +19 -0
- pgwidgets_python-0.1.3/LICENSE.md +29 -0
- pgwidgets_python-0.1.3/PKG-INFO +121 -0
- pgwidgets_python-0.1.3/README.md +92 -0
- pgwidgets_python-0.1.3/docs/Makefile +15 -0
- pgwidgets_python-0.1.3/docs/api/async.rst +27 -0
- pgwidgets_python-0.1.3/docs/api/index.rst +10 -0
- pgwidgets_python-0.1.3/docs/api/sync.rst +27 -0
- pgwidgets_python-0.1.3/docs/architecture.rst +94 -0
- pgwidgets_python-0.1.3/docs/async.rst +152 -0
- pgwidgets_python-0.1.3/docs/callbacks.rst +213 -0
- pgwidgets_python-0.1.3/docs/conf.py +32 -0
- pgwidgets_python-0.1.3/docs/getting-started.rst +95 -0
- pgwidgets_python-0.1.3/docs/index.rst +49 -0
- pgwidgets_python-0.1.3/docs/sync.rst +190 -0
- pgwidgets_python-0.1.3/docs/web-servers.rst +370 -0
- pgwidgets_python-0.1.3/docs/widgets.rst +579 -0
- pgwidgets_python-0.1.3/examples/README.md +24 -0
- pgwidgets_python-0.1.3/examples/demo_async.py +79 -0
- pgwidgets_python-0.1.3/examples/demo_sync.py +87 -0
- pgwidgets_python-0.1.3/examples/demo_treeview.py +94 -0
- pgwidgets_python-0.1.3/pgwidgets/__init__.py +19 -0
- pgwidgets_python-0.1.3/pgwidgets/async_/__init__.py +24 -0
- pgwidgets_python-0.1.3/pgwidgets/async_/application.py +660 -0
- pgwidgets_python-0.1.3/pgwidgets/async_/widget.py +171 -0
- pgwidgets_python-0.1.3/pgwidgets/defs.py +11 -0
- pgwidgets_python-0.1.3/pgwidgets/sync/__init__.py +25 -0
- pgwidgets_python-0.1.3/pgwidgets/sync/application.py +796 -0
- pgwidgets_python-0.1.3/pgwidgets/sync/widget.py +166 -0
- pgwidgets_python-0.1.3/pgwidgets_python.egg-info/PKG-INFO +121 -0
- pgwidgets_python-0.1.3/pgwidgets_python.egg-info/SOURCES.txt +40 -0
- pgwidgets_python-0.1.3/pgwidgets_python.egg-info/dependency_links.txt +1 -0
- pgwidgets_python-0.1.3/pgwidgets_python.egg-info/requires.txt +10 -0
- pgwidgets_python-0.1.3/pgwidgets_python.egg-info/top_level.txt +1 -0
- pgwidgets_python-0.1.3/pyproject.toml +44 -0
- pgwidgets_python-0.1.3/setup.cfg +4 -0
- pgwidgets_python-0.1.3/tests/__init__.py +0 -0
- pgwidgets_python-0.1.3/tests/test_defs.py +115 -0
- pgwidgets_python-0.1.3/tests/test_protocol.py +245 -0
- pgwidgets_python-0.1.3/tests/test_widget_classes.py +102 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Read the Docs configuration file
|
|
2
|
+
# See https://docs.readthedocs.io/en/stable/config-file/v2.html
|
|
3
|
+
|
|
4
|
+
version: 2
|
|
5
|
+
|
|
6
|
+
build:
|
|
7
|
+
os: ubuntu-24.04
|
|
8
|
+
tools:
|
|
9
|
+
python: "3.12"
|
|
10
|
+
|
|
11
|
+
sphinx:
|
|
12
|
+
configuration: docs/conf.py
|
|
13
|
+
|
|
14
|
+
python:
|
|
15
|
+
install:
|
|
16
|
+
- method: pip
|
|
17
|
+
path: .
|
|
18
|
+
extra_requirements:
|
|
19
|
+
- dev
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, PGWidgets developers
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pgwidgets-python
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: Python bindings for the pgwidgets JavaScript widget library
|
|
5
|
+
Author: PGWidgets Developers
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/naojsoft/pgwidgets-python
|
|
8
|
+
Project-URL: Repository, https://github.com/naojsoft/pgwidgets-python
|
|
9
|
+
Keywords: widgets,ui,gui,websocket,browser
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
17
|
+
Requires-Python: >=3.12
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE.md
|
|
20
|
+
Requires-Dist: pgwidgets-js
|
|
21
|
+
Requires-Dist: websockets>=12
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: sphinx; extra == "dev"
|
|
24
|
+
Requires-Dist: furo; extra == "dev"
|
|
25
|
+
Requires-Dist: sphinx-autodoc-typehints; extra == "dev"
|
|
26
|
+
Provides-Extra: test
|
|
27
|
+
Requires-Dist: pytest; extra == "test"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# pgwidgets — Python Bindings
|
|
31
|
+
|
|
32
|
+
Python bindings for the [pgwidgets](https://github.com/naojsoft/pgwidgets-js)
|
|
33
|
+
JavaScript widget library. Build desktop-style browser UIs from Python
|
|
34
|
+
with a familiar Qt/GTK-style API.
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install pgwidgets-python
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This will also install `pgwidgets-js` (the JavaScript assets) and
|
|
43
|
+
`websockets` as dependencies.
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from pgwidgets.sync import Application
|
|
49
|
+
|
|
50
|
+
app = Application()
|
|
51
|
+
|
|
52
|
+
@app.on_connect
|
|
53
|
+
def setup(session):
|
|
54
|
+
Widgets = session.get_widgets()
|
|
55
|
+
|
|
56
|
+
top = Widgets.TopLevel(title="Hello", resizable=True)
|
|
57
|
+
top.resize(400, 300)
|
|
58
|
+
|
|
59
|
+
vbox = Widgets.VBox(spacing=8, padding=10)
|
|
60
|
+
btn = Widgets.Button("Click me")
|
|
61
|
+
label = Widgets.Label("Ready")
|
|
62
|
+
|
|
63
|
+
btn.on("activated", lambda: label.set_text("Clicked!"))
|
|
64
|
+
|
|
65
|
+
vbox.add_widget(btn, 0)
|
|
66
|
+
vbox.add_widget(label, 1)
|
|
67
|
+
top.set_widget(vbox)
|
|
68
|
+
top.show()
|
|
69
|
+
|
|
70
|
+
app.run()
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Run the script, then open the printed URL in your browser.
|
|
74
|
+
|
|
75
|
+
## Sync vs Async
|
|
76
|
+
|
|
77
|
+
Both APIs provide the same widget classes and methods.
|
|
78
|
+
|
|
79
|
+
**Synchronous** (recommended for most use cases):
|
|
80
|
+
```python
|
|
81
|
+
from pgwidgets.sync import Application
|
|
82
|
+
app = Application()
|
|
83
|
+
|
|
84
|
+
@app.on_connect
|
|
85
|
+
def setup(session):
|
|
86
|
+
Widgets = session.get_widgets()
|
|
87
|
+
btn = Widgets.Button("Click") # blocking call
|
|
88
|
+
btn.set_text("New text") # blocking call
|
|
89
|
+
|
|
90
|
+
app.run()
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Asynchronous** (for asyncio applications):
|
|
94
|
+
```python
|
|
95
|
+
from pgwidgets.async_ import Application
|
|
96
|
+
app = Application()
|
|
97
|
+
|
|
98
|
+
@app.on_connect
|
|
99
|
+
async def setup(session):
|
|
100
|
+
Widgets = session.get_widgets()
|
|
101
|
+
btn = await Widgets.Button("Click") # awaitable
|
|
102
|
+
await btn.set_text("New text") # awaitable
|
|
103
|
+
|
|
104
|
+
await app.run()
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## How It Works
|
|
108
|
+
|
|
109
|
+
The `Application` class starts two servers:
|
|
110
|
+
- An **HTTP server** (default port 9501) that serves the pgwidgets JS/CSS
|
|
111
|
+
and a connector page
|
|
112
|
+
- A **WebSocket server** (default port 9500) for the JSON command protocol
|
|
113
|
+
|
|
114
|
+
When you open the URL in a browser, the page loads pgwidgets and connects
|
|
115
|
+
back over WebSocket. Python widget constructors and method calls are
|
|
116
|
+
translated to JSON messages and executed in the browser. Callbacks are
|
|
117
|
+
forwarded back to Python.
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
BSD 3-Clause
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# pgwidgets — Python Bindings
|
|
2
|
+
|
|
3
|
+
Python bindings for the [pgwidgets](https://github.com/naojsoft/pgwidgets-js)
|
|
4
|
+
JavaScript widget library. Build desktop-style browser UIs from Python
|
|
5
|
+
with a familiar Qt/GTK-style API.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install pgwidgets-python
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This will also install `pgwidgets-js` (the JavaScript assets) and
|
|
14
|
+
`websockets` as dependencies.
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from pgwidgets.sync import Application
|
|
20
|
+
|
|
21
|
+
app = Application()
|
|
22
|
+
|
|
23
|
+
@app.on_connect
|
|
24
|
+
def setup(session):
|
|
25
|
+
Widgets = session.get_widgets()
|
|
26
|
+
|
|
27
|
+
top = Widgets.TopLevel(title="Hello", resizable=True)
|
|
28
|
+
top.resize(400, 300)
|
|
29
|
+
|
|
30
|
+
vbox = Widgets.VBox(spacing=8, padding=10)
|
|
31
|
+
btn = Widgets.Button("Click me")
|
|
32
|
+
label = Widgets.Label("Ready")
|
|
33
|
+
|
|
34
|
+
btn.on("activated", lambda: label.set_text("Clicked!"))
|
|
35
|
+
|
|
36
|
+
vbox.add_widget(btn, 0)
|
|
37
|
+
vbox.add_widget(label, 1)
|
|
38
|
+
top.set_widget(vbox)
|
|
39
|
+
top.show()
|
|
40
|
+
|
|
41
|
+
app.run()
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Run the script, then open the printed URL in your browser.
|
|
45
|
+
|
|
46
|
+
## Sync vs Async
|
|
47
|
+
|
|
48
|
+
Both APIs provide the same widget classes and methods.
|
|
49
|
+
|
|
50
|
+
**Synchronous** (recommended for most use cases):
|
|
51
|
+
```python
|
|
52
|
+
from pgwidgets.sync import Application
|
|
53
|
+
app = Application()
|
|
54
|
+
|
|
55
|
+
@app.on_connect
|
|
56
|
+
def setup(session):
|
|
57
|
+
Widgets = session.get_widgets()
|
|
58
|
+
btn = Widgets.Button("Click") # blocking call
|
|
59
|
+
btn.set_text("New text") # blocking call
|
|
60
|
+
|
|
61
|
+
app.run()
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Asynchronous** (for asyncio applications):
|
|
65
|
+
```python
|
|
66
|
+
from pgwidgets.async_ import Application
|
|
67
|
+
app = Application()
|
|
68
|
+
|
|
69
|
+
@app.on_connect
|
|
70
|
+
async def setup(session):
|
|
71
|
+
Widgets = session.get_widgets()
|
|
72
|
+
btn = await Widgets.Button("Click") # awaitable
|
|
73
|
+
await btn.set_text("New text") # awaitable
|
|
74
|
+
|
|
75
|
+
await app.run()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## How It Works
|
|
79
|
+
|
|
80
|
+
The `Application` class starts two servers:
|
|
81
|
+
- An **HTTP server** (default port 9501) that serves the pgwidgets JS/CSS
|
|
82
|
+
and a connector page
|
|
83
|
+
- A **WebSocket server** (default port 9500) for the JSON command protocol
|
|
84
|
+
|
|
85
|
+
When you open the URL in a browser, the page loads pgwidgets and connects
|
|
86
|
+
back over WebSocket. Python widget constructors and method calls are
|
|
87
|
+
translated to JSON messages and executed in the browser. Callbacks are
|
|
88
|
+
forwarded back to Python.
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
BSD 3-Clause
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Minimal makefile for Sphinx documentation
|
|
2
|
+
|
|
3
|
+
SPHINXOPTS ?=
|
|
4
|
+
SPHINXBUILD ?= sphinx-build
|
|
5
|
+
SOURCEDIR = .
|
|
6
|
+
BUILDDIR = _build
|
|
7
|
+
|
|
8
|
+
help:
|
|
9
|
+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
10
|
+
|
|
11
|
+
.PHONY: help Makefile
|
|
12
|
+
|
|
13
|
+
# Catch-all target: route all unknown targets to Sphinx
|
|
14
|
+
%: Makefile
|
|
15
|
+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
pgwidgets.async\_
|
|
2
|
+
=================
|
|
3
|
+
|
|
4
|
+
.. module:: pgwidgets.async_
|
|
5
|
+
|
|
6
|
+
Asynchronous API for pgwidgets.
|
|
7
|
+
|
|
8
|
+
Application
|
|
9
|
+
-----------
|
|
10
|
+
|
|
11
|
+
.. autoclass:: pgwidgets.async_.application.Application
|
|
12
|
+
:members:
|
|
13
|
+
:show-inheritance:
|
|
14
|
+
|
|
15
|
+
Session
|
|
16
|
+
-------
|
|
17
|
+
|
|
18
|
+
.. autoclass:: pgwidgets.async_.application.Session
|
|
19
|
+
:members:
|
|
20
|
+
:show-inheritance:
|
|
21
|
+
|
|
22
|
+
Widget
|
|
23
|
+
------
|
|
24
|
+
|
|
25
|
+
.. autoclass:: pgwidgets.async_.widget.Widget
|
|
26
|
+
:members:
|
|
27
|
+
:show-inheritance:
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
pgwidgets.sync
|
|
2
|
+
==============
|
|
3
|
+
|
|
4
|
+
.. module:: pgwidgets.sync
|
|
5
|
+
|
|
6
|
+
Synchronous API for pgwidgets.
|
|
7
|
+
|
|
8
|
+
Application
|
|
9
|
+
-----------
|
|
10
|
+
|
|
11
|
+
.. autoclass:: pgwidgets.sync.application.Application
|
|
12
|
+
:members:
|
|
13
|
+
:show-inheritance:
|
|
14
|
+
|
|
15
|
+
Session
|
|
16
|
+
-------
|
|
17
|
+
|
|
18
|
+
.. autoclass:: pgwidgets.sync.application.Session
|
|
19
|
+
:members:
|
|
20
|
+
:show-inheritance:
|
|
21
|
+
|
|
22
|
+
Widget
|
|
23
|
+
------
|
|
24
|
+
|
|
25
|
+
.. autoclass:: pgwidgets.sync.widget.Widget
|
|
26
|
+
:members:
|
|
27
|
+
:show-inheritance:
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Architecture
|
|
2
|
+
============
|
|
3
|
+
|
|
4
|
+
Overview
|
|
5
|
+
--------
|
|
6
|
+
|
|
7
|
+
pgwidgets follows a client-server architecture. Python is the server; the
|
|
8
|
+
browser is the client. Widget constructors and method calls in Python are
|
|
9
|
+
translated to JSON messages and sent over WebSocket to the browser, where the
|
|
10
|
+
pgwidgets JavaScript library executes them. User interactions (clicks, input,
|
|
11
|
+
etc.) travel back as callback messages.
|
|
12
|
+
|
|
13
|
+
::
|
|
14
|
+
|
|
15
|
+
Python (server) Browser (client)
|
|
16
|
+
+-----------------+ +------------------+
|
|
17
|
+
| Application | WebSocket | pgwidgets JS |
|
|
18
|
+
| Session <----|----JSON------->| widget tree |
|
|
19
|
+
| widgets | | DOM rendering |
|
|
20
|
+
+-----------------+ +------------------+
|
|
21
|
+
|
|
|
22
|
+
| HTTP (static files)
|
|
23
|
+
v
|
|
24
|
+
JS/CSS assets served to browser
|
|
25
|
+
|
|
26
|
+
Servers
|
|
27
|
+
-------
|
|
28
|
+
|
|
29
|
+
The ``Application`` class starts two servers:
|
|
30
|
+
|
|
31
|
+
**HTTP server** (default port 9501)
|
|
32
|
+
Serves the pgwidgets JavaScript/CSS assets and a connector HTML page.
|
|
33
|
+
When a browser hits ``/``, it gets ``remote.html`` with the WebSocket URL
|
|
34
|
+
injected. Set ``http_server=False`` if you serve the static files from
|
|
35
|
+
your own web server (Flask, FastAPI, nginx, etc.).
|
|
36
|
+
|
|
37
|
+
**WebSocket server** (default port 9500)
|
|
38
|
+
Carries the JSON command protocol. Each browser tab opens one WebSocket
|
|
39
|
+
connection, which becomes one ``Session``.
|
|
40
|
+
|
|
41
|
+
JSON Protocol
|
|
42
|
+
-------------
|
|
43
|
+
|
|
44
|
+
All messages are JSON objects with a ``type`` field.
|
|
45
|
+
|
|
46
|
+
**Python -> Browser:**
|
|
47
|
+
|
|
48
|
+
- ``{"type": "init", "id": 0}`` -- reset the browser to a clean slate.
|
|
49
|
+
- ``{"type": "create", "wid": 1, "class": "Button", "args": ["Click"]}`` --
|
|
50
|
+
create a widget.
|
|
51
|
+
- ``{"type": "call", "wid": 1, "method": "set_text", "args": ["New"]}`` --
|
|
52
|
+
call a method on a widget.
|
|
53
|
+
- ``{"type": "listen", "wid": 1, "action": "activated"}`` -- subscribe to a
|
|
54
|
+
callback.
|
|
55
|
+
- ``{"type": "unlisten", "wid": 1, "action": "activated"}`` -- unsubscribe.
|
|
56
|
+
|
|
57
|
+
**Browser -> Python:**
|
|
58
|
+
|
|
59
|
+
- ``{"type": "result", "id": 1, "value": ...}`` -- method return value.
|
|
60
|
+
- ``{"type": "error", "id": 1, "error": "..."}`` -- method error.
|
|
61
|
+
- ``{"type": "callback", "wid": 1, "action": "activated", "args": [...]}`` --
|
|
62
|
+
user interaction.
|
|
63
|
+
- ``{"type": "file-chunk", ...}`` -- chunked file data (see :doc:`callbacks`).
|
|
64
|
+
|
|
65
|
+
Session Model
|
|
66
|
+
-------------
|
|
67
|
+
|
|
68
|
+
Each browser tab that connects gets its own ``Session`` object. The session
|
|
69
|
+
owns:
|
|
70
|
+
|
|
71
|
+
- A widget map (``wid`` -> Python widget wrapper)
|
|
72
|
+
- A callback registry (``"wid:action"`` -> handler function)
|
|
73
|
+
- A message-ID counter for request/response matching
|
|
74
|
+
|
|
75
|
+
Multiple sessions can be active concurrently (controlled by ``max_sessions``).
|
|
76
|
+
|
|
77
|
+
Lifecycle:
|
|
78
|
+
|
|
79
|
+
1. Browser opens the URL and loads ``remote.html``.
|
|
80
|
+
2. JavaScript connects to the WebSocket server.
|
|
81
|
+
3. Python sends ``init``; browser acknowledges.
|
|
82
|
+
4. ``on_connect`` callback fires with the new ``Session``.
|
|
83
|
+
5. User code creates widgets, registers callbacks.
|
|
84
|
+
6. When the browser tab closes, ``on_disconnect`` fires and the session is
|
|
85
|
+
cleaned up.
|
|
86
|
+
|
|
87
|
+
Widget References
|
|
88
|
+
-----------------
|
|
89
|
+
|
|
90
|
+
Widgets are identified by integer IDs (``wid``). When a Python widget is
|
|
91
|
+
passed as an argument to another widget's method (e.g., ``vbox.add_widget(btn,
|
|
92
|
+
0)``), the framework automatically converts the Python ``Widget`` object to a
|
|
93
|
+
``{"__wid__": N}`` reference on the wire, and converts it back on return
|
|
94
|
+
values.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
Asynchronous API
|
|
2
|
+
================
|
|
3
|
+
|
|
4
|
+
The async API lives in ``pgwidgets.async_``. All widget constructors and
|
|
5
|
+
method calls are coroutines that must be awaited.
|
|
6
|
+
|
|
7
|
+
.. code-block:: python
|
|
8
|
+
|
|
9
|
+
from pgwidgets.async_ import Application
|
|
10
|
+
|
|
11
|
+
Application
|
|
12
|
+
-----------
|
|
13
|
+
|
|
14
|
+
.. code-block:: python
|
|
15
|
+
|
|
16
|
+
app = Application(
|
|
17
|
+
ws_port=9500,
|
|
18
|
+
http_port=9501,
|
|
19
|
+
host="127.0.0.1",
|
|
20
|
+
http_server=True,
|
|
21
|
+
concurrency_handling="per_session",
|
|
22
|
+
max_sessions=1,
|
|
23
|
+
logger=None,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
The constructor parameters are the same as the sync version (see :doc:`sync`).
|
|
27
|
+
|
|
28
|
+
on_connect / on_disconnect
|
|
29
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
30
|
+
|
|
31
|
+
Handlers can be sync or async:
|
|
32
|
+
|
|
33
|
+
.. code-block:: python
|
|
34
|
+
|
|
35
|
+
@app.on_connect
|
|
36
|
+
async def setup(session):
|
|
37
|
+
Widgets = session.get_widgets()
|
|
38
|
+
top = await Widgets.TopLevel(title="Hello")
|
|
39
|
+
await top.show()
|
|
40
|
+
|
|
41
|
+
@app.on_disconnect
|
|
42
|
+
async def teardown(session):
|
|
43
|
+
print(f"Session {session.id} disconnected")
|
|
44
|
+
|
|
45
|
+
Running
|
|
46
|
+
~~~~~~~
|
|
47
|
+
|
|
48
|
+
.. code-block:: python
|
|
49
|
+
|
|
50
|
+
# Inside an async context
|
|
51
|
+
await app.run()
|
|
52
|
+
|
|
53
|
+
# Or with asyncio.run()
|
|
54
|
+
import asyncio
|
|
55
|
+
asyncio.run(main())
|
|
56
|
+
|
|
57
|
+
``await app.close()`` shuts down all sessions and causes ``run()`` to return.
|
|
58
|
+
|
|
59
|
+
Session
|
|
60
|
+
-------
|
|
61
|
+
|
|
62
|
+
The async ``Session`` has the same interface as the sync version, but methods
|
|
63
|
+
are coroutines:
|
|
64
|
+
|
|
65
|
+
.. code-block:: python
|
|
66
|
+
|
|
67
|
+
Widgets = session.get_widgets() # sync -- returns namespace
|
|
68
|
+
btn = await Widgets.Button("Click me") # async -- creates widget
|
|
69
|
+
await btn.set_text("New text") # async -- calls method
|
|
70
|
+
await session.close() # async
|
|
71
|
+
timer = await session.make_timer(duration=1000) # async
|
|
72
|
+
|
|
73
|
+
Concurrency Modes
|
|
74
|
+
-----------------
|
|
75
|
+
|
|
76
|
+
In the async API, concurrency is managed with ``asyncio.Lock`` instead of
|
|
77
|
+
threads:
|
|
78
|
+
|
|
79
|
+
**per_session** (default)
|
|
80
|
+
Each session gets its own ``asyncio.Lock``. Callbacks within a session
|
|
81
|
+
are serialized, but different sessions can interleave at ``await`` points.
|
|
82
|
+
|
|
83
|
+
**serialized**
|
|
84
|
+
All callbacks from all sessions are serialized under a single global
|
|
85
|
+
``asyncio.Lock``.
|
|
86
|
+
|
|
87
|
+
**concurrent**
|
|
88
|
+
Callbacks are dispatched via ``asyncio.ensure_future`` with no
|
|
89
|
+
serialization.
|
|
90
|
+
|
|
91
|
+
Callbacks
|
|
92
|
+
~~~~~~~~~
|
|
93
|
+
|
|
94
|
+
Callback handlers can be sync or async. Async handlers are awaited:
|
|
95
|
+
|
|
96
|
+
.. code-block:: python
|
|
97
|
+
|
|
98
|
+
async def on_click():
|
|
99
|
+
await status.set_text("Clicked!")
|
|
100
|
+
|
|
101
|
+
await btn.on("activated", on_click)
|
|
102
|
+
|
|
103
|
+
Full Example
|
|
104
|
+
------------
|
|
105
|
+
|
|
106
|
+
.. code-block:: python
|
|
107
|
+
|
|
108
|
+
import asyncio
|
|
109
|
+
import logging
|
|
110
|
+
from pgwidgets.async_ import Application
|
|
111
|
+
|
|
112
|
+
logging.basicConfig(level=logging.INFO)
|
|
113
|
+
logger = logging.getLogger("pgwidgets")
|
|
114
|
+
|
|
115
|
+
async def main():
|
|
116
|
+
app = Application(max_sessions=4, logger=logger)
|
|
117
|
+
|
|
118
|
+
@app.on_connect
|
|
119
|
+
async def on_session(session):
|
|
120
|
+
Widgets = session.get_widgets()
|
|
121
|
+
|
|
122
|
+
top = await Widgets.TopLevel(title="Async Demo", resizable=True)
|
|
123
|
+
await top.resize(400, 300)
|
|
124
|
+
|
|
125
|
+
vbox = await Widgets.VBox(spacing=8, padding=10)
|
|
126
|
+
status = await Widgets.Label("Click a button!")
|
|
127
|
+
|
|
128
|
+
hbox = await Widgets.HBox(spacing=6)
|
|
129
|
+
btn = await Widgets.Button("Hello")
|
|
130
|
+
await hbox.add_widget(btn, 0)
|
|
131
|
+
|
|
132
|
+
entry = await Widgets.TextEntry(text="Type here", linehistory=5)
|
|
133
|
+
|
|
134
|
+
await vbox.add_widget(hbox, 0)
|
|
135
|
+
await vbox.add_widget(entry, 0)
|
|
136
|
+
await vbox.add_widget(status, 1)
|
|
137
|
+
await top.set_widget(vbox)
|
|
138
|
+
await top.show()
|
|
139
|
+
|
|
140
|
+
async def on_hello():
|
|
141
|
+
await status.set_text("Hello!")
|
|
142
|
+
|
|
143
|
+
async def on_entry(text):
|
|
144
|
+
await status.set_text(f"Entered: {text}")
|
|
145
|
+
|
|
146
|
+
await btn.on("activated", on_hello)
|
|
147
|
+
await entry.on("activated", on_entry)
|
|
148
|
+
|
|
149
|
+
await app.run()
|
|
150
|
+
|
|
151
|
+
if __name__ == "__main__":
|
|
152
|
+
asyncio.run(main())
|