bootstack 0.1.0a1__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 +249 -0
- bootstack/__main__.py +5 -0
- bootstack/api/__init__.py +127 -0
- bootstack/api/app.py +30 -0
- bootstack/api/constants.py +3 -0
- bootstack/api/data.py +23 -0
- bootstack/api/dialogs.py +44 -0
- bootstack/api/i18n.py +17 -0
- bootstack/api/localization.py +16 -0
- bootstack/api/menu.py +7 -0
- bootstack/api/style.py +23 -0
- bootstack/api/utils.py +24 -0
- bootstack/api/widgets.py +137 -0
- bootstack/assets/__init__.py +24 -0
- bootstack/assets/bootstack-transparent.png +0 -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/border.png +0 -0
- bootstack/assets/elements/button-compact.png +0 -0
- bootstack/assets/elements/button-default.png +0 -0
- bootstack/assets/elements/button-group-horizontal-after-compact.png +0 -0
- bootstack/assets/elements/button-group-horizontal-after-default.png +0 -0
- bootstack/assets/elements/button-group-horizontal-before-compact.png +0 -0
- bootstack/assets/elements/button-group-horizontal-before-default.png +0 -0
- bootstack/assets/elements/button-group-horizontal-center-compact.png +0 -0
- bootstack/assets/elements/button-group-horizontal-center-default.png +0 -0
- bootstack/assets/elements/button-group-vertical-after-compact.png +0 -0
- bootstack/assets/elements/button-group-vertical-after-default.png +0 -0
- bootstack/assets/elements/button-group-vertical-before-compact.png +0 -0
- bootstack/assets/elements/button-group-vertical-before-default.png +0 -0
- bootstack/assets/elements/button-group-vertical-center-compact.png +0 -0
- bootstack/assets/elements/button-group-vertical-center-default.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-after-compact.png +0 -0
- bootstack/assets/elements/input-after-default.png +0 -0
- bootstack/assets/elements/input-before-compact.png +0 -0
- bootstack/assets/elements/input-before-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/manifest.toml +480 -0
- bootstack/assets/elements/menu-item.png +0 -0
- bootstack/assets/elements/nav-button-compact.png +0 -0
- bootstack/assets/elements/nav-button-default.png +0 -0
- bootstack/assets/elements/nav-icon-button-compact.png +0 -0
- bootstack/assets/elements/nav-icon-button-default.png +0 -0
- bootstack/assets/elements/notebook-client-border.png +0 -0
- bootstack/assets/elements/notebook-tab-active.png +0 -0
- bootstack/assets/elements/notebook-tab-bar.png +0 -0
- bootstack/assets/elements/notebook-tab-normal.png +0 -0
- bootstack/assets/elements/notebook-tab-pill.png +0 -0
- bootstack/assets/elements/progress-bar-horizontal-striped.png +0 -0
- bootstack/assets/elements/progress-bar-solid.png +0 -0
- bootstack/assets/elements/progress-bar-thin.png +0 -0
- bootstack/assets/elements/progress-bar-vertical-striped.png +0 -0
- bootstack/assets/elements/radio-selected.png +0 -0
- bootstack/assets/elements/radio-unselected.png +0 -0
- bootstack/assets/elements/scrollbar-horizontal.png +0 -0
- bootstack/assets/elements/scrollbar-vertical.png +0 -0
- bootstack/assets/elements/slider-handle-focus.png +0 -0
- bootstack/assets/elements/slider-handle.png +0 -0
- bootstack/assets/elements/slider-track-horizontal.png +0 -0
- bootstack/assets/elements/slider-track-vertical.png +0 -0
- bootstack/assets/elements/switch-off.png +0 -0
- bootstack/assets/elements/switch-on.png +0 -0
- bootstack/assets/elements/tabs-bar-horizontal.png +0 -0
- bootstack/assets/elements/tabs-bar-vertical.png +0 -0
- bootstack/assets/elements/tabs-pill.png +0 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +875 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +875 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +853 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +851 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +841 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +914 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +841 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +841 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +842 -0
- bootstack/assets/themes/__init__.py +0 -0
- bootstack/assets/themes/amber-dark.json +32 -0
- bootstack/assets/themes/amber-light.json +32 -0
- bootstack/assets/themes/aurora-dark.json +32 -0
- bootstack/assets/themes/aurora-light.json +32 -0
- bootstack/assets/themes/bootstrap-dark.json +32 -0
- bootstack/assets/themes/bootstrap-light.json +32 -0
- bootstack/assets/themes/classic-dark.json +32 -0
- bootstack/assets/themes/classic-light.json +32 -0
- bootstack/assets/themes/docs-dark.json +32 -0
- bootstack/assets/themes/docs-light.json +32 -0
- bootstack/assets/themes/forest-dark.json +32 -0
- bootstack/assets/themes/forest-light.json +32 -0
- bootstack/assets/themes/ocean-dark.json +32 -0
- bootstack/assets/themes/ocean-light.json +32 -0
- bootstack/assets/themes/rose-dark.json +32 -0
- bootstack/assets/themes/rose-light.json +32 -0
- bootstack/assets/widgets/__init__.py +0 -0
- bootstack/assets/widgets/badge-default.png +0 -0
- bootstack/assets/widgets/badge-pill.png +0 -0
- bootstack/assets/widgets/border.png +0 -0
- bootstack/assets/widgets/button-group-horizontal-after.png +0 -0
- bootstack/assets/widgets/button-group-horizontal-before.png +0 -0
- bootstack/assets/widgets/button-group-horizontal-center.png +0 -0
- bootstack/assets/widgets/button-group-vertical-after.png +0 -0
- bootstack/assets/widgets/button-group-vertical-before.png +0 -0
- bootstack/assets/widgets/button-group-vertical-center.png +0 -0
- bootstack/assets/widgets/button.png +0 -0
- bootstack/assets/widgets/checkbox-checked.png +0 -0
- bootstack/assets/widgets/checkbox-indeterminate.png +0 -0
- bootstack/assets/widgets/checkbox-unchecked.png +0 -0
- bootstack/assets/widgets/field.png +0 -0
- bootstack/assets/widgets/icon-button.png +0 -0
- bootstack/assets/widgets/input-inner.png +0 -0
- bootstack/assets/widgets/input-prefix.png +0 -0
- bootstack/assets/widgets/input-suffix.png +0 -0
- bootstack/assets/widgets/input.png +0 -0
- bootstack/assets/widgets/list-item-focus.png +0 -0
- bootstack/assets/widgets/list-item-separated.png +0 -0
- bootstack/assets/widgets/menu-item-separated.png +0 -0
- bootstack/assets/widgets/notebook-client-border.png +0 -0
- bootstack/assets/widgets/notebook-pill-active.png +0 -0
- bootstack/assets/widgets/notebook-pill-inactive.png +0 -0
- bootstack/assets/widgets/notebook-tab-active.png +0 -0
- bootstack/assets/widgets/notebook-tab-border.png +0 -0
- bootstack/assets/widgets/notebook-tab-normal.png +0 -0
- bootstack/assets/widgets/notebook-underline.png +0 -0
- bootstack/assets/widgets/progress-bar-horizontal-default.png +0 -0
- bootstack/assets/widgets/progress-bar-horizontal-striped.png +0 -0
- bootstack/assets/widgets/progress-bar-vertical-default.png +0 -0
- bootstack/assets/widgets/progress-bar-vertical-striped.png +0 -0
- bootstack/assets/widgets/progress-trough-horizontal.png +0 -0
- bootstack/assets/widgets/progress-trough-vertical.png +0 -0
- bootstack/assets/widgets/radio-selected.png +0 -0
- bootstack/assets/widgets/radio-unselected.png +0 -0
- bootstack/assets/widgets/scrollbar-horizontal-rounded.png +0 -0
- bootstack/assets/widgets/scrollbar-vertical-rounded.png +0 -0
- bootstack/assets/widgets/separator-horizontal.png +0 -0
- bootstack/assets/widgets/separator-vertical.png +0 -0
- bootstack/assets/widgets/slider-handle-focus.png +0 -0
- bootstack/assets/widgets/slider-handle.png +0 -0
- bootstack/assets/widgets/slider-track-horizontal.png +0 -0
- bootstack/assets/widgets/slider-track-vertical.png +0 -0
- bootstack/assets/widgets/switch-off.png +0 -0
- bootstack/assets/widgets/switch-on.png +0 -0
- bootstack/assets/widgets/tabs-bar-horizontal.png +0 -0
- bootstack/assets/widgets/tabs-bar-vertical.png +0 -0
- bootstack/assets/widgets/tabs-pill.png +0 -0
- bootstack/cli/__init__.py +124 -0
- bootstack/cli/__main__.py +6 -0
- bootstack/cli/add.py +439 -0
- bootstack/cli/build.py +115 -0
- bootstack/cli/config.py +287 -0
- bootstack/cli/demo.py +1267 -0
- bootstack/cli/doctor.py +195 -0
- bootstack/cli/list_cmd.py +71 -0
- bootstack/cli/promote.py +120 -0
- bootstack/cli/pyinstaller.py +246 -0
- bootstack/cli/run.py +99 -0
- bootstack/cli/start.py +105 -0
- bootstack/cli/templates/__init__.py +861 -0
- bootstack/constants.py +325 -0
- bootstack/core/__init__.py +34 -0
- bootstack/core/capabilities/__init__.py +45 -0
- bootstack/core/capabilities/after.py +103 -0
- bootstack/core/capabilities/bind.py +154 -0
- bootstack/core/capabilities/bindtags.py +112 -0
- bootstack/core/capabilities/busy.py +61 -0
- bootstack/core/capabilities/clipboard.py +88 -0
- bootstack/core/capabilities/focus.py +118 -0
- bootstack/core/capabilities/grab.py +65 -0
- bootstack/core/capabilities/grid.py +188 -0
- bootstack/core/capabilities/localization.py +231 -0
- bootstack/core/capabilities/pack.py +119 -0
- bootstack/core/capabilities/place.py +92 -0
- bootstack/core/capabilities/selection.py +136 -0
- bootstack/core/capabilities/signals.py +242 -0
- bootstack/core/capabilities/winfo.py +315 -0
- bootstack/core/colorutils.py +234 -0
- bootstack/core/exceptions.py +95 -0
- bootstack/core/images.py +283 -0
- bootstack/core/localization/README.md +90 -0
- bootstack/core/localization/__init__.py +13 -0
- bootstack/core/localization/intl_format.py +580 -0
- bootstack/core/localization/msgcat.py +425 -0
- bootstack/core/localization/specs.py +143 -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/publisher.py +147 -0
- bootstack/core/signals/README.md +112 -0
- bootstack/core/signals/__init__.py +8 -0
- bootstack/core/signals/integration.py +100 -0
- bootstack/core/signals/signal.py +317 -0
- bootstack/core/signals/types.py +4 -0
- bootstack/core/validation/__init__.py +5 -0
- bootstack/core/validation/types.py +13 -0
- bootstack/core/validation/validation_result.py +17 -0
- bootstack/core/validation/validation_rules.py +112 -0
- bootstack/core/variables.py +62 -0
- bootstack/datasource/README.md +607 -0
- bootstack/datasource/__init__.py +51 -0
- bootstack/datasource/base.py +474 -0
- bootstack/datasource/file_source.py +541 -0
- bootstack/datasource/memory_source.py +482 -0
- bootstack/datasource/sqlite_source.py +453 -0
- bootstack/datasource/types.py +259 -0
- bootstack/dialogs/__init__.py +56 -0
- bootstack/dialogs/colorchooser.py +674 -0
- bootstack/dialogs/colordropper.py +257 -0
- bootstack/dialogs/datedialog.py +404 -0
- bootstack/dialogs/dialog.py +514 -0
- bootstack/dialogs/filterdialog.py +358 -0
- bootstack/dialogs/fontdialog.py +339 -0
- bootstack/dialogs/formdialog.py +541 -0
- bootstack/dialogs/message.py +489 -0
- bootstack/dialogs/query.py +561 -0
- bootstack/py.typed +1 -0
- bootstack/runtime/__init__.py +3 -0
- bootstack/runtime/app.py +879 -0
- bootstack/runtime/base_window.py +786 -0
- bootstack/runtime/events.py +399 -0
- bootstack/runtime/menu.py +510 -0
- bootstack/runtime/shortcuts.py +423 -0
- bootstack/runtime/tk_patch.py +31 -0
- bootstack/runtime/toplevel.py +131 -0
- bootstack/runtime/utility.py +371 -0
- bootstack/runtime/visual_focus.py +228 -0
- bootstack/runtime/window_utilities.py +1043 -0
- bootstack/style/__init__.py +5498 -0
- bootstack/style/bootstyle.py +507 -0
- bootstack/style/bootstyle_builder_base.py +752 -0
- bootstack/style/bootstyle_builder_mixed.py +93 -0
- bootstack/style/bootstyle_builder_tk.py +109 -0
- bootstack/style/bootstyle_builder_ttk.py +354 -0
- bootstack/style/builders/__init__.py +51 -0
- bootstack/style/builders/badge.py +44 -0
- bootstack/style/builders/button.py +453 -0
- bootstack/style/builders/buttongroup.py +344 -0
- bootstack/style/builders/calendar.py +271 -0
- bootstack/style/builders/checkbutton.py +95 -0
- bootstack/style/builders/combobox.py +112 -0
- bootstack/style/builders/contextmenu.py +268 -0
- bootstack/style/builders/entry.py +83 -0
- bootstack/style/builders/expander.py +171 -0
- bootstack/style/builders/field.py +312 -0
- bootstack/style/builders/frame.py +27 -0
- bootstack/style/builders/label.py +28 -0
- bootstack/style/builders/labelframe.py +41 -0
- bootstack/style/builders/listview.py +267 -0
- bootstack/style/builders/menubar.py +74 -0
- bootstack/style/builders/menubutton.py +408 -0
- bootstack/style/builders/notebook.py +316 -0
- bootstack/style/builders/panedwindow.py +25 -0
- bootstack/style/builders/progressbar.py +71 -0
- bootstack/style/builders/radiobutton.py +68 -0
- bootstack/style/builders/scale.py +66 -0
- bootstack/style/builders/scrollbar.py +360 -0
- bootstack/style/builders/separator.py +45 -0
- bootstack/style/builders/sidenav.py +313 -0
- bootstack/style/builders/sizegrip.py +15 -0
- bootstack/style/builders/spinbox.py +119 -0
- bootstack/style/builders/switch.py +67 -0
- bootstack/style/builders/tabitem.py +205 -0
- bootstack/style/builders/toolbutton.py +260 -0
- bootstack/style/builders/tooltip.py +26 -0
- bootstack/style/builders/treeview.py +269 -0
- bootstack/style/builders/utils.py +404 -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/style.py +499 -0
- bootstack/style/theme_provider.py +449 -0
- bootstack/style/tk_patch.py +5 -0
- bootstack/style/token_maps.py +42 -0
- bootstack/style/types.py +32 -0
- bootstack/style/typography.py +527 -0
- bootstack/style/utility.py +696 -0
- bootstack/themes/__init__.py +12 -0
- bootstack/themes/standard.py +415 -0
- bootstack/themes/user.py +45 -0
- bootstack/widgets/__init__.py +53 -0
- bootstack/widgets/composites/__init__.py +38 -0
- bootstack/widgets/composites/accordion.py +385 -0
- bootstack/widgets/composites/appshell.py +445 -0
- bootstack/widgets/composites/buttongroup.py +391 -0
- bootstack/widgets/composites/calendar.py +914 -0
- bootstack/widgets/composites/compositeframe.py +282 -0
- bootstack/widgets/composites/contextmenu.py +1754 -0
- bootstack/widgets/composites/dateentry.py +261 -0
- bootstack/widgets/composites/dropdownbutton.py +190 -0
- bootstack/widgets/composites/expander.py +508 -0
- bootstack/widgets/composites/field.py +448 -0
- bootstack/widgets/composites/floodgauge.py +434 -0
- bootstack/widgets/composites/form.py +983 -0
- bootstack/widgets/composites/labeledscale.py +209 -0
- bootstack/widgets/composites/list/__init__.py +15 -0
- bootstack/widgets/composites/list/listitem.py +733 -0
- bootstack/widgets/composites/list/listview.py +1507 -0
- bootstack/widgets/composites/menubar.py +303 -0
- bootstack/widgets/composites/meter.py +882 -0
- bootstack/widgets/composites/numericentry.py +183 -0
- bootstack/widgets/composites/pagestack.py +330 -0
- bootstack/widgets/composites/passwordentry.py +149 -0
- bootstack/widgets/composites/pathentry.py +223 -0
- bootstack/widgets/composites/radiogroup.py +466 -0
- bootstack/widgets/composites/scrolledtext.py +388 -0
- bootstack/widgets/composites/scrolledtext.pyi +186 -0
- bootstack/widgets/composites/scrollview.py +675 -0
- bootstack/widgets/composites/selectbox.py +544 -0
- bootstack/widgets/composites/sidenav/__init__.py +24 -0
- bootstack/widgets/composites/sidenav/group.py +485 -0
- bootstack/widgets/composites/sidenav/header.py +83 -0
- bootstack/widgets/composites/sidenav/item.py +413 -0
- bootstack/widgets/composites/sidenav/separator.py +51 -0
- bootstack/widgets/composites/sidenav/view.py +919 -0
- bootstack/widgets/composites/spinnerentry.py +232 -0
- bootstack/widgets/composites/tableview/__init__.py +5 -0
- bootstack/widgets/composites/tableview/tableview.py +2254 -0
- bootstack/widgets/composites/tableview/types.py +169 -0
- bootstack/widgets/composites/tabs/__init__.py +6 -0
- bootstack/widgets/composites/tabs/tabitem.py +372 -0
- bootstack/widgets/composites/tabs/tabs.py +478 -0
- bootstack/widgets/composites/tabs/tabview.py +352 -0
- bootstack/widgets/composites/textentry.py +90 -0
- bootstack/widgets/composites/timeentry.py +189 -0
- bootstack/widgets/composites/toast.py +364 -0
- bootstack/widgets/composites/togglegroup.py +382 -0
- bootstack/widgets/composites/toolbar.py +393 -0
- bootstack/widgets/composites/tooltip.py +404 -0
- bootstack/widgets/internal/__init__.py +0 -0
- bootstack/widgets/internal/wrapper_base.py +304 -0
- bootstack/widgets/mixins/__init__.py +25 -0
- bootstack/widgets/mixins/configure_mixin.py +186 -0
- bootstack/widgets/mixins/entry_mixin.py +70 -0
- bootstack/widgets/mixins/font_mixin.py +346 -0
- bootstack/widgets/mixins/icon_mixin.py +38 -0
- bootstack/widgets/mixins/localization_mixin.py +255 -0
- bootstack/widgets/mixins/signal_mixin.py +272 -0
- bootstack/widgets/mixins/validation_mixin.py +204 -0
- bootstack/widgets/parts/__init__.py +11 -0
- bootstack/widgets/parts/numberentry_part.py +345 -0
- bootstack/widgets/parts/spinnerentry_part.py +394 -0
- bootstack/widgets/parts/textentry_part.py +344 -0
- bootstack/widgets/primitives/__init__.py +55 -0
- bootstack/widgets/primitives/badge.py +44 -0
- bootstack/widgets/primitives/button.py +89 -0
- bootstack/widgets/primitives/card.py +66 -0
- bootstack/widgets/primitives/checkbutton.py +124 -0
- bootstack/widgets/primitives/checktoggle.py +53 -0
- bootstack/widgets/primitives/combobox.py +165 -0
- bootstack/widgets/primitives/entry.py +98 -0
- bootstack/widgets/primitives/frame.py +206 -0
- bootstack/widgets/primitives/gridframe.py +479 -0
- bootstack/widgets/primitives/label.py +95 -0
- bootstack/widgets/primitives/labelframe.py +63 -0
- bootstack/widgets/primitives/menubutton.py +118 -0
- bootstack/widgets/primitives/notebook.py +551 -0
- bootstack/widgets/primitives/optionmenu.py +248 -0
- bootstack/widgets/primitives/packframe.py +228 -0
- bootstack/widgets/primitives/panedwindow.py +58 -0
- bootstack/widgets/primitives/progressbar.py +95 -0
- bootstack/widgets/primitives/radiobutton.py +115 -0
- bootstack/widgets/primitives/radiotoggle.py +50 -0
- bootstack/widgets/primitives/scale.py +85 -0
- bootstack/widgets/primitives/scrollbar.py +56 -0
- bootstack/widgets/primitives/separator.py +56 -0
- bootstack/widgets/primitives/sizegrip.py +47 -0
- bootstack/widgets/primitives/spinbox.py +91 -0
- bootstack/widgets/primitives/switch.py +41 -0
- bootstack/widgets/primitives/treeview.py +77 -0
- bootstack/widgets/types.py +20 -0
- bootstack-0.1.0a1.dist-info/METADATA +196 -0
- bootstack-0.1.0a1.dist-info/RECORD +419 -0
- bootstack-0.1.0a1.dist-info/WHEEL +5 -0
- bootstack-0.1.0a1.dist-info/entry_points.txt +2 -0
- bootstack-0.1.0a1.dist-info/licenses/LICENSE +22 -0
- bootstack-0.1.0a1.dist-info/licenses/NOTICE +10 -0
- bootstack-0.1.0a1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
"""File-based datasource with support for CSV, JSON, and lazy loading strategies.
|
|
2
|
+
|
|
3
|
+
Provides file-backed data storage with multiple loading strategies optimized for
|
|
4
|
+
different file sizes. Supports extensive transformation pipelines for data cleaning
|
|
5
|
+
and preprocessing.
|
|
6
|
+
|
|
7
|
+
Supported Formats (Built-in):
|
|
8
|
+
- CSV: Comma-separated values
|
|
9
|
+
- TSV: Tab-separated values
|
|
10
|
+
- JSON: Standard JSON arrays or objects
|
|
11
|
+
- JSONL/NDJSON: Line-delimited JSON (one record per line)
|
|
12
|
+
|
|
13
|
+
Loading Strategies:
|
|
14
|
+
- Eager: Load all data into memory at once (fast access, high memory)
|
|
15
|
+
- Lazy: Load data on-demand per page (slow access, low memory)
|
|
16
|
+
- Chunked: Load in configurable batches (balanced approach)
|
|
17
|
+
- Hybrid: Index in memory, lazy-load records (optimized balance)
|
|
18
|
+
- Auto: Automatically select strategy based on file size
|
|
19
|
+
|
|
20
|
+
Transformation Pipeline:
|
|
21
|
+
- Column renaming
|
|
22
|
+
- Type conversions
|
|
23
|
+
- Custom transformation functions per column
|
|
24
|
+
- Row-level filtering during load
|
|
25
|
+
- Row-level transformations
|
|
26
|
+
- Default values for missing data
|
|
27
|
+
|
|
28
|
+
Large File Optimization:
|
|
29
|
+
- Streaming parsing for minimal memory usage
|
|
30
|
+
- Threading for non-blocking loads
|
|
31
|
+
- Progress callbacks for UI updates
|
|
32
|
+
- Automatic strategy selection based on file size
|
|
33
|
+
|
|
34
|
+
Example:
|
|
35
|
+
```python
|
|
36
|
+
ds = FileDataSource("data.csv")
|
|
37
|
+
ds.load()
|
|
38
|
+
page = ds.get_page(0)
|
|
39
|
+
```
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
from __future__ import annotations
|
|
43
|
+
|
|
44
|
+
import csv
|
|
45
|
+
import json
|
|
46
|
+
import os
|
|
47
|
+
import threading
|
|
48
|
+
from dataclasses import dataclass, field
|
|
49
|
+
from pathlib import Path
|
|
50
|
+
from typing import (
|
|
51
|
+
Any, Callable, Dict, Iterator, List, Literal, Optional, Union
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
from bootstack.datasource.memory_source import MemoryDataSource
|
|
55
|
+
from bootstack.datasource.types import Record, Primitive
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class FileSourceConfig:
|
|
60
|
+
"""Configuration for file datasource loading and transformations.
|
|
61
|
+
|
|
62
|
+
Controls how files are parsed, loaded, and transformed. Provides extensive
|
|
63
|
+
customization for handling various data formats and scenarios.
|
|
64
|
+
|
|
65
|
+
Attributes:
|
|
66
|
+
file_format: Format type - auto-detected from extension if 'auto'.
|
|
67
|
+
encoding: Character encoding for reading the file.
|
|
68
|
+
delimiter: Field separator (None = auto-detect: ',' for CSV, tab for TSV).
|
|
69
|
+
quotechar: Quote character for fields containing the delimiter.
|
|
70
|
+
skip_rows: Number of header rows to skip.
|
|
71
|
+
header_row: Row index containing column names (None = no header).
|
|
72
|
+
has_header: Whether the first row contains column names.
|
|
73
|
+
json_lines: True for line-delimited JSON (JSONL/NDJSON format).
|
|
74
|
+
json_orient: Pandas-like orientation for JSON arrays.
|
|
75
|
+
column_renames: Map {old_name: new_name} for renaming columns.
|
|
76
|
+
column_types: Map {column: type} for type conversions.
|
|
77
|
+
column_transforms: Map {column: func} for custom transformations.
|
|
78
|
+
columns_to_load: List of columns to load (None = all columns).
|
|
79
|
+
default_values: Map {column: value} for missing/null values.
|
|
80
|
+
row_filter: Function(row_dict) -> bool to filter rows during load.
|
|
81
|
+
row_transform: Function(row_dict) -> row_dict for row-level transforms.
|
|
82
|
+
loading_strategy: How to load file ('eager', 'lazy', 'chunked', 'hybrid', 'auto').
|
|
83
|
+
chunk_size: Rows per chunk for chunked/lazy loading.
|
|
84
|
+
max_memory_rows: Threshold for auto-switching loading strategies.
|
|
85
|
+
use_threading: Load file in background thread (non-blocking).
|
|
86
|
+
progress_callback: Function(current, total) called during load.
|
|
87
|
+
on_complete: Function() called when loading completes.
|
|
88
|
+
on_error: Function(exception) called if loading fails.
|
|
89
|
+
|
|
90
|
+
Example:
|
|
91
|
+
```python
|
|
92
|
+
config = FileSourceConfig(
|
|
93
|
+
column_renames={'emp_id': 'id'},
|
|
94
|
+
column_types={'age': int},
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
file_format: Literal['auto', 'csv', 'tsv', 'json', 'jsonl'] = 'auto'
|
|
100
|
+
encoding: str = 'utf-8'
|
|
101
|
+
delimiter: Optional[str] = None
|
|
102
|
+
quotechar: str = '"'
|
|
103
|
+
skip_rows: int = 0
|
|
104
|
+
header_row: Optional[int] = 0
|
|
105
|
+
has_header: bool = True
|
|
106
|
+
json_lines: bool = False
|
|
107
|
+
json_orient: Literal['records', 'index', 'columns', 'values'] = 'records'
|
|
108
|
+
column_renames: Optional[Dict[str, str]] = None
|
|
109
|
+
column_types: Optional[Dict[str, type]] = None
|
|
110
|
+
column_transforms: Optional[Dict[str, Callable[[Any], Any]]] = None
|
|
111
|
+
columns_to_load: Optional[List[str]] = None
|
|
112
|
+
default_values: Optional[Dict[str, Any]] = None
|
|
113
|
+
row_filter: Optional[Callable[[Dict[str, Any]], bool]] = None
|
|
114
|
+
row_transform: Optional[Callable[[Dict[str, Any]], Dict[str, Any]]] = None
|
|
115
|
+
loading_strategy: Literal['eager', 'lazy', 'chunked', 'hybrid', 'auto'] = 'auto'
|
|
116
|
+
chunk_size: int = 10000
|
|
117
|
+
max_memory_rows: int = 100000
|
|
118
|
+
use_threading: bool = False
|
|
119
|
+
progress_callback: Optional[Callable[[int, int], None]] = None
|
|
120
|
+
on_complete: Optional[Callable[[], None]] = None
|
|
121
|
+
on_error: Optional[Callable[[Exception], None]] = None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class FileDataSource(MemoryDataSource):
|
|
125
|
+
"""File-based datasource with MemoryDataSource API and advanced loading strategies.
|
|
126
|
+
|
|
127
|
+
Extends MemoryDataSource to load data from files (CSV, JSON, JSONL) with support
|
|
128
|
+
for large files via multiple loading strategies. Provides extensive transformation
|
|
129
|
+
pipeline for data preprocessing.
|
|
130
|
+
|
|
131
|
+
The datasource automatically selects optimal loading strategy based on file size
|
|
132
|
+
and configuration. Supports background loading with progress callbacks for UI
|
|
133
|
+
integration.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
filepath: Path to data file
|
|
137
|
+
config: FileSourceConfig object (uses defaults if None)
|
|
138
|
+
page_size: Records per page for pagination (default: 10)
|
|
139
|
+
|
|
140
|
+
Attributes:
|
|
141
|
+
filepath: Path object for the data file
|
|
142
|
+
config: Active configuration
|
|
143
|
+
is_loaded: Whether file has been loaded
|
|
144
|
+
|
|
145
|
+
Loading Strategies:
|
|
146
|
+
Eager: Load entire file into memory
|
|
147
|
+
- Fastest access after initial load
|
|
148
|
+
- High memory usage
|
|
149
|
+
- Best for: Small files (< 100k rows)
|
|
150
|
+
|
|
151
|
+
Lazy: Load data on-demand per page
|
|
152
|
+
- Slowest access (re-parses on each request)
|
|
153
|
+
- Minimal memory usage
|
|
154
|
+
- Best for: Very large files (> 1M rows)
|
|
155
|
+
|
|
156
|
+
Chunked: Load file in batches
|
|
157
|
+
- Balanced performance and memory
|
|
158
|
+
- Shows progress during load
|
|
159
|
+
- Best for: Medium files (100k-500k rows)
|
|
160
|
+
|
|
161
|
+
Hybrid: Index in memory, lazy-load records
|
|
162
|
+
- Fast filtering/sorting
|
|
163
|
+
- Moderate memory usage
|
|
164
|
+
- Best for: Large files (500k-1M rows)
|
|
165
|
+
|
|
166
|
+
Auto: Automatically select based on file size
|
|
167
|
+
- < 100k rows: Eager
|
|
168
|
+
- 100k-500k: Chunked
|
|
169
|
+
- > 500k: Hybrid
|
|
170
|
+
|
|
171
|
+
Example:
|
|
172
|
+
```python
|
|
173
|
+
ds = FileDataSource("data.csv")
|
|
174
|
+
ds.load()
|
|
175
|
+
ds.set_filter("age > 25")
|
|
176
|
+
page = ds.get_page(0)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Note:
|
|
180
|
+
- File is re-parsed on reload()
|
|
181
|
+
- Threading uses daemon threads (auto-cleanup)
|
|
182
|
+
- All MemoryDataSource methods available after load
|
|
183
|
+
- Lazy strategy re-parses file on filter/sort changes
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
def __init__(
|
|
187
|
+
self,
|
|
188
|
+
filepath: str | Path,
|
|
189
|
+
config: Optional[FileSourceConfig] = None,
|
|
190
|
+
page_size: int = 10
|
|
191
|
+
):
|
|
192
|
+
"""Configure a file-backed datasource and detect file format.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
filepath: Location of the data file to be read.
|
|
196
|
+
config: Optional overrides for parsing, transforms, and threading.
|
|
197
|
+
page_size: Number of records returned per page after loading.
|
|
198
|
+
|
|
199
|
+
Raises:
|
|
200
|
+
FileNotFoundError: If the supplied file path cannot be found.
|
|
201
|
+
"""
|
|
202
|
+
super().__init__(page_size=page_size)
|
|
203
|
+
|
|
204
|
+
self.filepath = Path(filepath)
|
|
205
|
+
self.config = config or FileSourceConfig()
|
|
206
|
+
|
|
207
|
+
# Loading state
|
|
208
|
+
self.is_loaded = False
|
|
209
|
+
self._loading = False
|
|
210
|
+
self._load_progress = (0, 0)
|
|
211
|
+
self._load_thread = None
|
|
212
|
+
|
|
213
|
+
# Detect file format
|
|
214
|
+
self._detected_format = self._detect_format()
|
|
215
|
+
|
|
216
|
+
# Validate file exists
|
|
217
|
+
if not self.filepath.exists():
|
|
218
|
+
raise FileNotFoundError(f"File not found: {self.filepath}")
|
|
219
|
+
|
|
220
|
+
def _detect_format(self) -> str:
|
|
221
|
+
"""Detect file format from extension or config."""
|
|
222
|
+
if self.config.file_format != 'auto':
|
|
223
|
+
return self.config.file_format
|
|
224
|
+
|
|
225
|
+
ext = self.filepath.suffix.lower()
|
|
226
|
+
format_map = {
|
|
227
|
+
'.csv': 'csv',
|
|
228
|
+
'.tsv': 'tsv',
|
|
229
|
+
'.txt': 'csv', # Assume CSV for .txt
|
|
230
|
+
'.json': 'json',
|
|
231
|
+
'.jsonl': 'jsonl',
|
|
232
|
+
'.ndjson': 'jsonl',
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return format_map.get(ext, 'csv') # Default to CSV
|
|
236
|
+
|
|
237
|
+
def _estimate_row_count(self) -> int:
|
|
238
|
+
"""Estimate total row count for progress tracking."""
|
|
239
|
+
file_size = self.filepath.stat().st_size
|
|
240
|
+
|
|
241
|
+
# Estimate based on file format
|
|
242
|
+
if self._detected_format in ('csv', 'tsv'):
|
|
243
|
+
# Sample first few lines to estimate average row size
|
|
244
|
+
with open(self.filepath, 'r', encoding=self.config.encoding) as f:
|
|
245
|
+
sample_lines = []
|
|
246
|
+
for i, line in enumerate(f):
|
|
247
|
+
if i >= 100: # Sample first 100 lines
|
|
248
|
+
break
|
|
249
|
+
sample_lines.append(len(line.encode('utf-8')))
|
|
250
|
+
|
|
251
|
+
if sample_lines:
|
|
252
|
+
avg_line_size = sum(sample_lines) / len(sample_lines)
|
|
253
|
+
estimated = int(file_size / avg_line_size)
|
|
254
|
+
return max(estimated - self.config.skip_rows - 1, 0)
|
|
255
|
+
|
|
256
|
+
# For JSON, harder to estimate - use file size as proxy
|
|
257
|
+
return file_size // 100 # Very rough estimate
|
|
258
|
+
|
|
259
|
+
def _determine_strategy(self) -> str:
|
|
260
|
+
"""Determine optimal loading strategy."""
|
|
261
|
+
if self.config.loading_strategy != 'auto':
|
|
262
|
+
return self.config.loading_strategy
|
|
263
|
+
|
|
264
|
+
# Auto-select based on estimated row count
|
|
265
|
+
estimated_rows = self._estimate_row_count()
|
|
266
|
+
|
|
267
|
+
if estimated_rows < self.config.max_memory_rows:
|
|
268
|
+
return 'eager'
|
|
269
|
+
elif estimated_rows < self.config.max_memory_rows * 5:
|
|
270
|
+
return 'chunked'
|
|
271
|
+
else:
|
|
272
|
+
return 'hybrid'
|
|
273
|
+
|
|
274
|
+
def _parse_csv_records(self) -> Iterator[Record]:
|
|
275
|
+
"""Parse CSV/TSV file and yield records."""
|
|
276
|
+
delimiter = self.config.delimiter
|
|
277
|
+
if delimiter is None:
|
|
278
|
+
delimiter = '\t' if self._detected_format == 'tsv' else ','
|
|
279
|
+
|
|
280
|
+
with open(self.filepath, 'r', encoding=self.config.encoding, newline='') as f:
|
|
281
|
+
# Skip initial rows if configured
|
|
282
|
+
for _ in range(self.config.skip_rows):
|
|
283
|
+
next(f, None)
|
|
284
|
+
|
|
285
|
+
reader = csv.DictReader(
|
|
286
|
+
f,
|
|
287
|
+
delimiter=delimiter,
|
|
288
|
+
quotechar=self.config.quotechar
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
for row in reader:
|
|
292
|
+
yield dict(row)
|
|
293
|
+
|
|
294
|
+
def _parse_json_records(self) -> Iterator[Record]:
|
|
295
|
+
"""Parse JSON file and yield records."""
|
|
296
|
+
with open(self.filepath, 'r', encoding=self.config.encoding) as f:
|
|
297
|
+
if self.config.json_lines:
|
|
298
|
+
# JSONL format - one JSON object per line
|
|
299
|
+
for line in f:
|
|
300
|
+
line = line.strip()
|
|
301
|
+
if line:
|
|
302
|
+
yield json.loads(line)
|
|
303
|
+
else:
|
|
304
|
+
# Standard JSON
|
|
305
|
+
data = json.load(f)
|
|
306
|
+
|
|
307
|
+
# Handle different JSON structures
|
|
308
|
+
if isinstance(data, list):
|
|
309
|
+
for item in data:
|
|
310
|
+
if isinstance(item, dict):
|
|
311
|
+
yield item
|
|
312
|
+
else:
|
|
313
|
+
yield {'value': item}
|
|
314
|
+
elif isinstance(data, dict):
|
|
315
|
+
# Could be records, columns, etc.
|
|
316
|
+
if self.config.json_orient == 'records':
|
|
317
|
+
# Assume it's a dict of lists
|
|
318
|
+
for item in data.get('records', data.values()):
|
|
319
|
+
if isinstance(item, dict):
|
|
320
|
+
yield item
|
|
321
|
+
else:
|
|
322
|
+
# Single record
|
|
323
|
+
yield data
|
|
324
|
+
|
|
325
|
+
def _parse_records(self) -> Iterator[Record]:
|
|
326
|
+
"""Parse file and yield records based on format."""
|
|
327
|
+
if self._detected_format in ('csv', 'tsv'):
|
|
328
|
+
yield from self._parse_csv_records()
|
|
329
|
+
elif self._detected_format in ('json', 'jsonl'):
|
|
330
|
+
yield from self._parse_json_records()
|
|
331
|
+
else:
|
|
332
|
+
raise ValueError(f"Unsupported format: {self._detected_format}")
|
|
333
|
+
|
|
334
|
+
def _apply_transformations(self, record: Record) -> Optional[Record]:
|
|
335
|
+
"""Apply transformation pipeline to a single record.
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
Transformed record or None if filtered out
|
|
339
|
+
"""
|
|
340
|
+
# Apply row filter first
|
|
341
|
+
if self.config.row_filter and not self.config.row_filter(record):
|
|
342
|
+
return None
|
|
343
|
+
|
|
344
|
+
# Apply column selection
|
|
345
|
+
if self.config.columns_to_load:
|
|
346
|
+
record = {k: v for k, v in record.items() if k in self.config.columns_to_load}
|
|
347
|
+
|
|
348
|
+
# Apply column renames
|
|
349
|
+
if self.config.column_renames:
|
|
350
|
+
for old_name, new_name in self.config.column_renames.items():
|
|
351
|
+
if old_name in record:
|
|
352
|
+
record[new_name] = record.pop(old_name)
|
|
353
|
+
|
|
354
|
+
# Apply default values
|
|
355
|
+
if self.config.default_values:
|
|
356
|
+
for col, default in self.config.default_values.items():
|
|
357
|
+
if col not in record or record[col] is None:
|
|
358
|
+
record[col] = default
|
|
359
|
+
|
|
360
|
+
# Apply type conversions
|
|
361
|
+
if self.config.column_types:
|
|
362
|
+
for col, col_type in self.config.column_types.items():
|
|
363
|
+
if col in record and record[col] is not None:
|
|
364
|
+
try:
|
|
365
|
+
if callable(col_type):
|
|
366
|
+
record[col] = col_type(record[col])
|
|
367
|
+
else:
|
|
368
|
+
record[col] = col_type(record[col])
|
|
369
|
+
except (ValueError, TypeError):
|
|
370
|
+
pass # Keep original value if conversion fails
|
|
371
|
+
|
|
372
|
+
# Apply column transformations
|
|
373
|
+
if self.config.column_transforms:
|
|
374
|
+
for col, transform_func in self.config.column_transforms.items():
|
|
375
|
+
if col in record:
|
|
376
|
+
try:
|
|
377
|
+
record[col] = transform_func(record[col])
|
|
378
|
+
except Exception:
|
|
379
|
+
pass # Keep original value if transform fails
|
|
380
|
+
|
|
381
|
+
# Apply row-level transformation
|
|
382
|
+
if self.config.row_transform:
|
|
383
|
+
record = self.config.row_transform(record)
|
|
384
|
+
|
|
385
|
+
return record
|
|
386
|
+
|
|
387
|
+
def _load_eager(self) -> None:
|
|
388
|
+
"""Load all records into memory at once."""
|
|
389
|
+
records = []
|
|
390
|
+
estimated_total = self._estimate_row_count()
|
|
391
|
+
|
|
392
|
+
for i, raw_record in enumerate(self._parse_records()):
|
|
393
|
+
record = self._apply_transformations(raw_record)
|
|
394
|
+
if record is not None:
|
|
395
|
+
records.append(record)
|
|
396
|
+
|
|
397
|
+
if self.config.progress_callback and (i + 1) % 1000 == 0:
|
|
398
|
+
self._load_progress = (i + 1, estimated_total)
|
|
399
|
+
self.config.progress_callback(i + 1, estimated_total)
|
|
400
|
+
|
|
401
|
+
# Set data in parent MemoryDataSource
|
|
402
|
+
self.set_data(records)
|
|
403
|
+
self.is_loaded = True
|
|
404
|
+
self._load_progress = (len(records), len(records))
|
|
405
|
+
|
|
406
|
+
if self.config.progress_callback:
|
|
407
|
+
self.config.progress_callback(len(records), len(records))
|
|
408
|
+
|
|
409
|
+
def _load_chunked(self) -> None:
|
|
410
|
+
"""Load file in chunks."""
|
|
411
|
+
chunk = []
|
|
412
|
+
estimated_total = self._estimate_row_count()
|
|
413
|
+
loaded_count = 0
|
|
414
|
+
|
|
415
|
+
for i, raw_record in enumerate(self._parse_records()):
|
|
416
|
+
record = self._apply_transformations(raw_record)
|
|
417
|
+
if record is not None:
|
|
418
|
+
chunk.append(record)
|
|
419
|
+
|
|
420
|
+
# Process chunk when it reaches chunk_size
|
|
421
|
+
if len(chunk) >= self.config.chunk_size:
|
|
422
|
+
if not self.is_loaded:
|
|
423
|
+
self.set_data(chunk)
|
|
424
|
+
self.is_loaded = True
|
|
425
|
+
else:
|
|
426
|
+
# Append to existing data
|
|
427
|
+
for rec in chunk:
|
|
428
|
+
self.create_record(rec)
|
|
429
|
+
|
|
430
|
+
loaded_count += len(chunk)
|
|
431
|
+
self._load_progress = (loaded_count, estimated_total)
|
|
432
|
+
|
|
433
|
+
if self.config.progress_callback:
|
|
434
|
+
self.config.progress_callback(loaded_count, estimated_total)
|
|
435
|
+
|
|
436
|
+
chunk = []
|
|
437
|
+
|
|
438
|
+
# Process remaining records
|
|
439
|
+
if chunk:
|
|
440
|
+
if not self.is_loaded:
|
|
441
|
+
self.set_data(chunk)
|
|
442
|
+
self.is_loaded = True
|
|
443
|
+
else:
|
|
444
|
+
for rec in chunk:
|
|
445
|
+
self.create_record(rec)
|
|
446
|
+
|
|
447
|
+
loaded_count += len(chunk)
|
|
448
|
+
|
|
449
|
+
self._load_progress = (loaded_count, loaded_count)
|
|
450
|
+
if self.config.progress_callback:
|
|
451
|
+
self.config.progress_callback(loaded_count, loaded_count)
|
|
452
|
+
|
|
453
|
+
def _load_impl(self) -> None:
|
|
454
|
+
"""Internal load implementation (runs in thread if use_threading=True)."""
|
|
455
|
+
try:
|
|
456
|
+
strategy = self._determine_strategy()
|
|
457
|
+
|
|
458
|
+
if strategy == 'eager':
|
|
459
|
+
self._load_eager()
|
|
460
|
+
elif strategy == 'chunked':
|
|
461
|
+
self._load_chunked()
|
|
462
|
+
elif strategy in ('lazy', 'hybrid'):
|
|
463
|
+
# For now, fall back to eager (lazy/hybrid require more complex implementation)
|
|
464
|
+
self._load_eager()
|
|
465
|
+
|
|
466
|
+
self._loading = False
|
|
467
|
+
|
|
468
|
+
if self.config.on_complete:
|
|
469
|
+
self.config.on_complete()
|
|
470
|
+
|
|
471
|
+
except Exception as e:
|
|
472
|
+
self._loading = False
|
|
473
|
+
self.is_loaded = False
|
|
474
|
+
|
|
475
|
+
if self.config.on_error:
|
|
476
|
+
self.config.on_error(e)
|
|
477
|
+
else:
|
|
478
|
+
raise
|
|
479
|
+
|
|
480
|
+
def load(self, on_complete: Optional[Callable] = None) -> None:
|
|
481
|
+
"""Load file with configured strategy.
|
|
482
|
+
|
|
483
|
+
For threaded loading, returns immediately and calls on_complete when done.
|
|
484
|
+
For synchronous loading, blocks until complete.
|
|
485
|
+
|
|
486
|
+
Args:
|
|
487
|
+
on_complete: Optional callback when loading finishes (overrides config)
|
|
488
|
+
"""
|
|
489
|
+
if self._loading:
|
|
490
|
+
return # Already loading
|
|
491
|
+
|
|
492
|
+
self._loading = True
|
|
493
|
+
self.is_loaded = False
|
|
494
|
+
self._load_progress = (0, 0)
|
|
495
|
+
|
|
496
|
+
# Override on_complete if provided
|
|
497
|
+
if on_complete:
|
|
498
|
+
original_callback = self.config.on_complete
|
|
499
|
+
self.config.on_complete = on_complete
|
|
500
|
+
|
|
501
|
+
if self.config.use_threading:
|
|
502
|
+
# Load in background thread
|
|
503
|
+
self._load_thread = threading.Thread(target=self._load_impl, daemon=True)
|
|
504
|
+
self._load_thread.start()
|
|
505
|
+
else:
|
|
506
|
+
# Load synchronously
|
|
507
|
+
self._load_impl()
|
|
508
|
+
|
|
509
|
+
# Restore original callback if overridden
|
|
510
|
+
if on_complete:
|
|
511
|
+
self.config.on_complete = original_callback
|
|
512
|
+
|
|
513
|
+
def reload(self) -> None:
|
|
514
|
+
"""Reload from file, clearing current data."""
|
|
515
|
+
self.is_loaded = False
|
|
516
|
+
self._data.clear()
|
|
517
|
+
self._columns.clear()
|
|
518
|
+
self._id_index.clear()
|
|
519
|
+
self.load()
|
|
520
|
+
|
|
521
|
+
def is_loading(self) -> bool:
|
|
522
|
+
"""Check if file is currently loading."""
|
|
523
|
+
return self._loading
|
|
524
|
+
|
|
525
|
+
def get_load_progress(self) -> tuple[int, int]:
|
|
526
|
+
"""Get loading progress as (current, total) rows."""
|
|
527
|
+
return self._load_progress
|
|
528
|
+
|
|
529
|
+
def wait_for_load(self, timeout: Optional[float] = None) -> bool:
|
|
530
|
+
"""Wait for loading to complete (if threaded).
|
|
531
|
+
|
|
532
|
+
Args:
|
|
533
|
+
timeout: Max seconds to wait (None = wait forever)
|
|
534
|
+
|
|
535
|
+
Returns:
|
|
536
|
+
True if load completed, False if timed out
|
|
537
|
+
"""
|
|
538
|
+
if self._load_thread and self._load_thread.is_alive():
|
|
539
|
+
self._load_thread.join(timeout=timeout)
|
|
540
|
+
return not self._load_thread.is_alive()
|
|
541
|
+
return True
|