bootstack.DataTable#

class bootstack.DataTable(*, columns=None, rows=None, data_source=None, selection_mode='single', sorting_mode='single', searchable=True, allow_filter=True, paging_mode='standard', page_size=25, allow_add=False, allow_edit=False, allow_delete=False, allow_export=False, export_formats=None, striped=True, density='default', allow_group=False, show_status_bar=True, show_column_chooser=False, show_selection_controls=False, id_field='id', form=None, parent=None, **kwargs)#

Bases: PublicWidgetBase

A feature-rich data table with sorting, filtering, search, and grouping.

Backed by an in-memory SqliteDataSource by default. Supply rows= to pre-load data, or pass an existing data_source= to share any data source that implements the data-source protocol (SqliteDataSource, MemoryDataSource, FileDataSource, or your own).

Parameters:
  • columns (list[str | ColumnSpec] | None) – Column definitions — a list of field-key strings, or ColumnSpec dicts ('key', 'text', 'width', 'minwidth', 'anchor', and the editor keys 'editor', 'editor_options', 'dtype', 'readonly', 'required' that shape the add/edit dialog).

  • rows (list | None) – Initial data rows (list of dicts or sequences).

  • data_source (DataSourceProtocol | None) – Existing data source to use instead of creating one. Any object implementing the data-source protocol is accepted (SqliteDataSource, MemoryDataSource, FileDataSource, or a custom source).

  • selection_mode (SelectionMode) – Row selection behavior. Default 'single'.

  • sorting_mode (Literal['single', 'none']) – Whether columns can be sorted. Default 'single'.

  • searchable (bool) – Show the search bar. Default True.

  • allow_filter (bool) – Enable column filtering. Default True.

  • paging_mode (Literal['standard', 'virtual']) – 'standard' paginates the rows; 'virtual' scrolls them in a single virtual view. Default 'standard'.

  • page_size (int) – Rows per page in standard paging mode. Default 25.

  • allow_add (bool) – Allow adding rows via a form dialog. Default False.

  • allow_edit (bool) – Allow editing rows via a form dialog. Default False.

  • allow_delete (bool) – Allow deleting rows. Default False.

  • allow_export (bool) – Show an export menu (Copy to clipboard / Save to file). The actions export the selected rows if any are selected, otherwise all rows in the current filter/sort. Saves CSV; Excel (.xlsx) is also offered when the optional bootstack[excel] dependency is installed. For explicit control use export_file() / to_csv() with scope=. Default False.

  • export_formats (list[Literal['csv', 'tsv', 'xlsx', 'json', 'jsonl', 'xml', 'parquet', 'feather', 'hdf5']] | None) – Which formats the export menu offers. Default ('csv',). Formats needing an optional dependency (xlsx`→`bootstack[excel], parquet/feather`→`bootstack[parquet], hdf5`→`bootstack[hdf5]) appear only when it is installed. Registry formats export the displayed columns; for the full record set use data_source.save(path).

  • striped (bool) – Alternate row background colors. Default True.

  • density (WidgetDensity) – Row compactness — 'default' or 'compact' (tighter row height, smaller body font and padding). Default 'default'.

  • allow_group (bool) – Allow grouping rows by a column. Default False.

  • show_status_bar (bool) – Show the footer — the filter/sort/group status and the pager. The pager auto-hides on a single page, and the whole footer collapses when there’s nothing to show. Default True.

  • show_column_chooser (bool) – Show a button to toggle column visibility. Default False.

  • show_selection_controls (bool) – Show a per-row checkbox in the leading icon slot while in 'multi' selection mode — filled with the accent when selected, a muted outline otherwise. With the checkboxes visible, a plain click toggles the row (no Ctrl/Shift needed). Has no effect in 'single' mode (the row highlight is enough) or while grouped, where the slot holds the group’s expand/collapse control. Default False.

  • id_field (str) – Record field used as the stable row identity. When your rows carry this field (default 'id'), its value becomes the row id used by select_rows, events, and update_rows/delete_rows, so your own ids round-trip; otherwise an id is auto-assigned. Ignored when you pass your own data_source (set it on the source instead). Default 'id'.

  • form (FormOptions | None) – Layout options for the built-in add/edit dialog — a FormOptions dict (col_count, min_col_width, scrollable, resizable).

  • parent (Any) – Override the context-stack parent.

  • **kwargs (Any) – Layout placement options applied by the parent container — fill, expand, anchor, margin, row, column, sticky. See Layout & Spacing.

property current_page: int#

Zero-based index of the page currently shown.

property data_source: DataSourceProtocol#

The underlying data source instance.

property is_attached: bool#

Whether the widget is currently placed in its layout.

True while the widget occupies space in its parent; False after detach (or before it has ever been placed). A detached widget keeps its state and can be returned to the layout with attach.

property page_count: int#

Total number of pages for the current filter/search.

property schedule: Schedule#

Scheduler tied to this widget’s lifetime.

All jobs are automatically cancelled when the widget is destroyed. First access creates the Schedule instance; subsequent accesses return the same instance.

Usage:

self.schedule.delay(500, callback)
self.schedule.every(1000, tick)
job = self.schedule.idle(refresh)
job.cancel()
property selection: dict | list[dict] | None#

The selected row(s) — the data bag.

In 'single' mode, the selected record dict (or None when nothing is selected). In 'multi' mode, a list of record dicts (empty when nothing is selected). Each record carries its non-displayed fields too, indexed by key like any record. Read-only.

attach(**kwargs)#

Return a detached widget to its layout, optionally moving it.

With no arguments, restores the widget to exactly where detach took it from. Any layout kwargs accepted by the original placement (e.g. fill, expand, anchor, sticky, margin) override the stored options. For stacked widgets, index= sets the position among the currently attached siblings (or pass an explicit before=/after= sibling); without one, the snapshotted position is used.

Calling attach on a widget that is already attached moves it (the kwargs are re-applied). Fires on_attach.

Parameters:

**kwargs (Any) – Layout placement options to override for this placement.

Raises:

ParentResolutionError – If the widget was never placed in a layout.

clear_filters()#

Remove all active column filters (leaves the search term intact).

clear_grouping()#

Remove the active grouping.

Clear the free-text search term (leaves column filters intact).

clear_selection()#

Clear the selection.

Users can also press Escape over the table to clear it — useful in single-select mode, where clicking cannot return to an empty selection.

clear_sorting()#

Remove all active sort orders.

collapse_all()#

Collapse all groups.

delete_rows(rows_or_ids)#

Delete rows by record id or by record dict.

Parameters:

rows_or_ids (list) – List of record ids or record dicts with an id key.

deselect_rows(record_ids)#

Remove the given rows from the selection by record id.

Parameters:

record_ids (list) – Record ids to deselect.

destroy()#

Destroy the widget and release the resources it holds.

Removes the widget from its parent, destroys its children, and cancels any pending or repeating jobs on its schedule. After this the widget must not be used again. Destroying a container destroys everything inside it.

detach()#

Remove the widget from its layout without destroying it.

The widget stops occupying space but keeps its state, children, and event bindings, ready to be returned with attach. The current position is snapshotted so a plain attach() restores it exactly — for stacked siblings this is the index among the currently attached siblings, so detaching other siblings first shifts that index.

Calling detach on a widget that is already detached, or one that was never placed in a layout, does nothing. Fires on_detach.

edit_row(record_id)#

Open the built-in Edit Record dialog for a row and save on submit.

On save the row is updated and a rows_update event fires (or rows_delete if the user deletes it); the saved record is also returned.

Parameters:

record_id (Any) – Record id of the row to edit.

Returns:

The updated record, or None if cancelled or deleted.

Return type:

dict | None

emit(event, *, data=None)#

Fire a named event on this widget, as if it produced the event itself.

This is how a composite widget surfaces high-level activity to its listeners, and the generic counterpart to the on_*() shorthands for firing events that have no dedicated method.

Parameters:
  • event (str) – The event name, unprefixed — the same name you pass to on() or an on_<event>() shorthand (e.g. 'change', 'select').

  • data (Any) – The payload delivered to handlers. For a data-carrying event, pass the matching payload dataclass from bootstack.events — the same object an on_<event>() handler receives. Leave as None for native events (click, hover, focus, …), which carry no payload.

Example

widget.emit("change", data=bs.events.ChangeEvent(value=new_value))
expand_all()#

Expand all groups.

export_file(path, scope='all', *, format=None, chunk_size=1000, on_progress=None)#

Stream the data to path, paging so memory stays flat.

The format is inferred from the path extension (.csv, .tsv, .xlsx) unless format is given. .xlsx requires bootstack[excel].

Parameters:
  • path (str) – Destination file path.

  • scope (ExportScope) – 'all', 'page', or 'selection'.

  • format (ExportFormat | None) – Override the format — 'csv', 'tsv', or 'xlsx'.

  • chunk_size (int) – Rows read/written per batch.

  • on_progress (Callable[[int, int], Any] | None) – Called as on_progress(written, total) after each batch.

Returns:

The number of rows written.

Return type:

int

export_file_async(path, scope='all', *, format=None, chunk_size=1000, on_progress=None, on_done=None)#

Stream the data to path without blocking the UI; return a job.

Writes one chunk per event-loop idle tick, so the UI stays responsive and the export can be cancelled mid-run via the returned job’s cancel(). A cancelled or failed export removes the partial file.

Parameters:
  • path (str) – Destination file path.

  • scope (ExportScope) – 'all', 'page', or 'selection'.

  • format (ExportFormat | None) – Override the format — 'csv', 'tsv', or 'xlsx'.

  • chunk_size (int) – Rows read/written per idle tick.

  • on_progress (Callable[[int, int], Any] | None) – Called as on_progress(written, total) after each chunk.

  • on_done (Callable[[str, int, Any], Any] | None) – Called as on_done(status, written, error) at the end, with status one of 'completed', 'cancelled', or 'error'.

Returns:

A job handle with a cancel() method.

Return type:

ExportJob

get_clipboard()#

Return the current text contents of the system clipboard.

Returns:

The clipboard text, or an empty string when the clipboard is empty or holds non-text data.

Return type:

str

get_filters()#

Return the active column filters as {column_key: allowed_values}.

get_grouping()#

Return the currently grouped column key, or None.

Return the active free-text search term.

get_sorting()#

Return the current sort state as {column_key: ascending} dict.

go_to_page(index)#

Show the page at the given zero-based index.

Parameters:

index (int) – Zero-based page index.

group_by(column)#

Group rows by a column (collapsible group headers).

Parameters:

column (str) – Column key to group by.

insert_rows(rows)#

Insert new rows.

Parameters:

rows (list) – List of dicts or sequences to insert.

iter_rows(scope='all', chunk_size=1000)#

Lazily yield record dicts one at a time, paging the data source.

Memory stays flat regardless of size — suitable for very large exports.

Parameters:
  • scope (ExportScope) – 'all', 'page', or 'selection'.

  • chunk_size (int) – Rows read from the source per batch.

new_row(defaults=None)#

Open the built-in New Record dialog and insert on save.

Honors each column’s editor configuration (editor, dtype, readonly, required). On save the row is inserted and an rows_insert event fires; the saved record is also returned here.

Parameters:

defaults (dict | None) – Field values to pre-fill the form with.

Returns:

The new record, or None if the dialog was cancelled.

Return type:

dict | None

next_page()#

Advance to the next page (no-op on the last page).

on(event, handler=None)#

Bind handler to event, or return a composable Stream.

With a handler — binds immediately and returns a Subscription:

sub = widget.on("change", handler)
sub.cancel()

Without a handler — returns a Stream for operator chaining. The Tk binding is created lazily when .listen() is called:

sub = widget.on("change").debounce(300).listen(handler)
sub.cancel()
Parameters:
  • event (str) – Event name (e.g. "change", "click").

  • handler (Callable[[Any], Any] | None) – Optional callback. If omitted, a Stream is returned.

Returns:

Subscription when a handler is provided; Stream otherwise.

Return type:

Stream | Subscription

on_attach(handler=None)#

Register a callback fired when the widget enters the layout.

Fires each time the widget becomes visible in its parent — on initial placement and on every attach. Pair it with on_detach to keep per-visibility resources (timers, observers) tied to the widget’s presence on screen. The handler receives a curated Event.

Parameters:

handler (Callable[[Event], Any] | None) – Called when the widget is attached. Omit to get a composable Stream.

Returns:

A cancellable Subscription when a handler is given, otherwise a Stream.

Return type:

Stream | Subscription

on_destroy(handler=None)#

Register a callback fired when the widget is destroyed.

Fires once, as the widget is torn down — the place to release resources the widget owns that aren’t cleaned up automatically (file handles, observers, external subscriptions). The handler receives a curated Event.

Parameters:

handler (Callable[[Event], Any] | None) – Called as the widget is destroyed. Omit to get a composable Stream.

Returns:

A cancellable Subscription when a handler is given, otherwise a Stream.

Return type:

Stream | Subscription

on_detach(handler=None)#

Register a callback fired when the widget leaves the layout.

Fires each time the widget stops occupying space in its parent — on detach and when an ancestor hides it. Pair it with on_attach to release per-visibility resources. The handler receives a curated Event.

Parameters:

handler (Callable[[Event], Any] | None) – Called when the widget is detached. Omit to get a composable Stream.

Returns:

A cancellable Subscription when a handler is given, otherwise a Stream.

Return type:

Stream | Subscription

on_export() Stream#
on_export(handler: Callable[[ExportEvent], Any]) Subscription

Fired after the data is exported (copied or saved).

Parameters:

handler (Callable[[ExportEvent], Any] | None) – Called with an ExportEvent (count, target'clipboard' or 'file'format, and path). Omit to get a composable Stream instead.

Returns:

A cancellable Subscription when a handler is given, otherwise a Stream.

Return type:

Stream | Subscription

on_row_click() Stream#
on_row_click(handler: Callable[[RowEvent], Any]) Subscription

Fired when a row is clicked.

Parameters:

handler (Callable[[RowEvent], Any] | None) – Called with a RowEvent (record, id). Omit to get a composable Stream instead.

Returns:

A cancellable Subscription when a handler is given, otherwise a Stream.

Return type:

Stream | Subscription

on_row_double_click() Stream#
on_row_double_click(handler: Callable[[RowEvent], Any]) Subscription

Fired when a row is double-clicked.

Parameters:

handler (Callable[[RowEvent], Any] | None) – Called with a RowEvent (record, id). Omit to get a composable Stream instead.

Returns:

A cancellable Subscription when a handler is given, otherwise a Stream.

Return type:

Stream | Subscription

on_row_right_click() Stream#
on_row_right_click(handler: Callable[[RowEvent], Any]) Subscription

Fired when a row is right-clicked.

Parameters:

handler (Callable[[RowEvent], Any] | None) – Called with a RowEvent (record, id). Omit to get a composable Stream instead.

Returns:

A cancellable Subscription when a handler is given, otherwise a Stream.

Return type:

Stream | Subscription

on_rows_delete() Stream#
on_rows_delete(handler: Callable[[RowsEvent], Any]) Subscription

Fired after rows are deleted.

Parameters:

handler (Callable[[RowsEvent], Any] | None) – Called with a RowsEvent carrying the deleted records. Omit to get a composable Stream instead.

Returns:

A cancellable Subscription when a handler is given, otherwise a Stream.

Return type:

Stream | Subscription

on_rows_insert() Stream#
on_rows_insert(handler: Callable[[RowsEvent], Any]) Subscription

Fired after rows are inserted.

Parameters:

handler (Callable[[RowsEvent], Any] | None) – Called with a RowsEvent carrying the inserted records. Omit to get a composable Stream instead.

Returns:

A cancellable Subscription when a handler is given, otherwise a Stream.

Return type:

Stream | Subscription

on_rows_move() Stream#
on_rows_move(handler: Callable[[RowsEvent], Any]) Subscription

Fired after rows are reordered.

Parameters:

handler (Callable[[RowsEvent], Any] | None) – Called with a RowsEvent carrying the moved records. Omit to get a composable Stream instead.

Returns:

A cancellable Subscription when a handler is given, otherwise a Stream.

Return type:

Stream | Subscription

on_rows_update() Stream#
on_rows_update(handler: Callable[[RowsEvent], Any]) Subscription

Fired after rows are updated.

Parameters:

handler (Callable[[RowsEvent], Any] | None) – Called with a RowsEvent carrying the updated records. Omit to get a composable Stream instead.

Returns:

A cancellable Subscription when a handler is given, otherwise a Stream.

Return type:

Stream | Subscription

on_select() Stream#
on_select(handler: Callable[[SelectionEvent], Any]) Subscription

Fired when the set of selected rows changes.

Parameters:

handler (Callable[[SelectionEvent], Any] | None) – Called with a SelectionEvent (records, ids). Omit to get a composable Stream instead.

Returns:

A cancellable Subscription when a handler is given, otherwise a Stream.

Return type:

Stream | Subscription

prev_page()#

Go to the previous page (no-op on the first page).

scroll_to_row(record_id)#

Scroll the table so the row with the given id is visible.

Parameters:

record_id (Any) – Record id of the row.

select_all()#

Select all rows in the current view.

select_rows(record_ids)#

Select rows by record id.

Only rows on the current page can be selected.

Parameters:

record_ids (list) – Record ids of the rows to select.

set_clipboard(text)#

Replace the system clipboard contents with text.

Parameters:

text (str) – The text to place on the clipboard.

set_filter(column, values=None)#

Filter a column to the given values, or clear it when values is None.

Composes with the search term and other column filters.

Parameters:
  • column (str) – Column key to filter.

  • values (list | None) – Allowed values; None clears this column’s filter.

set_rows(rows)#

Replace the entire dataset with rows.

Parameters:

rows (list) – List of dicts (or sequences) to load.

Set the free-text search term.

Searches across all columns. Leaves any active column filters intact.

Parameters:

text (str) – The search term. Pass an empty string to clear the search.

sort_by(column, ascending=True)#

Sort the table by a column.

Parameters:
  • column (str) – Column key to sort by.

  • ascending (bool) – Sort ascending (default) or descending.

to_csv(scope='all', *, max_rows=100000)#

Return the data as a CSV string (materialized — small data).

For large datasets use export_file(), which streams to disk.

Parameters:
  • scope (ExportScope) – 'all', 'page', or 'selection'.

  • max_rows (int | None) – Raise if the row count exceeds this. None lifts the cap.

to_rows(scope='all', *, max_rows=100000)#

Return the data as a list of record dicts (materialized — small data).

Loads every matching row into memory. For large datasets use iter_rows() (streaming) or export_file() instead.

Parameters:
  • scope (ExportScope) – Which rows — 'all' (the filtered set), 'page' (current page), or 'selection' (selected rows).

  • max_rows (int | None) – Raise if the row count exceeds this. Pass None to lift the cap (at your own memory risk).

update_rows(rows)#

Update existing rows by merging the given dicts.

Parameters:

rows (list[dict]) – List of dicts, each with an id key and the fields to update.