pgwidgets-python 0.3.4__py3-none-any.whl → 0.4.0__py3-none-any.whl
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/async_/application.py +101 -2
- pgwidgets/async_/widget.py +436 -2
- pgwidgets/method_types.py +165 -8
- pgwidgets/sync/application.py +126 -2
- pgwidgets/sync/widget.py +437 -2
- pgwidgets/tree_model.py +356 -0
- {pgwidgets_python-0.3.4.dist-info → pgwidgets_python-0.4.0.dist-info}/METADATA +3 -4
- {pgwidgets_python-0.3.4.dist-info → pgwidgets_python-0.4.0.dist-info}/RECORD +11 -12
- {pgwidgets_python-0.3.4.dist-info → pgwidgets_python-0.4.0.dist-info}/WHEEL +1 -1
- pgwidgets_python-0.3.4.dist-info/scm_file_list.json +0 -71
- pgwidgets_python-0.3.4.dist-info/scm_version.json +0 -8
- {pgwidgets_python-0.3.4.dist-info → pgwidgets_python-0.4.0.dist-info}/licenses/LICENSE.md +0 -0
- {pgwidgets_python-0.3.4.dist-info → pgwidgets_python-0.4.0.dist-info}/top_level.txt +0 -0
pgwidgets/tree_model.py
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
"""Python-side model of TreeView / TableView content.
|
|
2
|
+
|
|
3
|
+
pgwidgets-python drives the browser over a websocket: the browser is a
|
|
4
|
+
*view* that can be discarded and rebuilt at any moment -- a page reload, a
|
|
5
|
+
network blip, a second browser attaching to the same session. Everything
|
|
6
|
+
needed to rebuild it therefore has to live on the Python side, which is the
|
|
7
|
+
source of truth.
|
|
8
|
+
|
|
9
|
+
Historically only the bulk calls (``set_tree`` / ``set_rows`` /
|
|
10
|
+
``set_columns``) were tracked, and every later mutation -- ``set_cell``,
|
|
11
|
+
``add_item``, ``add_tree``, ``delete_tree``, the row and column edits --
|
|
12
|
+
was fire-and-forget. A reconnect then replayed the *original* bulk
|
|
13
|
+
snapshot, silently reverting every change made since. These helpers keep
|
|
14
|
+
the model current as those calls go by, so reconstruction replays one
|
|
15
|
+
accurate ``set_tree`` / ``set_rows``.
|
|
16
|
+
|
|
17
|
+
Having the model on hand also makes ``update_tree`` incremental: rather
|
|
18
|
+
than shipping the whole tree and having the browser rebuild it (losing
|
|
19
|
+
expansion state, cell styles and any open editor), :func:`diff_tree`
|
|
20
|
+
computes the minimal set of operations against the model and only those go
|
|
21
|
+
over the wire.
|
|
22
|
+
|
|
23
|
+
Everything here is a pure function over plain dicts and lists, so the sync
|
|
24
|
+
and async wrappers share one implementation and it can be tested without a
|
|
25
|
+
session or a browser.
|
|
26
|
+
|
|
27
|
+
Tree shape (mirroring the JS side)
|
|
28
|
+
----------------------------------
|
|
29
|
+
A tree is a dict keyed by stable string ids. A node's children are its
|
|
30
|
+
dict-valued entries; its own column values are either the ``__values__``
|
|
31
|
+
entry, when present, or its primitive entries. A node with no dict-valued
|
|
32
|
+
entries is a leaf, and the dict *is* its column values.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
import copy
|
|
36
|
+
|
|
37
|
+
VALUES_KEY = '__values__'
|
|
38
|
+
|
|
39
|
+
_MISSING = object()
|
|
40
|
+
|
|
41
|
+
__all__ = ['VALUES_KEY', 'node_at', 'children_of', 'values_of',
|
|
42
|
+
'values_target', 'copy_tree', 'set_cell', 'add_item',
|
|
43
|
+
'remove_item', 'remove_items', 'merge_tree', 'delete_tree',
|
|
44
|
+
'diff_tree', 'row_set_cell', 'insert_row', 'append_row',
|
|
45
|
+
'delete_row', 'column_key', 'insert_column', 'append_column',
|
|
46
|
+
'delete_column']
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# ----- tree navigation ---------------------------------------------
|
|
50
|
+
|
|
51
|
+
def copy_tree(tree):
|
|
52
|
+
"""Return a deep copy, so the model can't be mutated behind our back
|
|
53
|
+
by a caller that keeps and edits the structure it handed us."""
|
|
54
|
+
try:
|
|
55
|
+
return copy.deepcopy(tree)
|
|
56
|
+
except Exception:
|
|
57
|
+
# exotic values (e.g. an unpicklable object stashed in a row)
|
|
58
|
+
# -- fall back to sharing rather than failing the call
|
|
59
|
+
return tree
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def node_at(tree, path):
|
|
63
|
+
"""Return the node at `path` (a sequence of keys), or None.
|
|
64
|
+
|
|
65
|
+
An empty path denotes the root, i.e. `tree` itself.
|
|
66
|
+
"""
|
|
67
|
+
node = tree
|
|
68
|
+
for key in (path or ()):
|
|
69
|
+
if not isinstance(node, dict) or key not in node:
|
|
70
|
+
return None
|
|
71
|
+
node = node[key]
|
|
72
|
+
return node
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def children_of(node):
|
|
76
|
+
"""Return {key: child} for a node's dict-valued entries."""
|
|
77
|
+
if not isinstance(node, dict):
|
|
78
|
+
return {}
|
|
79
|
+
return {k: v for k, v in node.items()
|
|
80
|
+
if k != VALUES_KEY and isinstance(v, dict)}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def values_of(node):
|
|
84
|
+
"""Return a node's column values (read-only use)."""
|
|
85
|
+
if not isinstance(node, dict):
|
|
86
|
+
return {}
|
|
87
|
+
vals = node.get(VALUES_KEY)
|
|
88
|
+
if isinstance(vals, dict):
|
|
89
|
+
return vals
|
|
90
|
+
return {k: v for k, v in node.items() if not isinstance(v, dict)}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def values_target(node):
|
|
94
|
+
"""Return the dict a node's column values should be *written* to.
|
|
95
|
+
|
|
96
|
+
That is ``__values__`` for an interior node that carries one, else the
|
|
97
|
+
node itself (a leaf, or an interior whose values sit alongside its
|
|
98
|
+
children).
|
|
99
|
+
"""
|
|
100
|
+
if not isinstance(node, dict):
|
|
101
|
+
return None
|
|
102
|
+
vals = node.get(VALUES_KEY)
|
|
103
|
+
if isinstance(vals, dict):
|
|
104
|
+
return vals
|
|
105
|
+
return node
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# ----- tree mutation -----------------------------------------------
|
|
109
|
+
|
|
110
|
+
def set_cell(tree, path, col_key, value):
|
|
111
|
+
"""Write one column value. Returns True if the path resolved."""
|
|
112
|
+
node = node_at(tree, path)
|
|
113
|
+
target = values_target(node)
|
|
114
|
+
if target is None:
|
|
115
|
+
return False
|
|
116
|
+
target[col_key] = value
|
|
117
|
+
return True
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def add_item(tree, parent, key, values):
|
|
121
|
+
"""Add/replace a single child under `parent` (None/[] = root)."""
|
|
122
|
+
node = node_at(tree, parent)
|
|
123
|
+
if not isinstance(node, dict):
|
|
124
|
+
return False
|
|
125
|
+
node[key] = copy_tree(values) if isinstance(values, dict) else values
|
|
126
|
+
return True
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def remove_item(tree, path):
|
|
130
|
+
"""Remove the node at `path` together with its subtree."""
|
|
131
|
+
if not path:
|
|
132
|
+
return False
|
|
133
|
+
parent = node_at(tree, path[:-1])
|
|
134
|
+
if not isinstance(parent, dict) or path[-1] not in parent:
|
|
135
|
+
return False
|
|
136
|
+
del parent[path[-1]]
|
|
137
|
+
return True
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def remove_items(tree, paths):
|
|
141
|
+
return sum(1 for p in (paths or []) if remove_item(tree, p))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def merge_tree(tree, subtree, parent=None):
|
|
145
|
+
"""Merge `subtree` under `parent`, replacing same-key children
|
|
146
|
+
subtree-deep -- the semantics of the JS ``add_tree``."""
|
|
147
|
+
node = node_at(tree, parent)
|
|
148
|
+
if not isinstance(node, dict) or not isinstance(subtree, dict):
|
|
149
|
+
return False
|
|
150
|
+
for key, value in subtree.items():
|
|
151
|
+
node[key] = copy_tree(value) if isinstance(value, dict) else value
|
|
152
|
+
return True
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def delete_tree(tree, spec, prune_empty=True):
|
|
156
|
+
"""Delete the nodes named by `spec`, mirroring the JS ``_deleteSpec``.
|
|
157
|
+
|
|
158
|
+
A key mapping to an empty dict (or a non-dict) removes that node and
|
|
159
|
+
its whole subtree; a key mapping to a non-empty dict is descended into
|
|
160
|
+
so only the named descendants go. Keys absent from the tree are
|
|
161
|
+
ignored. With `prune_empty`, a branch left childless is removed too,
|
|
162
|
+
cascading upward. Returns the number of nodes removed.
|
|
163
|
+
"""
|
|
164
|
+
def _delete(node, sub_spec):
|
|
165
|
+
count = 0
|
|
166
|
+
for key in list(sub_spec.keys()):
|
|
167
|
+
if not isinstance(node, dict) or key not in node:
|
|
168
|
+
continue # not present; skip
|
|
169
|
+
child = node[key]
|
|
170
|
+
sub = sub_spec[key]
|
|
171
|
+
descend = (isinstance(sub, dict) and len(sub) > 0
|
|
172
|
+
and len(children_of(child)) > 0)
|
|
173
|
+
if not descend:
|
|
174
|
+
del node[key]
|
|
175
|
+
count += 1
|
|
176
|
+
else:
|
|
177
|
+
count += _delete(child, sub)
|
|
178
|
+
if prune_empty and len(children_of(child)) == 0:
|
|
179
|
+
del node[key]
|
|
180
|
+
count += 1
|
|
181
|
+
return count
|
|
182
|
+
|
|
183
|
+
return _delete(tree, spec or {})
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# ----- tree diffing -------------------------------------------------
|
|
187
|
+
|
|
188
|
+
def _count_nodes(tree):
|
|
189
|
+
total = 0
|
|
190
|
+
for child in children_of(tree).values():
|
|
191
|
+
total += 1 + _count_nodes(child)
|
|
192
|
+
return total
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def diff_tree(old, new, max_ops=None):
|
|
196
|
+
"""Return the operations that turn `old` into `new`.
|
|
197
|
+
|
|
198
|
+
Each operation is a tuple ``(method_name, *args)`` ready to dispatch
|
|
199
|
+
at the browser: ``remove_items``, ``add_tree`` and ``set_cell``.
|
|
200
|
+
Deletions are emitted first so additions never collide with a node on
|
|
201
|
+
its way out.
|
|
202
|
+
|
|
203
|
+
Returns None when the trees differ so broadly that replacing the whole
|
|
204
|
+
thing is cheaper than patching it -- the caller should fall back to
|
|
205
|
+
``set_tree``. `max_ops` defaults to the node count of `new`.
|
|
206
|
+
"""
|
|
207
|
+
removals = []
|
|
208
|
+
additions = []
|
|
209
|
+
updates = []
|
|
210
|
+
|
|
211
|
+
def walk(old_node, new_node, path):
|
|
212
|
+
old_children = children_of(old_node)
|
|
213
|
+
new_children = children_of(new_node)
|
|
214
|
+
|
|
215
|
+
for key in old_children:
|
|
216
|
+
if key not in new_children:
|
|
217
|
+
removals.append(list(path) + [key])
|
|
218
|
+
|
|
219
|
+
for key, new_child in new_children.items():
|
|
220
|
+
old_child = old_children.get(key, _MISSING)
|
|
221
|
+
if old_child is _MISSING:
|
|
222
|
+
additions.append((list(path), key, new_child))
|
|
223
|
+
else:
|
|
224
|
+
walk(old_child, new_child, tuple(path) + (key,))
|
|
225
|
+
|
|
226
|
+
if path: # the root has no column values of its own
|
|
227
|
+
old_vals = values_of(old_node)
|
|
228
|
+
new_vals = values_of(new_node)
|
|
229
|
+
for col_key, value in new_vals.items():
|
|
230
|
+
if old_vals.get(col_key, _MISSING) != value:
|
|
231
|
+
updates.append((list(path), col_key, value))
|
|
232
|
+
|
|
233
|
+
walk(old if isinstance(old, dict) else {},
|
|
234
|
+
new if isinstance(new, dict) else {}, ())
|
|
235
|
+
|
|
236
|
+
if max_ops is None:
|
|
237
|
+
max_ops = max(_count_nodes(new), 1)
|
|
238
|
+
if len(removals) + len(additions) + len(updates) > max_ops:
|
|
239
|
+
return None # wholesale replacement is cheaper
|
|
240
|
+
|
|
241
|
+
ops = []
|
|
242
|
+
if removals:
|
|
243
|
+
ops.append(('remove_items', removals))
|
|
244
|
+
for parent, key, node in additions:
|
|
245
|
+
ops.append(('add_tree', {key: node}, parent or None))
|
|
246
|
+
for path, col_key, value in updates:
|
|
247
|
+
ops.append(('set_cell', path, col_key, value))
|
|
248
|
+
return ops
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
# ----- flat rows (TableView) ----------------------------------------
|
|
252
|
+
|
|
253
|
+
def column_key(column, index=None):
|
|
254
|
+
"""Extract a column's key from any descriptor form."""
|
|
255
|
+
if isinstance(column, dict):
|
|
256
|
+
return column.get('key') or column.get('label') or (
|
|
257
|
+
f'col{index}' if index is not None else None)
|
|
258
|
+
if isinstance(column, (list, tuple)):
|
|
259
|
+
return column[1] if len(column) > 1 else column[0]
|
|
260
|
+
return column
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def row_set_cell(rows, columns, row, col, value):
|
|
264
|
+
"""Write one cell of a flat row list.
|
|
265
|
+
|
|
266
|
+
`row` is an index; `col` is a column key or index. Rows may be dicts
|
|
267
|
+
(keyed by column key) or positional sequences.
|
|
268
|
+
"""
|
|
269
|
+
if not isinstance(rows, list) or not isinstance(row, int):
|
|
270
|
+
return False
|
|
271
|
+
if not (0 <= row < len(rows)):
|
|
272
|
+
return False
|
|
273
|
+
keys = [column_key(c, i) for i, c in enumerate(columns or [])]
|
|
274
|
+
target = rows[row]
|
|
275
|
+
if isinstance(target, dict):
|
|
276
|
+
key = col if not isinstance(col, int) else (
|
|
277
|
+
keys[col] if 0 <= col < len(keys) else None)
|
|
278
|
+
if key is None:
|
|
279
|
+
return False
|
|
280
|
+
target[key] = value
|
|
281
|
+
return True
|
|
282
|
+
if isinstance(target, list):
|
|
283
|
+
idx = col if isinstance(col, int) else (
|
|
284
|
+
keys.index(col) if col in keys else None)
|
|
285
|
+
if idx is None or not (0 <= idx < len(target)):
|
|
286
|
+
return False
|
|
287
|
+
target[idx] = value
|
|
288
|
+
return True
|
|
289
|
+
return False
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def insert_row(rows, index, values):
|
|
293
|
+
if not isinstance(rows, list):
|
|
294
|
+
return False
|
|
295
|
+
if not isinstance(index, int) or index < 0 or index > len(rows):
|
|
296
|
+
index = len(rows) # out of range clamps to append, as in JS
|
|
297
|
+
rows.insert(index, copy_tree(values))
|
|
298
|
+
return True
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def append_row(rows, values):
|
|
302
|
+
if not isinstance(rows, list):
|
|
303
|
+
return False
|
|
304
|
+
rows.append(copy_tree(values))
|
|
305
|
+
return True
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def delete_row(rows, index):
|
|
309
|
+
if not isinstance(rows, list) or not isinstance(index, int):
|
|
310
|
+
return False
|
|
311
|
+
if not (0 <= index < len(rows)):
|
|
312
|
+
return False
|
|
313
|
+
del rows[index]
|
|
314
|
+
return True
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
# ----- columns -------------------------------------------------------
|
|
318
|
+
|
|
319
|
+
def insert_column(columns, column, before=None, index=None):
|
|
320
|
+
"""Insert a column.
|
|
321
|
+
|
|
322
|
+
TreeView names the insertion point by column key (`before`); TableView
|
|
323
|
+
by position (`index`). Pass whichever the caller supplied.
|
|
324
|
+
"""
|
|
325
|
+
if not isinstance(columns, list):
|
|
326
|
+
return False
|
|
327
|
+
if index is None and before is not None:
|
|
328
|
+
keys = [column_key(c, i) for i, c in enumerate(columns)]
|
|
329
|
+
index = keys.index(before) if before in keys else len(columns)
|
|
330
|
+
if not isinstance(index, int) or index < 0 or index > len(columns):
|
|
331
|
+
index = len(columns)
|
|
332
|
+
columns.insert(index, copy_tree(column))
|
|
333
|
+
return True
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def append_column(columns, column):
|
|
337
|
+
if not isinstance(columns, list):
|
|
338
|
+
return False
|
|
339
|
+
columns.append(copy_tree(column))
|
|
340
|
+
return True
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def delete_column(columns, col):
|
|
344
|
+
"""Delete by column key (TreeView) or by index (TableView)."""
|
|
345
|
+
if not isinstance(columns, list):
|
|
346
|
+
return False
|
|
347
|
+
if isinstance(col, int):
|
|
348
|
+
if not (0 <= col < len(columns)):
|
|
349
|
+
return False
|
|
350
|
+
del columns[col]
|
|
351
|
+
return True
|
|
352
|
+
keys = [column_key(c, i) for i, c in enumerate(columns)]
|
|
353
|
+
if col not in keys:
|
|
354
|
+
return False
|
|
355
|
+
del columns[keys.index(col)]
|
|
356
|
+
return True
|
|
@@ -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"
|
|
@@ -3,24 +3,23 @@ pgwidgets/_json.py,sha256=o21qywJ6yAldbqxTq3nLwgK9O67r3a8JxhvI_WAJnMY,2184
|
|
|
3
3
|
pgwidgets/buffer.py,sha256=BYj_nb6fuyGsBqUp3_Z1YrzUm4h5VmoWPGKvpTjWrYk,4048
|
|
4
4
|
pgwidgets/callbacks.py,sha256=gA2FnX0N5BmbmOMyEV35yHySgAfVjfAZcZhZbo7j4-c,3314
|
|
5
5
|
pgwidgets/defs.py,sha256=Q8qhvTeansFU1Av6j5ncdwF2xSNHrpXuKErdHWQ3HiA,436
|
|
6
|
-
pgwidgets/method_types.py,sha256=
|
|
6
|
+
pgwidgets/method_types.py,sha256=XDT8EfE6UWFcs_S2moqZs3SulUDLIjwNFx6UigNAeRk,24811
|
|
7
7
|
pgwidgets/text_model.py,sha256=xlSAoh1ikIG9ZYVwx5wHWrohkGkyu7R6Cbs36_xnbMg,29313
|
|
8
|
+
pgwidgets/tree_model.py,sha256=agif6vSeLzcGfLe1tB-O2MIkXv7Qgb53uWMoTXDSgAc,12144
|
|
8
9
|
pgwidgets/async_/Widgets.py,sha256=vld2xkvusBBEVbhbezaJsjBRRj3L7c17Up0pOVs8PGo,723
|
|
9
10
|
pgwidgets/async_/__init__.py,sha256=rXB-v9XRrYt1imYuYikhkzIyiRqaYhlTpQei2LHaQ18,564
|
|
10
|
-
pgwidgets/async_/application.py,sha256=
|
|
11
|
-
pgwidgets/async_/widget.py,sha256=
|
|
11
|
+
pgwidgets/async_/application.py,sha256=U-D1WzazOnuuZQ1djQS2vZyS-xtcRY0RVWtbgr0bCrQ,85368
|
|
12
|
+
pgwidgets/async_/widget.py,sha256=nHWszUHXgwv37SOmvjA2AqGNYdxOVaEugPOjfRrwB48,57043
|
|
12
13
|
pgwidgets/extras/__init__.py,sha256=AXUmFtnn4RSlp6pJX6z55j-m_29VmfzpYIjQvAy7pO0,325
|
|
13
14
|
pgwidgets/extras/file_browser.py,sha256=Lkz_n56_YW7rV_k8pmhgZrwulGsujk9vrzv66zgIgec,17347
|
|
14
15
|
pgwidgets/icons/pgw-python-logo.png,sha256=_Rxicp9EKy2Pzz1HtobxEnntSI-y5TtAVPlIzZIsfYs,2428840
|
|
15
16
|
pgwidgets/sync/Widgets.py,sha256=7SaocMVMzFHhi51pjuEAr47Wez90b_a61kDbiv0jOfM,706
|
|
16
17
|
pgwidgets/sync/__init__.py,sha256=SF5RTAvtu8BbYBWzpiCPipy6DJzNXf6nqk9xdvwxUhQ,542
|
|
17
|
-
pgwidgets/sync/application.py,sha256=
|
|
18
|
+
pgwidgets/sync/application.py,sha256=WOWQAR8FeieVWjwc9yHFHqGposBdZk8AcGqIKj27bwM,102478
|
|
18
19
|
pgwidgets/sync/text_source.py,sha256=Vz8mJsQXLFOAVwZCOrAXSixNfYQHjYZxxAIZjWBCAnY,8341
|
|
19
|
-
pgwidgets/sync/widget.py,sha256=
|
|
20
|
-
pgwidgets_python-0.
|
|
21
|
-
pgwidgets_python-0.
|
|
22
|
-
pgwidgets_python-0.
|
|
23
|
-
pgwidgets_python-0.
|
|
24
|
-
pgwidgets_python-0.
|
|
25
|
-
pgwidgets_python-0.3.4.dist-info/top_level.txt,sha256=wwL6fBq0gU-JwzlM6TdduY1qYpu39ysqnnbQT-1bqAs,10
|
|
26
|
-
pgwidgets_python-0.3.4.dist-info/RECORD,,
|
|
20
|
+
pgwidgets/sync/widget.py,sha256=gp7cio4uB822hrk-AcqoGWsOsABaweB5wUpIro3yV0g,57455
|
|
21
|
+
pgwidgets_python-0.4.0.dist-info/licenses/LICENSE.md,sha256=LoM3fMTiMnQuHRCJghdjOtjnCrL8soBpu2PFk24Xvyg,1528
|
|
22
|
+
pgwidgets_python-0.4.0.dist-info/METADATA,sha256=-lBgjLX8ifsJ7rtF7HqniV8I_JN2fmBGv8mr11kCnEs,4643
|
|
23
|
+
pgwidgets_python-0.4.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
24
|
+
pgwidgets_python-0.4.0.dist-info/top_level.txt,sha256=wwL6fBq0gU-JwzlM6TdduY1qYpu39ysqnnbQT-1bqAs,10
|
|
25
|
+
pgwidgets_python-0.4.0.dist-info/RECORD,,
|
|
@@ -1,71 +0,0 @@
|
|
|
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_text_source.py",
|
|
32
|
-
"tests/test_async_session.py",
|
|
33
|
-
"tests/test_defs.py",
|
|
34
|
-
"tests/test_fonts.py",
|
|
35
|
-
"tests/test_session.py",
|
|
36
|
-
"tests/test_extras.py",
|
|
37
|
-
"tests/test_widget_classes.py",
|
|
38
|
-
"tests/test_reconstruct.py",
|
|
39
|
-
"pgwidgets/_json.py",
|
|
40
|
-
"pgwidgets/__init__.py",
|
|
41
|
-
"pgwidgets/callbacks.py",
|
|
42
|
-
"pgwidgets/method_types.py",
|
|
43
|
-
"pgwidgets/buffer.py",
|
|
44
|
-
"pgwidgets/text_model.py",
|
|
45
|
-
"pgwidgets/defs.py",
|
|
46
|
-
"pgwidgets/sync/Widgets.py",
|
|
47
|
-
"pgwidgets/sync/__init__.py",
|
|
48
|
-
"pgwidgets/sync/application.py",
|
|
49
|
-
"pgwidgets/sync/text_source.py",
|
|
50
|
-
"pgwidgets/sync/widget.py",
|
|
51
|
-
"pgwidgets/extras/__init__.py",
|
|
52
|
-
"pgwidgets/extras/file_browser.py",
|
|
53
|
-
"pgwidgets/icons/pgw-python-logo.png",
|
|
54
|
-
"pgwidgets/async_/Widgets.py",
|
|
55
|
-
"pgwidgets/async_/application.py",
|
|
56
|
-
"pgwidgets/async_/__init__.py",
|
|
57
|
-
"pgwidgets/async_/widget.py",
|
|
58
|
-
"examples/demo_sync.py",
|
|
59
|
-
"examples/all_widgets.py",
|
|
60
|
-
"examples/all_widgets_async.py",
|
|
61
|
-
"examples/README.md",
|
|
62
|
-
"examples/demo_treeview.py",
|
|
63
|
-
"examples/demo_async.py",
|
|
64
|
-
"examples/flask-multi-process/server.py",
|
|
65
|
-
"examples/flask-multi-process/README.md",
|
|
66
|
-
"examples/flask-multi-process/nginx.conf",
|
|
67
|
-
"examples/flask-multi-process/gunicorn.conf.py",
|
|
68
|
-
"examples/flask-multi-process/user_app.py",
|
|
69
|
-
".github/workflows/tests.yml"
|
|
70
|
-
]
|
|
71
|
-
}
|
|
File without changes
|
|
File without changes
|