bootstack 0.1.0__py3-none-any.whl
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.
- bootstack/__init__.py +157 -0
- bootstack/__main__.py +5 -0
- bootstack/_core/__init__.py +21 -0
- bootstack/_core/capabilities/__init__.py +45 -0
- bootstack/_core/capabilities/after.py +103 -0
- bootstack/_core/capabilities/bind.py +192 -0
- bootstack/_core/capabilities/bindtags.py +112 -0
- bootstack/_core/capabilities/busy.py +72 -0
- bootstack/_core/capabilities/clipboard.py +89 -0
- bootstack/_core/capabilities/focus.py +118 -0
- bootstack/_core/capabilities/grab.py +65 -0
- bootstack/_core/capabilities/grid.py +211 -0
- bootstack/_core/capabilities/localization.py +231 -0
- bootstack/_core/capabilities/pack.py +140 -0
- bootstack/_core/capabilities/place.py +113 -0
- bootstack/_core/capabilities/selection.py +136 -0
- bootstack/_core/capabilities/signals.py +244 -0
- bootstack/_core/capabilities/winfo.py +315 -0
- bootstack/_core/colorutils.py +234 -0
- bootstack/_core/exceptions.py +25 -0
- bootstack/_core/images.py +463 -0
- bootstack/_core/mixins/__init__.py +1 -0
- bootstack/_core/mixins/ttk_state.py +35 -0
- bootstack/_core/mixins/widget.py +132 -0
- bootstack/_core/paths.py +49 -0
- bootstack/_core/publisher.py +149 -0
- bootstack/_core/variables.py +62 -0
- bootstack/_runtime/__init__.py +3 -0
- bootstack/_runtime/app.py +930 -0
- bootstack/_runtime/base_window.py +945 -0
- bootstack/_runtime/events.py +399 -0
- bootstack/_runtime/shortcuts.py +496 -0
- bootstack/_runtime/tk_patch.py +43 -0
- bootstack/_runtime/toplevel.py +276 -0
- bootstack/_runtime/utility.py +457 -0
- bootstack/_runtime/visual_focus.py +240 -0
- bootstack/_runtime/window_utilities.py +1123 -0
- bootstack/assets/__init__.py +21 -0
- bootstack/assets/bootstack.ico +0 -0
- bootstack/assets/bootstack.png +0 -0
- bootstack/assets/elements/__init__.py +0 -0
- bootstack/assets/elements/badge-pill.png +0 -0
- bootstack/assets/elements/badge-square.png +0 -0
- bootstack/assets/elements/button-compact.png +0 -0
- bootstack/assets/elements/button-default.png +0 -0
- bootstack/assets/elements/buttongroup-after-h-compact.png +0 -0
- bootstack/assets/elements/buttongroup-after-h-default.png +0 -0
- bootstack/assets/elements/buttongroup-after-v-compact.png +0 -0
- bootstack/assets/elements/buttongroup-after-v-default.png +0 -0
- bootstack/assets/elements/buttongroup-before-h-compact.png +0 -0
- bootstack/assets/elements/buttongroup-before-h-default.png +0 -0
- bootstack/assets/elements/buttongroup-before-v-compact.png +0 -0
- bootstack/assets/elements/buttongroup-before-v-default.png +0 -0
- bootstack/assets/elements/buttongroup-center-h-compact.png +0 -0
- bootstack/assets/elements/buttongroup-center-h-default.png +0 -0
- bootstack/assets/elements/buttongroup-center-v-compact.png +0 -0
- bootstack/assets/elements/buttongroup-center-v-default.png +0 -0
- bootstack/assets/elements/card.png +0 -0
- bootstack/assets/elements/checkbox-checked.png +0 -0
- bootstack/assets/elements/checkbox-indeterminate.png +0 -0
- bootstack/assets/elements/checkbox-unchecked.png +0 -0
- bootstack/assets/elements/field.png +0 -0
- bootstack/assets/elements/input-addon-compact.png +0 -0
- bootstack/assets/elements/input-addon-default.png +0 -0
- bootstack/assets/elements/input-compact.png +0 -0
- bootstack/assets/elements/input-default.png +0 -0
- bootstack/assets/elements/list-item-separated.png +0 -0
- bootstack/assets/elements/list-item.png +0 -0
- bootstack/assets/elements/listrow-compact.png +0 -0
- bootstack/assets/elements/listrow-default.png +0 -0
- bootstack/assets/elements/manifest.toml +361 -0
- bootstack/assets/elements/menu-item.png +0 -0
- bootstack/assets/elements/navitem-compact.png +0 -0
- bootstack/assets/elements/navitem-default.png +0 -0
- bootstack/assets/elements/progressbar-h-compact.png +0 -0
- bootstack/assets/elements/progressbar-h-default.png +0 -0
- bootstack/assets/elements/progressbar-v-compact.png +0 -0
- bootstack/assets/elements/progressbar-v-default.png +0 -0
- bootstack/assets/elements/radiobutton.png +0 -0
- bootstack/assets/elements/scrollbar-horizontal.png +0 -0
- bootstack/assets/elements/scrollbar-vertical.png +0 -0
- bootstack/assets/elements/slider-handle.png +0 -0
- bootstack/assets/elements/slider-track-h.png +0 -0
- bootstack/assets/elements/slider-track-v.png +0 -0
- bootstack/assets/elements/switch-off.png +0 -0
- bootstack/assets/elements/switch-on.png +0 -0
- bootstack/assets/elements/tab-h.png +0 -0
- bootstack/assets/elements/tab-v.png +0 -0
- bootstack/assets/icons/bootstrap.ttf +0 -0
- bootstack/assets/icons/glyphmap.json +2080 -0
- bootstack/assets/icons/icon_metrics.json +12470 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/bg/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +878 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +878 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +854 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +844 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +917 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +844 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +844 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +845 -0
- bootstack/cli/__init__.py +133 -0
- bootstack/cli/__main__.py +6 -0
- bootstack/cli/add.py +395 -0
- bootstack/cli/appicon.py +285 -0
- bootstack/cli/build.py +115 -0
- bootstack/cli/config.py +313 -0
- bootstack/cli/demo.py +564 -0
- bootstack/cli/dev.py +153 -0
- bootstack/cli/doctor.py +195 -0
- bootstack/cli/icons.py +98 -0
- bootstack/cli/promote.py +120 -0
- bootstack/cli/pyinstaller.py +268 -0
- bootstack/cli/run.py +95 -0
- bootstack/cli/start.py +117 -0
- bootstack/cli/templates/__init__.py +931 -0
- bootstack/clipboard.py +48 -0
- bootstack/constants.py +318 -0
- bootstack/data/README.md +615 -0
- bootstack/data/__init__.py +78 -0
- bootstack/data/_observable.py +276 -0
- bootstack/data/base.py +780 -0
- bootstack/data/file_source.py +367 -0
- bootstack/data/memory_source.py +450 -0
- bootstack/data/query.py +367 -0
- bootstack/data/readers.py +289 -0
- bootstack/data/sqlite_source.py +869 -0
- bootstack/data/types.py +354 -0
- bootstack/data/writers.py +232 -0
- bootstack/dev/__init__.py +36 -0
- bootstack/dev/_capture.py +141 -0
- bootstack/dev/_env.py +43 -0
- bootstack/dev/_registry.py +140 -0
- bootstack/dev/_reloader.py +351 -0
- bootstack/dev/_reset.py +50 -0
- bootstack/dev/_watcher.py +91 -0
- bootstack/dialogs/__init__.py +948 -0
- bootstack/dialogs/_impl/__init__.py +47 -0
- bootstack/dialogs/_impl/colorchooser.py +588 -0
- bootstack/dialogs/_impl/datedialog.py +450 -0
- bootstack/dialogs/_impl/dialog.py +594 -0
- bootstack/dialogs/_impl/filterdialog.py +358 -0
- bootstack/dialogs/_impl/fontdialog.py +364 -0
- bootstack/dialogs/_impl/formdialog.py +564 -0
- bootstack/dialogs/_impl/message.py +486 -0
- bootstack/dialogs/_impl/query.py +570 -0
- bootstack/errors.py +67 -0
- bootstack/events/__init__.py +111 -0
- bootstack/events/_event.py +135 -0
- bootstack/events/_payloads.py +539 -0
- bootstack/events/_subscription.py +38 -0
- bootstack/i18n/README.md +77 -0
- bootstack/i18n/__init__.py +23 -0
- bootstack/i18n/catalog.py +121 -0
- bootstack/i18n/intl_format.py +584 -0
- bootstack/i18n/msgcat.py +425 -0
- bootstack/i18n/specs.py +156 -0
- bootstack/images.py +563 -0
- bootstack/py.typed +1 -0
- bootstack/scheduling/__init__.py +11 -0
- bootstack/scheduling/_schedule.py +218 -0
- bootstack/shortcuts.py +21 -0
- bootstack/signals/README.md +98 -0
- bootstack/signals/__init__.py +10 -0
- bootstack/signals/integration.py +100 -0
- bootstack/signals/signal.py +353 -0
- bootstack/signals/types.py +6 -0
- bootstack/store.py +286 -0
- bootstack/streams/__init__.py +12 -0
- bootstack/streams/_stream.py +321 -0
- bootstack/style/__init__.py +38 -0
- bootstack/style/builders/__init__.py +51 -0
- bootstack/style/builders/badge.py +46 -0
- bootstack/style/builders/button.py +339 -0
- bootstack/style/builders/buttongroup.py +311 -0
- bootstack/style/builders/calendar.py +271 -0
- bootstack/style/builders/checkbutton.py +110 -0
- bootstack/style/builders/combobox.py +113 -0
- bootstack/style/builders/contextmenu.py +268 -0
- bootstack/style/builders/entry.py +82 -0
- bootstack/style/builders/expander.py +148 -0
- bootstack/style/builders/field.py +335 -0
- bootstack/style/builders/frame.py +50 -0
- bootstack/style/builders/label.py +28 -0
- bootstack/style/builders/labelframe.py +34 -0
- bootstack/style/builders/listview.py +369 -0
- bootstack/style/builders/menubar.py +91 -0
- bootstack/style/builders/menubutton.py +359 -0
- bootstack/style/builders/panedwindow.py +25 -0
- bootstack/style/builders/progressbar.py +67 -0
- bootstack/style/builders/radiobutton.py +99 -0
- bootstack/style/builders/scale.py +64 -0
- bootstack/style/builders/scrollbar.py +225 -0
- bootstack/style/builders/separator.py +49 -0
- bootstack/style/builders/sidenav.py +643 -0
- bootstack/style/builders/sizegrip.py +15 -0
- bootstack/style/builders/spinbox.py +119 -0
- bootstack/style/builders/switch.py +70 -0
- bootstack/style/builders/tabitem.py +204 -0
- bootstack/style/builders/togglegroup.py +294 -0
- bootstack/style/builders/toolbutton.py +275 -0
- bootstack/style/builders/tooltip.py +26 -0
- bootstack/style/builders/treeview.py +193 -0
- bootstack/style/builders/utils.py +455 -0
- bootstack/style/builders_tk/__init__.py +16 -0
- bootstack/style/builders_tk/defaults.py +229 -0
- bootstack/style/element.py +173 -0
- bootstack/style/fonts.py +123 -0
- bootstack/style/style.py +609 -0
- bootstack/style/style_builder_base.py +716 -0
- bootstack/style/style_builder_mixed.py +93 -0
- bootstack/style/style_builder_tk.py +109 -0
- bootstack/style/style_builder_ttk.py +353 -0
- bootstack/style/style_resolver.py +447 -0
- bootstack/style/theme.py +245 -0
- bootstack/style/theme_provider.py +471 -0
- bootstack/style/themes/__init__.py +128 -0
- bootstack/style/tk_patch.py +5 -0
- bootstack/style/token_maps.py +41 -0
- bootstack/style/types.py +32 -0
- bootstack/style/typography.py +523 -0
- bootstack/style/utility.py +746 -0
- bootstack/types.py +39 -0
- bootstack/validation/__init__.py +6 -0
- bootstack/validation/types.py +15 -0
- bootstack/validation/validation_result.py +17 -0
- bootstack/validation/validation_rules.py +205 -0
- bootstack/widgets/__init__.py +74 -0
- bootstack/widgets/_core/__init__.py +31 -0
- bootstack/widgets/_core/app_config.py +266 -0
- bootstack/widgets/_core/base.py +461 -0
- bootstack/widgets/_core/container.py +334 -0
- bootstack/widgets/_core/context.py +35 -0
- bootstack/widgets/_core/events.py +130 -0
- bootstack/widgets/_core/field_mixin.py +353 -0
- bootstack/widgets/_core/icon_image_props.py +72 -0
- bootstack/widgets/_core/image_binding.py +93 -0
- bootstack/widgets/_core/navmodel.py +345 -0
- bootstack/widgets/_core/options.py +210 -0
- bootstack/widgets/_core/selection_group.py +130 -0
- bootstack/widgets/_core/window_controls.py +96 -0
- bootstack/widgets/_core/window_menu.py +246 -0
- bootstack/widgets/_impl/__init__.py +1 -0
- bootstack/widgets/_impl/_internal/__init__.py +0 -0
- bootstack/widgets/_impl/_internal/wrapper_base.py +307 -0
- bootstack/widgets/_impl/_parts/__init__.py +11 -0
- bootstack/widgets/_impl/_parts/numberentry_part.py +385 -0
- bootstack/widgets/_impl/_parts/spinnerentry_part.py +434 -0
- bootstack/widgets/_impl/_parts/textentry_part.py +406 -0
- bootstack/widgets/_impl/composites/__init__.py +33 -0
- bootstack/widgets/_impl/composites/_dateutils.py +33 -0
- bootstack/widgets/_impl/composites/_image_fit.py +105 -0
- bootstack/widgets/_impl/composites/accordion.py +381 -0
- bootstack/widgets/_impl/composites/avatar.py +191 -0
- bootstack/widgets/_impl/composites/buttongroup.py +371 -0
- bootstack/widgets/_impl/composites/calendar.py +972 -0
- bootstack/widgets/_impl/composites/carousel.py +567 -0
- bootstack/widgets/_impl/composites/chart.py +882 -0
- bootstack/widgets/_impl/composites/compositeframe.py +298 -0
- bootstack/widgets/_impl/composites/contextmenu.py +1951 -0
- bootstack/widgets/_impl/composites/dateentry.py +404 -0
- bootstack/widgets/_impl/composites/dropdownbutton.py +325 -0
- bootstack/widgets/_impl/composites/expander.py +515 -0
- bootstack/widgets/_impl/composites/field.py +670 -0
- bootstack/widgets/_impl/composites/form.py +1066 -0
- bootstack/widgets/_impl/composites/gallery.py +551 -0
- bootstack/widgets/_impl/composites/list/__init__.py +15 -0
- bootstack/widgets/_impl/composites/list/listitem.py +802 -0
- bootstack/widgets/_impl/composites/list/listview.py +1433 -0
- bootstack/widgets/_impl/composites/menu/__init__.py +18 -0
- bootstack/widgets/_impl/composites/menu/model.py +358 -0
- bootstack/widgets/_impl/composites/menu/render_native.py +134 -0
- bootstack/widgets/_impl/composites/menu/render_themed.py +134 -0
- bootstack/widgets/_impl/composites/meter.py +860 -0
- bootstack/widgets/_impl/composites/numericentry.py +201 -0
- bootstack/widgets/_impl/composites/pagestack.py +395 -0
- bootstack/widgets/_impl/composites/passwordentry.py +142 -0
- bootstack/widgets/_impl/composites/pathentry.py +168 -0
- bootstack/widgets/_impl/composites/picture.py +289 -0
- bootstack/widgets/_impl/composites/radiogroup.py +511 -0
- bootstack/widgets/_impl/composites/scrolledtext.py +375 -0
- bootstack/widgets/_impl/composites/scrolledtext.pyi +186 -0
- bootstack/widgets/_impl/composites/scrollview.py +764 -0
- bootstack/widgets/_impl/composites/selectbox.py +1026 -0
- bootstack/widgets/_impl/composites/shell/__init__.py +40 -0
- bootstack/widgets/_impl/composites/shell/content_host.py +52 -0
- bootstack/widgets/_impl/composites/shell/layout.py +335 -0
- bootstack/widgets/_impl/composites/shell/nav_panel.py +345 -0
- bootstack/widgets/_impl/composites/shell/providers.py +558 -0
- bootstack/widgets/_impl/composites/shell/rail.py +117 -0
- bootstack/widgets/_impl/composites/shell/shell.py +581 -0
- bootstack/widgets/_impl/composites/shell/workspace.py +273 -0
- bootstack/widgets/_impl/composites/sidenav/__init__.py +16 -0
- bootstack/widgets/_impl/composites/sidenav/header.py +81 -0
- bootstack/widgets/_impl/composites/sidenav/separator.py +44 -0
- bootstack/widgets/_impl/composites/slider/__init__.py +7 -0
- bootstack/widgets/_impl/composites/slider/_shared.py +195 -0
- bootstack/widgets/_impl/composites/slider/rangeslider.py +982 -0
- bootstack/widgets/_impl/composites/slider/slider.py +851 -0
- bootstack/widgets/_impl/composites/spinnerentry.py +185 -0
- bootstack/widgets/_impl/composites/tableview/__init__.py +5 -0
- bootstack/widgets/_impl/composites/tableview/tableview.py +3388 -0
- bootstack/widgets/_impl/composites/tableview/types.py +169 -0
- bootstack/widgets/_impl/composites/tabs/__init__.py +23 -0
- bootstack/widgets/_impl/composites/tabs/tabitem.py +389 -0
- bootstack/widgets/_impl/composites/tabs/tabs.py +974 -0
- bootstack/widgets/_impl/composites/tabs/tabview.py +650 -0
- bootstack/widgets/_impl/composites/textarea/__init__.py +26 -0
- bootstack/widgets/_impl/composites/textarea/change.py +46 -0
- bootstack/widgets/_impl/composites/textarea/codeeditor.py +526 -0
- bootstack/widgets/_impl/composites/textarea/core.py +495 -0
- bootstack/widgets/_impl/composites/textarea/decoration.py +42 -0
- bootstack/widgets/_impl/composites/textarea/diff.py +127 -0
- bootstack/widgets/_impl/composites/textarea/extensions/__init__.py +1 -0
- bootstack/widgets/_impl/composites/textarea/extensions/bracket_matcher.py +138 -0
- bootstack/widgets/_impl/composites/textarea/extensions/indent_guides.py +158 -0
- bootstack/widgets/_impl/composites/textarea/extensions/line_numbers.py +138 -0
- bootstack/widgets/_impl/composites/textarea/extensions/pygments_highlighter.py +312 -0
- bootstack/widgets/_impl/composites/textarea/extensions/smart_indent.py +177 -0
- bootstack/widgets/_impl/composites/textarea/filter.py +171 -0
- bootstack/widgets/_impl/composites/textarea/search_overlay.py +459 -0
- bootstack/widgets/_impl/composites/textarea/sidebar.py +88 -0
- bootstack/widgets/_impl/composites/textarea/style_registry.py +178 -0
- bootstack/widgets/_impl/composites/textarea/textarea.py +606 -0
- bootstack/widgets/_impl/composites/textarea/undo.py +217 -0
- bootstack/widgets/_impl/composites/textentry.py +57 -0
- bootstack/widgets/_impl/composites/timeentry.py +176 -0
- bootstack/widgets/_impl/composites/toast.py +390 -0
- bootstack/widgets/_impl/composites/toast_stack.py +156 -0
- bootstack/widgets/_impl/composites/togglegroup.py +418 -0
- bootstack/widgets/_impl/composites/toolbar.py +608 -0
- bootstack/widgets/_impl/composites/tooltip.py +491 -0
- bootstack/widgets/_impl/composites/tree/__init__.py +7 -0
- bootstack/widgets/_impl/composites/tree/source_binding.py +136 -0
- bootstack/widgets/_impl/composites/tree/treeitem.py +393 -0
- bootstack/widgets/_impl/composites/tree/treenode.py +174 -0
- bootstack/widgets/_impl/composites/tree/treeview.py +841 -0
- bootstack/widgets/_impl/mixins/__init__.py +24 -0
- bootstack/widgets/_impl/mixins/configure_mixin.py +216 -0
- bootstack/widgets/_impl/mixins/entry_mixin.py +134 -0
- bootstack/widgets/_impl/mixins/font_mixin.py +368 -0
- bootstack/widgets/_impl/mixins/icon_mixin.py +61 -0
- bootstack/widgets/_impl/mixins/localization_mixin.py +253 -0
- bootstack/widgets/_impl/mixins/signal_mixin.py +268 -0
- bootstack/widgets/_impl/mixins/validation_mixin.py +226 -0
- bootstack/widgets/_impl/primitives/__init__.py +49 -0
- bootstack/widgets/_impl/primitives/_menubutton.py +107 -0
- bootstack/widgets/_impl/primitives/badge.py +45 -0
- bootstack/widgets/_impl/primitives/button.py +76 -0
- bootstack/widgets/_impl/primitives/card.py +45 -0
- bootstack/widgets/_impl/primitives/checkbutton.py +124 -0
- bootstack/widgets/_impl/primitives/checktoggle.py +62 -0
- bootstack/widgets/_impl/primitives/combobox.py +156 -0
- bootstack/widgets/_impl/primitives/entry.py +87 -0
- bootstack/widgets/_impl/primitives/flexframe.py +448 -0
- bootstack/widgets/_impl/primitives/frame.py +185 -0
- bootstack/widgets/_impl/primitives/gridframe.py +546 -0
- bootstack/widgets/_impl/primitives/label.py +84 -0
- bootstack/widgets/_impl/primitives/labelframe.py +54 -0
- bootstack/widgets/_impl/primitives/optionmenu.py +387 -0
- bootstack/widgets/_impl/primitives/packframe.py +227 -0
- bootstack/widgets/_impl/primitives/panedwindow.py +45 -0
- bootstack/widgets/_impl/primitives/progressbar.py +83 -0
- bootstack/widgets/_impl/primitives/radiobutton.py +115 -0
- bootstack/widgets/_impl/primitives/radiotoggle.py +54 -0
- bootstack/widgets/_impl/primitives/scrollbar.py +42 -0
- bootstack/widgets/_impl/primitives/separator.py +43 -0
- bootstack/widgets/_impl/primitives/sizegrip.py +33 -0
- bootstack/widgets/_impl/primitives/spinbox.py +95 -0
- bootstack/widgets/_impl/primitives/switch.py +44 -0
- bootstack/widgets/_impl/primitives/treeview.py +69 -0
- bootstack/widgets/app.py +371 -0
- bootstack/widgets/appshell.py +1179 -0
- bootstack/widgets/avatar.py +140 -0
- bootstack/widgets/boolean_controls.py +455 -0
- bootstack/widgets/button.py +224 -0
- bootstack/widgets/buttongroup.py +239 -0
- bootstack/widgets/calendar.py +195 -0
- bootstack/widgets/card.py +159 -0
- bootstack/widgets/carousel.py +241 -0
- bootstack/widgets/chart.py +302 -0
- bootstack/widgets/codeeditor.py +675 -0
- bootstack/widgets/contextmenu.py +371 -0
- bootstack/widgets/datatable.py +688 -0
- bootstack/widgets/datefield.py +395 -0
- bootstack/widgets/divider.py +60 -0
- bootstack/widgets/expander.py +579 -0
- bootstack/widgets/form.py +200 -0
- bootstack/widgets/gallery.py +250 -0
- bootstack/widgets/gauge.py +168 -0
- bootstack/widgets/grid.py +121 -0
- bootstack/widgets/groupbox.py +162 -0
- bootstack/widgets/label.py +229 -0
- bootstack/widgets/listview.py +354 -0
- bootstack/widgets/menubutton.py +419 -0
- bootstack/widgets/numberfield.py +431 -0
- bootstack/widgets/pagestack.py +345 -0
- bootstack/widgets/passwordfield.py +383 -0
- bootstack/widgets/pathfield.py +454 -0
- bootstack/widgets/picture.py +251 -0
- bootstack/widgets/progressbar.py +106 -0
- bootstack/widgets/radio_variants.py +271 -0
- bootstack/widgets/radiogroup.py +228 -0
- bootstack/widgets/scrollbar.py +64 -0
- bootstack/widgets/scrollview.py +186 -0
- bootstack/widgets/select.py +302 -0
- bootstack/widgets/selectbutton.py +182 -0
- bootstack/widgets/sidebar_toggle.py +130 -0
- bootstack/widgets/sizegrip.py +42 -0
- bootstack/widgets/slider.py +408 -0
- bootstack/widgets/spinbox.py +147 -0
- bootstack/widgets/spinnerfield.py +413 -0
- bootstack/widgets/splash.py +367 -0
- bootstack/widgets/splitview.py +482 -0
- bootstack/widgets/stacks.py +249 -0
- bootstack/widgets/statusbar.py +189 -0
- bootstack/widgets/tabs.py +425 -0
- bootstack/widgets/textarea.py +459 -0
- bootstack/widgets/textfield.py +410 -0
- bootstack/widgets/theme_toggle.py +92 -0
- bootstack/widgets/timefield.py +340 -0
- bootstack/widgets/toast.py +314 -0
- bootstack/widgets/togglegroup.py +231 -0
- bootstack/widgets/toolbar.py +347 -0
- bootstack/widgets/tooltip.py +94 -0
- bootstack/widgets/tree.py +648 -0
- bootstack/widgets/types.py +305 -0
- bootstack/widgets/window.py +307 -0
- bootstack-0.1.0.dist-info/METADATA +301 -0
- bootstack-0.1.0.dist-info/RECORD +471 -0
- bootstack-0.1.0.dist-info/WHEEL +5 -0
- bootstack-0.1.0.dist-info/entry_points.txt +2 -0
- bootstack-0.1.0.dist-info/licenses/LICENSE +21 -0
- bootstack-0.1.0.dist-info/licenses/NOTICE +10 -0
- bootstack-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Literal, Protocol, overload
|
|
4
|
+
|
|
5
|
+
from bootstack.widgets._impl.composites.tableview.tableview import (
|
|
6
|
+
TableView as _InternalTableView,
|
|
7
|
+
)
|
|
8
|
+
from bootstack.data.types import DataSourceProtocol
|
|
9
|
+
from bootstack.events import RowEvent, RowsEvent, SelectionEvent, ExportEvent, Subscription
|
|
10
|
+
from bootstack.streams import Stream
|
|
11
|
+
from bootstack.widgets._core.base import PublicWidgetBase
|
|
12
|
+
from bootstack.widgets._core.events import register_widget_events
|
|
13
|
+
from bootstack.widgets.types import (
|
|
14
|
+
ColumnSpec, FormOptions, WidgetDensity, ExportScope, ExportFormat, SelectionMode,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ExportJob(Protocol):
|
|
19
|
+
"""Handle for an in-progress async export (returned by `export_file_async`)."""
|
|
20
|
+
|
|
21
|
+
def cancel(self) -> None:
|
|
22
|
+
"""Stop the export. The partial file is removed."""
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DataTable(PublicWidgetBase):
|
|
27
|
+
"""A feature-rich data table with sorting, filtering, search, and grouping.
|
|
28
|
+
|
|
29
|
+
Backed by an in-memory `SqliteDataSource` by default. Supply `rows=` to
|
|
30
|
+
pre-load data, or pass an existing `data_source=` to share any data source
|
|
31
|
+
that implements the data-source protocol (`SqliteDataSource`,
|
|
32
|
+
`MemoryDataSource`, `FileDataSource`, or your own).
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
columns: Column definitions — a list of field-key strings, or
|
|
36
|
+
`ColumnSpec` dicts (`'key'`, `'text'`, `'width'`, `'minwidth'`,
|
|
37
|
+
`'anchor'`, and the editor keys `'editor'`, `'editor_options'`,
|
|
38
|
+
`'dtype'`, `'readonly'`, `'required'` that shape the add/edit dialog).
|
|
39
|
+
rows: Initial data rows (list of dicts or sequences).
|
|
40
|
+
data_source: Existing data source to use instead of creating one. Any
|
|
41
|
+
object implementing the data-source protocol is accepted
|
|
42
|
+
(`SqliteDataSource`, `MemoryDataSource`, `FileDataSource`, or a
|
|
43
|
+
custom source).
|
|
44
|
+
selection_mode: Row selection behavior. Default `'single'`.
|
|
45
|
+
sorting_mode: Whether columns can be sorted. Default `'single'`.
|
|
46
|
+
searchable: Show the search bar. Default `True`.
|
|
47
|
+
broadcast_search: When `True`, the search box and column filters write
|
|
48
|
+
their combined condition to the shared data source (un-silenced),
|
|
49
|
+
so all other views bound to the same source — including `Chart`
|
|
50
|
+
widgets — re-render to match. Defaults to `False` (per-view only).
|
|
51
|
+
|
|
52
|
+
.. warning::
|
|
53
|
+
|
|
54
|
+
Only enable this on **one** `DataTable` per source. Two tables
|
|
55
|
+
with ``broadcast_search=True`` on the same source will clobber
|
|
56
|
+
each other's filters on every keystroke, reproducing the
|
|
57
|
+
source-mutation coupling that per-view isolation was designed
|
|
58
|
+
to prevent.
|
|
59
|
+
allow_filter: Enable column filtering. Default `True`.
|
|
60
|
+
paging_mode: `'standard'` paginates the rows; `'virtual'` scrolls them
|
|
61
|
+
in a single virtual view. Default `'standard'`.
|
|
62
|
+
page_size: Rows per page in standard paging mode. Default `25`.
|
|
63
|
+
allow_add: Allow adding rows via a form dialog. Default `False`.
|
|
64
|
+
allow_edit: Allow editing rows via a form dialog. Default `False`.
|
|
65
|
+
allow_delete: Allow deleting rows. Default `False`.
|
|
66
|
+
allow_export: Show an export menu (Copy to clipboard / Save to file).
|
|
67
|
+
The actions export the selected rows if any are selected, otherwise
|
|
68
|
+
all rows in the current filter/sort. Saves CSV; Excel (`.xlsx`) is
|
|
69
|
+
also offered when the optional `bootstack[excel]` dependency is
|
|
70
|
+
installed. For explicit control use `export_file()` / `to_csv()`
|
|
71
|
+
with `scope=`. Default `False`.
|
|
72
|
+
export_formats: Which formats the export menu offers. Default `('csv',)`.
|
|
73
|
+
Formats needing an optional dependency (`xlsx`→`bootstack[excel]`,
|
|
74
|
+
`parquet`/`feather`→`bootstack[parquet]`, `hdf5`→`bootstack[hdf5]`)
|
|
75
|
+
appear only when it is installed. Registry formats export the
|
|
76
|
+
displayed columns; for the full record set use `data_source.save(path)`.
|
|
77
|
+
striped: Alternate row background colors. Default `True`.
|
|
78
|
+
density: Row compactness — `'default'` or `'compact'` (tighter row
|
|
79
|
+
height, smaller body font and padding). Default `'default'`.
|
|
80
|
+
allow_group: Allow grouping rows by a column. Default `False`.
|
|
81
|
+
show_status_bar: Show the footer — the filter/sort/group status and the
|
|
82
|
+
pager. The pager auto-hides on a single page, and the whole footer
|
|
83
|
+
collapses when there's nothing to show. Default `True`.
|
|
84
|
+
show_column_chooser: Show a button to toggle column visibility.
|
|
85
|
+
Default `False`.
|
|
86
|
+
show_selection_controls: Show a per-row checkbox in the leading icon slot
|
|
87
|
+
while in `'multi'` selection mode — filled with the accent when
|
|
88
|
+
selected, a muted outline otherwise. With the checkboxes visible, a
|
|
89
|
+
plain click toggles the row (no Ctrl/Shift needed). Has no effect in
|
|
90
|
+
`'single'` mode (the row highlight is enough) or while grouped, where
|
|
91
|
+
the slot holds the group's expand/collapse control. Default `False`.
|
|
92
|
+
id_field: Record field used as the stable row identity. When your rows
|
|
93
|
+
carry this field (default `'id'`), its value becomes the row id used
|
|
94
|
+
by `select_rows`, events, and `update_rows`/`delete_rows`, so your own
|
|
95
|
+
ids round-trip; otherwise an id is auto-assigned. Ignored when you pass
|
|
96
|
+
your own `data_source` (set it on the source instead). Default `'id'`.
|
|
97
|
+
form: Layout options for the built-in add/edit dialog — a `FormOptions`
|
|
98
|
+
dict (`col_count`, `min_col_width`, `scrollable`, `resizable`).
|
|
99
|
+
parent: Override the context-stack parent.
|
|
100
|
+
**kwargs: Layout placement options applied by the parent container —
|
|
101
|
+
`fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
|
|
102
|
+
See :doc:`/tasks/layout`.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def __init__(
|
|
106
|
+
self,
|
|
107
|
+
*,
|
|
108
|
+
columns: list[str | ColumnSpec] | None = None,
|
|
109
|
+
rows: list | None = None,
|
|
110
|
+
data_source: DataSourceProtocol | None = None,
|
|
111
|
+
selection_mode: SelectionMode = "single",
|
|
112
|
+
sorting_mode: Literal["single", "none"] = "single",
|
|
113
|
+
searchable: bool = True,
|
|
114
|
+
broadcast_search: bool = False,
|
|
115
|
+
allow_filter: bool = True,
|
|
116
|
+
paging_mode: Literal["standard", "virtual"] = "standard",
|
|
117
|
+
page_size: int = 25,
|
|
118
|
+
allow_add: bool = False,
|
|
119
|
+
allow_edit: bool = False,
|
|
120
|
+
allow_delete: bool = False,
|
|
121
|
+
allow_export: bool = False,
|
|
122
|
+
export_formats: list[
|
|
123
|
+
Literal["csv", "tsv", "xlsx", "json", "jsonl", "xml", "parquet", "feather", "hdf5"]
|
|
124
|
+
] | None = None,
|
|
125
|
+
striped: bool = True,
|
|
126
|
+
density: WidgetDensity = "default",
|
|
127
|
+
allow_group: bool = False,
|
|
128
|
+
show_status_bar: bool = True,
|
|
129
|
+
show_column_chooser: bool = False,
|
|
130
|
+
show_selection_controls: bool = False,
|
|
131
|
+
id_field: str = "id",
|
|
132
|
+
form: FormOptions | None = None,
|
|
133
|
+
parent: Any = None,
|
|
134
|
+
**kwargs: Any,
|
|
135
|
+
) -> None:
|
|
136
|
+
self._parent = self._resolve_parent(parent)
|
|
137
|
+
self._selection_mode = selection_mode
|
|
138
|
+
layout_kw = self._split_layout_kwargs(kwargs)
|
|
139
|
+
tk_master = self._parent._child_master() if self._parent else None
|
|
140
|
+
|
|
141
|
+
internal_kwargs: dict[str, Any] = {
|
|
142
|
+
"selection_mode": selection_mode,
|
|
143
|
+
"sorting_mode": sorting_mode,
|
|
144
|
+
"enable_search": searchable,
|
|
145
|
+
"broadcast_search": broadcast_search,
|
|
146
|
+
"enable_filtering": allow_filter,
|
|
147
|
+
"paging_mode": paging_mode,
|
|
148
|
+
"page_size": page_size,
|
|
149
|
+
"enable_adding": allow_add,
|
|
150
|
+
"enable_editing": allow_edit,
|
|
151
|
+
"enable_deleting": allow_delete,
|
|
152
|
+
"enable_exporting": allow_export,
|
|
153
|
+
"striped": striped,
|
|
154
|
+
"density": density,
|
|
155
|
+
"allow_grouping": allow_group,
|
|
156
|
+
"show_table_status": show_status_bar,
|
|
157
|
+
"show_column_chooser": show_column_chooser,
|
|
158
|
+
"show_selection_controls": show_selection_controls,
|
|
159
|
+
"id_field": id_field,
|
|
160
|
+
}
|
|
161
|
+
if columns is not None:
|
|
162
|
+
internal_kwargs["columns"] = columns
|
|
163
|
+
if rows is not None:
|
|
164
|
+
internal_kwargs["rows"] = rows
|
|
165
|
+
if data_source is not None:
|
|
166
|
+
internal_kwargs["datasource"] = data_source
|
|
167
|
+
if form is not None:
|
|
168
|
+
internal_kwargs["form_options"] = form
|
|
169
|
+
if export_formats is not None:
|
|
170
|
+
internal_kwargs["export_formats"] = tuple(export_formats)
|
|
171
|
+
|
|
172
|
+
self._internal = _InternalTableView(tk_master, **internal_kwargs)
|
|
173
|
+
self._attach_to_parent(layout_kw)
|
|
174
|
+
|
|
175
|
+
# ----- Data -----
|
|
176
|
+
|
|
177
|
+
def set_rows(self, rows: list) -> None:
|
|
178
|
+
"""Replace the entire dataset with `rows`.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
rows: List of dicts (or sequences) to load.
|
|
182
|
+
"""
|
|
183
|
+
self._internal.set_data(rows)
|
|
184
|
+
|
|
185
|
+
def insert_rows(self, rows: list) -> None:
|
|
186
|
+
"""Insert new rows.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
rows: List of dicts or sequences to insert.
|
|
190
|
+
"""
|
|
191
|
+
self._internal.insert_rows(rows)
|
|
192
|
+
|
|
193
|
+
def update_rows(self, rows: list[dict]) -> None:
|
|
194
|
+
"""Update existing rows by merging the given dicts.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
rows: List of dicts, each with an `id` key and the fields to update.
|
|
198
|
+
"""
|
|
199
|
+
self._internal.update_rows(rows)
|
|
200
|
+
|
|
201
|
+
def delete_rows(self, rows_or_ids: list) -> None:
|
|
202
|
+
"""Delete rows by record `id` or by record dict.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
rows_or_ids: List of record ids or record dicts with an `id` key.
|
|
206
|
+
"""
|
|
207
|
+
self._internal.delete_rows(rows_or_ids)
|
|
208
|
+
|
|
209
|
+
def new_row(self, defaults: dict | None = None) -> dict | None:
|
|
210
|
+
"""Open the built-in *New Record* dialog and insert on save.
|
|
211
|
+
|
|
212
|
+
Honors each column's editor configuration (`editor`, `dtype`,
|
|
213
|
+
`readonly`, `required`). On save the row is inserted and an `rows_insert`
|
|
214
|
+
event fires; the saved record is also returned here.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
defaults: Field values to pre-fill the form with.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
The new record, or `None` if the dialog was cancelled.
|
|
221
|
+
"""
|
|
222
|
+
return self._internal.new_row(defaults)
|
|
223
|
+
|
|
224
|
+
def edit_row(self, record_id: Any) -> dict | None:
|
|
225
|
+
"""Open the built-in *Edit Record* dialog for a row and save on submit.
|
|
226
|
+
|
|
227
|
+
On save the row is updated and a `rows_update` event fires (or
|
|
228
|
+
`rows_delete` if the user deletes it); the saved record is also returned.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
record_id: Record id of the row to edit.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
The updated record, or `None` if cancelled or deleted.
|
|
235
|
+
"""
|
|
236
|
+
return self._internal.edit_row(record_id)
|
|
237
|
+
|
|
238
|
+
# ----- Selection -----
|
|
239
|
+
|
|
240
|
+
def select_rows(self, record_ids: list) -> None:
|
|
241
|
+
"""Select rows by record `id`.
|
|
242
|
+
|
|
243
|
+
Only rows on the current page can be selected.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
record_ids: Record ids of the rows to select.
|
|
247
|
+
"""
|
|
248
|
+
self._internal.select_rows(record_ids)
|
|
249
|
+
|
|
250
|
+
def select_all(self) -> None:
|
|
251
|
+
"""Select all rows in the current view."""
|
|
252
|
+
self._internal.select_all()
|
|
253
|
+
|
|
254
|
+
def deselect_rows(self, record_ids: list) -> None:
|
|
255
|
+
"""Remove the given rows from the selection by record `id`.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
record_ids: Record ids to deselect.
|
|
259
|
+
"""
|
|
260
|
+
self._internal.deselect_rows(record_ids)
|
|
261
|
+
|
|
262
|
+
def clear_selection(self) -> None:
|
|
263
|
+
"""Clear the selection.
|
|
264
|
+
|
|
265
|
+
Users can also press `Escape` over the table to clear it — useful in
|
|
266
|
+
single-select mode, where clicking cannot return to an empty selection.
|
|
267
|
+
"""
|
|
268
|
+
self._internal.deselect_all()
|
|
269
|
+
|
|
270
|
+
@property
|
|
271
|
+
def selection(self) -> dict | list[dict] | None:
|
|
272
|
+
"""The selected row(s) — the data bag.
|
|
273
|
+
|
|
274
|
+
In `'single'` mode, the selected record `dict` (or `None` when nothing
|
|
275
|
+
is selected). In `'multi'` mode, a `list` of record dicts (empty when
|
|
276
|
+
nothing is selected). Each record carries its non-displayed fields too,
|
|
277
|
+
indexed by key like any record. Read-only.
|
|
278
|
+
"""
|
|
279
|
+
rows = self._internal.selected_rows
|
|
280
|
+
if self._selection_mode == "multi":
|
|
281
|
+
return rows
|
|
282
|
+
return rows[0] if rows else None
|
|
283
|
+
|
|
284
|
+
# ----- Scroll -----
|
|
285
|
+
|
|
286
|
+
def scroll_to_row(self, record_id: Any) -> None:
|
|
287
|
+
"""Scroll the table so the row with the given `id` is visible.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
record_id: Record id of the row.
|
|
291
|
+
"""
|
|
292
|
+
self._internal.scroll_to_row(record_id)
|
|
293
|
+
|
|
294
|
+
# ----- Paging -----
|
|
295
|
+
|
|
296
|
+
def go_to_page(self, index: int) -> None:
|
|
297
|
+
"""Show the page at the given zero-based index.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
index: Zero-based page index.
|
|
301
|
+
"""
|
|
302
|
+
self._internal.go_to_page(index)
|
|
303
|
+
|
|
304
|
+
def next_page(self) -> None:
|
|
305
|
+
"""Advance to the next page (no-op on the last page)."""
|
|
306
|
+
self._internal.next_page()
|
|
307
|
+
|
|
308
|
+
def prev_page(self) -> None:
|
|
309
|
+
"""Go to the previous page (no-op on the first page)."""
|
|
310
|
+
self._internal.previous_page()
|
|
311
|
+
|
|
312
|
+
@property
|
|
313
|
+
def current_page(self) -> int:
|
|
314
|
+
"""Zero-based index of the page currently shown."""
|
|
315
|
+
return self._internal.current_page
|
|
316
|
+
|
|
317
|
+
@property
|
|
318
|
+
def page_count(self) -> int:
|
|
319
|
+
"""Total number of pages for the current filter/search."""
|
|
320
|
+
return self._internal.page_count
|
|
321
|
+
|
|
322
|
+
# ----- Export -----
|
|
323
|
+
|
|
324
|
+
def to_rows(self, scope: ExportScope = "all", *, max_rows: int | None = 100_000) -> list[dict]:
|
|
325
|
+
"""Return the data as a list of record dicts (materialized — small data).
|
|
326
|
+
|
|
327
|
+
Loads every matching row into memory. For large datasets use
|
|
328
|
+
`iter_rows()` (streaming) or `export_file()` instead.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
scope: Which rows — `'all'` (the filtered set), `'page'` (current
|
|
332
|
+
page), or `'selection'` (selected rows).
|
|
333
|
+
max_rows: Raise if the row count exceeds this. Pass `None` to lift
|
|
334
|
+
the cap (at your own memory risk).
|
|
335
|
+
"""
|
|
336
|
+
return self._internal.to_rows(scope, max_rows=max_rows)
|
|
337
|
+
|
|
338
|
+
def to_csv(self, scope: ExportScope = "all", *, max_rows: int | None = 100_000) -> str:
|
|
339
|
+
"""Return the data as a CSV string (materialized — small data).
|
|
340
|
+
|
|
341
|
+
For large datasets use `export_file()`, which streams to disk.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
scope: `'all'`, `'page'`, or `'selection'`.
|
|
345
|
+
max_rows: Raise if the row count exceeds this. `None` lifts the cap.
|
|
346
|
+
"""
|
|
347
|
+
return self._internal.to_csv(scope, max_rows=max_rows)
|
|
348
|
+
|
|
349
|
+
def iter_rows(self, scope: ExportScope = "all", chunk_size: int = 1000):
|
|
350
|
+
"""Lazily yield record dicts one at a time, paging the data source.
|
|
351
|
+
|
|
352
|
+
Memory stays flat regardless of size — suitable for very large exports.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
scope: `'all'`, `'page'`, or `'selection'`.
|
|
356
|
+
chunk_size: Rows read from the source per batch.
|
|
357
|
+
"""
|
|
358
|
+
return self._internal.iter_rows(scope, chunk_size)
|
|
359
|
+
|
|
360
|
+
def export_file(
|
|
361
|
+
self,
|
|
362
|
+
path: str,
|
|
363
|
+
scope: ExportScope = "all",
|
|
364
|
+
*,
|
|
365
|
+
format: ExportFormat | None = None,
|
|
366
|
+
chunk_size: int = 1000,
|
|
367
|
+
on_progress: Callable[[int, int], Any] | None = None,
|
|
368
|
+
) -> int:
|
|
369
|
+
"""Stream the data to `path`, paging so memory stays flat.
|
|
370
|
+
|
|
371
|
+
The format is inferred from the path extension (`.csv`, `.tsv`, `.xlsx`)
|
|
372
|
+
unless `format` is given. `.xlsx` requires `bootstack[excel]`.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
path: Destination file path.
|
|
376
|
+
scope: `'all'`, `'page'`, or `'selection'`.
|
|
377
|
+
format: Override the format — `'csv'`, `'tsv'`, or `'xlsx'`.
|
|
378
|
+
chunk_size: Rows read/written per batch.
|
|
379
|
+
on_progress: Called as `on_progress(written, total)` after each batch.
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
The number of rows written.
|
|
383
|
+
"""
|
|
384
|
+
return self._internal.export_file(
|
|
385
|
+
path, scope, format=format, chunk_size=chunk_size, on_progress=on_progress
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
def export_file_async(
|
|
389
|
+
self,
|
|
390
|
+
path: str,
|
|
391
|
+
scope: ExportScope = "all",
|
|
392
|
+
*,
|
|
393
|
+
format: ExportFormat | None = None,
|
|
394
|
+
chunk_size: int = 1000,
|
|
395
|
+
on_progress: Callable[[int, int], Any] | None = None,
|
|
396
|
+
on_done: Callable[[str, int, Any], Any] | None = None,
|
|
397
|
+
) -> ExportJob:
|
|
398
|
+
"""Stream the data to `path` without blocking the UI; return a job.
|
|
399
|
+
|
|
400
|
+
Writes one chunk per event-loop idle tick, so the UI stays responsive
|
|
401
|
+
and the export can be cancelled mid-run via the returned job's
|
|
402
|
+
`cancel()`. A cancelled or failed export removes the partial file.
|
|
403
|
+
|
|
404
|
+
Args:
|
|
405
|
+
path: Destination file path.
|
|
406
|
+
scope: `'all'`, `'page'`, or `'selection'`.
|
|
407
|
+
format: Override the format — `'csv'`, `'tsv'`, or `'xlsx'`.
|
|
408
|
+
chunk_size: Rows read/written per idle tick.
|
|
409
|
+
on_progress: Called as `on_progress(written, total)` after each chunk.
|
|
410
|
+
on_done: Called as `on_done(status, written, error)` at the end, with
|
|
411
|
+
`status` one of `'completed'`, `'cancelled'`, or `'error'`.
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
A job handle with a `cancel()` method.
|
|
415
|
+
"""
|
|
416
|
+
return self._internal.export_file_async(
|
|
417
|
+
path, scope, format=format, chunk_size=chunk_size,
|
|
418
|
+
on_progress=on_progress, on_done=on_done,
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
# ----- Search -----
|
|
422
|
+
|
|
423
|
+
def get_search(self) -> str:
|
|
424
|
+
"""Return the active free-text search term."""
|
|
425
|
+
return self._internal.get_search()
|
|
426
|
+
|
|
427
|
+
def set_search(self, text: str) -> None:
|
|
428
|
+
"""Set the free-text search term.
|
|
429
|
+
|
|
430
|
+
Searches across all columns. Leaves any active column filters intact.
|
|
431
|
+
|
|
432
|
+
Args:
|
|
433
|
+
text: The search term. Pass an empty string to clear the search.
|
|
434
|
+
"""
|
|
435
|
+
self._internal.set_search(text)
|
|
436
|
+
|
|
437
|
+
def clear_search(self) -> None:
|
|
438
|
+
"""Clear the free-text search term (leaves column filters intact)."""
|
|
439
|
+
self._internal.clear_search()
|
|
440
|
+
|
|
441
|
+
# ----- Filter / sort / group -----
|
|
442
|
+
|
|
443
|
+
def get_filters(self) -> dict[str, list]:
|
|
444
|
+
"""Return the active column filters as `{column_key: allowed_values}`."""
|
|
445
|
+
return self._internal.get_filters()
|
|
446
|
+
|
|
447
|
+
def clear_filters(self) -> None:
|
|
448
|
+
"""Remove all active column filters (leaves the search term intact)."""
|
|
449
|
+
self._internal.clear_filters()
|
|
450
|
+
|
|
451
|
+
def set_filter(self, column: str, values: list | None = None) -> None:
|
|
452
|
+
"""Filter a column to the given values, or clear it when `values` is None.
|
|
453
|
+
|
|
454
|
+
Composes with the search term and other column filters.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
column: Column key to filter.
|
|
458
|
+
values: Allowed values; `None` clears this column's filter.
|
|
459
|
+
"""
|
|
460
|
+
self._internal.set_filter(column, values)
|
|
461
|
+
|
|
462
|
+
def get_sorting(self) -> dict[str, bool]:
|
|
463
|
+
"""Return the current sort state as `{column_key: ascending}` dict."""
|
|
464
|
+
return self._internal.get_sorting()
|
|
465
|
+
|
|
466
|
+
def clear_sorting(self) -> None:
|
|
467
|
+
"""Remove all active sort orders."""
|
|
468
|
+
self._internal.clear_sorting()
|
|
469
|
+
|
|
470
|
+
def sort_by(self, column: str, ascending: bool = True) -> None:
|
|
471
|
+
"""Sort the table by a column.
|
|
472
|
+
|
|
473
|
+
Args:
|
|
474
|
+
column: Column key to sort by.
|
|
475
|
+
ascending: Sort ascending (default) or descending.
|
|
476
|
+
"""
|
|
477
|
+
self._internal.set_sorting(column, ascending)
|
|
478
|
+
|
|
479
|
+
def get_grouping(self) -> str | None:
|
|
480
|
+
"""Return the currently grouped column key, or `None`."""
|
|
481
|
+
return self._internal.get_grouping()
|
|
482
|
+
|
|
483
|
+
def clear_grouping(self) -> None:
|
|
484
|
+
"""Remove the active grouping."""
|
|
485
|
+
self._internal.clear_grouping()
|
|
486
|
+
|
|
487
|
+
def group_by(self, column: str) -> None:
|
|
488
|
+
"""Group rows by a column (collapsible group headers).
|
|
489
|
+
|
|
490
|
+
Args:
|
|
491
|
+
column: Column key to group by.
|
|
492
|
+
"""
|
|
493
|
+
self._internal.set_grouping(column)
|
|
494
|
+
|
|
495
|
+
def expand_all(self) -> None:
|
|
496
|
+
"""Expand all groups."""
|
|
497
|
+
self._internal.expand_all()
|
|
498
|
+
|
|
499
|
+
def collapse_all(self) -> None:
|
|
500
|
+
"""Collapse all groups."""
|
|
501
|
+
self._internal.collapse_all()
|
|
502
|
+
|
|
503
|
+
# ----- Properties -----
|
|
504
|
+
|
|
505
|
+
@property
|
|
506
|
+
def data_source(self) -> DataSourceProtocol:
|
|
507
|
+
"""The underlying data source instance."""
|
|
508
|
+
return self._internal._datasource
|
|
509
|
+
|
|
510
|
+
# ----- Events -----
|
|
511
|
+
|
|
512
|
+
@overload
|
|
513
|
+
def on_select(self) -> Stream: ...
|
|
514
|
+
@overload
|
|
515
|
+
def on_select(self, handler: Callable[[SelectionEvent], Any]) -> Subscription: ...
|
|
516
|
+
def on_select(self, handler: Callable[[SelectionEvent], Any] | None = None) -> Stream | Subscription:
|
|
517
|
+
"""Fired when the set of selected rows changes.
|
|
518
|
+
|
|
519
|
+
Args:
|
|
520
|
+
handler: Called with a :class:`~bootstack.events.SelectionEvent`
|
|
521
|
+
(`records`, `ids`). Omit to get a composable
|
|
522
|
+
:class:`~bootstack.streams.Stream` instead.
|
|
523
|
+
|
|
524
|
+
Returns:
|
|
525
|
+
A cancellable :class:`~bootstack.events.Subscription` when a handler
|
|
526
|
+
is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
527
|
+
"""
|
|
528
|
+
return self.on("select", handler)
|
|
529
|
+
|
|
530
|
+
@overload
|
|
531
|
+
def on_row_click(self) -> Stream: ...
|
|
532
|
+
@overload
|
|
533
|
+
def on_row_click(self, handler: Callable[[RowEvent], Any]) -> Subscription: ...
|
|
534
|
+
def on_row_click(self, handler: Callable[[RowEvent], Any] | None = None) -> Stream | Subscription:
|
|
535
|
+
"""Fired when a row is clicked.
|
|
536
|
+
|
|
537
|
+
Args:
|
|
538
|
+
handler: Called with a :class:`~bootstack.events.RowEvent` (`record`,
|
|
539
|
+
`id`). Omit to get a composable
|
|
540
|
+
:class:`~bootstack.streams.Stream` instead.
|
|
541
|
+
|
|
542
|
+
Returns:
|
|
543
|
+
A cancellable :class:`~bootstack.events.Subscription` when a handler
|
|
544
|
+
is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
545
|
+
"""
|
|
546
|
+
return self.on("row_click", handler)
|
|
547
|
+
|
|
548
|
+
@overload
|
|
549
|
+
def on_row_double_click(self) -> Stream: ...
|
|
550
|
+
@overload
|
|
551
|
+
def on_row_double_click(self, handler: Callable[[RowEvent], Any]) -> Subscription: ...
|
|
552
|
+
def on_row_double_click(self, handler: Callable[[RowEvent], Any] | None = None) -> Stream | Subscription:
|
|
553
|
+
"""Fired when a row is double-clicked.
|
|
554
|
+
|
|
555
|
+
Args:
|
|
556
|
+
handler: Called with a :class:`~bootstack.events.RowEvent` (`record`,
|
|
557
|
+
`id`). Omit to get a composable
|
|
558
|
+
:class:`~bootstack.streams.Stream` instead.
|
|
559
|
+
|
|
560
|
+
Returns:
|
|
561
|
+
A cancellable :class:`~bootstack.events.Subscription` when a handler
|
|
562
|
+
is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
563
|
+
"""
|
|
564
|
+
return self.on("row_double_click", handler)
|
|
565
|
+
|
|
566
|
+
@overload
|
|
567
|
+
def on_row_right_click(self) -> Stream: ...
|
|
568
|
+
@overload
|
|
569
|
+
def on_row_right_click(self, handler: Callable[[RowEvent], Any]) -> Subscription: ...
|
|
570
|
+
def on_row_right_click(self, handler: Callable[[RowEvent], Any] | None = None) -> Stream | Subscription:
|
|
571
|
+
"""Fired when a row is right-clicked.
|
|
572
|
+
|
|
573
|
+
Args:
|
|
574
|
+
handler: Called with a :class:`~bootstack.events.RowEvent` (`record`,
|
|
575
|
+
`id`). Omit to get a composable
|
|
576
|
+
:class:`~bootstack.streams.Stream` instead.
|
|
577
|
+
|
|
578
|
+
Returns:
|
|
579
|
+
A cancellable :class:`~bootstack.events.Subscription` when a handler
|
|
580
|
+
is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
581
|
+
"""
|
|
582
|
+
return self.on("row_right_click", handler)
|
|
583
|
+
|
|
584
|
+
@overload
|
|
585
|
+
def on_rows_insert(self) -> Stream: ...
|
|
586
|
+
@overload
|
|
587
|
+
def on_rows_insert(self, handler: Callable[[RowsEvent], Any]) -> Subscription: ...
|
|
588
|
+
def on_rows_insert(self, handler: Callable[[RowsEvent], Any] | None = None) -> Stream | Subscription:
|
|
589
|
+
"""Fired after rows are inserted.
|
|
590
|
+
|
|
591
|
+
Args:
|
|
592
|
+
handler: Called with a :class:`~bootstack.events.RowsEvent` carrying
|
|
593
|
+
the inserted `records`. Omit to get a composable
|
|
594
|
+
:class:`~bootstack.streams.Stream` instead.
|
|
595
|
+
|
|
596
|
+
Returns:
|
|
597
|
+
A cancellable :class:`~bootstack.events.Subscription` when a handler
|
|
598
|
+
is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
599
|
+
"""
|
|
600
|
+
return self.on("rows_insert", handler)
|
|
601
|
+
|
|
602
|
+
@overload
|
|
603
|
+
def on_rows_update(self) -> Stream: ...
|
|
604
|
+
@overload
|
|
605
|
+
def on_rows_update(self, handler: Callable[[RowsEvent], Any]) -> Subscription: ...
|
|
606
|
+
def on_rows_update(self, handler: Callable[[RowsEvent], Any] | None = None) -> Stream | Subscription:
|
|
607
|
+
"""Fired after rows are updated.
|
|
608
|
+
|
|
609
|
+
Args:
|
|
610
|
+
handler: Called with a :class:`~bootstack.events.RowsEvent` carrying
|
|
611
|
+
the updated `records`. Omit to get a composable
|
|
612
|
+
:class:`~bootstack.streams.Stream` instead.
|
|
613
|
+
|
|
614
|
+
Returns:
|
|
615
|
+
A cancellable :class:`~bootstack.events.Subscription` when a handler
|
|
616
|
+
is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
617
|
+
"""
|
|
618
|
+
return self.on("rows_update", handler)
|
|
619
|
+
|
|
620
|
+
@overload
|
|
621
|
+
def on_rows_delete(self) -> Stream: ...
|
|
622
|
+
@overload
|
|
623
|
+
def on_rows_delete(self, handler: Callable[[RowsEvent], Any]) -> Subscription: ...
|
|
624
|
+
def on_rows_delete(self, handler: Callable[[RowsEvent], Any] | None = None) -> Stream | Subscription:
|
|
625
|
+
"""Fired after rows are deleted.
|
|
626
|
+
|
|
627
|
+
Args:
|
|
628
|
+
handler: Called with a :class:`~bootstack.events.RowsEvent` carrying
|
|
629
|
+
the deleted `records`. Omit to get a composable
|
|
630
|
+
:class:`~bootstack.streams.Stream` instead.
|
|
631
|
+
|
|
632
|
+
Returns:
|
|
633
|
+
A cancellable :class:`~bootstack.events.Subscription` when a handler
|
|
634
|
+
is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
635
|
+
"""
|
|
636
|
+
return self.on("rows_delete", handler)
|
|
637
|
+
|
|
638
|
+
@overload
|
|
639
|
+
def on_rows_move(self) -> Stream: ...
|
|
640
|
+
@overload
|
|
641
|
+
def on_rows_move(self, handler: Callable[[RowsEvent], Any]) -> Subscription: ...
|
|
642
|
+
def on_rows_move(self, handler: Callable[[RowsEvent], Any] | None = None) -> Stream | Subscription:
|
|
643
|
+
"""Fired after rows are reordered.
|
|
644
|
+
|
|
645
|
+
Args:
|
|
646
|
+
handler: Called with a :class:`~bootstack.events.RowsEvent` carrying
|
|
647
|
+
the moved `records`. Omit to get a composable
|
|
648
|
+
:class:`~bootstack.streams.Stream` instead.
|
|
649
|
+
|
|
650
|
+
Returns:
|
|
651
|
+
A cancellable :class:`~bootstack.events.Subscription` when a handler
|
|
652
|
+
is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
653
|
+
"""
|
|
654
|
+
return self.on("rows_move", handler)
|
|
655
|
+
|
|
656
|
+
@overload
|
|
657
|
+
def on_export(self) -> Stream: ...
|
|
658
|
+
@overload
|
|
659
|
+
def on_export(self, handler: Callable[[ExportEvent], Any]) -> Subscription: ...
|
|
660
|
+
def on_export(self, handler: Callable[[ExportEvent], Any] | None = None) -> Stream | Subscription:
|
|
661
|
+
"""Fired after the data is exported (copied or saved).
|
|
662
|
+
|
|
663
|
+
Args:
|
|
664
|
+
handler: Called with an :class:`~bootstack.events.ExportEvent`
|
|
665
|
+
(`count`, `target` — `'clipboard'` or `'file'` — `format`, and
|
|
666
|
+
`path`). Omit to get a composable
|
|
667
|
+
:class:`~bootstack.streams.Stream` instead.
|
|
668
|
+
|
|
669
|
+
Returns:
|
|
670
|
+
A cancellable :class:`~bootstack.events.Subscription` when a handler
|
|
671
|
+
is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
672
|
+
"""
|
|
673
|
+
return self.on("export", handler)
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
_DATATABLE_EVENTS: dict[str, str] = {
|
|
677
|
+
"select": "<<SelectionChange>>",
|
|
678
|
+
"row_click": "<<RowClick>>",
|
|
679
|
+
"row_double_click": "<<RowDoubleClick>>",
|
|
680
|
+
"row_right_click": "<<RowRightClick>>",
|
|
681
|
+
"rows_insert": "<<RowsInsert>>",
|
|
682
|
+
"rows_update": "<<RowsUpdate>>",
|
|
683
|
+
"rows_delete": "<<RowsDelete>>",
|
|
684
|
+
"rows_move": "<<RowsMove>>",
|
|
685
|
+
"export": "<<Export>>",
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
register_widget_events(DataTable, _DATATABLE_EVENTS)
|