pgwidgets-python 0.3.4__tar.gz → 0.4.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.
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/PKG-INFO +3 -4
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/docs/WhatsNew.rst +35 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/docs/architecture.rst +45 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets/async_/application.py +101 -2
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets/async_/widget.py +436 -2
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets/method_types.py +165 -8
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets/sync/application.py +126 -2
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets/sync/widget.py +437 -2
- pgwidgets_python-0.4.0/pgwidgets/tree_model.py +356 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets_python.egg-info/PKG-INFO +3 -4
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets_python.egg-info/SOURCES.txt +4 -1
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets_python.egg-info/requires.txt +1 -1
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets_python.egg-info/scm_file_list.json +55 -52
- pgwidgets_python-0.4.0/pgwidgets_python.egg-info/scm_version.json +8 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pyproject.toml +11 -10
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/tests/test_session.py +59 -0
- pgwidgets_python-0.4.0/tests/test_tree_model.py +864 -0
- pgwidgets_python-0.4.0/tools/sync_async_tree_model.py +89 -0
- pgwidgets_python-0.3.4/pgwidgets_python.egg-info/scm_version.json +0 -8
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/.flake8 +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/.github/workflows/tests.yml +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/.gitignore +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/.readthedocs.yaml +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/LICENSE.md +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/README.md +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/docs/Makefile +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/docs/api/async.rst +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/docs/api/index.rst +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/docs/api/sync.rst +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/docs/async.rst +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/docs/callbacks.rst +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/docs/conf.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/docs/extras.rst +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/docs/getting-started.rst +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/docs/index.rst +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/docs/subclassing.rst +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/docs/sync.rst +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/docs/utilities.rst +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/docs/web-servers.rst +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/docs/widgets.rst +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/examples/README.md +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/examples/all_widgets.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/examples/all_widgets_async.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/examples/demo_async.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/examples/demo_sync.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/examples/demo_treeview.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/examples/flask-multi-process/README.md +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/examples/flask-multi-process/gunicorn.conf.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/examples/flask-multi-process/nginx.conf +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/examples/flask-multi-process/server.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/examples/flask-multi-process/user_app.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets/__init__.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets/_json.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets/async_/Widgets.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets/async_/__init__.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets/buffer.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets/callbacks.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets/defs.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets/extras/__init__.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets/extras/file_browser.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets/icons/pgw-python-logo.png +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets/sync/Widgets.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets/sync/__init__.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets/sync/text_source.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets/text_model.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets_python.egg-info/dependency_links.txt +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/pgwidgets_python.egg-info/top_level.txt +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/setup.cfg +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/test_pg2.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/tests/__init__.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/tests/test_async_session.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/tests/test_defs.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/tests/test_extras.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/tests/test_fonts.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/tests/test_protocol.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/tests/test_reconstruct.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/tests/test_resolve_kwargs.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/tests/test_stateful.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/tests/test_text_source.py +0 -0
- {pgwidgets_python-0.3.4 → pgwidgets_python-0.4.0}/tests/test_widget_classes.py +0 -0
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pgwidgets-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Python bindings for the pgwidgets JavaScript widget library
|
|
5
5
|
Author: PGWidgets Developers
|
|
6
|
-
License: BSD-3-Clause
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
7
|
Project-URL: Homepage, https://github.com/naojsoft/pgwidgets-python
|
|
8
8
|
Project-URL: Repository, https://github.com/naojsoft/pgwidgets-python
|
|
9
9
|
Keywords: widgets,ui,gui,websocket,browser
|
|
10
10
|
Classifier: Development Status :: 3 - Alpha
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: License :: OSI Approved :: BSD License
|
|
13
12
|
Classifier: Programming Language :: Python :: 3
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
@@ -17,7 +16,7 @@ Classifier: Topic :: Software Development :: User Interfaces
|
|
|
17
16
|
Requires-Python: >=3.12
|
|
18
17
|
Description-Content-Type: text/markdown
|
|
19
18
|
License-File: LICENSE.md
|
|
20
|
-
Requires-Dist: pgwidgets-js>=0.
|
|
19
|
+
Requires-Dist: pgwidgets-js>=0.4.0
|
|
21
20
|
Requires-Dist: websockets>=12
|
|
22
21
|
Provides-Extra: dev
|
|
23
22
|
Requires-Dist: sphinx; extra == "dev"
|
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
What's New
|
|
2
2
|
==========
|
|
3
3
|
|
|
4
|
+
Recent changes — since ``v0.3.5``
|
|
5
|
+
---------------------------------
|
|
6
|
+
|
|
7
|
+
TreeView / TableView: Python-authoritative model, batched updates
|
|
8
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
9
|
+
|
|
10
|
+
Python is now the source of truth for the tree/table model, so a
|
|
11
|
+
browser reconnect rebuilds the *current* state instead of replaying
|
|
12
|
+
the original snapshot. Previously only the bulk setters were tracked,
|
|
13
|
+
so later mutations were fire-and-forget: a reconnect silently reverted
|
|
14
|
+
every change made since the last full load and dropped the colour
|
|
15
|
+
overrides entirely.
|
|
16
|
+
|
|
17
|
+
* All the incremental edits -- ``set_cell``, ``add_item`` /
|
|
18
|
+
``remove_item`` / ``remove_items``, ``add_tree`` / ``delete_tree``,
|
|
19
|
+
and the row / column edits -- now apply to the stored model, so they
|
|
20
|
+
survive reconnect. Bulk setters deep-copy their input, so a caller
|
|
21
|
+
that keeps editing the structure it passed in can't corrupt the model.
|
|
22
|
+
* Colour overrides accumulate per cell / row / column / table and are
|
|
23
|
+
replayed after the data on reconnect, in chunked ``set_colors``
|
|
24
|
+
batches rather than one call per cell.
|
|
25
|
+
* ``update_tree`` diffs against the model and sends only the deltas, so
|
|
26
|
+
the browser keeps its expansion state, cell styles, and any open
|
|
27
|
+
editor.
|
|
28
|
+
* ``Session.batch()`` (and ``widget.batch()``) buffers a burst of calls
|
|
29
|
+
into a single ``batch`` message; runs of colour calls fold into
|
|
30
|
+
``set_colors``, so a plain per-cell loop still redraws once. State
|
|
31
|
+
updates as each call is made, so a reconnect mid-batch still
|
|
32
|
+
reconstructs correctly, and a browser predating the batch message
|
|
33
|
+
falls back to individual calls.
|
|
34
|
+
* Fixed expand/collapse tracking after ``expand_all()`` /
|
|
35
|
+
``collapse_all()`` (the ``"_all"`` sentinel raised ``AttributeError``
|
|
36
|
+
in the websocket handler when a node was later expanded or
|
|
37
|
+
collapsed).
|
|
38
|
+
|
|
4
39
|
Recent changes — since ``v0.3.0``
|
|
5
40
|
---------------------------------
|
|
6
41
|
|
|
@@ -54,6 +54,9 @@ All messages are JSON objects with a ``type`` field.
|
|
|
54
54
|
call a method on a widget.
|
|
55
55
|
- ``{"type": "call", ..., "silent": true}`` -- call a method without
|
|
56
56
|
triggering callbacks (used for cross-browser sync).
|
|
57
|
+
- ``{"type": "batch", "calls": [{"wid": 1, "method": "set_cell",
|
|
58
|
+
"args": [...]}, ...]}`` -- apply many calls as one message (see
|
|
59
|
+
`Batching Updates`_).
|
|
57
60
|
- ``{"type": "listen", "wid": 1, "action": "activated"}`` -- subscribe to a
|
|
58
61
|
callback.
|
|
59
62
|
- ``{"type": "unlisten", "wid": 1, "action": "activated"}`` -- unsubscribe.
|
|
@@ -180,3 +183,45 @@ passed as an argument to another widget's method (e.g., ``vbox.add_widget(btn,
|
|
|
180
183
|
0)``), the framework automatically converts the Python ``Widget`` object to a
|
|
181
184
|
``{"__wid__": N}`` reference on the wire, and converts it back on return
|
|
182
185
|
values.
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
Batching Updates
|
|
189
|
+
----------------
|
|
190
|
+
|
|
191
|
+
Every widget call is normally its own message, and blocks until the
|
|
192
|
+
browser replies. While the UI is being built that costs nothing --
|
|
193
|
+
with no browser attached the calls return immediately -- so the cost
|
|
194
|
+
only shows up when a connected browser is being updated in bulk: a
|
|
195
|
+
refresh rewriting a few hundred cells, say. Two things make that slow:
|
|
196
|
+
one round-trip per call, and widgets such as ``TreeView`` re-rendering
|
|
197
|
+
on each one, which also makes the update paint in visible stages.
|
|
198
|
+
|
|
199
|
+
``Session.batch()`` (also reachable as ``widget.batch()``, since
|
|
200
|
+
batching is per session) buffers the calls and sends them as a single
|
|
201
|
+
``batch`` message. The browser applies them with rendering suspended
|
|
202
|
+
on each widget it touches, so every widget draws once:
|
|
203
|
+
|
|
204
|
+
.. code-block:: python
|
|
205
|
+
|
|
206
|
+
with tree.batch():
|
|
207
|
+
for path, col_key, value in changes:
|
|
208
|
+
tree.set_cell(path, col_key, value)
|
|
209
|
+
status.set_text("updated")
|
|
210
|
+
|
|
211
|
+
Semantics worth knowing:
|
|
212
|
+
|
|
213
|
+
- **State is still updated immediately.** Only the traffic is
|
|
214
|
+
deferred, so the Python-side model stays correct throughout and a
|
|
215
|
+
reconnect landing mid-batch reconstructs from it correctly.
|
|
216
|
+
- **Batched calls return None.** Methods that genuinely need an answer
|
|
217
|
+
from the browser -- getters, and the browser-only queries -- flush the
|
|
218
|
+
pending batch and execute immediately, so ordering is preserved.
|
|
219
|
+
- **Nesting is allowed;** only the outermost block flushes.
|
|
220
|
+
- **The buffer is flushed even if the body raises.** Those calls have
|
|
221
|
+
already been applied to the model, so dropping them would leave the
|
|
222
|
+
browser diverged from it.
|
|
223
|
+
|
|
224
|
+
Reconnection does its own batching: the colour overrides a tree or
|
|
225
|
+
table carries are replayed as a few large ``set_colors`` calls rather
|
|
226
|
+
than one per cell, so restoring a heavily coloured table is not
|
|
227
|
+
proportional to the number of round-trips.
|
|
@@ -11,6 +11,7 @@ the UI can be reconstructed when a browser reconnects.
|
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
import asyncio
|
|
14
|
+
import contextlib
|
|
14
15
|
import json
|
|
15
16
|
import logging
|
|
16
17
|
import mimetypes
|
|
@@ -31,6 +32,8 @@ from pgwidgets.method_types import (
|
|
|
31
32
|
STATE_SYNC_CALLBACKS, STATE_SYNC_REQUIRES_OPTION,
|
|
32
33
|
WIDGET_CALLBACK_SYNC, POST_CHILDREN_STATE_KEYS, ITEM_LIST_CONFIG,
|
|
33
34
|
CHILD_CLOSE_CALLBACKS, REPLAY_METHODS, TREE_VIEW_WIDGETS,
|
|
35
|
+
TREE_OVERRIDE_REPLAY, colour_batches, JS_ONLY_METHODS,
|
|
36
|
+
coalesce_colour_calls,
|
|
34
37
|
BINARY_STATE_KEYS, _send_binary_auto,
|
|
35
38
|
)
|
|
36
39
|
from pgwidgets.async_.widget import Widget, build_all_widget_classes
|
|
@@ -142,6 +145,14 @@ class Session:
|
|
|
142
145
|
|
|
143
146
|
self._reconstructing = False # suppress callbacks during reconstruction
|
|
144
147
|
|
|
148
|
+
# Batching (see batch()). While _batch_depth is non-zero, calls
|
|
149
|
+
# are buffered here instead of being sent one at a time.
|
|
150
|
+
self._batch_depth = 0
|
|
151
|
+
self._batch_calls = []
|
|
152
|
+
# cleared if a browser rejects the 'batch' message; a new
|
|
153
|
+
# connection may be a newer client, so add_connection resets it
|
|
154
|
+
self._batch_supported = True
|
|
155
|
+
|
|
145
156
|
# Browser viewport size (updated by 'viewport' messages from JS).
|
|
146
157
|
self._screen_size = (0, 0)
|
|
147
158
|
|
|
@@ -198,6 +209,9 @@ class Session:
|
|
|
198
209
|
"""Add a browser connection to this session."""
|
|
199
210
|
if ws not in self._connections:
|
|
200
211
|
self._connections.append(ws)
|
|
212
|
+
# a reconnecting browser may be a newer client than the one
|
|
213
|
+
# that made us give up on batching
|
|
214
|
+
self._batch_supported = True
|
|
201
215
|
|
|
202
216
|
def remove_connection(self, ws):
|
|
203
217
|
"""Remove a browser connection from this session."""
|
|
@@ -434,6 +448,14 @@ class Session:
|
|
|
434
448
|
widget._state[spec[0]] = tuple(args)
|
|
435
449
|
else:
|
|
436
450
|
widget._state[spec] = args[0]
|
|
451
|
+
# ComboBox: a value committed with Enter in the browser may be
|
|
452
|
+
# a new entry the browser appended to its list; mirror that
|
|
453
|
+
# append so our _items (and thus get_index) stays consistent.
|
|
454
|
+
if (widget._js_class == "ComboBox" and action == "activated"
|
|
455
|
+
and len(args) >= 2):
|
|
456
|
+
items = widget._state.setdefault("_items", [])
|
|
457
|
+
if args[1] not in items:
|
|
458
|
+
items.append(args[1])
|
|
437
459
|
|
|
438
460
|
# Dialog autoclose: the JS side auto-hides when a button is
|
|
439
461
|
# clicked. Sync that to Python so reconstruction doesn't
|
|
@@ -451,7 +473,8 @@ class Session:
|
|
|
451
473
|
key_path = tuple(path) if isinstance(path, list) else path
|
|
452
474
|
expanded = widget._state.setdefault(
|
|
453
475
|
"_expanded_paths", set())
|
|
454
|
-
expanded
|
|
476
|
+
if expanded != "_all":
|
|
477
|
+
expanded.add(key_path)
|
|
455
478
|
collapsed = widget._state.get("_collapsed_paths")
|
|
456
479
|
if collapsed is not None and collapsed != "_all":
|
|
457
480
|
collapsed.discard(key_path)
|
|
@@ -460,7 +483,7 @@ class Session:
|
|
|
460
483
|
path = args[1]
|
|
461
484
|
key_path = tuple(path) if isinstance(path, list) else path
|
|
462
485
|
expanded = widget._state.get("_expanded_paths")
|
|
463
|
-
if expanded is not None:
|
|
486
|
+
if expanded is not None and expanded != "_all":
|
|
464
487
|
expanded.discard(key_path)
|
|
465
488
|
collapsed = widget._state.setdefault(
|
|
466
489
|
"_collapsed_paths", set())
|
|
@@ -670,11 +693,76 @@ class Session:
|
|
|
670
693
|
})
|
|
671
694
|
return wid
|
|
672
695
|
|
|
696
|
+
@contextlib.asynccontextmanager
|
|
697
|
+
async def batch(self):
|
|
698
|
+
"""Apply many updates as one message.
|
|
699
|
+
|
|
700
|
+
The async counterpart of the sync ``Session.batch``; see that
|
|
701
|
+
docstring for the semantics. Used as::
|
|
702
|
+
|
|
703
|
+
async with tree.batch():
|
|
704
|
+
for path, col, value in changes:
|
|
705
|
+
await tree.set_cell(path, col, value)
|
|
706
|
+
"""
|
|
707
|
+
self._batch_depth += 1
|
|
708
|
+
try:
|
|
709
|
+
yield self
|
|
710
|
+
finally:
|
|
711
|
+
self._batch_depth -= 1
|
|
712
|
+
if self._batch_depth == 0:
|
|
713
|
+
await self._flush_batch()
|
|
714
|
+
|
|
715
|
+
async def _flush_batch(self):
|
|
716
|
+
"""Send everything buffered by batch() as one message.
|
|
717
|
+
|
|
718
|
+
A browser older than this server won't know the ``batch``
|
|
719
|
+
message -- a page that was loaded before the server was
|
|
720
|
+
upgraded, most commonly. Rather than failing the caller's
|
|
721
|
+
update, fall back to sending the calls individually and stop
|
|
722
|
+
trying to batch for this connection. Reloading the page picks
|
|
723
|
+
up the current client and re-enables it.
|
|
724
|
+
"""
|
|
725
|
+
calls, self._batch_calls = self._batch_calls, []
|
|
726
|
+
if not calls:
|
|
727
|
+
return None
|
|
728
|
+
# _send handles the no-browser case (and is the single place
|
|
729
|
+
# that policy lives)
|
|
730
|
+
# Fold runs of colour calls into set_colors: one re-render in
|
|
731
|
+
# the browser instead of one per cell.
|
|
732
|
+
calls = coalesce_colour_calls(calls)
|
|
733
|
+
if self._batch_supported:
|
|
734
|
+
self._logger.debug("flushing a batch of %d call(s)", len(calls))
|
|
735
|
+
try:
|
|
736
|
+
return await self._send({"type": "batch", "calls": calls})
|
|
737
|
+
except RuntimeError as e:
|
|
738
|
+
if "Unknown message type" not in str(e):
|
|
739
|
+
raise
|
|
740
|
+
self._batch_supported = False
|
|
741
|
+
self._logger.warning(
|
|
742
|
+
"browser does not understand batched updates (it is "
|
|
743
|
+
"running an older pgwidgets-js); sending calls "
|
|
744
|
+
"individually. Reload the page to re-enable batching.")
|
|
745
|
+
for call in calls:
|
|
746
|
+
await self._send({"type": "call", **call})
|
|
747
|
+
return None
|
|
748
|
+
|
|
749
|
+
def _needs_result(self, method):
|
|
750
|
+
"""True if `method` has to reach the browser now to be useful."""
|
|
751
|
+
return method.startswith("get_") or method in JS_ONLY_METHODS
|
|
752
|
+
|
|
673
753
|
async def _call(self, wid, method, *args):
|
|
674
754
|
"""Call a method on a JS widget.
|
|
675
755
|
|
|
676
756
|
Returns None if no browser is connected.
|
|
677
757
|
"""
|
|
758
|
+
if self._batch_depth > 0:
|
|
759
|
+
if not self._needs_result(method):
|
|
760
|
+
self._batch_calls.append({"wid": wid, "method": method,
|
|
761
|
+
"args": list(args)})
|
|
762
|
+
return None
|
|
763
|
+
# has to go now -- flush what's queued so it stays ordered
|
|
764
|
+
await self._flush_batch()
|
|
765
|
+
|
|
678
766
|
result = await self._send({
|
|
679
767
|
"type": "call",
|
|
680
768
|
"wid": wid,
|
|
@@ -1327,6 +1415,13 @@ class Session:
|
|
|
1327
1415
|
list(path))
|
|
1328
1416
|
continue
|
|
1329
1417
|
|
|
1418
|
+
# Tree/table colour overrides are replayed together as
|
|
1419
|
+
# batches once the whole state has been walked (see
|
|
1420
|
+
# below) -- one round-trip and one browser re-render per
|
|
1421
|
+
# batch, rather than per coloured cell.
|
|
1422
|
+
if key in TREE_OVERRIDE_REPLAY or key == "_table_style":
|
|
1423
|
+
continue
|
|
1424
|
+
|
|
1330
1425
|
# Tree/table sort
|
|
1331
1426
|
if key == "_sort":
|
|
1332
1427
|
col, asc = value
|
|
@@ -1342,6 +1437,10 @@ class Session:
|
|
|
1342
1437
|
else:
|
|
1343
1438
|
await self._call(widget._wid, method_name, value)
|
|
1344
1439
|
|
|
1440
|
+
# Colour overrides, batched
|
|
1441
|
+
for spec in colour_batches(widget):
|
|
1442
|
+
await self._call(widget._wid, "set_colors", spec)
|
|
1443
|
+
|
|
1345
1444
|
# Show/hide
|
|
1346
1445
|
for key, value in widget._state.items():
|
|
1347
1446
|
if key in self._FIXED_STATE_KEYS:
|