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.
@@ -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
@@ -0,0 +1,11 @@
1
+ node_modules/
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .venv/
6
+ dist/
7
+ build/
8
+ .DS_Store
9
+ .Rhistory
10
+ *.tsbuildinfo
11
+ src/dt2/static/index.js.map
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)