lumut 0.1.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.
- lumut-0.1.0/.gitignore +8 -0
- lumut-0.1.0/LICENSE +21 -0
- lumut-0.1.0/PKG-INFO +233 -0
- lumut-0.1.0/README.md +184 -0
- lumut-0.1.0/lumut/__init__.py +11 -0
- lumut-0.1.0/lumut/data_editor_enhance.py +100 -0
- lumut-0.1.0/lumut/exp_data_editor.py +140 -0
- lumut-0.1.0/lumut/static/data-editor-enchance.js +237 -0
- lumut-0.1.0/lumut/static/exp-data-editor.js +87 -0
- lumut-0.1.0/pyproject.toml +40 -0
lumut-0.1.0/.gitignore
ADDED
lumut-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 lumut contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
lumut-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: lumut
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Experimental AnyWidget data editor for notebook environments
|
|
5
|
+
Project-URL: Homepage, https://github.com/banditelol/lumut
|
|
6
|
+
Project-URL: Repository, https://github.com/banditelol/lumut
|
|
7
|
+
Project-URL: Issues, https://github.com/banditelol/lumut/issues
|
|
8
|
+
Author: lumut contributors
|
|
9
|
+
License: MIT License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2026 lumut contributors
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
+
in the Software without restriction, including without limitation the rights
|
|
16
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
+
furnished to do so, subject to the following conditions:
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in all
|
|
21
|
+
copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
|
+
SOFTWARE.
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Keywords: anywidget,data-editor,jupyter,marimo,notebook
|
|
32
|
+
Classifier: Development Status :: 3 - Alpha
|
|
33
|
+
Classifier: Intended Audience :: Developers
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
40
|
+
Requires-Python: >=3.11
|
|
41
|
+
Requires-Dist: anywidget>=0.11.0
|
|
42
|
+
Requires-Dist: traitlets>=5.14.0
|
|
43
|
+
Provides-Extra: docs
|
|
44
|
+
Requires-Dist: mkdocstrings[python]>=0.25.1; extra == 'docs'
|
|
45
|
+
Requires-Dist: zensical>=0.0.38; extra == 'docs'
|
|
46
|
+
Provides-Extra: test
|
|
47
|
+
Requires-Dist: pytest>=8.0; extra == 'test'
|
|
48
|
+
Description-Content-Type: text/markdown
|
|
49
|
+
|
|
50
|
+
# lumut
|
|
51
|
+
|
|
52
|
+
An experimental, MIT-licensed AnyWidget data editor for notebook runtimes. It
|
|
53
|
+
uses TanStack Table for table state and TanStack Virtual for lazy, measured row
|
|
54
|
+
heights.
|
|
55
|
+
|
|
56
|
+
## Gallery
|
|
57
|
+
|
|
58
|
+
<div align="center">
|
|
59
|
+
<a href="https://molab.marimo.io/github/banditelol/lumut/blob/main/demos/exp_data_editor.py/wasm?utm_source=lumut">
|
|
60
|
+
<img src="docs/assets/gallery/exp-data-editor.svg" alt="The exp_data_editor showing wrapped text rows" width="640">
|
|
61
|
+
</a>
|
|
62
|
+
<br>
|
|
63
|
+
<strong>exp_data_editor</strong><br>
|
|
64
|
+
<a href="https://molab.marimo.io/github/banditelol/lumut/blob/main/demos/exp_data_editor.py/wasm?utm_source=lumut">molab</a> ·
|
|
65
|
+
<a href="https://adityarp.com/lumut/reference/exp-data-editor/">API</a> ·
|
|
66
|
+
<a href="docs/reference/exp-data-editor.md">Markdown</a>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div align="center">
|
|
70
|
+
<a href="https://molab.marimo.io/github/banditelol/lumut/blob/main/demos/data_editor_enchance.py/wasm?utm_source=lumut">
|
|
71
|
+
<img src="docs/assets/gallery/data-editor-enchance.svg" alt="The data_editor_enchance Glide editor showing wrapped text rows" width="640">
|
|
72
|
+
</a>
|
|
73
|
+
<br>
|
|
74
|
+
<strong>data_editor_enchance</strong> (Glide rough resize experiment)<br>
|
|
75
|
+
<a href="https://molab.marimo.io/github/banditelol/lumut/blob/main/demos/data_editor_enchance.py/wasm?utm_source=lumut">molab</a> ·
|
|
76
|
+
<a href="https://adityarp.com/lumut/reference/data-editor-enchance/">API</a> ·
|
|
77
|
+
<a href="docs/reference/data-editor-enchance.md">Markdown</a>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
The preview is an illustration of the widget. The MoLab demo runs the actual
|
|
81
|
+
AnyWidget from this repository.
|
|
82
|
+
|
|
83
|
+
## Why it exists
|
|
84
|
+
|
|
85
|
+
`lumut.exp_data_editor` explores a small, marimo-compatible data-editor
|
|
86
|
+
contract without inheriting a grid library's entire feature surface. It accepts
|
|
87
|
+
row-oriented data, column-oriented data, or pandas-like dataframes; honours
|
|
88
|
+
`editable_columns`; and synchronizes fully edited rows as `value`.
|
|
89
|
+
|
|
90
|
+
Rows are measured only after they are rendered. Wrapped content can grow a row,
|
|
91
|
+
but never beyond `max_row_height`. Resizing a column triggers a remeasurement
|
|
92
|
+
of rendered rows, rather than an O(N) pass over all data.
|
|
93
|
+
|
|
94
|
+
## Why this does not use Glide Data Grid
|
|
95
|
+
|
|
96
|
+
Glide Data Grid remains a capable canvas-based editor, but its public
|
|
97
|
+
`rowHeight` API is either one constant or a callback indexed by row. In the
|
|
98
|
+
current implementation, the callback path requires the grid to walk row
|
|
99
|
+
heights to derive total height and map a scroll offset to a row. A cached
|
|
100
|
+
callback therefore does not make exact variable wrapped-row height suitable for
|
|
101
|
+
very large datasets. The upstream "fit content" request was discussed as a
|
|
102
|
+
consumer-side canvas-measurement workaround rather than delivered as a native
|
|
103
|
+
auto-height API.
|
|
104
|
+
|
|
105
|
+
Lumut instead uses TanStack Virtual's dynamic measurement model: unseen rows
|
|
106
|
+
have an estimate, rendered rows are measured, and the virtualizer adjusts
|
|
107
|
+
geometry as measurements arrive. `max_row_height` makes that refinement
|
|
108
|
+
bounded and predictable after column resizing.
|
|
109
|
+
|
|
110
|
+
Maintenance is a second consideration. Glide's most recent stable GitHub
|
|
111
|
+
release is 6.0.3 (February 2024), while development since then has appeared as
|
|
112
|
+
intermittent 6.0.4 alpha commits. That does not mean the project is abandoned,
|
|
113
|
+
but it makes an upstream change to its scroll geometry a higher-risk dependency
|
|
114
|
+
for an experimental component. Lumut depends on the MIT-licensed TanStack
|
|
115
|
+
Table and TanStack Virtual projects instead, and owns only the narrow editor
|
|
116
|
+
surface it needs.
|
|
117
|
+
|
|
118
|
+
References:
|
|
119
|
+
|
|
120
|
+
- [Glide `rowHeight` API](https://github.com/glideapps/glide-data-grid/blob/main/packages/core/API.md)
|
|
121
|
+
- [Glide issue #581: Make Row Height Fit Content](https://github.com/glideapps/glide-data-grid/issues/581)
|
|
122
|
+
- [Glide releases](https://github.com/glideapps/glide-data-grid/releases)
|
|
123
|
+
- [TanStack Virtual dynamic measurement example](https://tanstack.com/virtual/latest/docs/framework/react/examples/dynamic)
|
|
124
|
+
|
|
125
|
+
## Install and use
|
|
126
|
+
|
|
127
|
+
Install the published package with:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
uv pip install lumut
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
For local development, build the JavaScript bundle before using the editable
|
|
134
|
+
package:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
uv pip install -e .
|
|
138
|
+
npm install
|
|
139
|
+
npm run build
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
import marimo as mo
|
|
144
|
+
from lumut import exp_data_editor
|
|
145
|
+
|
|
146
|
+
editor = mo.ui.anywidget(exp_data_editor(
|
|
147
|
+
[
|
|
148
|
+
{"name": "Ada", "notes": "A long value that can wrap naturally."},
|
|
149
|
+
{"name": "Grace", "notes": "Another editable row."},
|
|
150
|
+
],
|
|
151
|
+
label="People",
|
|
152
|
+
editable_columns=["notes"],
|
|
153
|
+
max_row_height=160,
|
|
154
|
+
))
|
|
155
|
+
|
|
156
|
+
editor
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`editor.value` is the edited list of row dictionaries.
|
|
160
|
+
|
|
161
|
+
## Glide rough resize experiment
|
|
162
|
+
|
|
163
|
+
`data_editor_enchance` intentionally retains the spelling in its public name.
|
|
164
|
+
It is a Glide Data Grid implementation of the same small input/value contract
|
|
165
|
+
as `exp_data_editor` and `mo.ui.data_editor`, with `wrapped_columns` added.
|
|
166
|
+
During a wrapped-column resize it estimates a single capped row height from the
|
|
167
|
+
visible row window plus a 20-row buffer. On pointer release it applies that
|
|
168
|
+
sampled height to all rows. This avoids an O(N) measurement pass while dragging
|
|
169
|
+
but is an approximation, not content-fit auto-height: off-screen rows can be
|
|
170
|
+
over- or under-sized.
|
|
171
|
+
|
|
172
|
+
It deliberately pins the browser bundle to React 18: Glide 6.0.3 declares
|
|
173
|
+
React 16–18 peer support, whereas the TanStack-only editor has no such Glide
|
|
174
|
+
constraint.
|
|
175
|
+
|
|
176
|
+
Use this only for experimentation with eager small-to-medium data. Glide's
|
|
177
|
+
variable `rowHeight` callback still makes its scroll geometry a poor fit for a
|
|
178
|
+
million-row exact-auto-height editor. For a measured, bounded-height design,
|
|
179
|
+
use `exp_data_editor` and continue the planned windowed-data work in
|
|
180
|
+
[issue #1](https://github.com/banditelol/lumut/issues/1).
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from lumut import data_editor_enchance
|
|
184
|
+
|
|
185
|
+
glide_editor = data_editor_enchance(
|
|
186
|
+
[{"notes": "Resize the notes column to try rough wrapping."}],
|
|
187
|
+
editable_columns=["notes"],
|
|
188
|
+
wrapped_columns=["notes"],
|
|
189
|
+
max_row_height=160,
|
|
190
|
+
)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Scope
|
|
194
|
+
|
|
195
|
+
This first experiment intentionally includes typed cell editing, column resize,
|
|
196
|
+
wrapped text, capped dynamic heights, and the AnyWidget value bridge. It does
|
|
197
|
+
not yet implement spreadsheet range selection, fill handles, structural
|
|
198
|
+
row/column operations, search, or server/windowed data.
|
|
199
|
+
|
|
200
|
+
## Project conventions
|
|
201
|
+
|
|
202
|
+
The project follows the useful parts of [wigglystuff](https://github.com/koaning/wigglystuff): a Python AnyWidget class, a bundled JavaScript entry point,
|
|
203
|
+
static assets packaged by Hatchling, a `Makefile`, and focused Python tests.
|
|
204
|
+
|
|
205
|
+
`AGENTS.md` is the Codex-native replacement for wigglystuff's Claude-facing
|
|
206
|
+
instructions. `.codex/workflows.md` records the equivalent development
|
|
207
|
+
commands. Conductor's declarative workspace schema (`.conductor/settings.toml`)
|
|
208
|
+
has no Codex equivalent, so it is intentionally not copied; the command mapping
|
|
209
|
+
is documented in `.codex/workflows.md` instead.
|
|
210
|
+
|
|
211
|
+
## Development
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
make install
|
|
215
|
+
make build
|
|
216
|
+
make test
|
|
217
|
+
make dev
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
To inspect the widget in marimo after installing the package locally, run
|
|
221
|
+
`uv run --with marimo marimo edit demos/exp_data_editor.py`.
|
|
222
|
+
|
|
223
|
+
## Releasing to PyPI
|
|
224
|
+
|
|
225
|
+
Releases use GitHub Actions and [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/), so a PyPI API token is not stored in this repository.
|
|
226
|
+
|
|
227
|
+
One-time setup:
|
|
228
|
+
|
|
229
|
+
1. If `lumut` is not yet on PyPI, add a **pending** Trusted Publisher in your PyPI account settings. If it already exists, add a Trusted Publisher in that project's Publishing settings. In both cases use project name `lumut`, owner `banditelol`, repository `lumut`, workflow file `publish.yml`, and environment `pypi`.
|
|
230
|
+
2. The first successful run of a pending publisher creates the PyPI project; its configured project name must exactly match `project.name`.
|
|
231
|
+
3. In GitHub, create the protected `pypi` environment if you want approvals before publication. The workflow works without protection too.
|
|
232
|
+
|
|
233
|
+
For each release, update `version` in `pyproject.toml`, add release notes, and create a GitHub release whose tag is `v<version>` (for example, `v0.1.0`). The publishing workflow rebuilds the frontend, builds and checks the wheel and source distribution, then uploads those exact artifacts to PyPI. To verify a build locally before creating the release, run `make package` and `uvx twine check dist/*`.
|
lumut-0.1.0/README.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# lumut
|
|
2
|
+
|
|
3
|
+
An experimental, MIT-licensed AnyWidget data editor for notebook runtimes. It
|
|
4
|
+
uses TanStack Table for table state and TanStack Virtual for lazy, measured row
|
|
5
|
+
heights.
|
|
6
|
+
|
|
7
|
+
## Gallery
|
|
8
|
+
|
|
9
|
+
<div align="center">
|
|
10
|
+
<a href="https://molab.marimo.io/github/banditelol/lumut/blob/main/demos/exp_data_editor.py/wasm?utm_source=lumut">
|
|
11
|
+
<img src="docs/assets/gallery/exp-data-editor.svg" alt="The exp_data_editor showing wrapped text rows" width="640">
|
|
12
|
+
</a>
|
|
13
|
+
<br>
|
|
14
|
+
<strong>exp_data_editor</strong><br>
|
|
15
|
+
<a href="https://molab.marimo.io/github/banditelol/lumut/blob/main/demos/exp_data_editor.py/wasm?utm_source=lumut">molab</a> ·
|
|
16
|
+
<a href="https://adityarp.com/lumut/reference/exp-data-editor/">API</a> ·
|
|
17
|
+
<a href="docs/reference/exp-data-editor.md">Markdown</a>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div align="center">
|
|
21
|
+
<a href="https://molab.marimo.io/github/banditelol/lumut/blob/main/demos/data_editor_enchance.py/wasm?utm_source=lumut">
|
|
22
|
+
<img src="docs/assets/gallery/data-editor-enchance.svg" alt="The data_editor_enchance Glide editor showing wrapped text rows" width="640">
|
|
23
|
+
</a>
|
|
24
|
+
<br>
|
|
25
|
+
<strong>data_editor_enchance</strong> (Glide rough resize experiment)<br>
|
|
26
|
+
<a href="https://molab.marimo.io/github/banditelol/lumut/blob/main/demos/data_editor_enchance.py/wasm?utm_source=lumut">molab</a> ·
|
|
27
|
+
<a href="https://adityarp.com/lumut/reference/data-editor-enchance/">API</a> ·
|
|
28
|
+
<a href="docs/reference/data-editor-enchance.md">Markdown</a>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
The preview is an illustration of the widget. The MoLab demo runs the actual
|
|
32
|
+
AnyWidget from this repository.
|
|
33
|
+
|
|
34
|
+
## Why it exists
|
|
35
|
+
|
|
36
|
+
`lumut.exp_data_editor` explores a small, marimo-compatible data-editor
|
|
37
|
+
contract without inheriting a grid library's entire feature surface. It accepts
|
|
38
|
+
row-oriented data, column-oriented data, or pandas-like dataframes; honours
|
|
39
|
+
`editable_columns`; and synchronizes fully edited rows as `value`.
|
|
40
|
+
|
|
41
|
+
Rows are measured only after they are rendered. Wrapped content can grow a row,
|
|
42
|
+
but never beyond `max_row_height`. Resizing a column triggers a remeasurement
|
|
43
|
+
of rendered rows, rather than an O(N) pass over all data.
|
|
44
|
+
|
|
45
|
+
## Why this does not use Glide Data Grid
|
|
46
|
+
|
|
47
|
+
Glide Data Grid remains a capable canvas-based editor, but its public
|
|
48
|
+
`rowHeight` API is either one constant or a callback indexed by row. In the
|
|
49
|
+
current implementation, the callback path requires the grid to walk row
|
|
50
|
+
heights to derive total height and map a scroll offset to a row. A cached
|
|
51
|
+
callback therefore does not make exact variable wrapped-row height suitable for
|
|
52
|
+
very large datasets. The upstream "fit content" request was discussed as a
|
|
53
|
+
consumer-side canvas-measurement workaround rather than delivered as a native
|
|
54
|
+
auto-height API.
|
|
55
|
+
|
|
56
|
+
Lumut instead uses TanStack Virtual's dynamic measurement model: unseen rows
|
|
57
|
+
have an estimate, rendered rows are measured, and the virtualizer adjusts
|
|
58
|
+
geometry as measurements arrive. `max_row_height` makes that refinement
|
|
59
|
+
bounded and predictable after column resizing.
|
|
60
|
+
|
|
61
|
+
Maintenance is a second consideration. Glide's most recent stable GitHub
|
|
62
|
+
release is 6.0.3 (February 2024), while development since then has appeared as
|
|
63
|
+
intermittent 6.0.4 alpha commits. That does not mean the project is abandoned,
|
|
64
|
+
but it makes an upstream change to its scroll geometry a higher-risk dependency
|
|
65
|
+
for an experimental component. Lumut depends on the MIT-licensed TanStack
|
|
66
|
+
Table and TanStack Virtual projects instead, and owns only the narrow editor
|
|
67
|
+
surface it needs.
|
|
68
|
+
|
|
69
|
+
References:
|
|
70
|
+
|
|
71
|
+
- [Glide `rowHeight` API](https://github.com/glideapps/glide-data-grid/blob/main/packages/core/API.md)
|
|
72
|
+
- [Glide issue #581: Make Row Height Fit Content](https://github.com/glideapps/glide-data-grid/issues/581)
|
|
73
|
+
- [Glide releases](https://github.com/glideapps/glide-data-grid/releases)
|
|
74
|
+
- [TanStack Virtual dynamic measurement example](https://tanstack.com/virtual/latest/docs/framework/react/examples/dynamic)
|
|
75
|
+
|
|
76
|
+
## Install and use
|
|
77
|
+
|
|
78
|
+
Install the published package with:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
uv pip install lumut
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
For local development, build the JavaScript bundle before using the editable
|
|
85
|
+
package:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
uv pip install -e .
|
|
89
|
+
npm install
|
|
90
|
+
npm run build
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
import marimo as mo
|
|
95
|
+
from lumut import exp_data_editor
|
|
96
|
+
|
|
97
|
+
editor = mo.ui.anywidget(exp_data_editor(
|
|
98
|
+
[
|
|
99
|
+
{"name": "Ada", "notes": "A long value that can wrap naturally."},
|
|
100
|
+
{"name": "Grace", "notes": "Another editable row."},
|
|
101
|
+
],
|
|
102
|
+
label="People",
|
|
103
|
+
editable_columns=["notes"],
|
|
104
|
+
max_row_height=160,
|
|
105
|
+
))
|
|
106
|
+
|
|
107
|
+
editor
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`editor.value` is the edited list of row dictionaries.
|
|
111
|
+
|
|
112
|
+
## Glide rough resize experiment
|
|
113
|
+
|
|
114
|
+
`data_editor_enchance` intentionally retains the spelling in its public name.
|
|
115
|
+
It is a Glide Data Grid implementation of the same small input/value contract
|
|
116
|
+
as `exp_data_editor` and `mo.ui.data_editor`, with `wrapped_columns` added.
|
|
117
|
+
During a wrapped-column resize it estimates a single capped row height from the
|
|
118
|
+
visible row window plus a 20-row buffer. On pointer release it applies that
|
|
119
|
+
sampled height to all rows. This avoids an O(N) measurement pass while dragging
|
|
120
|
+
but is an approximation, not content-fit auto-height: off-screen rows can be
|
|
121
|
+
over- or under-sized.
|
|
122
|
+
|
|
123
|
+
It deliberately pins the browser bundle to React 18: Glide 6.0.3 declares
|
|
124
|
+
React 16–18 peer support, whereas the TanStack-only editor has no such Glide
|
|
125
|
+
constraint.
|
|
126
|
+
|
|
127
|
+
Use this only for experimentation with eager small-to-medium data. Glide's
|
|
128
|
+
variable `rowHeight` callback still makes its scroll geometry a poor fit for a
|
|
129
|
+
million-row exact-auto-height editor. For a measured, bounded-height design,
|
|
130
|
+
use `exp_data_editor` and continue the planned windowed-data work in
|
|
131
|
+
[issue #1](https://github.com/banditelol/lumut/issues/1).
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from lumut import data_editor_enchance
|
|
135
|
+
|
|
136
|
+
glide_editor = data_editor_enchance(
|
|
137
|
+
[{"notes": "Resize the notes column to try rough wrapping."}],
|
|
138
|
+
editable_columns=["notes"],
|
|
139
|
+
wrapped_columns=["notes"],
|
|
140
|
+
max_row_height=160,
|
|
141
|
+
)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Scope
|
|
145
|
+
|
|
146
|
+
This first experiment intentionally includes typed cell editing, column resize,
|
|
147
|
+
wrapped text, capped dynamic heights, and the AnyWidget value bridge. It does
|
|
148
|
+
not yet implement spreadsheet range selection, fill handles, structural
|
|
149
|
+
row/column operations, search, or server/windowed data.
|
|
150
|
+
|
|
151
|
+
## Project conventions
|
|
152
|
+
|
|
153
|
+
The project follows the useful parts of [wigglystuff](https://github.com/koaning/wigglystuff): a Python AnyWidget class, a bundled JavaScript entry point,
|
|
154
|
+
static assets packaged by Hatchling, a `Makefile`, and focused Python tests.
|
|
155
|
+
|
|
156
|
+
`AGENTS.md` is the Codex-native replacement for wigglystuff's Claude-facing
|
|
157
|
+
instructions. `.codex/workflows.md` records the equivalent development
|
|
158
|
+
commands. Conductor's declarative workspace schema (`.conductor/settings.toml`)
|
|
159
|
+
has no Codex equivalent, so it is intentionally not copied; the command mapping
|
|
160
|
+
is documented in `.codex/workflows.md` instead.
|
|
161
|
+
|
|
162
|
+
## Development
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
make install
|
|
166
|
+
make build
|
|
167
|
+
make test
|
|
168
|
+
make dev
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
To inspect the widget in marimo after installing the package locally, run
|
|
172
|
+
`uv run --with marimo marimo edit demos/exp_data_editor.py`.
|
|
173
|
+
|
|
174
|
+
## Releasing to PyPI
|
|
175
|
+
|
|
176
|
+
Releases use GitHub Actions and [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/), so a PyPI API token is not stored in this repository.
|
|
177
|
+
|
|
178
|
+
One-time setup:
|
|
179
|
+
|
|
180
|
+
1. If `lumut` is not yet on PyPI, add a **pending** Trusted Publisher in your PyPI account settings. If it already exists, add a Trusted Publisher in that project's Publishing settings. In both cases use project name `lumut`, owner `banditelol`, repository `lumut`, workflow file `publish.yml`, and environment `pypi`.
|
|
181
|
+
2. The first successful run of a pending publisher creates the PyPI project; its configured project name must exactly match `project.name`.
|
|
182
|
+
3. In GitHub, create the protected `pypi` environment if you want approvals before publication. The workflow works without protection too.
|
|
183
|
+
|
|
184
|
+
For each release, update `version` in `pyproject.toml`, add release notes, and create a GitHub release whose tag is `v<version>` (for example, `v0.1.0`). The publishing workflow rebuilds the frontend, builds and checks the wheel and source distribution, then uploads those exact artifacts to PyPI. To verify a build locally before creating the release, run `make package` and `uvx twine check dist/*`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Experimental AnyWidget components for notebook environments."""
|
|
2
|
+
|
|
3
|
+
from .exp_data_editor import ExpDataEditor, exp_data_editor
|
|
4
|
+
from .data_editor_enhance import DataEditorEnchance, data_editor_enchance
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"DataEditorEnchance",
|
|
8
|
+
"ExpDataEditor",
|
|
9
|
+
"data_editor_enchance",
|
|
10
|
+
"exp_data_editor",
|
|
11
|
+
]
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""An experimental Glide Data Grid AnyWidget with rough wrapped-row sizing."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping, Sequence
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Literal
|
|
8
|
+
|
|
9
|
+
import anywidget
|
|
10
|
+
import traitlets
|
|
11
|
+
|
|
12
|
+
from .exp_data_editor import (
|
|
13
|
+
EditableColumns,
|
|
14
|
+
Row,
|
|
15
|
+
WrappedColumns,
|
|
16
|
+
_column_names,
|
|
17
|
+
_infer_field_types,
|
|
18
|
+
_to_rows,
|
|
19
|
+
_validate_columns,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DataEditorEnchance(anywidget.AnyWidget):
|
|
24
|
+
"""Edit tabular data with Glide Data Grid and approximate wrapped heights.
|
|
25
|
+
|
|
26
|
+
This is deliberately separate from :class:`lumut.ExpDataEditor`. It carries
|
|
27
|
+
the experimental Glide strategy from marimo's ``feat/data-editor-wrap``:
|
|
28
|
+
when a wrapped column is resized, it estimates the maximum height in the
|
|
29
|
+
visible window (plus a small buffer), temporarily applies it there, then
|
|
30
|
+
applies that sampled height to all data rows when the pointer is released.
|
|
31
|
+
|
|
32
|
+
The method avoids measuring every row during a resize, but it is not exact:
|
|
33
|
+
rows outside the sample can be too tall or too short. Glide's variable-row
|
|
34
|
+
callback also has poor scroll-geometry characteristics for huge datasets,
|
|
35
|
+
so this widget is for experimentation and small-to-medium eager data only.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
_esm = Path(__file__).parent / "static" / "data-editor-enchance.js"
|
|
39
|
+
|
|
40
|
+
data = traitlets.List(traitlets.Dict(), default_value=[]).tag(sync=True)
|
|
41
|
+
value = traitlets.List(traitlets.Dict(), default_value=[]).tag(sync=True)
|
|
42
|
+
editable_columns = traitlets.Any("all").tag(sync=True)
|
|
43
|
+
wrapped_columns = traitlets.Any(default_value=[]).tag(sync=True)
|
|
44
|
+
field_types = traitlets.Dict(default_value={}).tag(sync=True)
|
|
45
|
+
label = traitlets.Unicode("").tag(sync=True)
|
|
46
|
+
width = traitlets.Unicode("100%").tag(sync=True)
|
|
47
|
+
height = traitlets.Int(450).tag(sync=True)
|
|
48
|
+
max_row_height = traitlets.Int(240).tag(sync=True)
|
|
49
|
+
estimated_row_height = traitlets.Int(34).tag(sync=True)
|
|
50
|
+
wrapped_row_height_strategy = traitlets.Unicode(
|
|
51
|
+
"approxIncrementalRough"
|
|
52
|
+
).tag(sync=True)
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
data: Sequence[Mapping[str, Any]] | Mapping[str, Sequence[Any]] | Any,
|
|
57
|
+
*,
|
|
58
|
+
label: str = "",
|
|
59
|
+
editable_columns: EditableColumns = "all",
|
|
60
|
+
wrapped_columns: WrappedColumns = (),
|
|
61
|
+
max_row_height: int = 240,
|
|
62
|
+
estimated_row_height: int = 34,
|
|
63
|
+
width: str = "100%",
|
|
64
|
+
height: int = 450,
|
|
65
|
+
) -> None:
|
|
66
|
+
if max_row_height < estimated_row_height:
|
|
67
|
+
raise ValueError("max_row_height must be at least estimated_row_height")
|
|
68
|
+
if height <= 0:
|
|
69
|
+
raise ValueError("height must be positive")
|
|
70
|
+
|
|
71
|
+
rows: list[Row] = _to_rows(data)
|
|
72
|
+
columns = list(rows[0]) if rows else _column_names(data)
|
|
73
|
+
_validate_columns(editable_columns, columns, "editable_columns")
|
|
74
|
+
_validate_columns(wrapped_columns, columns, "wrapped_columns")
|
|
75
|
+
|
|
76
|
+
super().__init__(
|
|
77
|
+
data=rows,
|
|
78
|
+
value=rows,
|
|
79
|
+
editable_columns=(
|
|
80
|
+
"all" if editable_columns == "all" else list(editable_columns)
|
|
81
|
+
),
|
|
82
|
+
wrapped_columns=(
|
|
83
|
+
"all" if wrapped_columns == "all" else list(wrapped_columns)
|
|
84
|
+
),
|
|
85
|
+
field_types=_infer_field_types(rows, columns),
|
|
86
|
+
label=label,
|
|
87
|
+
width=width,
|
|
88
|
+
height=height,
|
|
89
|
+
max_row_height=max_row_height,
|
|
90
|
+
estimated_row_height=estimated_row_height,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def data_editor_enchance(
|
|
95
|
+
data: Sequence[Mapping[str, Any]] | Mapping[str, Sequence[Any]] | Any,
|
|
96
|
+
**kwargs: Any,
|
|
97
|
+
) -> DataEditorEnchance:
|
|
98
|
+
"""Create a :class:`DataEditorEnchance` (name retained intentionally)."""
|
|
99
|
+
|
|
100
|
+
return DataEditorEnchance(data, **kwargs)
|