pgwidgets-python 0.3.1__tar.gz → 0.3.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.3.1 → pgwidgets_python-0.3.3}/PKG-INFO +5 -1
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/README.md +4 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/docs/WhatsNew.rst +34 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/docs/async.rst +4 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/docs/conf.py +2 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/docs/sync.rst +13 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/docs/widgets.rst +6 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pgwidgets/async_/application.py +18 -1
- pgwidgets_python-0.3.3/pgwidgets/icons/pgw-python-logo.png +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pgwidgets/method_types.py +1 -1
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pgwidgets/sync/application.py +41 -8
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pgwidgets_python.egg-info/PKG-INFO +5 -1
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pgwidgets_python.egg-info/SOURCES.txt +4 -0
- pgwidgets_python-0.3.3/pgwidgets_python.egg-info/scm_file_list.json +68 -0
- pgwidgets_python-0.3.3/pgwidgets_python.egg-info/scm_version.json +8 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pyproject.toml +3 -0
- pgwidgets_python-0.3.3/tests/test_async_session.py +68 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/tests/test_session.py +50 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/.flake8 +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/.github/workflows/tests.yml +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/.gitignore +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/.readthedocs.yaml +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/LICENSE.md +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/docs/Makefile +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/docs/api/async.rst +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/docs/api/index.rst +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/docs/api/sync.rst +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/docs/architecture.rst +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/docs/callbacks.rst +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/docs/extras.rst +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/docs/getting-started.rst +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/docs/index.rst +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/docs/subclassing.rst +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/docs/utilities.rst +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/docs/web-servers.rst +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/examples/README.md +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/examples/all_widgets.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/examples/all_widgets_async.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/examples/demo_async.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/examples/demo_sync.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/examples/demo_treeview.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/examples/flask-multi-process/README.md +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/examples/flask-multi-process/gunicorn.conf.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/examples/flask-multi-process/nginx.conf +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/examples/flask-multi-process/server.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/examples/flask-multi-process/user_app.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pgwidgets/__init__.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pgwidgets/_json.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pgwidgets/async_/Widgets.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pgwidgets/async_/__init__.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pgwidgets/async_/widget.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pgwidgets/buffer.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pgwidgets/callbacks.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pgwidgets/defs.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pgwidgets/extras/__init__.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pgwidgets/extras/file_browser.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pgwidgets/sync/Widgets.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pgwidgets/sync/__init__.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pgwidgets/sync/widget.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pgwidgets_python.egg-info/dependency_links.txt +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pgwidgets_python.egg-info/requires.txt +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pgwidgets_python.egg-info/top_level.txt +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/setup.cfg +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/test_pg2.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/tests/__init__.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/tests/test_defs.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/tests/test_extras.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/tests/test_fonts.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/tests/test_protocol.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/tests/test_reconstruct.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/tests/test_resolve_kwargs.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/tests/test_stateful.py +0 -0
- {pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/tests/test_widget_classes.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pgwidgets-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Python bindings for the pgwidgets JavaScript widget library
|
|
5
5
|
Author: PGWidgets Developers
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -27,6 +27,10 @@ Provides-Extra: test
|
|
|
27
27
|
Requires-Dist: pytest; extra == "test"
|
|
28
28
|
Dynamic: license-file
|
|
29
29
|
|
|
30
|
+
<p align="center">
|
|
31
|
+
<img src="pgwidgets/icons/pgw-python-logo.png" alt="pgwidgets-python logo" width="500">
|
|
32
|
+
</p>
|
|
33
|
+
|
|
30
34
|
# pgwidgets — Python Bindings
|
|
31
35
|
|
|
32
36
|
Python bindings for the [pgwidgets](https://github.com/naojsoft/pgwidgets-js)
|
|
@@ -4,6 +4,40 @@ What's New
|
|
|
4
4
|
Recent changes — since ``v0.3.0``
|
|
5
5
|
---------------------------------
|
|
6
6
|
|
|
7
|
+
``Application.open_url``
|
|
8
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
9
|
+
|
|
10
|
+
A new method asks the connected browser(s) to open a URL in a new
|
|
11
|
+
tab/window (``window.open``):
|
|
12
|
+
|
|
13
|
+
.. code-block:: python
|
|
14
|
+
|
|
15
|
+
app.open_url('https://example.com/')
|
|
16
|
+
|
|
17
|
+
This opens the link in the *user's* browser rather than on the host
|
|
18
|
+
running Python. It is a fire-and-forget, per-session broadcast (like
|
|
19
|
+
``set_default_font``) and is available on both the sync and async
|
|
20
|
+
``Application``. (Note: a browser popup blocker may suppress it if
|
|
21
|
+
the originating user-activation has lapsed.)
|
|
22
|
+
|
|
23
|
+
Containers report their children: ``get_children``
|
|
24
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
25
|
+
|
|
26
|
+
``Frame``, ``Expander``, ``TopLevel`` and ``Page`` (single-child
|
|
27
|
+
containers) and the menu / toolbar widgets ``Menu``, ``MenuBar`` and
|
|
28
|
+
``ToolBar`` are now container widgets, so their generated wrappers
|
|
29
|
+
expose the container method set -- ``get_children`` / ``num_children``
|
|
30
|
+
/ ``remove`` / ``remove_widget`` / ``remove_all``. ``get_children()``
|
|
31
|
+
returns the contained widgets (a 0- or 1-element list for the
|
|
32
|
+
single-child containers; the menu actions / submenus / toolbar items
|
|
33
|
+
for the menu/toolbar widgets).
|
|
34
|
+
|
|
35
|
+
TreeView / TableView: ``set_row_spacing`` / ``set_column_spacing``
|
|
36
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
37
|
+
|
|
38
|
+
Two new methods set the vertical (row) and horizontal (column) cell
|
|
39
|
+
padding (in pixels). ``set_row_spacing(0)`` yields tight rows.
|
|
40
|
+
|
|
7
41
|
Custom fonts: ``Application.register_font`` / ``set_default_font``
|
|
8
42
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
9
43
|
|
|
@@ -79,6 +79,10 @@ are replayed to every new and reconnecting session before
|
|
|
79
79
|
with ``set_font(family, ...)`` always find the face already
|
|
80
80
|
declared.
|
|
81
81
|
|
|
82
|
+
``open_url(url)`` is also available (same as the sync backend; see
|
|
83
|
+
:doc:`sync`) -- it asks the connected browser(s) to open ``url`` in a
|
|
84
|
+
new tab via ``window.open``.
|
|
85
|
+
|
|
82
86
|
Session
|
|
83
87
|
-------
|
|
84
88
|
|
|
@@ -17,6 +17,8 @@ exclude_patterns = ["_build"]
|
|
|
17
17
|
# -- HTML output --
|
|
18
18
|
html_theme = "furo"
|
|
19
19
|
html_title = "pgwidgets-python"
|
|
20
|
+
# project logo (single source: the package's icons/ data folder)
|
|
21
|
+
html_logo = "../pgwidgets/icons/pgw-python-logo.png"
|
|
20
22
|
|
|
21
23
|
# -- autodoc --
|
|
22
24
|
autodoc_member_order = "bysource"
|
|
@@ -117,6 +117,19 @@ nginx the same route is served from the Python process.
|
|
|
117
117
|
See also the JS-side write-up at :ref:`custom-fonts` in the
|
|
118
118
|
pgwidgets-js documentation.
|
|
119
119
|
|
|
120
|
+
Opening a URL in the browser
|
|
121
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
122
|
+
|
|
123
|
+
.. code-block:: python
|
|
124
|
+
|
|
125
|
+
app.open_url('https://example.com/')
|
|
126
|
+
|
|
127
|
+
``open_url(url)`` asks the connected browser(s) to open ``url`` in a
|
|
128
|
+
new tab/window (``window.open``) -- i.e. in the *user's* browser, not
|
|
129
|
+
on the host running Python. It is a fire-and-forget, per-session
|
|
130
|
+
broadcast. (A browser popup blocker may suppress it if the
|
|
131
|
+
user-activation from the originating click has already lapsed.)
|
|
132
|
+
|
|
120
133
|
Session
|
|
121
134
|
-------
|
|
122
135
|
|
|
@@ -631,6 +631,7 @@ Hierarchical tree/list display.
|
|
|
631
631
|
``select_all(state)``,
|
|
632
632
|
``set_column_width(col_key, width)``,
|
|
633
633
|
``set_optimal_column_widths()``,
|
|
634
|
+
``set_row_spacing(px)``, ``set_column_spacing(px)``,
|
|
634
635
|
``sort_by_column(col_key, ascending)``, ``scroll_to_path(path)``,
|
|
635
636
|
``scroll_to_end()``, ``get_column_count()``, ``get_row_count()``,
|
|
636
637
|
``set_show_grid(tf)``, ``set_show_row_numbers(tf)``,
|
|
@@ -815,6 +816,11 @@ Browser file open/save dialog.
|
|
|
815
816
|
Menus and Toolbars
|
|
816
817
|
------------------
|
|
817
818
|
|
|
819
|
+
``MenuBar``, ``Menu`` and ``ToolBar`` are container widgets: the menu
|
|
820
|
+
actions, submenus and toolbar items they hold are reported by
|
|
821
|
+
``get_children()`` / ``num_children()`` (separators and spacers
|
|
822
|
+
excluded).
|
|
823
|
+
|
|
818
824
|
MenuBar
|
|
819
825
|
~~~~~~~
|
|
820
826
|
|
|
@@ -1557,6 +1557,15 @@ class Application:
|
|
|
1557
1557
|
}
|
|
1558
1558
|
self._broadcast_font_msg(self._font_default_msg())
|
|
1559
1559
|
|
|
1560
|
+
def open_url(self, url):
|
|
1561
|
+
"""Ask the connected browser(s) to open *url* in a new tab/window.
|
|
1562
|
+
|
|
1563
|
+
Fire-and-forget app-level command; opens the link in the *user's*
|
|
1564
|
+
browser rather than on the host running Python. (Reuses the
|
|
1565
|
+
generic per-session broadcast helper.)
|
|
1566
|
+
"""
|
|
1567
|
+
self._broadcast_font_msg({"type": "open-url", "url": str(url)})
|
|
1568
|
+
|
|
1560
1569
|
def _font_register_msg(self, entry):
|
|
1561
1570
|
return {
|
|
1562
1571
|
"type": "register-font",
|
|
@@ -1788,8 +1797,14 @@ class Application:
|
|
|
1788
1797
|
The newly created session.
|
|
1789
1798
|
"""
|
|
1790
1799
|
if session_id is None:
|
|
1800
|
+
# skip ids already taken by explicitly-created sessions
|
|
1801
|
+
# (e.g. a pre-created default session) so we never
|
|
1802
|
+
# silently overwrite an existing session
|
|
1791
1803
|
session_id = self._next_session_id
|
|
1792
1804
|
self._next_session_id += 1
|
|
1805
|
+
while session_id in self._sessions:
|
|
1806
|
+
session_id = self._next_session_id
|
|
1807
|
+
self._next_session_id += 1
|
|
1793
1808
|
elif session_id in self._sessions:
|
|
1794
1809
|
# If the existing session was auto-created by a browser
|
|
1795
1810
|
# reconnect with the same token, return it so the user's
|
|
@@ -1954,12 +1969,14 @@ class Application:
|
|
|
1954
1969
|
|
|
1955
1970
|
In the async API, callbacks are dispatched by the asyncio event
|
|
1956
1971
|
loop directly, so this simply yields control for the requested
|
|
1957
|
-
duration.
|
|
1972
|
+
duration. A *timeout* of ``0`` yields once (a non-blocking drain
|
|
1973
|
+
of ready callbacks), matching the sync API's contract.
|
|
1958
1974
|
|
|
1959
1975
|
Parameters
|
|
1960
1976
|
----------
|
|
1961
1977
|
timeout : float
|
|
1962
1978
|
Seconds to yield to the event loop (default 0.1).
|
|
1979
|
+
Use ``0`` for a single non-blocking yield.
|
|
1963
1980
|
"""
|
|
1964
1981
|
await asyncio.sleep(timeout)
|
|
1965
1982
|
|
|
Binary file
|
|
@@ -1828,6 +1828,20 @@ class Application:
|
|
|
1828
1828
|
except Exception:
|
|
1829
1829
|
pass
|
|
1830
1830
|
|
|
1831
|
+
def open_url(self, url):
|
|
1832
|
+
"""Ask the connected browser(s) to open *url* in a new tab/window.
|
|
1833
|
+
|
|
1834
|
+
Fire-and-forget app-level command (mirrors set_default_font's
|
|
1835
|
+
per-session send). This opens the link in the *user's* browser
|
|
1836
|
+
rather than on the host running Python.
|
|
1837
|
+
"""
|
|
1838
|
+
msg = {"type": "open-url", "url": str(url)}
|
|
1839
|
+
for session in list(self._sessions.values()):
|
|
1840
|
+
try:
|
|
1841
|
+
session._send(msg)
|
|
1842
|
+
except Exception:
|
|
1843
|
+
pass
|
|
1844
|
+
|
|
1831
1845
|
def _font_register_msg(self, entry):
|
|
1832
1846
|
return {
|
|
1833
1847
|
"type": "register-font",
|
|
@@ -2062,8 +2076,14 @@ class Application:
|
|
|
2062
2076
|
"""
|
|
2063
2077
|
with self._session_lock:
|
|
2064
2078
|
if session_id is None:
|
|
2079
|
+
# skip ids already taken by explicitly-created sessions
|
|
2080
|
+
# (e.g. a pre-created default session) so we never
|
|
2081
|
+
# silently overwrite an existing session
|
|
2065
2082
|
session_id = self._next_session_id
|
|
2066
2083
|
self._next_session_id += 1
|
|
2084
|
+
while session_id in self._sessions:
|
|
2085
|
+
session_id = self._next_session_id
|
|
2086
|
+
self._next_session_id += 1
|
|
2067
2087
|
elif session_id in self._sessions:
|
|
2068
2088
|
# If the existing session was auto-created by a browser
|
|
2069
2089
|
# reconnect with the same token, return it so the user's
|
|
@@ -2279,22 +2299,35 @@ class Application:
|
|
|
2279
2299
|
``concurrent`` modes, callbacks already run on their own threads
|
|
2280
2300
|
so this method simply yields control briefly.
|
|
2281
2301
|
|
|
2302
|
+
A *timeout* of ``0`` drains every callback currently queued and
|
|
2303
|
+
returns immediately without blocking -- useful when pumping from
|
|
2304
|
+
within another event loop (e.g. a Jupyter kernel's asyncio loop).
|
|
2305
|
+
|
|
2282
2306
|
Parameters
|
|
2283
2307
|
----------
|
|
2284
2308
|
timeout : float
|
|
2285
2309
|
Maximum seconds to spend processing events (default 0.1).
|
|
2310
|
+
Use ``0`` for a non-blocking drain of the pending queue.
|
|
2286
2311
|
"""
|
|
2287
2312
|
if self._concurrency == "serialized":
|
|
2313
|
+
non_blocking = (timeout == 0)
|
|
2288
2314
|
deadline = time.monotonic() + timeout
|
|
2289
2315
|
while not self._shutdown.is_set():
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2316
|
+
if non_blocking:
|
|
2317
|
+
try:
|
|
2318
|
+
handler, args, kwargs, result_slot = \
|
|
2319
|
+
self._cb_queue.get_nowait()
|
|
2320
|
+
except queue.Empty:
|
|
2321
|
+
break
|
|
2322
|
+
else:
|
|
2323
|
+
remaining = deadline - time.monotonic()
|
|
2324
|
+
if remaining <= 0:
|
|
2325
|
+
break
|
|
2326
|
+
try:
|
|
2327
|
+
handler, args, kwargs, result_slot = \
|
|
2328
|
+
self._cb_queue.get(timeout=min(remaining, 0.5))
|
|
2329
|
+
except queue.Empty:
|
|
2330
|
+
continue
|
|
2298
2331
|
try:
|
|
2299
2332
|
rv = handler(*args, **kwargs)
|
|
2300
2333
|
if result_slot is not None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pgwidgets-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Python bindings for the pgwidgets JavaScript widget library
|
|
5
5
|
Author: PGWidgets Developers
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -27,6 +27,10 @@ Provides-Extra: test
|
|
|
27
27
|
Requires-Dist: pytest; extra == "test"
|
|
28
28
|
Dynamic: license-file
|
|
29
29
|
|
|
30
|
+
<p align="center">
|
|
31
|
+
<img src="pgwidgets/icons/pgw-python-logo.png" alt="pgwidgets-python logo" width="500">
|
|
32
|
+
</p>
|
|
33
|
+
|
|
30
34
|
# pgwidgets — Python Bindings
|
|
31
35
|
|
|
32
36
|
Python bindings for the [pgwidgets](https://github.com/naojsoft/pgwidgets-js)
|
|
@@ -46,6 +46,7 @@ pgwidgets/async_/application.py
|
|
|
46
46
|
pgwidgets/async_/widget.py
|
|
47
47
|
pgwidgets/extras/__init__.py
|
|
48
48
|
pgwidgets/extras/file_browser.py
|
|
49
|
+
pgwidgets/icons/pgw-python-logo.png
|
|
49
50
|
pgwidgets/sync/Widgets.py
|
|
50
51
|
pgwidgets/sync/__init__.py
|
|
51
52
|
pgwidgets/sync/application.py
|
|
@@ -54,8 +55,11 @@ pgwidgets_python.egg-info/PKG-INFO
|
|
|
54
55
|
pgwidgets_python.egg-info/SOURCES.txt
|
|
55
56
|
pgwidgets_python.egg-info/dependency_links.txt
|
|
56
57
|
pgwidgets_python.egg-info/requires.txt
|
|
58
|
+
pgwidgets_python.egg-info/scm_file_list.json
|
|
59
|
+
pgwidgets_python.egg-info/scm_version.json
|
|
57
60
|
pgwidgets_python.egg-info/top_level.txt
|
|
58
61
|
tests/__init__.py
|
|
62
|
+
tests/test_async_session.py
|
|
59
63
|
tests/test_defs.py
|
|
60
64
|
tests/test_extras.py
|
|
61
65
|
tests/test_fonts.py
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"files": [
|
|
3
|
+
".flake8",
|
|
4
|
+
"test_pg2.py",
|
|
5
|
+
"README.md",
|
|
6
|
+
"pyproject.toml",
|
|
7
|
+
"LICENSE.md",
|
|
8
|
+
".readthedocs.yaml",
|
|
9
|
+
".gitignore",
|
|
10
|
+
"docs/web-servers.rst",
|
|
11
|
+
"docs/WhatsNew.rst",
|
|
12
|
+
"docs/subclassing.rst",
|
|
13
|
+
"docs/sync.rst",
|
|
14
|
+
"docs/utilities.rst",
|
|
15
|
+
"docs/extras.rst",
|
|
16
|
+
"docs/async.rst",
|
|
17
|
+
"docs/architecture.rst",
|
|
18
|
+
"docs/widgets.rst",
|
|
19
|
+
"docs/conf.py",
|
|
20
|
+
"docs/Makefile",
|
|
21
|
+
"docs/index.rst",
|
|
22
|
+
"docs/callbacks.rst",
|
|
23
|
+
"docs/getting-started.rst",
|
|
24
|
+
"docs/api/async.rst",
|
|
25
|
+
"docs/api/sync.rst",
|
|
26
|
+
"docs/api/index.rst",
|
|
27
|
+
"tests/test_protocol.py",
|
|
28
|
+
"tests/__init__.py",
|
|
29
|
+
"tests/test_stateful.py",
|
|
30
|
+
"tests/test_resolve_kwargs.py",
|
|
31
|
+
"tests/test_async_session.py",
|
|
32
|
+
"tests/test_defs.py",
|
|
33
|
+
"tests/test_fonts.py",
|
|
34
|
+
"tests/test_session.py",
|
|
35
|
+
"tests/test_extras.py",
|
|
36
|
+
"tests/test_widget_classes.py",
|
|
37
|
+
"tests/test_reconstruct.py",
|
|
38
|
+
"pgwidgets/_json.py",
|
|
39
|
+
"pgwidgets/__init__.py",
|
|
40
|
+
"pgwidgets/callbacks.py",
|
|
41
|
+
"pgwidgets/method_types.py",
|
|
42
|
+
"pgwidgets/buffer.py",
|
|
43
|
+
"pgwidgets/defs.py",
|
|
44
|
+
"pgwidgets/sync/Widgets.py",
|
|
45
|
+
"pgwidgets/sync/__init__.py",
|
|
46
|
+
"pgwidgets/sync/application.py",
|
|
47
|
+
"pgwidgets/sync/widget.py",
|
|
48
|
+
"pgwidgets/extras/__init__.py",
|
|
49
|
+
"pgwidgets/extras/file_browser.py",
|
|
50
|
+
"pgwidgets/icons/pgw-python-logo.png",
|
|
51
|
+
"pgwidgets/async_/Widgets.py",
|
|
52
|
+
"pgwidgets/async_/application.py",
|
|
53
|
+
"pgwidgets/async_/__init__.py",
|
|
54
|
+
"pgwidgets/async_/widget.py",
|
|
55
|
+
"examples/demo_sync.py",
|
|
56
|
+
"examples/all_widgets.py",
|
|
57
|
+
"examples/all_widgets_async.py",
|
|
58
|
+
"examples/README.md",
|
|
59
|
+
"examples/demo_treeview.py",
|
|
60
|
+
"examples/demo_async.py",
|
|
61
|
+
"examples/flask-multi-process/server.py",
|
|
62
|
+
"examples/flask-multi-process/README.md",
|
|
63
|
+
"examples/flask-multi-process/nginx.conf",
|
|
64
|
+
"examples/flask-multi-process/gunicorn.conf.py",
|
|
65
|
+
"examples/flask-multi-process/user_app.py",
|
|
66
|
+
".github/workflows/tests.yml"
|
|
67
|
+
]
|
|
68
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for the async backend's session lifecycle.
|
|
3
|
+
|
|
4
|
+
``create_session`` is a plain (synchronous) method, so these tests need
|
|
5
|
+
no running event loop. They mirror the auto-id / collision-safety tests
|
|
6
|
+
of the sync backend in ``test_session.py``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import threading
|
|
11
|
+
|
|
12
|
+
from pgwidgets.async_.application import Application
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _make_app(**kwargs):
|
|
16
|
+
"""Create an async Application without starting servers."""
|
|
17
|
+
app = Application.__new__(Application)
|
|
18
|
+
app._host = "127.0.0.1"
|
|
19
|
+
app._ws_port = 9500
|
|
20
|
+
app._http_port = 9501
|
|
21
|
+
app._use_http_server = False
|
|
22
|
+
app._concurrency = kwargs.get("concurrency", "concurrent")
|
|
23
|
+
app._max_sessions = None
|
|
24
|
+
app._sessions = {}
|
|
25
|
+
app._next_session_id = 1
|
|
26
|
+
app._on_connect = None
|
|
27
|
+
app._on_disconnect = None
|
|
28
|
+
app._widget_classes = {}
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger("pgwidgets.test.async")
|
|
31
|
+
logger.addHandler(logging.NullHandler())
|
|
32
|
+
app._logger = logger
|
|
33
|
+
|
|
34
|
+
return app
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TestAsyncSessionCreation:
|
|
38
|
+
def test_create_session_auto_id(self):
|
|
39
|
+
app = _make_app()
|
|
40
|
+
s1 = app.create_session()
|
|
41
|
+
s2 = app.create_session()
|
|
42
|
+
assert s1.id == 1
|
|
43
|
+
assert s2.id == 2
|
|
44
|
+
|
|
45
|
+
def test_create_session_auto_id_skips_existing(self):
|
|
46
|
+
# A default session is created with an explicit id, then more are
|
|
47
|
+
# auto-allocated. An auto-allocated id must not collide with /
|
|
48
|
+
# silently overwrite the explicitly-created one.
|
|
49
|
+
app = _make_app()
|
|
50
|
+
default = app.create_session(session_id=1)
|
|
51
|
+
extra = app.create_session() # auto-allocated
|
|
52
|
+
assert extra.id != default.id
|
|
53
|
+
assert app._sessions[default.id] is default # not overwritten
|
|
54
|
+
assert app._sessions[extra.id] is extra
|
|
55
|
+
|
|
56
|
+
def test_create_session_explicit_id(self):
|
|
57
|
+
app = _make_app()
|
|
58
|
+
s = app.create_session(session_id="my-session")
|
|
59
|
+
assert s.id == "my-session"
|
|
60
|
+
|
|
61
|
+
def test_create_session_duplicate_id_raises(self):
|
|
62
|
+
app = _make_app()
|
|
63
|
+
app.create_session(session_id="dup")
|
|
64
|
+
try:
|
|
65
|
+
app.create_session(session_id="dup")
|
|
66
|
+
assert False, "Should have raised ValueError"
|
|
67
|
+
except ValueError as e:
|
|
68
|
+
assert "dup" in str(e)
|
|
@@ -6,7 +6,9 @@ tokens, manage multiple connections, handle graceful no-connection
|
|
|
6
6
|
operation, and can be destroyed.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
import queue
|
|
9
10
|
import threading
|
|
11
|
+
import time
|
|
10
12
|
from unittest.mock import MagicMock, AsyncMock
|
|
11
13
|
|
|
12
14
|
from pgwidgets.sync.application import Application, Session
|
|
@@ -59,6 +61,18 @@ class TestSessionCreation:
|
|
|
59
61
|
assert s1.id == 1
|
|
60
62
|
assert s2.id == 2
|
|
61
63
|
|
|
64
|
+
def test_create_session_auto_id_skips_existing(self):
|
|
65
|
+
# Mirrors the Ginga pgw Application pattern: a default session is
|
|
66
|
+
# created with an explicit id, then more are auto-allocated. An
|
|
67
|
+
# auto-allocated id must not collide with / silently overwrite the
|
|
68
|
+
# explicitly-created one.
|
|
69
|
+
app = _make_app()
|
|
70
|
+
default = app.create_session(session_id=1)
|
|
71
|
+
extra = app.create_session() # auto-allocated
|
|
72
|
+
assert extra.id != default.id
|
|
73
|
+
assert app._sessions[default.id] is default # not overwritten
|
|
74
|
+
assert app._sessions[extra.id] is extra
|
|
75
|
+
|
|
62
76
|
def test_create_session_explicit_id(self):
|
|
63
77
|
app = _make_app()
|
|
64
78
|
s = app.create_session(session_id="my-session")
|
|
@@ -375,3 +389,39 @@ class TestCallbackSuppression:
|
|
|
375
389
|
types = [m["type"] for m in messages]
|
|
376
390
|
assert types[0] == "reconstruct-start"
|
|
377
391
|
assert types[-1] == "reconstruct-end"
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
class TestProcessEventsDrain:
|
|
395
|
+
"""Test the non-blocking drain mode of process_events (timeout=0)."""
|
|
396
|
+
|
|
397
|
+
def _serial_app(self):
|
|
398
|
+
app = _make_app(concurrency="serialized")
|
|
399
|
+
app._cb_queue = queue.Queue()
|
|
400
|
+
app._shutdown = threading.Event()
|
|
401
|
+
return app
|
|
402
|
+
|
|
403
|
+
def test_zero_drains_all_without_blocking(self):
|
|
404
|
+
app = self._serial_app()
|
|
405
|
+
ran = []
|
|
406
|
+
for i in range(3):
|
|
407
|
+
app._cb_queue.put((lambda i=i: ran.append(i), (), {}, None))
|
|
408
|
+
start = time.monotonic()
|
|
409
|
+
app.process_events(0)
|
|
410
|
+
elapsed = time.monotonic() - start
|
|
411
|
+
assert ran == [0, 1, 2]
|
|
412
|
+
assert app._cb_queue.empty()
|
|
413
|
+
assert elapsed < 0.05 # returned without blocking
|
|
414
|
+
|
|
415
|
+
def test_zero_empty_queue_returns_immediately(self):
|
|
416
|
+
app = self._serial_app()
|
|
417
|
+
start = time.monotonic()
|
|
418
|
+
app.process_events(0) # nothing queued
|
|
419
|
+
assert time.monotonic() - start < 0.05
|
|
420
|
+
|
|
421
|
+
def test_zero_propagates_result_slot(self):
|
|
422
|
+
app = self._serial_app()
|
|
423
|
+
slot = {"event": threading.Event()}
|
|
424
|
+
app._cb_queue.put((lambda: 42, (), {}, slot))
|
|
425
|
+
app.process_events(0)
|
|
426
|
+
assert slot["value"] == 42
|
|
427
|
+
assert slot["event"].is_set()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/examples/flask-multi-process/gunicorn.conf.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pgwidgets_python-0.3.1 → pgwidgets_python-0.3.3}/pgwidgets_python.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|