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.
Files changed (471) hide show
  1. bootstack/__init__.py +157 -0
  2. bootstack/__main__.py +5 -0
  3. bootstack/_core/__init__.py +21 -0
  4. bootstack/_core/capabilities/__init__.py +45 -0
  5. bootstack/_core/capabilities/after.py +103 -0
  6. bootstack/_core/capabilities/bind.py +192 -0
  7. bootstack/_core/capabilities/bindtags.py +112 -0
  8. bootstack/_core/capabilities/busy.py +72 -0
  9. bootstack/_core/capabilities/clipboard.py +89 -0
  10. bootstack/_core/capabilities/focus.py +118 -0
  11. bootstack/_core/capabilities/grab.py +65 -0
  12. bootstack/_core/capabilities/grid.py +211 -0
  13. bootstack/_core/capabilities/localization.py +231 -0
  14. bootstack/_core/capabilities/pack.py +140 -0
  15. bootstack/_core/capabilities/place.py +113 -0
  16. bootstack/_core/capabilities/selection.py +136 -0
  17. bootstack/_core/capabilities/signals.py +244 -0
  18. bootstack/_core/capabilities/winfo.py +315 -0
  19. bootstack/_core/colorutils.py +234 -0
  20. bootstack/_core/exceptions.py +25 -0
  21. bootstack/_core/images.py +463 -0
  22. bootstack/_core/mixins/__init__.py +1 -0
  23. bootstack/_core/mixins/ttk_state.py +35 -0
  24. bootstack/_core/mixins/widget.py +132 -0
  25. bootstack/_core/paths.py +49 -0
  26. bootstack/_core/publisher.py +149 -0
  27. bootstack/_core/variables.py +62 -0
  28. bootstack/_runtime/__init__.py +3 -0
  29. bootstack/_runtime/app.py +930 -0
  30. bootstack/_runtime/base_window.py +945 -0
  31. bootstack/_runtime/events.py +399 -0
  32. bootstack/_runtime/shortcuts.py +496 -0
  33. bootstack/_runtime/tk_patch.py +43 -0
  34. bootstack/_runtime/toplevel.py +276 -0
  35. bootstack/_runtime/utility.py +457 -0
  36. bootstack/_runtime/visual_focus.py +240 -0
  37. bootstack/_runtime/window_utilities.py +1123 -0
  38. bootstack/assets/__init__.py +21 -0
  39. bootstack/assets/bootstack.ico +0 -0
  40. bootstack/assets/bootstack.png +0 -0
  41. bootstack/assets/elements/__init__.py +0 -0
  42. bootstack/assets/elements/badge-pill.png +0 -0
  43. bootstack/assets/elements/badge-square.png +0 -0
  44. bootstack/assets/elements/button-compact.png +0 -0
  45. bootstack/assets/elements/button-default.png +0 -0
  46. bootstack/assets/elements/buttongroup-after-h-compact.png +0 -0
  47. bootstack/assets/elements/buttongroup-after-h-default.png +0 -0
  48. bootstack/assets/elements/buttongroup-after-v-compact.png +0 -0
  49. bootstack/assets/elements/buttongroup-after-v-default.png +0 -0
  50. bootstack/assets/elements/buttongroup-before-h-compact.png +0 -0
  51. bootstack/assets/elements/buttongroup-before-h-default.png +0 -0
  52. bootstack/assets/elements/buttongroup-before-v-compact.png +0 -0
  53. bootstack/assets/elements/buttongroup-before-v-default.png +0 -0
  54. bootstack/assets/elements/buttongroup-center-h-compact.png +0 -0
  55. bootstack/assets/elements/buttongroup-center-h-default.png +0 -0
  56. bootstack/assets/elements/buttongroup-center-v-compact.png +0 -0
  57. bootstack/assets/elements/buttongroup-center-v-default.png +0 -0
  58. bootstack/assets/elements/card.png +0 -0
  59. bootstack/assets/elements/checkbox-checked.png +0 -0
  60. bootstack/assets/elements/checkbox-indeterminate.png +0 -0
  61. bootstack/assets/elements/checkbox-unchecked.png +0 -0
  62. bootstack/assets/elements/field.png +0 -0
  63. bootstack/assets/elements/input-addon-compact.png +0 -0
  64. bootstack/assets/elements/input-addon-default.png +0 -0
  65. bootstack/assets/elements/input-compact.png +0 -0
  66. bootstack/assets/elements/input-default.png +0 -0
  67. bootstack/assets/elements/list-item-separated.png +0 -0
  68. bootstack/assets/elements/list-item.png +0 -0
  69. bootstack/assets/elements/listrow-compact.png +0 -0
  70. bootstack/assets/elements/listrow-default.png +0 -0
  71. bootstack/assets/elements/manifest.toml +361 -0
  72. bootstack/assets/elements/menu-item.png +0 -0
  73. bootstack/assets/elements/navitem-compact.png +0 -0
  74. bootstack/assets/elements/navitem-default.png +0 -0
  75. bootstack/assets/elements/progressbar-h-compact.png +0 -0
  76. bootstack/assets/elements/progressbar-h-default.png +0 -0
  77. bootstack/assets/elements/progressbar-v-compact.png +0 -0
  78. bootstack/assets/elements/progressbar-v-default.png +0 -0
  79. bootstack/assets/elements/radiobutton.png +0 -0
  80. bootstack/assets/elements/scrollbar-horizontal.png +0 -0
  81. bootstack/assets/elements/scrollbar-vertical.png +0 -0
  82. bootstack/assets/elements/slider-handle.png +0 -0
  83. bootstack/assets/elements/slider-track-h.png +0 -0
  84. bootstack/assets/elements/slider-track-v.png +0 -0
  85. bootstack/assets/elements/switch-off.png +0 -0
  86. bootstack/assets/elements/switch-on.png +0 -0
  87. bootstack/assets/elements/tab-h.png +0 -0
  88. bootstack/assets/elements/tab-v.png +0 -0
  89. bootstack/assets/icons/bootstrap.ttf +0 -0
  90. bootstack/assets/icons/glyphmap.json +2080 -0
  91. bootstack/assets/icons/icon_metrics.json +12470 -0
  92. bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
  93. bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +856 -0
  94. bootstack/assets/locales/bg/LC_MESSAGES/bootstack.mo +0 -0
  95. bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +878 -0
  96. bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
  97. bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +856 -0
  98. bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
  99. bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +856 -0
  100. bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
  101. bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +856 -0
  102. bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
  103. bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +878 -0
  104. bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
  105. bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +856 -0
  106. bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
  107. bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +856 -0
  108. bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
  109. bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +854 -0
  110. bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
  111. bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +845 -0
  112. bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
  113. bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +844 -0
  114. bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
  115. bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +917 -0
  116. bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
  117. bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +845 -0
  118. bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
  119. bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +844 -0
  120. bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
  121. bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +844 -0
  122. bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
  123. bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +845 -0
  124. bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
  125. bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +845 -0
  126. bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
  127. bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +845 -0
  128. bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
  129. bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +845 -0
  130. bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
  131. bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +845 -0
  132. bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
  133. bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +845 -0
  134. bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
  135. bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +845 -0
  136. bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
  137. bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +845 -0
  138. bootstack/cli/__init__.py +133 -0
  139. bootstack/cli/__main__.py +6 -0
  140. bootstack/cli/add.py +395 -0
  141. bootstack/cli/appicon.py +285 -0
  142. bootstack/cli/build.py +115 -0
  143. bootstack/cli/config.py +313 -0
  144. bootstack/cli/demo.py +564 -0
  145. bootstack/cli/dev.py +153 -0
  146. bootstack/cli/doctor.py +195 -0
  147. bootstack/cli/icons.py +98 -0
  148. bootstack/cli/promote.py +120 -0
  149. bootstack/cli/pyinstaller.py +268 -0
  150. bootstack/cli/run.py +95 -0
  151. bootstack/cli/start.py +117 -0
  152. bootstack/cli/templates/__init__.py +931 -0
  153. bootstack/clipboard.py +48 -0
  154. bootstack/constants.py +318 -0
  155. bootstack/data/README.md +615 -0
  156. bootstack/data/__init__.py +78 -0
  157. bootstack/data/_observable.py +276 -0
  158. bootstack/data/base.py +780 -0
  159. bootstack/data/file_source.py +367 -0
  160. bootstack/data/memory_source.py +450 -0
  161. bootstack/data/query.py +367 -0
  162. bootstack/data/readers.py +289 -0
  163. bootstack/data/sqlite_source.py +869 -0
  164. bootstack/data/types.py +354 -0
  165. bootstack/data/writers.py +232 -0
  166. bootstack/dev/__init__.py +36 -0
  167. bootstack/dev/_capture.py +141 -0
  168. bootstack/dev/_env.py +43 -0
  169. bootstack/dev/_registry.py +140 -0
  170. bootstack/dev/_reloader.py +351 -0
  171. bootstack/dev/_reset.py +50 -0
  172. bootstack/dev/_watcher.py +91 -0
  173. bootstack/dialogs/__init__.py +948 -0
  174. bootstack/dialogs/_impl/__init__.py +47 -0
  175. bootstack/dialogs/_impl/colorchooser.py +588 -0
  176. bootstack/dialogs/_impl/datedialog.py +450 -0
  177. bootstack/dialogs/_impl/dialog.py +594 -0
  178. bootstack/dialogs/_impl/filterdialog.py +358 -0
  179. bootstack/dialogs/_impl/fontdialog.py +364 -0
  180. bootstack/dialogs/_impl/formdialog.py +564 -0
  181. bootstack/dialogs/_impl/message.py +486 -0
  182. bootstack/dialogs/_impl/query.py +570 -0
  183. bootstack/errors.py +67 -0
  184. bootstack/events/__init__.py +111 -0
  185. bootstack/events/_event.py +135 -0
  186. bootstack/events/_payloads.py +539 -0
  187. bootstack/events/_subscription.py +38 -0
  188. bootstack/i18n/README.md +77 -0
  189. bootstack/i18n/__init__.py +23 -0
  190. bootstack/i18n/catalog.py +121 -0
  191. bootstack/i18n/intl_format.py +584 -0
  192. bootstack/i18n/msgcat.py +425 -0
  193. bootstack/i18n/specs.py +156 -0
  194. bootstack/images.py +563 -0
  195. bootstack/py.typed +1 -0
  196. bootstack/scheduling/__init__.py +11 -0
  197. bootstack/scheduling/_schedule.py +218 -0
  198. bootstack/shortcuts.py +21 -0
  199. bootstack/signals/README.md +98 -0
  200. bootstack/signals/__init__.py +10 -0
  201. bootstack/signals/integration.py +100 -0
  202. bootstack/signals/signal.py +353 -0
  203. bootstack/signals/types.py +6 -0
  204. bootstack/store.py +286 -0
  205. bootstack/streams/__init__.py +12 -0
  206. bootstack/streams/_stream.py +321 -0
  207. bootstack/style/__init__.py +38 -0
  208. bootstack/style/builders/__init__.py +51 -0
  209. bootstack/style/builders/badge.py +46 -0
  210. bootstack/style/builders/button.py +339 -0
  211. bootstack/style/builders/buttongroup.py +311 -0
  212. bootstack/style/builders/calendar.py +271 -0
  213. bootstack/style/builders/checkbutton.py +110 -0
  214. bootstack/style/builders/combobox.py +113 -0
  215. bootstack/style/builders/contextmenu.py +268 -0
  216. bootstack/style/builders/entry.py +82 -0
  217. bootstack/style/builders/expander.py +148 -0
  218. bootstack/style/builders/field.py +335 -0
  219. bootstack/style/builders/frame.py +50 -0
  220. bootstack/style/builders/label.py +28 -0
  221. bootstack/style/builders/labelframe.py +34 -0
  222. bootstack/style/builders/listview.py +369 -0
  223. bootstack/style/builders/menubar.py +91 -0
  224. bootstack/style/builders/menubutton.py +359 -0
  225. bootstack/style/builders/panedwindow.py +25 -0
  226. bootstack/style/builders/progressbar.py +67 -0
  227. bootstack/style/builders/radiobutton.py +99 -0
  228. bootstack/style/builders/scale.py +64 -0
  229. bootstack/style/builders/scrollbar.py +225 -0
  230. bootstack/style/builders/separator.py +49 -0
  231. bootstack/style/builders/sidenav.py +643 -0
  232. bootstack/style/builders/sizegrip.py +15 -0
  233. bootstack/style/builders/spinbox.py +119 -0
  234. bootstack/style/builders/switch.py +70 -0
  235. bootstack/style/builders/tabitem.py +204 -0
  236. bootstack/style/builders/togglegroup.py +294 -0
  237. bootstack/style/builders/toolbutton.py +275 -0
  238. bootstack/style/builders/tooltip.py +26 -0
  239. bootstack/style/builders/treeview.py +193 -0
  240. bootstack/style/builders/utils.py +455 -0
  241. bootstack/style/builders_tk/__init__.py +16 -0
  242. bootstack/style/builders_tk/defaults.py +229 -0
  243. bootstack/style/element.py +173 -0
  244. bootstack/style/fonts.py +123 -0
  245. bootstack/style/style.py +609 -0
  246. bootstack/style/style_builder_base.py +716 -0
  247. bootstack/style/style_builder_mixed.py +93 -0
  248. bootstack/style/style_builder_tk.py +109 -0
  249. bootstack/style/style_builder_ttk.py +353 -0
  250. bootstack/style/style_resolver.py +447 -0
  251. bootstack/style/theme.py +245 -0
  252. bootstack/style/theme_provider.py +471 -0
  253. bootstack/style/themes/__init__.py +128 -0
  254. bootstack/style/tk_patch.py +5 -0
  255. bootstack/style/token_maps.py +41 -0
  256. bootstack/style/types.py +32 -0
  257. bootstack/style/typography.py +523 -0
  258. bootstack/style/utility.py +746 -0
  259. bootstack/types.py +39 -0
  260. bootstack/validation/__init__.py +6 -0
  261. bootstack/validation/types.py +15 -0
  262. bootstack/validation/validation_result.py +17 -0
  263. bootstack/validation/validation_rules.py +205 -0
  264. bootstack/widgets/__init__.py +74 -0
  265. bootstack/widgets/_core/__init__.py +31 -0
  266. bootstack/widgets/_core/app_config.py +266 -0
  267. bootstack/widgets/_core/base.py +461 -0
  268. bootstack/widgets/_core/container.py +334 -0
  269. bootstack/widgets/_core/context.py +35 -0
  270. bootstack/widgets/_core/events.py +130 -0
  271. bootstack/widgets/_core/field_mixin.py +353 -0
  272. bootstack/widgets/_core/icon_image_props.py +72 -0
  273. bootstack/widgets/_core/image_binding.py +93 -0
  274. bootstack/widgets/_core/navmodel.py +345 -0
  275. bootstack/widgets/_core/options.py +210 -0
  276. bootstack/widgets/_core/selection_group.py +130 -0
  277. bootstack/widgets/_core/window_controls.py +96 -0
  278. bootstack/widgets/_core/window_menu.py +246 -0
  279. bootstack/widgets/_impl/__init__.py +1 -0
  280. bootstack/widgets/_impl/_internal/__init__.py +0 -0
  281. bootstack/widgets/_impl/_internal/wrapper_base.py +307 -0
  282. bootstack/widgets/_impl/_parts/__init__.py +11 -0
  283. bootstack/widgets/_impl/_parts/numberentry_part.py +385 -0
  284. bootstack/widgets/_impl/_parts/spinnerentry_part.py +434 -0
  285. bootstack/widgets/_impl/_parts/textentry_part.py +406 -0
  286. bootstack/widgets/_impl/composites/__init__.py +33 -0
  287. bootstack/widgets/_impl/composites/_dateutils.py +33 -0
  288. bootstack/widgets/_impl/composites/_image_fit.py +105 -0
  289. bootstack/widgets/_impl/composites/accordion.py +381 -0
  290. bootstack/widgets/_impl/composites/avatar.py +191 -0
  291. bootstack/widgets/_impl/composites/buttongroup.py +371 -0
  292. bootstack/widgets/_impl/composites/calendar.py +972 -0
  293. bootstack/widgets/_impl/composites/carousel.py +567 -0
  294. bootstack/widgets/_impl/composites/chart.py +882 -0
  295. bootstack/widgets/_impl/composites/compositeframe.py +298 -0
  296. bootstack/widgets/_impl/composites/contextmenu.py +1951 -0
  297. bootstack/widgets/_impl/composites/dateentry.py +404 -0
  298. bootstack/widgets/_impl/composites/dropdownbutton.py +325 -0
  299. bootstack/widgets/_impl/composites/expander.py +515 -0
  300. bootstack/widgets/_impl/composites/field.py +670 -0
  301. bootstack/widgets/_impl/composites/form.py +1066 -0
  302. bootstack/widgets/_impl/composites/gallery.py +551 -0
  303. bootstack/widgets/_impl/composites/list/__init__.py +15 -0
  304. bootstack/widgets/_impl/composites/list/listitem.py +802 -0
  305. bootstack/widgets/_impl/composites/list/listview.py +1433 -0
  306. bootstack/widgets/_impl/composites/menu/__init__.py +18 -0
  307. bootstack/widgets/_impl/composites/menu/model.py +358 -0
  308. bootstack/widgets/_impl/composites/menu/render_native.py +134 -0
  309. bootstack/widgets/_impl/composites/menu/render_themed.py +134 -0
  310. bootstack/widgets/_impl/composites/meter.py +860 -0
  311. bootstack/widgets/_impl/composites/numericentry.py +201 -0
  312. bootstack/widgets/_impl/composites/pagestack.py +395 -0
  313. bootstack/widgets/_impl/composites/passwordentry.py +142 -0
  314. bootstack/widgets/_impl/composites/pathentry.py +168 -0
  315. bootstack/widgets/_impl/composites/picture.py +289 -0
  316. bootstack/widgets/_impl/composites/radiogroup.py +511 -0
  317. bootstack/widgets/_impl/composites/scrolledtext.py +375 -0
  318. bootstack/widgets/_impl/composites/scrolledtext.pyi +186 -0
  319. bootstack/widgets/_impl/composites/scrollview.py +764 -0
  320. bootstack/widgets/_impl/composites/selectbox.py +1026 -0
  321. bootstack/widgets/_impl/composites/shell/__init__.py +40 -0
  322. bootstack/widgets/_impl/composites/shell/content_host.py +52 -0
  323. bootstack/widgets/_impl/composites/shell/layout.py +335 -0
  324. bootstack/widgets/_impl/composites/shell/nav_panel.py +345 -0
  325. bootstack/widgets/_impl/composites/shell/providers.py +558 -0
  326. bootstack/widgets/_impl/composites/shell/rail.py +117 -0
  327. bootstack/widgets/_impl/composites/shell/shell.py +581 -0
  328. bootstack/widgets/_impl/composites/shell/workspace.py +273 -0
  329. bootstack/widgets/_impl/composites/sidenav/__init__.py +16 -0
  330. bootstack/widgets/_impl/composites/sidenav/header.py +81 -0
  331. bootstack/widgets/_impl/composites/sidenav/separator.py +44 -0
  332. bootstack/widgets/_impl/composites/slider/__init__.py +7 -0
  333. bootstack/widgets/_impl/composites/slider/_shared.py +195 -0
  334. bootstack/widgets/_impl/composites/slider/rangeslider.py +982 -0
  335. bootstack/widgets/_impl/composites/slider/slider.py +851 -0
  336. bootstack/widgets/_impl/composites/spinnerentry.py +185 -0
  337. bootstack/widgets/_impl/composites/tableview/__init__.py +5 -0
  338. bootstack/widgets/_impl/composites/tableview/tableview.py +3388 -0
  339. bootstack/widgets/_impl/composites/tableview/types.py +169 -0
  340. bootstack/widgets/_impl/composites/tabs/__init__.py +23 -0
  341. bootstack/widgets/_impl/composites/tabs/tabitem.py +389 -0
  342. bootstack/widgets/_impl/composites/tabs/tabs.py +974 -0
  343. bootstack/widgets/_impl/composites/tabs/tabview.py +650 -0
  344. bootstack/widgets/_impl/composites/textarea/__init__.py +26 -0
  345. bootstack/widgets/_impl/composites/textarea/change.py +46 -0
  346. bootstack/widgets/_impl/composites/textarea/codeeditor.py +526 -0
  347. bootstack/widgets/_impl/composites/textarea/core.py +495 -0
  348. bootstack/widgets/_impl/composites/textarea/decoration.py +42 -0
  349. bootstack/widgets/_impl/composites/textarea/diff.py +127 -0
  350. bootstack/widgets/_impl/composites/textarea/extensions/__init__.py +1 -0
  351. bootstack/widgets/_impl/composites/textarea/extensions/bracket_matcher.py +138 -0
  352. bootstack/widgets/_impl/composites/textarea/extensions/indent_guides.py +158 -0
  353. bootstack/widgets/_impl/composites/textarea/extensions/line_numbers.py +138 -0
  354. bootstack/widgets/_impl/composites/textarea/extensions/pygments_highlighter.py +312 -0
  355. bootstack/widgets/_impl/composites/textarea/extensions/smart_indent.py +177 -0
  356. bootstack/widgets/_impl/composites/textarea/filter.py +171 -0
  357. bootstack/widgets/_impl/composites/textarea/search_overlay.py +459 -0
  358. bootstack/widgets/_impl/composites/textarea/sidebar.py +88 -0
  359. bootstack/widgets/_impl/composites/textarea/style_registry.py +178 -0
  360. bootstack/widgets/_impl/composites/textarea/textarea.py +606 -0
  361. bootstack/widgets/_impl/composites/textarea/undo.py +217 -0
  362. bootstack/widgets/_impl/composites/textentry.py +57 -0
  363. bootstack/widgets/_impl/composites/timeentry.py +176 -0
  364. bootstack/widgets/_impl/composites/toast.py +390 -0
  365. bootstack/widgets/_impl/composites/toast_stack.py +156 -0
  366. bootstack/widgets/_impl/composites/togglegroup.py +418 -0
  367. bootstack/widgets/_impl/composites/toolbar.py +608 -0
  368. bootstack/widgets/_impl/composites/tooltip.py +491 -0
  369. bootstack/widgets/_impl/composites/tree/__init__.py +7 -0
  370. bootstack/widgets/_impl/composites/tree/source_binding.py +136 -0
  371. bootstack/widgets/_impl/composites/tree/treeitem.py +393 -0
  372. bootstack/widgets/_impl/composites/tree/treenode.py +174 -0
  373. bootstack/widgets/_impl/composites/tree/treeview.py +841 -0
  374. bootstack/widgets/_impl/mixins/__init__.py +24 -0
  375. bootstack/widgets/_impl/mixins/configure_mixin.py +216 -0
  376. bootstack/widgets/_impl/mixins/entry_mixin.py +134 -0
  377. bootstack/widgets/_impl/mixins/font_mixin.py +368 -0
  378. bootstack/widgets/_impl/mixins/icon_mixin.py +61 -0
  379. bootstack/widgets/_impl/mixins/localization_mixin.py +253 -0
  380. bootstack/widgets/_impl/mixins/signal_mixin.py +268 -0
  381. bootstack/widgets/_impl/mixins/validation_mixin.py +226 -0
  382. bootstack/widgets/_impl/primitives/__init__.py +49 -0
  383. bootstack/widgets/_impl/primitives/_menubutton.py +107 -0
  384. bootstack/widgets/_impl/primitives/badge.py +45 -0
  385. bootstack/widgets/_impl/primitives/button.py +76 -0
  386. bootstack/widgets/_impl/primitives/card.py +45 -0
  387. bootstack/widgets/_impl/primitives/checkbutton.py +124 -0
  388. bootstack/widgets/_impl/primitives/checktoggle.py +62 -0
  389. bootstack/widgets/_impl/primitives/combobox.py +156 -0
  390. bootstack/widgets/_impl/primitives/entry.py +87 -0
  391. bootstack/widgets/_impl/primitives/flexframe.py +448 -0
  392. bootstack/widgets/_impl/primitives/frame.py +185 -0
  393. bootstack/widgets/_impl/primitives/gridframe.py +546 -0
  394. bootstack/widgets/_impl/primitives/label.py +84 -0
  395. bootstack/widgets/_impl/primitives/labelframe.py +54 -0
  396. bootstack/widgets/_impl/primitives/optionmenu.py +387 -0
  397. bootstack/widgets/_impl/primitives/packframe.py +227 -0
  398. bootstack/widgets/_impl/primitives/panedwindow.py +45 -0
  399. bootstack/widgets/_impl/primitives/progressbar.py +83 -0
  400. bootstack/widgets/_impl/primitives/radiobutton.py +115 -0
  401. bootstack/widgets/_impl/primitives/radiotoggle.py +54 -0
  402. bootstack/widgets/_impl/primitives/scrollbar.py +42 -0
  403. bootstack/widgets/_impl/primitives/separator.py +43 -0
  404. bootstack/widgets/_impl/primitives/sizegrip.py +33 -0
  405. bootstack/widgets/_impl/primitives/spinbox.py +95 -0
  406. bootstack/widgets/_impl/primitives/switch.py +44 -0
  407. bootstack/widgets/_impl/primitives/treeview.py +69 -0
  408. bootstack/widgets/app.py +371 -0
  409. bootstack/widgets/appshell.py +1179 -0
  410. bootstack/widgets/avatar.py +140 -0
  411. bootstack/widgets/boolean_controls.py +455 -0
  412. bootstack/widgets/button.py +224 -0
  413. bootstack/widgets/buttongroup.py +239 -0
  414. bootstack/widgets/calendar.py +195 -0
  415. bootstack/widgets/card.py +159 -0
  416. bootstack/widgets/carousel.py +241 -0
  417. bootstack/widgets/chart.py +302 -0
  418. bootstack/widgets/codeeditor.py +675 -0
  419. bootstack/widgets/contextmenu.py +371 -0
  420. bootstack/widgets/datatable.py +688 -0
  421. bootstack/widgets/datefield.py +395 -0
  422. bootstack/widgets/divider.py +60 -0
  423. bootstack/widgets/expander.py +579 -0
  424. bootstack/widgets/form.py +200 -0
  425. bootstack/widgets/gallery.py +250 -0
  426. bootstack/widgets/gauge.py +168 -0
  427. bootstack/widgets/grid.py +121 -0
  428. bootstack/widgets/groupbox.py +162 -0
  429. bootstack/widgets/label.py +229 -0
  430. bootstack/widgets/listview.py +354 -0
  431. bootstack/widgets/menubutton.py +419 -0
  432. bootstack/widgets/numberfield.py +431 -0
  433. bootstack/widgets/pagestack.py +345 -0
  434. bootstack/widgets/passwordfield.py +383 -0
  435. bootstack/widgets/pathfield.py +454 -0
  436. bootstack/widgets/picture.py +251 -0
  437. bootstack/widgets/progressbar.py +106 -0
  438. bootstack/widgets/radio_variants.py +271 -0
  439. bootstack/widgets/radiogroup.py +228 -0
  440. bootstack/widgets/scrollbar.py +64 -0
  441. bootstack/widgets/scrollview.py +186 -0
  442. bootstack/widgets/select.py +302 -0
  443. bootstack/widgets/selectbutton.py +182 -0
  444. bootstack/widgets/sidebar_toggle.py +130 -0
  445. bootstack/widgets/sizegrip.py +42 -0
  446. bootstack/widgets/slider.py +408 -0
  447. bootstack/widgets/spinbox.py +147 -0
  448. bootstack/widgets/spinnerfield.py +413 -0
  449. bootstack/widgets/splash.py +367 -0
  450. bootstack/widgets/splitview.py +482 -0
  451. bootstack/widgets/stacks.py +249 -0
  452. bootstack/widgets/statusbar.py +189 -0
  453. bootstack/widgets/tabs.py +425 -0
  454. bootstack/widgets/textarea.py +459 -0
  455. bootstack/widgets/textfield.py +410 -0
  456. bootstack/widgets/theme_toggle.py +92 -0
  457. bootstack/widgets/timefield.py +340 -0
  458. bootstack/widgets/toast.py +314 -0
  459. bootstack/widgets/togglegroup.py +231 -0
  460. bootstack/widgets/toolbar.py +347 -0
  461. bootstack/widgets/tooltip.py +94 -0
  462. bootstack/widgets/tree.py +648 -0
  463. bootstack/widgets/types.py +305 -0
  464. bootstack/widgets/window.py +307 -0
  465. bootstack-0.1.0.dist-info/METADATA +301 -0
  466. bootstack-0.1.0.dist-info/RECORD +471 -0
  467. bootstack-0.1.0.dist-info/WHEEL +5 -0
  468. bootstack-0.1.0.dist-info/entry_points.txt +2 -0
  469. bootstack-0.1.0.dist-info/licenses/LICENSE +21 -0
  470. bootstack-0.1.0.dist-info/licenses/NOTICE +10 -0
  471. bootstack-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,615 @@
1
+ # bootstack DataSource Module
2
+
3
+ A flexible, extensible data management system providing unified interfaces for working with data from various sources (memory, databases, files) with built-in support for pagination, filtering, sorting, and CRUD operations.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Architecture Overview](#architecture-overview)
8
+ - [Built-in Implementations](#built-in-implementations)
9
+ - [Creating Custom DataSources](#creating-custom-datasources)
10
+ - [API Reference](#api-reference)
11
+ - [Examples](#examples)
12
+
13
+ ## Architecture Overview
14
+
15
+ The datasource module is built on three key components:
16
+
17
+ ### 1. DataSourceProtocol (types.py)
18
+
19
+ A Protocol (duck-typing interface) that defines the contract all datasources must follow. This enables type checking and IDE support without requiring inheritance.
20
+
21
+ ```python
22
+ from typing import Protocol
23
+
24
+ class DataSourceProtocol(Protocol):
25
+ page_size: int
26
+ def load(self, records): ...
27
+ def page(self, page=None): ...
28
+ # ... other methods
29
+ ```
30
+
31
+ ### 2. BaseDataSource (base.py)
32
+
33
+ An abstract base class that provides:
34
+ - **Abstract method definitions** - Enforces implementation of required methods
35
+ - **Shared utility methods** - Common functionality like type inference and literal parsing
36
+ - **Hook methods** - Extension points for customization (e.g., `_before_create`, `_after_update`)
37
+
38
+ This is the **recommended base class** for creating custom datasources.
39
+
40
+ ### 3. Concrete Implementations
41
+
42
+ Built-in datasources that inherit from `BaseDataSource`:
43
+ - **MemoryDataSource** - Fast in-memory storage
44
+ - **SqliteDataSource** - Persistent SQLite storage
45
+ - **FileDataSource** - File-based storage (CSV, JSON, JSONL, TSV)
46
+
47
+ ## Built-in Implementations
48
+
49
+ ### MemoryDataSource
50
+
51
+ Best for small to medium datasets that fit comfortably in memory.
52
+
53
+ ```python
54
+ from bootstack.data import MemoryDataSource
55
+ from bootstack import col
56
+
57
+ ds = MemoryDataSource(page_size=20)
58
+ ds.load([
59
+ {"name": "Alice", "age": 30, "city": "NYC"},
60
+ {"name": "Bob", "age": 25, "city": "LA"},
61
+ ])
62
+
63
+ ds.where(col("age") >= 25)
64
+ ds.order("name")
65
+ page = ds.page(0)
66
+ ```
67
+
68
+ **Features:**
69
+ - Expressive filtering with the `col` expression API
70
+ - Multi-column sorting
71
+ - O(1) ID lookups via internal index
72
+ - Automatic ID generation
73
+
74
+ ### SqliteDataSource
75
+
76
+ Best for large datasets requiring persistence or SQL capabilities.
77
+
78
+ ```python
79
+ from bootstack.data import SqliteDataSource
80
+
81
+ # Persistent database
82
+ ds = SqliteDataSource("mydata.db", page_size=50)
83
+ ds.load([...])
84
+
85
+ # In-memory database
86
+ ds = SqliteDataSource(":memory:", page_size=50)
87
+ ```
88
+
89
+ **Features:**
90
+ - SQL-native filtering and sorting
91
+ - Persistent storage across sessions
92
+ - Efficient for large datasets
93
+ - Automatic schema inference
94
+
95
+ ### FileDataSource
96
+
97
+ Best for loading data from files with preprocessing needs.
98
+
99
+ ```python
100
+ from bootstack.data import FileDataSource, FileSourceConfig
101
+
102
+ # Simple CSV loading
103
+ ds = FileDataSource("data.csv")
104
+ ds.load()
105
+
106
+ # With transformations
107
+ config = FileSourceConfig(
108
+ column_renames={'old_name': 'new_name'},
109
+ column_types={'age': int, 'salary': float},
110
+ row_filter=lambda r: r['status'] == 'active'
111
+ )
112
+ ds = FileDataSource("data.json", config=config)
113
+ ds.load()
114
+ ```
115
+
116
+ **Features:**
117
+ - Supports CSV, TSV, JSON, JSONL
118
+ - Multiple loading strategies (eager, lazy, chunked, hybrid)
119
+ - Transformation pipeline
120
+ - Progress callbacks for large files
121
+
122
+ ## Creating Custom DataSources
123
+
124
+ ### Method 1: Inherit from BaseDataSource (Recommended)
125
+
126
+ This is the easiest and most common approach. You get utility methods for free and only need to implement storage-specific logic.
127
+
128
+ ```python
129
+ from bootstack.data import BaseDataSource
130
+ from typing import Any, Dict, List, Optional
131
+
132
+ class RedisDataSource(BaseDataSource):
133
+ """Custom datasource backed by Redis."""
134
+
135
+ def __init__(self, redis_client, page_size=10):
136
+ super().__init__(page_size)
137
+ self.redis = redis_client
138
+ self._key_prefix = "myapp:records:"
139
+
140
+ def load(self, records):
141
+ """Store records in Redis."""
142
+ for record in records:
143
+ record_id = record.get('id', self._generate_id())
144
+ key = f"{self._key_prefix}{record_id}"
145
+ self.redis.set(key, json.dumps(record))
146
+ return self
147
+
148
+ def page(self, page=None):
149
+ """Retrieve a page of records."""
150
+ if page is not None:
151
+ self._page = page
152
+
153
+ # Get all keys and paginate
154
+ keys = self.redis.keys(f"{self._key_prefix}*")
155
+ start = self._page * self.page_size
156
+ end = start + self.page_size
157
+
158
+ records = []
159
+ for key in keys[start:end]:
160
+ data = self.redis.get(key)
161
+ records.append(json.loads(data))
162
+
163
+ return records
164
+
165
+ def insert(self, record):
166
+ """Create new record in Redis."""
167
+ record_id = record.get('id', self._generate_id())
168
+ record['id'] = record_id
169
+
170
+ # Use hook for logging/validation
171
+ record = self._before_create(record)
172
+
173
+ key = f"{self._key_prefix}{record_id}"
174
+ self.redis.set(key, json.dumps(record))
175
+
176
+ # Use hook for post-creation tasks
177
+ self._after_create(record_id, record)
178
+
179
+ return record_id
180
+
181
+ def get(self, record_id):
182
+ """Read single record from Redis."""
183
+ key = f"{self._key_prefix}{record_id}"
184
+ data = self.redis.get(key)
185
+ return json.loads(data) if data else None
186
+
187
+ def update(self, record_id, updates):
188
+ """Update record in Redis."""
189
+ record = self.get(record_id)
190
+ if not record:
191
+ return False
192
+
193
+ updates = self._before_update(record_id, updates)
194
+ record.update(updates)
195
+
196
+ key = f"{self._key_prefix}{record_id}"
197
+ self.redis.set(key, json.dumps(record))
198
+
199
+ self._after_update(record_id, updates, True)
200
+ return True
201
+
202
+ def delete(self, record_id):
203
+ """Delete record from Redis."""
204
+ self._before_delete(record_id)
205
+ key = f"{self._key_prefix}{record_id}"
206
+ result = self.redis.delete(key) > 0
207
+ self._after_delete(record_id, result)
208
+ return result
209
+
210
+ # Implement remaining abstract methods...
211
+ # (where, order, next_page, prev_page, has_next_page,
212
+ # count, selection methods, export_csv, etc.)
213
+ ```
214
+
215
+ ### Method 2: Implement the Protocol (Advanced)
216
+
217
+ For maximum flexibility, implement the `DataSourceProtocol` without inheritance. This is useful when you need to integrate with existing classes or have complex inheritance requirements.
218
+
219
+ ```python
220
+ from bootstack.data import DataSourceProtocol
221
+ from typing import List, Dict, Any, Optional
222
+
223
+ class APIDataSource:
224
+ """Datasource that fetches data from a REST API."""
225
+
226
+ def __init__(self, api_url: str, page_size: int = 10):
227
+ self.api_url = api_url
228
+ self.page_size = page_size
229
+ self._page = 0
230
+ self._cache = []
231
+
232
+ def load(self, records):
233
+ """Cache data locally."""
234
+ self._cache = list(records)
235
+ return self
236
+
237
+ def page(self, page: Optional[int] = None) -> List[Dict[str, Any]]:
238
+ """Fetch page from API or cache."""
239
+ if page is not None:
240
+ self._page = page
241
+
242
+ # Make API request with pagination
243
+ response = requests.get(
244
+ self.api_url,
245
+ params={'page': self._page, 'per_page': self.page_size}
246
+ )
247
+ return response.json()['data']
248
+
249
+ # Implement all other protocol methods...
250
+ ```
251
+
252
+ ### Using Utility Methods
253
+
254
+ The `BaseDataSource` provides several utility methods you can use:
255
+
256
+ ```python
257
+ class MyDataSource(BaseDataSource):
258
+ def process_record(self, raw_data: str):
259
+ # Parse literals
260
+ age = self._coerce_literal("25") # Returns int 25
261
+ active = self._coerce_literal("true") # Returns bool True
262
+ name = self._coerce_literal("'Alice'") # Returns str "Alice"
263
+
264
+ # Infer SQL types
265
+ age_type = self._infer_type(25) # Returns "INTEGER"
266
+ name_type = self._infer_type("Alice") # Returns "TEXT"
267
+
268
+ # Validate records
269
+ record = {"name": name, "age": age}
270
+ self._validate_record(record) # Raises ValueError if not dict
271
+
272
+ return record
273
+ ```
274
+
275
+ ### Using Hook Methods
276
+
277
+ Hook methods let you add custom behavior without overriding core logic:
278
+
279
+ ```python
280
+ class AuditedDataSource(BaseDataSource):
281
+ def __init__(self):
282
+ super().__init__()
283
+ self.audit_log = []
284
+
285
+ def _before_create(self, record):
286
+ """Add timestamp before creating."""
287
+ record['created_at'] = datetime.now().isoformat()
288
+ return record
289
+
290
+ def _after_create(self, record_id, record):
291
+ """Log creation to audit trail."""
292
+ self.audit_log.append({
293
+ 'action': 'CREATE',
294
+ 'record_id': record_id,
295
+ 'timestamp': datetime.now()
296
+ })
297
+
298
+ def _before_update(self, record_id, updates):
299
+ """Add modified timestamp."""
300
+ updates['modified_at'] = datetime.now().isoformat()
301
+ return updates
302
+
303
+ def _after_update(self, record_id, updates, success):
304
+ """Log update to audit trail."""
305
+ if success:
306
+ self.audit_log.append({
307
+ 'action': 'UPDATE',
308
+ 'record_id': record_id,
309
+ 'changes': updates,
310
+ 'timestamp': datetime.now()
311
+ })
312
+ ```
313
+
314
+ ## API Reference
315
+
316
+ ### Required Methods (All Implementations)
317
+
318
+ #### Data & View Configuration
319
+ - `load(records)` - Load data into the datasource
320
+ - `where(condition)` - Filter rows by a `col` condition (None clears)
321
+ - `order(*keys)` - Sort by column names ("name", "-age") or `col` specs
322
+
323
+ #### Pagination
324
+ - `page(page=None)` - Get records for specified page
325
+ - `next_page()` - Move to next page
326
+ - `prev_page()` - Move to previous page
327
+ - `has_next_page()` - Check if next page exists
328
+ - `count` - Get total record count
329
+
330
+ #### CRUD Operations
331
+ - `insert(record)` - Create new record, returns ID
332
+ - `get(record_id)` - Read single record by ID
333
+ - `update(record_id, updates)` - Update record fields
334
+ - `delete(record_id)` - Delete record by ID
335
+
336
+ #### Selection Management
337
+ - `is_selected(record_id)` - Check whether a record is selected
338
+ - `select(record_id)` - Mark record as selected
339
+ - `deselect(record_id)` - Unmark record
340
+ - `select_all(current_page_only=False)` - Select all/page records
341
+ - `deselect_all(current_page_only=False)` - Deselect all/page records
342
+ - `selected(page=None)` - Get selected records
343
+ - `selected_count` - Count selected records
344
+
345
+ #### Lifecycle / reorder
346
+ - `reload()` - Re-read from source (no-op for in-memory)
347
+ - `move(record_id, target_index)` - Reorder a record
348
+
349
+ #### Export
350
+ - `export_csv(filepath, include_all=True)` - Export to CSV
351
+
352
+ #### Index-based Access
353
+ - `page_slice(start_index, count)` - Get records by index range
354
+
355
+ ### Utility Methods (Inherited from BaseDataSource)
356
+
357
+ - `_infer_type(value)` - Infer SQL type from Python value
358
+ - `_is_mapping(x)` - Check if value is dict-like
359
+ - `_coerce_literal(s)` - Parse string literal to Python value
360
+ - `_validate_record(record)` - Validate record is a dictionary
361
+
362
+ ### Hook Methods (Inherited from BaseDataSource)
363
+
364
+ Override these to add custom behavior:
365
+
366
+ - `_before_create(record)` - Called before creating record
367
+ - `_after_create(record_id, record)` - Called after creating record
368
+ - `_before_update(record_id, updates)` - Called before updating
369
+ - `_after_update(record_id, updates, success)` - Called after updating
370
+ - `_before_delete(record_id)` - Called before deleting
371
+ - `_after_delete(record_id, success)` - Called after deleting
372
+
373
+ ## Examples
374
+
375
+ ### Example 1: MongoDB DataSource
376
+
377
+ ```python
378
+ from bootstack.data import BaseDataSource
379
+ from pymongo import MongoClient
380
+
381
+ class MongoDataSource(BaseDataSource):
382
+ def __init__(self, connection_string, database, collection, page_size=10):
383
+ super().__init__(page_size)
384
+ self.client = MongoClient(connection_string)
385
+ self.db = self.client[database]
386
+ self.collection = self.db[collection]
387
+ self._filter = {}
388
+ self._sort = []
389
+
390
+ def load(self, records):
391
+ """Insert records into MongoDB."""
392
+ if records:
393
+ self.collection.insert_many(list(records))
394
+ return self
395
+
396
+ def where(self, condition=None):
397
+ """Store the active filter condition (a `col` expression, or None).
398
+
399
+ For a query backend you translate the condition tree (Compare/Match/
400
+ And/Or/... nodes) into a native query. As a simple fallback you can
401
+ evaluate `condition.matches(row)` in Python over fetched rows.
402
+ """
403
+ self._filter = condition
404
+ return self
405
+
406
+ def order(self, *keys):
407
+ """Store the active sort keys (column names; "-name" for descending)."""
408
+ self._sort = [
409
+ (k[1:], -1) if k.startswith("-") else (k, 1)
410
+ for k in keys
411
+ ]
412
+ return self
413
+
414
+ def page(self, page=None):
415
+ if page is not None:
416
+ self._page = page
417
+
418
+ skip = self._page * self.page_size
419
+ cursor = self.collection.find(self._filter).sort(self._sort).skip(skip).limit(self.page_size)
420
+ return list(cursor)
421
+
422
+ def insert(self, record):
423
+ result = self.collection.insert_one(record)
424
+ return result.inserted_id
425
+
426
+ def get(self, record_id):
427
+ return self.collection.find_one({"_id": record_id})
428
+
429
+ def update(self, record_id, updates):
430
+ result = self.collection.update_one({"_id": record_id}, {"$set": updates})
431
+ return result.modified_count > 0
432
+
433
+ def delete(self, record_id):
434
+ result = self.collection.delete_one({"_id": record_id})
435
+ return result.deleted_count > 0
436
+
437
+ # ... implement remaining methods
438
+ ```
439
+
440
+ ### Example 2: Cached API DataSource
441
+
442
+ ```python
443
+ from bootstack.data import BaseDataSource
444
+ import requests
445
+ from functools import lru_cache
446
+ from datetime import datetime, timedelta
447
+
448
+ class CachedAPIDataSource(BaseDataSource):
449
+ def __init__(self, api_url, cache_ttl=300, page_size=10):
450
+ super().__init__(page_size)
451
+ self.api_url = api_url
452
+ self.cache_ttl = cache_ttl
453
+ self._cache = {}
454
+ self._cache_time = {}
455
+
456
+ def _is_cache_valid(self, key):
457
+ """Check if cached data is still valid."""
458
+ if key not in self._cache_time:
459
+ return False
460
+ age = datetime.now() - self._cache_time[key]
461
+ return age < timedelta(seconds=self.cache_ttl)
462
+
463
+ def page(self, page=None):
464
+ if page is not None:
465
+ self._page = page
466
+
467
+ cache_key = f"page_{self._page}"
468
+
469
+ # Return cached data if valid
470
+ if self._is_cache_valid(cache_key):
471
+ return self._cache[cache_key]
472
+
473
+ # Fetch from API
474
+ response = requests.get(
475
+ f"{self.api_url}/records",
476
+ params={'page': self._page, 'per_page': self.page_size}
477
+ )
478
+ data = response.json()
479
+
480
+ # Cache the result
481
+ self._cache[cache_key] = data
482
+ self._cache_time[cache_key] = datetime.now()
483
+
484
+ return data
485
+
486
+ def insert(self, record):
487
+ # Invalidate cache on write
488
+ self._cache.clear()
489
+ self._cache_time.clear()
490
+
491
+ response = requests.post(f"{self.api_url}/records", json=record)
492
+ return response.json()['id']
493
+
494
+ # ... implement remaining methods
495
+ ```
496
+
497
+ ### Example 3: Multi-Source DataSource
498
+
499
+ ```python
500
+ from bootstack.data import BaseDataSource
501
+
502
+ class MultiSourceDataSource(BaseDataSource):
503
+ """Aggregates data from multiple datasources."""
504
+
505
+ def __init__(self, sources, page_size=10):
506
+ super().__init__(page_size)
507
+ self.sources = sources # List of datasources
508
+ self._aggregated_data = []
509
+
510
+ def load(self, records):
511
+ """Not applicable for multi-source."""
512
+ raise NotImplementedError("Use add_source() instead")
513
+
514
+ def add_source(self, datasource):
515
+ """Add a datasource to aggregate."""
516
+ self.sources.append(datasource)
517
+
518
+ def _aggregate_data(self):
519
+ """Combine data from all sources."""
520
+ all_records = []
521
+ for source in self.sources:
522
+ source_records = source.page_slice(0, source.count)
523
+ all_records.extend(source_records)
524
+ return all_records
525
+
526
+ def page(self, page=None):
527
+ if page is not None:
528
+ self._page = page
529
+
530
+ all_data = self._aggregate_data()
531
+ start = self._page * self.page_size
532
+ end = start + self.page_size
533
+ return all_data[start:end]
534
+
535
+ # ... implement remaining methods
536
+ ```
537
+
538
+ ## Best Practices
539
+
540
+ 1. **Always call `super().__init__(page_size)`** in your `__init__` method
541
+ 2. **Use hook methods** for cross-cutting concerns (logging, validation, caching)
542
+ 3. **Inherit utility methods** instead of reimplementing them
543
+ 4. **Handle errors gracefully** - return False/None for failures rather than raising
544
+ 5. **Document your datasource** - explain what backend it uses and any special requirements
545
+ 6. **Test thoroughly** - ensure all protocol methods work correctly
546
+ 7. **Consider performance** - implement efficient filtering and pagination for large datasets
547
+
548
+ ## Testing Your DataSource
549
+
550
+ ```python
551
+ def test_custom_datasource():
552
+ ds = MyCustomDataSource()
553
+
554
+ # Test basic operations
555
+ ds.load([{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}])
556
+ assert ds.count == 2
557
+
558
+ # Test CRUD
559
+ new_id = ds.insert({"name": "Charlie"})
560
+ assert ds.get(new_id) is not None
561
+
562
+ # Test pagination
563
+ page = ds.page(0)
564
+ assert isinstance(page, list)
565
+
566
+ # Test filtering
567
+ from bootstack import col
568
+ ds.where(col("name") == "Alice")
569
+ filtered = ds.page(0)
570
+ assert len(filtered) == 1
571
+
572
+ # Test it's an instance of BaseDataSource
573
+ assert isinstance(ds, BaseDataSource)
574
+ ```
575
+
576
+ ## Migration Guide
577
+
578
+ If you have an existing datasource implementation, here's how to migrate to use `BaseDataSource`:
579
+
580
+ ### Before (Standalone Class)
581
+
582
+ ```python
583
+ class MyDataSource:
584
+ def __init__(self, page_size=10):
585
+ self.page_size = page_size
586
+ self._page = 0
587
+
588
+ @staticmethod
589
+ def _infer_type(value):
590
+ # Your implementation
591
+ pass
592
+ ```
593
+
594
+ ### After (Inheriting BaseDataSource)
595
+
596
+ ```python
597
+ from bootstack.data import BaseDataSource
598
+
599
+ class MyDataSource(BaseDataSource):
600
+ def __init__(self, page_size=10):
601
+ super().__init__(page_size) # Initialize base class
602
+ # BaseDataSource now provides _page and utility methods
603
+
604
+ # Remove _infer_type - inherited from BaseDataSource
605
+ ```
606
+
607
+ ## Support
608
+
609
+ For questions, issues, or feature requests, please visit:
610
+ - GitHub Issues: https://github.com/israel-dryer/bootstack/issues
611
+ - Documentation: https://bootstack.org/
612
+
613
+ ## License
614
+
615
+ This module is part of bootstack and follows the same license terms.
@@ -0,0 +1,78 @@
1
+ """Data source abstraction for bootstack widgets.
2
+
3
+ Provides unified interface for data management with multiple backend implementations:
4
+ - MemoryDataSource: Fast in-memory storage for small to medium datasets
5
+ - SqliteDataSource: Persistent SQLite storage for large datasets
6
+ - FileDataSource: File-based storage with support for CSV, JSON, and various formats
7
+
8
+ All datasources support:
9
+ - Pagination with configurable page size
10
+ - Filtering and sorting with the `col` expression API
11
+ - Full CRUD operations (insert, get, update, delete)
12
+ - Record selection tracking
13
+ - CSV export
14
+
15
+ Usage:
16
+ from bootstack.data import MemoryDataSource, SqliteDataSource, FileDataSource, col
17
+
18
+ # In-memory datasource
19
+ ds = MemoryDataSource(page_size=20)
20
+ ds.load([{"name": "Alice", "age": 30}, ...])
21
+
22
+ # SQLite datasource (persistent)
23
+ db = SqliteDataSource("mydata.db", page_size=50)
24
+ db.load([{"name": "Bob", "age": 25}, ...])
25
+
26
+ # File datasource (CSV, JSON, etc.)
27
+ file_ds = FileDataSource("data.csv", page_size=25)
28
+ file_ds.load()
29
+
30
+ # Common operations (work with all)
31
+ ds.where(col("age") >= 25)
32
+ ds.order("name")
33
+ first = ds.page(0)
34
+ """
35
+
36
+ from bootstack.data.base import BaseDataSource
37
+ from bootstack.data.memory_source import MemoryDataSource
38
+ from bootstack.data.sqlite_source import SqliteDataSource
39
+ from bootstack.data.file_source import FileDataSource, FileSourceConfig
40
+ from bootstack.data.query import col, Column, Condition, SortKey, any_of, all_of
41
+ from bootstack.data.types import DataSourceProtocol, Record, Primitive
42
+ from bootstack.data.readers import (
43
+ register_reader,
44
+ read_records,
45
+ get_reader,
46
+ supported_read_extensions,
47
+ )
48
+ from bootstack.data.writers import (
49
+ register_writer,
50
+ write_records,
51
+ get_writer,
52
+ supported_write_extensions,
53
+ )
54
+
55
+ __all__ = [
56
+ 'BaseDataSource',
57
+ 'MemoryDataSource',
58
+ 'SqliteDataSource',
59
+ 'FileDataSource',
60
+ 'FileSourceConfig',
61
+ 'DataSourceProtocol',
62
+ 'Record',
63
+ 'Primitive',
64
+ 'col',
65
+ 'Column',
66
+ 'Condition',
67
+ 'SortKey',
68
+ 'any_of',
69
+ 'all_of',
70
+ 'register_reader',
71
+ 'read_records',
72
+ 'get_reader',
73
+ 'supported_read_extensions',
74
+ 'register_writer',
75
+ 'write_records',
76
+ 'get_writer',
77
+ 'supported_write_extensions',
78
+ ]