dt2 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.
- dt2-0.1.0/.github/workflows/ci.yml +55 -0
- dt2-0.1.0/.github/workflows/release.yml +40 -0
- dt2-0.1.0/.gitignore +11 -0
- dt2-0.1.0/CHANGELOG.md +50 -0
- dt2-0.1.0/LICENSE +21 -0
- dt2-0.1.0/PKG-INFO +161 -0
- dt2-0.1.0/README.md +121 -0
- dt2-0.1.0/ROADMAP.md +108 -0
- dt2-0.1.0/build.mjs +36 -0
- dt2-0.1.0/examples/app.py +39 -0
- dt2-0.1.0/examples/app_config.py +52 -0
- dt2-0.1.0/examples/app_extensions.py +46 -0
- dt2-0.1.0/examples/app_proxy_inputs.py +64 -0
- dt2-0.1.0/examples/app_serverside.py +37 -0
- dt2-0.1.0/js/extensions.js +61 -0
- dt2-0.1.0/js/index.js +253 -0
- dt2-0.1.0/package-lock.json +951 -0
- dt2-0.1.0/package.json +35 -0
- dt2-0.1.0/pyproject.toml +55 -0
- dt2-0.1.0/src/dt2/__init__.py +7 -0
- dt2-0.1.0/src/dt2/options.py +520 -0
- dt2-0.1.0/src/dt2/server.py +86 -0
- dt2-0.1.0/src/dt2/static/index.css +10 -0
- dt2-0.1.0/src/dt2/static/index.js +228 -0
- dt2-0.1.0/src/dt2/widget.py +192 -0
- dt2-0.1.0/tests/test_extensions.py +120 -0
- dt2-0.1.0/tests/test_options.py +147 -0
- dt2-0.1.0/tests/test_server.py +85 -0
- dt2-0.1.0/tests/test_shiny.py +97 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
python-version: ["3.9", "3.11", "3.13"]
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- uses: actions/setup-node@v4
|
|
20
|
+
with:
|
|
21
|
+
node-version: "20"
|
|
22
|
+
|
|
23
|
+
- name: Build JS bundle
|
|
24
|
+
run: |
|
|
25
|
+
npm ci
|
|
26
|
+
npm run build
|
|
27
|
+
|
|
28
|
+
- name: Verify the committed bundle is up to date
|
|
29
|
+
run: git diff --exit-code -- src/dt2/static/ || (
|
|
30
|
+
echo "::error::src/dt2/static is stale — run 'npm run build' and commit." && exit 1)
|
|
31
|
+
|
|
32
|
+
- uses: actions/setup-python@v5
|
|
33
|
+
with:
|
|
34
|
+
python-version: ${{ matrix.python-version }}
|
|
35
|
+
|
|
36
|
+
- name: Install package + test deps
|
|
37
|
+
run: |
|
|
38
|
+
python -m pip install --upgrade pip
|
|
39
|
+
python -m pip install -e ".[shiny,pandas,dev]"
|
|
40
|
+
|
|
41
|
+
- name: Run tests
|
|
42
|
+
run: python -m pytest
|
|
43
|
+
|
|
44
|
+
build-js-check:
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
steps:
|
|
47
|
+
- uses: actions/checkout@v4
|
|
48
|
+
- uses: actions/setup-node@v4
|
|
49
|
+
with:
|
|
50
|
+
node-version: "20"
|
|
51
|
+
- run: npm ci
|
|
52
|
+
- name: Syntax-check adapter sources
|
|
53
|
+
run: |
|
|
54
|
+
node --check js/index.js
|
|
55
|
+
node --check js/extensions.js
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: Release to PyPI
|
|
2
|
+
|
|
3
|
+
# Fires only when the maintainer publishes a GitHub Release. Uses PyPI Trusted
|
|
4
|
+
# Publishing (OIDC) — configure the publisher on PyPI first; no token is stored.
|
|
5
|
+
on:
|
|
6
|
+
release:
|
|
7
|
+
types: [published]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build-and-publish:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
environment: pypi
|
|
13
|
+
permissions:
|
|
14
|
+
id-token: write # required for trusted publishing
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-node@v4
|
|
19
|
+
with:
|
|
20
|
+
node-version: "20"
|
|
21
|
+
|
|
22
|
+
- name: Build JS bundle
|
|
23
|
+
run: |
|
|
24
|
+
npm ci
|
|
25
|
+
npm run build
|
|
26
|
+
|
|
27
|
+
- uses: actions/setup-python@v5
|
|
28
|
+
with:
|
|
29
|
+
python-version: "3.12"
|
|
30
|
+
|
|
31
|
+
- name: Build sdist + wheel
|
|
32
|
+
run: |
|
|
33
|
+
python -m pip install --upgrade build
|
|
34
|
+
python -m build
|
|
35
|
+
|
|
36
|
+
- name: Publish to PyPI
|
|
37
|
+
# Pinned to a commit SHA (not a moving branch/tag) — this step runs with
|
|
38
|
+
# id-token: write (PyPI publish rights), so a hijacked ref must not be
|
|
39
|
+
# able to execute here. SHA is v1.14.0.
|
|
40
|
+
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
dt2-0.1.0/.gitignore
ADDED
dt2-0.1.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to **dt2** (the Python port) are documented here.
|
|
4
|
+
Format loosely follows [Keep a Changelog](https://keepachangelog.com).
|
|
5
|
+
|
|
6
|
+
## [0.1.0] — 2026-06-26
|
|
7
|
+
|
|
8
|
+
First release. Feature parity with the R
|
|
9
|
+
[DT2](https://github.com/StrategicProjects/DT2) package, verified live in a
|
|
10
|
+
browser (Shiny for Python).
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Core widget** (`dt2()`, `Dt2`): anywidget binding for DataTables v2 (2.3.4),
|
|
14
|
+
for Shiny for Python and Jupyter. Accepts pandas/polars frames and records.
|
|
15
|
+
- **Config builder** (`Options`): chainable port of the R `dt2_options` /
|
|
16
|
+
`dt2_formats` helpers — `order`, `search_global`, `length_menu`, `language`,
|
|
17
|
+
`use_buttons`, `cols_align/width/hide/escape`,
|
|
18
|
+
`format_number/datetime/number_abbrev/time_relative`, `cols_render`,
|
|
19
|
+
`cols_render_orthogonal`, `register_renderer`/`use_renderer`.
|
|
20
|
+
- **`JS()`**: parity for `htmlwidgets::JS()` — renderer source is revived into a
|
|
21
|
+
real function client-side (with `DataTable`/`$`/`moment` in scope).
|
|
22
|
+
- **Extensions** (all 15 bundled): Buttons, Select, Responsive, FixedHeader,
|
|
23
|
+
FixedColumns, KeyTable, Scroller, RowGroup, RowReorder, ColReorder, DateTime,
|
|
24
|
+
SearchBuilder, SearchPanes, StateRestore, ColumnControl. Activation helpers on
|
|
25
|
+
`Options` + an `extensions()` registry. jszip bundled for Excel/CSV export.
|
|
26
|
+
- **Server-side processing** (`server_side=True`): data stays Python-side and
|
|
27
|
+
pages are filtered/ordered/paginated over the Comm (`dt2.server.process_ssp`).
|
|
28
|
+
- **Proxy API** on the widget: `replace_data`, `draw`, `reload`, `order`,
|
|
29
|
+
`search`, `clear_search`, `page`, `select_rows`.
|
|
30
|
+
- **Events**: `selected_rows`, `state`, `row_check`, `row_button` traits
|
|
31
|
+
(read reactively with `shinywidgets.reactive_read`).
|
|
32
|
+
- **Inline inputs**: `Options.col_checkbox`, `Options.col_button`.
|
|
33
|
+
- **moment.js bundled** — powers `format_datetime` and `format_time_relative`
|
|
34
|
+
(`DataTable.render.datetime` requires it).
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
- Verified end to end in a real browser (Shiny for Python): core render,
|
|
38
|
+
selection events, SSP (50k rows: render + server-side search), config
|
|
39
|
+
renderers (number/datetime/custom), extensions (RowGroup, Buttons), and inline
|
|
40
|
+
inputs with `row_check`/`row_button` events.
|
|
41
|
+
- `format_datetime` failed ("Formatted date without Moment.js or Luxon"), which
|
|
42
|
+
surfaced as a blocking `alert()` — moment is now bundled and DataTables'
|
|
43
|
+
`errMode` is set to `throw` so future warnings log instead of freezing the tab.
|
|
44
|
+
- `Options.buttons()` now places buttons in the layout when no `target` is given,
|
|
45
|
+
so they actually render (DataTables 2.x only shows buttons referenced there).
|
|
46
|
+
|
|
47
|
+
### Known gaps / deferred
|
|
48
|
+
- PDF export (pdfmake) not bundled; `format_time_relative` needs
|
|
49
|
+
moment-with-locales for non-`en` locales; extensions are all-bundled (no
|
|
50
|
+
modular/lazy loading yet); SSP search is global-only (matches R).
|
dt2-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 André Leite
|
|
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.
|
dt2-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dt2
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: DataTables v2 for Shiny for Python — anywidget binding, Python port of the R DT2 package
|
|
5
|
+
Project-URL: Homepage, https://github.com/StrategicProjects/dt2py
|
|
6
|
+
Project-URL: Changelog, https://github.com/StrategicProjects/dt2py/blob/main/CHANGELOG.md
|
|
7
|
+
Project-URL: Issues, https://github.com/StrategicProjects/dt2py/issues
|
|
8
|
+
Project-URL: R package, https://github.com/StrategicProjects/DT2
|
|
9
|
+
Author-email: André Leite <leite@de.ufpe.br>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: anywidget,datatables,htmlwidgets,shiny,tables
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Framework :: Jupyter
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Requires-Dist: anywidget>=0.9
|
|
27
|
+
Requires-Dist: traitlets>=5
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: anywidget[dev]; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
31
|
+
Requires-Dist: watchfiles; extra == 'dev'
|
|
32
|
+
Provides-Extra: pandas
|
|
33
|
+
Requires-Dist: pandas; extra == 'pandas'
|
|
34
|
+
Provides-Extra: polars
|
|
35
|
+
Requires-Dist: polars; extra == 'polars'
|
|
36
|
+
Provides-Extra: shiny
|
|
37
|
+
Requires-Dist: shiny>=1.2; extra == 'shiny'
|
|
38
|
+
Requires-Dist: shinywidgets>=0.4; extra == 'shiny'
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
# dt2 — DataTables v2 for Shiny for Python
|
|
42
|
+
|
|
43
|
+
A Python port of the R [**DT2**](https://github.com/StrategicProjects/DT2)
|
|
44
|
+
package: an [anywidget](https://anywidget.dev) binding for
|
|
45
|
+
[DataTables v2](https://datatables.net), designed for
|
|
46
|
+
[Shiny for Python](https://shiny.posit.co/py/) (and usable in Jupyter).
|
|
47
|
+
|
|
48
|
+
It configures DataTables via plain Python (1:1 with the JS API), reusing the
|
|
49
|
+
same DataTables runtime (2.3.4) and extensions as the R package.
|
|
50
|
+
|
|
51
|
+
> Status: **feature-complete toward R parity, pre-release.** Config, all 15
|
|
52
|
+
> extensions, server-side processing, proxy, events and inline inputs are
|
|
53
|
+
> implemented and unit-tested. The live in-browser Comm transport has not yet
|
|
54
|
+
> been visually verified. See [ROADMAP.md](ROADMAP.md) and [CHANGELOG.md](CHANGELOG.md).
|
|
55
|
+
|
|
56
|
+
## Install (dev)
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install && npm run build # build the JS bundle into src/dt2/static/
|
|
60
|
+
uv venv && uv pip install -e ".[shiny,pandas]"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
(The built bundle is committed, so an installed wheel needs no Node toolchain.)
|
|
64
|
+
|
|
65
|
+
## Quick start
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
import pandas as pd
|
|
69
|
+
from shiny import App, ui
|
|
70
|
+
from shinywidgets import output_widget, render_widget
|
|
71
|
+
from dt2 import dt2
|
|
72
|
+
|
|
73
|
+
df = pd.read_csv("data.csv")
|
|
74
|
+
|
|
75
|
+
app_ui = ui.page_fluid(output_widget("tbl"))
|
|
76
|
+
|
|
77
|
+
def server(input, output, session):
|
|
78
|
+
@render_widget
|
|
79
|
+
def tbl():
|
|
80
|
+
return dt2(df, select=True, pageLength=10)
|
|
81
|
+
|
|
82
|
+
app = App(app_ui, server)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Configure with `Options`
|
|
86
|
+
|
|
87
|
+
Chainable builder mirroring the R pipe helpers:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from dt2 import Options, JS, dt2
|
|
91
|
+
|
|
92
|
+
opts = (Options(df)
|
|
93
|
+
.cols_align(["revenue"], "right")
|
|
94
|
+
.format_number(["revenue"], thousands=".", decimal=",", digits=2, prefix="R$ ")
|
|
95
|
+
.format_datetime(["updated"], from_="YYYY-MM-DD", to="DD/MM/YYYY")
|
|
96
|
+
.cols_render(["score"], JS("function(d,t){ return t==='display' ? d+'%' : d; }"))
|
|
97
|
+
.order(("revenue", "desc"))
|
|
98
|
+
.length_menu([10, 25, -1]))
|
|
99
|
+
|
|
100
|
+
dt2(df, options=opts)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`JS(...)` is the parity for `htmlwidgets::JS()`: the source is revived into a
|
|
104
|
+
real function in the browser, with `DataTable`, `$` and `moment` in scope.
|
|
105
|
+
|
|
106
|
+
## Extensions
|
|
107
|
+
|
|
108
|
+
All 15 DataTables extensions are bundled; activate via `Options`:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
opts = (Options(df)
|
|
112
|
+
.buttons(["copyHtml5", "csvHtml5", "excelHtml5"]) # jszip bundled; PDF needs pdfmake
|
|
113
|
+
.select({"style": "os"})
|
|
114
|
+
.responsive()
|
|
115
|
+
.fixed_header()
|
|
116
|
+
.row_group("field"))
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`dt2.extensions()` lists what is bundled.
|
|
120
|
+
|
|
121
|
+
## Server-side processing
|
|
122
|
+
|
|
123
|
+
Keep large data Python-side; DataTables fetches pages over the Comm:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
dt2(big_df, server_side=True, pageLength=25)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Filtering/ordering/paging run in `dt2.server.process_ssp`.
|
|
130
|
+
|
|
131
|
+
## Proxy, events and inline inputs
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
# proxy (Python -> table): call on the rendered widget
|
|
135
|
+
tbl.widget.search("ada")
|
|
136
|
+
tbl.widget.order(("field", "desc"))
|
|
137
|
+
tbl.widget.select_rows([1, 3])
|
|
138
|
+
|
|
139
|
+
# events (table -> Python): read reactively
|
|
140
|
+
from shinywidgets import reactive_read
|
|
141
|
+
reactive_read(tbl.widget, "selected_rows") # also: state, row_check, row_button
|
|
142
|
+
|
|
143
|
+
# inline row inputs
|
|
144
|
+
opts = Options(df).col_checkbox("select", value_col="active").col_button("act", label="Ping")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
See [`examples/`](examples/) for runnable apps:
|
|
148
|
+
`app.py`, `app_config.py`, `app_extensions.py`, `app_serverside.py`,
|
|
149
|
+
`app_proxy_inputs.py`.
|
|
150
|
+
|
|
151
|
+
## How it relates to the R package
|
|
152
|
+
|
|
153
|
+
The DataTables runtime is shared conceptually with R DT2. What differs is the
|
|
154
|
+
**transport**: R talks to Shiny via `window.Shiny`; here the widget talks to the
|
|
155
|
+
Python kernel over the anywidget Comm, bridged to Shiny reactivity by
|
|
156
|
+
`shinywidgets`. The `htmlwidgets::JS()` mechanism becomes the `JS()` marker +
|
|
157
|
+
client-side reviver. See the header of `js/index.js` for the full mapping.
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT © André Leite
|
dt2-0.1.0/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# dt2 — DataTables v2 for Shiny for Python
|
|
2
|
+
|
|
3
|
+
A Python port of the R [**DT2**](https://github.com/StrategicProjects/DT2)
|
|
4
|
+
package: an [anywidget](https://anywidget.dev) binding for
|
|
5
|
+
[DataTables v2](https://datatables.net), designed for
|
|
6
|
+
[Shiny for Python](https://shiny.posit.co/py/) (and usable in Jupyter).
|
|
7
|
+
|
|
8
|
+
It configures DataTables via plain Python (1:1 with the JS API), reusing the
|
|
9
|
+
same DataTables runtime (2.3.4) and extensions as the R package.
|
|
10
|
+
|
|
11
|
+
> Status: **feature-complete toward R parity, pre-release.** Config, all 15
|
|
12
|
+
> extensions, server-side processing, proxy, events and inline inputs are
|
|
13
|
+
> implemented and unit-tested. The live in-browser Comm transport has not yet
|
|
14
|
+
> been visually verified. See [ROADMAP.md](ROADMAP.md) and [CHANGELOG.md](CHANGELOG.md).
|
|
15
|
+
|
|
16
|
+
## Install (dev)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install && npm run build # build the JS bundle into src/dt2/static/
|
|
20
|
+
uv venv && uv pip install -e ".[shiny,pandas]"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
(The built bundle is committed, so an installed wheel needs no Node toolchain.)
|
|
24
|
+
|
|
25
|
+
## Quick start
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
import pandas as pd
|
|
29
|
+
from shiny import App, ui
|
|
30
|
+
from shinywidgets import output_widget, render_widget
|
|
31
|
+
from dt2 import dt2
|
|
32
|
+
|
|
33
|
+
df = pd.read_csv("data.csv")
|
|
34
|
+
|
|
35
|
+
app_ui = ui.page_fluid(output_widget("tbl"))
|
|
36
|
+
|
|
37
|
+
def server(input, output, session):
|
|
38
|
+
@render_widget
|
|
39
|
+
def tbl():
|
|
40
|
+
return dt2(df, select=True, pageLength=10)
|
|
41
|
+
|
|
42
|
+
app = App(app_ui, server)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Configure with `Options`
|
|
46
|
+
|
|
47
|
+
Chainable builder mirroring the R pipe helpers:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from dt2 import Options, JS, dt2
|
|
51
|
+
|
|
52
|
+
opts = (Options(df)
|
|
53
|
+
.cols_align(["revenue"], "right")
|
|
54
|
+
.format_number(["revenue"], thousands=".", decimal=",", digits=2, prefix="R$ ")
|
|
55
|
+
.format_datetime(["updated"], from_="YYYY-MM-DD", to="DD/MM/YYYY")
|
|
56
|
+
.cols_render(["score"], JS("function(d,t){ return t==='display' ? d+'%' : d; }"))
|
|
57
|
+
.order(("revenue", "desc"))
|
|
58
|
+
.length_menu([10, 25, -1]))
|
|
59
|
+
|
|
60
|
+
dt2(df, options=opts)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`JS(...)` is the parity for `htmlwidgets::JS()`: the source is revived into a
|
|
64
|
+
real function in the browser, with `DataTable`, `$` and `moment` in scope.
|
|
65
|
+
|
|
66
|
+
## Extensions
|
|
67
|
+
|
|
68
|
+
All 15 DataTables extensions are bundled; activate via `Options`:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
opts = (Options(df)
|
|
72
|
+
.buttons(["copyHtml5", "csvHtml5", "excelHtml5"]) # jszip bundled; PDF needs pdfmake
|
|
73
|
+
.select({"style": "os"})
|
|
74
|
+
.responsive()
|
|
75
|
+
.fixed_header()
|
|
76
|
+
.row_group("field"))
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`dt2.extensions()` lists what is bundled.
|
|
80
|
+
|
|
81
|
+
## Server-side processing
|
|
82
|
+
|
|
83
|
+
Keep large data Python-side; DataTables fetches pages over the Comm:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
dt2(big_df, server_side=True, pageLength=25)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Filtering/ordering/paging run in `dt2.server.process_ssp`.
|
|
90
|
+
|
|
91
|
+
## Proxy, events and inline inputs
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
# proxy (Python -> table): call on the rendered widget
|
|
95
|
+
tbl.widget.search("ada")
|
|
96
|
+
tbl.widget.order(("field", "desc"))
|
|
97
|
+
tbl.widget.select_rows([1, 3])
|
|
98
|
+
|
|
99
|
+
# events (table -> Python): read reactively
|
|
100
|
+
from shinywidgets import reactive_read
|
|
101
|
+
reactive_read(tbl.widget, "selected_rows") # also: state, row_check, row_button
|
|
102
|
+
|
|
103
|
+
# inline row inputs
|
|
104
|
+
opts = Options(df).col_checkbox("select", value_col="active").col_button("act", label="Ping")
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
See [`examples/`](examples/) for runnable apps:
|
|
108
|
+
`app.py`, `app_config.py`, `app_extensions.py`, `app_serverside.py`,
|
|
109
|
+
`app_proxy_inputs.py`.
|
|
110
|
+
|
|
111
|
+
## How it relates to the R package
|
|
112
|
+
|
|
113
|
+
The DataTables runtime is shared conceptually with R DT2. What differs is the
|
|
114
|
+
**transport**: R talks to Shiny via `window.Shiny`; here the widget talks to the
|
|
115
|
+
Python kernel over the anywidget Comm, bridged to Shiny reactivity by
|
|
116
|
+
`shinywidgets`. The `htmlwidgets::JS()` mechanism becomes the `JS()` marker +
|
|
117
|
+
client-side reviver. See the header of `js/index.js` for the full mapping.
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
MIT © André Leite
|
dt2-0.1.0/ROADMAP.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# dt2 (Python) — parity roadmap
|
|
2
|
+
|
|
3
|
+
Goal: **feature parity** with the R DT2 package, delivered via anywidget for
|
|
4
|
+
Shiny for Python. The DataTables v2 runtime (2.3.4) and extensions are reused;
|
|
5
|
+
the work is reimplementing the R binding layer in Python and re-wiring the
|
|
6
|
+
Shiny transport onto the anywidget Comm.
|
|
7
|
+
|
|
8
|
+
## Architecture decisions (locked)
|
|
9
|
+
|
|
10
|
+
- **Repo:** `StrategicProjects/dt2py` (separate from the R repo).
|
|
11
|
+
- **PyPI / import name:** `dt2`.
|
|
12
|
+
- **Mechanism:** anywidget (`shinywidgets` bridges it to Shiny for Python).
|
|
13
|
+
- **JS:** bundled with esbuild from `js/index.js` → `src/dt2/static/index.{js,css}`.
|
|
14
|
+
- **DataTables:** pinned to `2.3.4` / jQuery `3.7.1` to match the R bundle.
|
|
15
|
+
|
|
16
|
+
## Transport mapping (R → Python)
|
|
17
|
+
|
|
18
|
+
| R (htmlwidgets / Shiny) | Python (anywidget) |
|
|
19
|
+
| ---------------------------------------- | ------------------------------------------- |
|
|
20
|
+
| `Shiny.setInputValue(id+'_state', v)` | `model.set('state', v); model.save_changes()` |
|
|
21
|
+
| `Shiny.setInputValue(id+'_row_check')` | trait + `model.save_changes()` |
|
|
22
|
+
| `Shiny.addCustomMessageHandler(id+'_proxy')` | `model.on('msg:custom', fn)` |
|
|
23
|
+
| SSP Ajax via custom message round-trip | `model.send(req)` + `model.on('msg:custom')` reply |
|
|
24
|
+
|
|
25
|
+
## Phases
|
|
26
|
+
|
|
27
|
+
### Phase 0 — scaffold ✅ (this commit)
|
|
28
|
+
- [x] Repo, pyproject (hatchling), package.json (esbuild)
|
|
29
|
+
- [x] anywidget adapter `js/index.js` (core render + proxy/event skeleton)
|
|
30
|
+
- [x] `Dt2` widget + `dt2()` constructor (pandas/polars/records)
|
|
31
|
+
- [x] Shiny example app
|
|
32
|
+
- [x] Build bundle; widget constructs, traits populate, app serves (HTTP 200)
|
|
33
|
+
- [ ] Visual in-browser render + selection (needs Chrome extension online)
|
|
34
|
+
|
|
35
|
+
### Phase 1 — config parity ✅
|
|
36
|
+
- [x] `Options` builder (chainable) covering the R `dt2_options`/`dt2_formats`
|
|
37
|
+
surface: `order`, `search_global`, `length_menu`, `language`, `use_buttons`,
|
|
38
|
+
`cols_align/width/hide/escape`, `format_number/datetime/number_abbrev/time_relative`,
|
|
39
|
+
`cols_render`, `cols_render_orthogonal`, `register/use_renderer`.
|
|
40
|
+
- [x] Name→index resolution (`_name_to_idx`, ported with warnings); seed names
|
|
41
|
+
from a DataFrame/records via `Options(df)` — removes the R "forgot to set
|
|
42
|
+
options$columns" footgun.
|
|
43
|
+
- [x] **`JS()` parity for `htmlwidgets::JS()`:** Python marks renderer source as
|
|
44
|
+
`{"__dt2_js__": code}`; `index.js` `reviveJs()` recursively compiles markers
|
|
45
|
+
into functions with `DataTable`/`$`/`moment` in scope. Proven in node.
|
|
46
|
+
- [x] `dt2(df, options=opts, **kw)` merge. 22 unit tests + JS revive test green.
|
|
47
|
+
- [x] Quote-safe values via `json.dumps` (`_js_str`, port of `.dt2_js_str`).
|
|
48
|
+
|
|
49
|
+
### Phase 2 — extensions ✅
|
|
50
|
+
- [x] All 15 extensions bundled via esbuild (`js/extensions.js`): Buttons,
|
|
51
|
+
Select, Responsive, FixedHeader, FixedColumns, KeyTable, Scroller,
|
|
52
|
+
RowGroup, RowReorder, ColReorder, DateTime, SearchBuilder, SearchPanes,
|
|
53
|
+
StateRestore, ColumnControl. Verified present in the built bundle.
|
|
54
|
+
- [x] jszip bundled (Excel/CSV/copy export via `window.JSZip`).
|
|
55
|
+
- [x] Python activation helpers on `Options`: `select/responsive/fixed_header/
|
|
56
|
+
fixed_columns/key_table/col_reorder/row_reorder/row_group/scroller/
|
|
57
|
+
search_panes/search_builder/state_restore/column_control/buttons`, plus
|
|
58
|
+
`extensions()` registry (parity with R `dt2_extensions()`).
|
|
59
|
+
- [x] `buttons(target=...)` relocation (port of R `dt2_buttons`) handled in JS.
|
|
60
|
+
- [x] `_momentLocale` applied client-side. 15 extension tests green (46 total).
|
|
61
|
+
- [ ] **Deferred follow-ups:** pdfmake (PDF export, ~1MB) as an optional extra;
|
|
62
|
+
moment-with-locales for `format_time_relative`; modular/lazy loading so the
|
|
63
|
+
base wheel ships only requested extensions (parity with R's per-ext loading).
|
|
64
|
+
|
|
65
|
+
### Phase 3 — Shiny integration
|
|
66
|
+
- [x] **Server-side processing (de-risked first):** DataTables `ajax` as a
|
|
67
|
+
function routed over the Comm; `dt2.server.process_ssp` ports the R
|
|
68
|
+
filter/order/paginate logic (no query-string parse needed — request
|
|
69
|
+
arrives structured). Widget keeps full data Python-side; `_on_msg`
|
|
70
|
+
replies with a correlated `dt2_ssp_response`. Unit + handler tests green.
|
|
71
|
+
**Still to verify:** the live Comm round-trip in a browser (needs frontend).
|
|
72
|
+
- [x] **Proxy parity** (`cmd` protocol mirroring R/dt2_proxy.R): widget methods
|
|
73
|
+
`replace_data`, `draw`, `reload`, `order`, `search`, `clear_search`,
|
|
74
|
+
`page`, `select_rows`. Order resolves header names → indices client-side.
|
|
75
|
+
- [x] **Events parity**: enriched `state` ({reason, order, search, page,
|
|
76
|
+
selected}) + `selected_rows`; a monotonic `_seq` makes event traits
|
|
77
|
+
re-fire under `reactive_read` (anywidget equivalent of Shiny's
|
|
78
|
+
`priority:"event"`).
|
|
79
|
+
- [x] **Inline inputs** (port of R/dt2_inputs.R): `Options.col_checkbox` /
|
|
80
|
+
`col_button` render delegated controls; clicks set `row_check` /
|
|
81
|
+
`row_button` event traits. Verified in node (render compiles, seeds
|
|
82
|
+
checked state) + 12 tests (58 total). Example `app_proxy_inputs.py`.
|
|
83
|
+
- [ ] Per-column search in SSP (R does global only — match, then optionally extend)
|
|
84
|
+
|
|
85
|
+
### Phase 4 — polish & release
|
|
86
|
+
- [x] Tests: 58 pytest cases (config, extensions, SSP, proxy/inputs) + node
|
|
87
|
+
smoke checks for the JS adapter. `[tool.pytest.ini_options]` configured.
|
|
88
|
+
- [x] CI (`.github/workflows/ci.yml`): builds the JS bundle, asserts the
|
|
89
|
+
committed `src/dt2/static` is fresh, installs and runs pytest on Python
|
|
90
|
+
3.9/3.11/3.13; syntax-checks the adapter sources.
|
|
91
|
+
- [x] Wheel verified: includes the built bundle; fresh `pip install` works with
|
|
92
|
+
no Node toolchain (703kb bundle, 15 extensions, widget builds).
|
|
93
|
+
- [x] Release workflow (`release.yml`): builds + publishes to PyPI via Trusted
|
|
94
|
+
Publishing on a published GitHub Release (maintainer-triggered).
|
|
95
|
+
- [x] README (full API), CHANGELOG, examples gallery (5 runnable apps).
|
|
96
|
+
- [x] **Live in-browser verification** (Shiny for Python): core render +
|
|
97
|
+
selection, SSP (50k rows: render + search), config renderers
|
|
98
|
+
(number/datetime/custom badge), extensions (RowGroup + Buttons), and
|
|
99
|
+
inline inputs with row_check/row_button events — all confirmed. Two bugs
|
|
100
|
+
found & fixed: moment.js now bundled (datetime), buttons placed in layout.
|
|
101
|
+
- [ ] **PyPI publish** — maintainer action: configure the PyPI trusted publisher,
|
|
102
|
+
then publish a GitHub Release to trigger `release.yml`.
|
|
103
|
+
|
|
104
|
+
## Open questions
|
|
105
|
+
- Bundle size: shipping all extensions vs. lazy/optional extras. Lean toward
|
|
106
|
+
optional `pip install dt2[buttons,searchpanes,...]` extras mapping to JS
|
|
107
|
+
chunks, to keep the base wheel small.
|
|
108
|
+
- SSP transport latency over Comm vs. a Starlette route — benchmark in Phase 3.
|
dt2-0.1.0/build.mjs
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Bundles the anywidget adapter + DataTables v2 stack into src/dt2/static/.
|
|
2
|
+
// Output: index.js (ESM, anywidget _esm) and index.css (anywidget _css).
|
|
3
|
+
import * as esbuild from "esbuild";
|
|
4
|
+
|
|
5
|
+
const watch = process.argv.includes("--watch");
|
|
6
|
+
|
|
7
|
+
/** @type {import('esbuild').BuildOptions} */
|
|
8
|
+
const opts = {
|
|
9
|
+
entryPoints: ["js/index.js"],
|
|
10
|
+
outfile: "src/dt2/static/index.js",
|
|
11
|
+
bundle: true,
|
|
12
|
+
format: "esm",
|
|
13
|
+
minify: !watch,
|
|
14
|
+
sourcemap: watch,
|
|
15
|
+
// DataTables ships font/image-free CSS for the bs5 integration; bundle CSS
|
|
16
|
+
// imported from JS into a sibling index.css that anywidget loads as _css.
|
|
17
|
+
loader: {
|
|
18
|
+
".css": "css",
|
|
19
|
+
".woff": "dataurl",
|
|
20
|
+
".woff2": "dataurl",
|
|
21
|
+
".ttf": "dataurl",
|
|
22
|
+
".eot": "dataurl",
|
|
23
|
+
".svg": "dataurl",
|
|
24
|
+
".png": "dataurl",
|
|
25
|
+
},
|
|
26
|
+
logLevel: "info",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
if (watch) {
|
|
30
|
+
const ctx = await esbuild.context(opts);
|
|
31
|
+
await ctx.watch();
|
|
32
|
+
console.log("[dt2] watching js/ for changes…");
|
|
33
|
+
} else {
|
|
34
|
+
await esbuild.build(opts);
|
|
35
|
+
console.log("[dt2] built src/dt2/static/index.{js,css}");
|
|
36
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Minimal Shiny for Python app exercising the dt2 widget.
|
|
2
|
+
|
|
3
|
+
Run with: shiny run examples/app.py
|
|
4
|
+
Requires: pip install -e ".[shiny,pandas]"
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
from shiny import App, reactive, render, ui
|
|
9
|
+
from shinywidgets import output_widget, render_widget, reactive_read
|
|
10
|
+
|
|
11
|
+
from dt2 import dt2
|
|
12
|
+
|
|
13
|
+
df = pd.DataFrame(
|
|
14
|
+
{
|
|
15
|
+
"name": ["Ada", "Alan", "Grace", "Linus", "Margaret"],
|
|
16
|
+
"field": ["Math", "Logic", "Navy", "Kernel", "Apollo"],
|
|
17
|
+
"year": [1815, 1912, 1906, 1969, 1936],
|
|
18
|
+
}
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
app_ui = ui.page_fluid(
|
|
22
|
+
ui.h3("dt2 — DataTables v2 in Shiny for Python"),
|
|
23
|
+
output_widget("tbl"),
|
|
24
|
+
ui.output_text_verbatim("selection"),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def server(input, output, session):
|
|
29
|
+
@render_widget
|
|
30
|
+
def tbl():
|
|
31
|
+
return dt2(df, select=True, pageLength=5)
|
|
32
|
+
|
|
33
|
+
@render.text
|
|
34
|
+
def selection():
|
|
35
|
+
rows = reactive_read(tbl.widget, "selected_rows")
|
|
36
|
+
return f"selected rows (1-based): {rows}"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
app = App(app_ui, server)
|