A Python port of the R DT2 package: an anywidget binding for DataTables v2, designed for Shiny for Python (and usable in Jupyter).
It configures DataTables via plain Python (1:1 with the JS API), reusing the same DataTables runtime (2.3.4) and extensions as the R package.
📖 Documentation: https://strategicprojects.github.io/dt2py/ · 📦 PyPI: https://pypi.org/project/dt2/
Status: feature-complete toward R parity, pre-release. Config, all 15 extensions, server-side processing, proxy, events and inline inputs are implemented and unit-tested. The live in-browser Comm transport has not yet been visually verified. See ROADMAP.md and CHANGELOG.md.
npm install && npm run build # build the JS bundle into src/dt2/static/
uv venv && uv pip install -e ".[shiny,pandas]"(The built bundle is committed, so an installed wheel needs no Node toolchain.)
import pandas as pd
from shiny import App, ui
from shinywidgets import output_widget, render_widget
from dt2 import dt2
df = pd.read_csv("data.csv")
app_ui = ui.page_fluid(output_widget("tbl"))
def server(input, output, session):
@render_widget
def tbl():
return dt2(df, select=True, pageLength=10)
app = App(app_ui, server)Chainable builder mirroring the R pipe helpers:
from dt2 import Options, JS, dt2
opts = (Options(df)
.cols_align(["revenue"], "right")
.format_number(["revenue"], thousands=".", decimal=",", digits=2, prefix="R$ ")
.format_datetime(["updated"], from_="YYYY-MM-DD", to="DD/MM/YYYY")
.cols_render(["score"], JS("function(d,t){ return t==='display' ? d+'%' : d; }"))
.order(("revenue", "desc"))
.length_menu([10, 25, -1]))
dt2(df, options=opts)JS(...) is the parity for htmlwidgets::JS(): the source is revived into a
real function in the browser, with DataTable, $ and moment in scope.
All 15 DataTables extensions are bundled; activate via Options:
opts = (Options(df)
.buttons(["copyHtml5", "csvHtml5", "excelHtml5"]) # jszip bundled; PDF needs pdfmake
.select({"style": "os"})
.responsive()
.fixed_header()
.row_group("field"))dt2.extensions() lists what is bundled.
Keep large data Python-side; DataTables fetches pages over the Comm:
dt2(big_df, server_side=True, pageLength=25)Filtering/ordering/paging run in dt2.server.process_ssp.
# proxy (Python -> table): call on the rendered widget
tbl.widget.search("ada")
tbl.widget.order(("field", "desc"))
tbl.widget.select_rows([1, 3])
# events (table -> Python): read reactively
from shinywidgets import reactive_read
reactive_read(tbl.widget, "selected_rows") # also: state, row_check, row_button
# inline row inputs
opts = Options(df).col_checkbox("select", value_col="active").col_button("act", label="Ping")See examples/ for runnable apps:
app.py, app_config.py, app_extensions.py, app_serverside.py,
app_proxy_inputs.py.
The DataTables runtime is shared conceptually with R DT2. What differs is the
transport: R talks to Shiny via window.Shiny; here the widget talks to the
Python kernel over the anywidget Comm, bridged to Shiny reactivity by
shinywidgets. The htmlwidgets::JS() mechanism becomes the JS() marker +
client-side reviver. See the header of js/index.js for the full mapping.
MIT © André Leite