scitex 2.7.3__py3-none-any.whl → 2.10.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 (563) hide show
  1. scitex/__init__.py +15 -7
  2. scitex/__version__.py +1 -2
  3. scitex/_install_guide.py +250 -0
  4. scitex/_optional_deps.py +206 -39
  5. scitex/ai/_gen_ai/_Groq.py +2 -4
  6. scitex/ai/_gen_ai/_OpenAI.py +5 -2
  7. scitex/ai/_gen_ai/_Perplexity.py +20 -6
  8. scitex/audio/__init__.py +24 -15
  9. scitex/audio/_cross_process_lock.py +139 -0
  10. scitex/audio/_mcp_handlers.py +256 -0
  11. scitex/audio/_mcp_tool_schemas.py +203 -0
  12. scitex/audio/engines/elevenlabs_engine.py +5 -2
  13. scitex/audio/mcp_server.py +98 -457
  14. scitex/bridge/__init__.py +30 -19
  15. scitex/bridge/_figrecipe.py +245 -0
  16. scitex/bridge/_helpers.py +2 -1
  17. scitex/bridge/_plt_vis.py +23 -10
  18. scitex/bridge/_stats_plt.py +18 -5
  19. scitex/bridge/_stats_vis.py +16 -2
  20. scitex/browser/__init__.py +84 -44
  21. scitex/browser/automation/__init__.py +5 -1
  22. scitex/browser/core/BrowserMixin.py +17 -4
  23. scitex/browser/core/__init__.py +11 -2
  24. scitex/browser/remote/CaptchaHandler.py +1 -1
  25. scitex/browser/remote/ZenRowsAPIClient.py +1 -1
  26. scitex/capture/grid.py +487 -0
  27. scitex/capture/mcp_handlers.py +401 -0
  28. scitex/capture/mcp_tool_defs.py +192 -0
  29. scitex/capture/mcp_tools.py +241 -0
  30. scitex/capture/mcp_utils.py +30 -0
  31. scitex/cli/convert.py +421 -0
  32. scitex/cli/main.py +6 -4
  33. scitex/datetime/__init__.py +46 -0
  34. scitex/datetime/_linspace.py +100 -0
  35. scitex/datetime/_normalize_timestamp.py +306 -0
  36. scitex/db/_delete_duplicates.py +4 -4
  37. scitex/db/_sqlite3/_delete_duplicates.py +11 -2
  38. scitex/dev/plt/__init__.py +61 -62
  39. scitex/dev/plt/demo_plotters/__init__.py +0 -0
  40. scitex/dev/plt/demo_plotters/plot_mpl_axhline.py +28 -0
  41. scitex/dev/plt/demo_plotters/plot_mpl_axhspan.py +28 -0
  42. scitex/dev/plt/demo_plotters/plot_mpl_axvline.py +28 -0
  43. scitex/dev/plt/demo_plotters/plot_mpl_axvspan.py +28 -0
  44. scitex/dev/plt/demo_plotters/plot_mpl_bar.py +29 -0
  45. scitex/dev/plt/demo_plotters/plot_mpl_barh.py +29 -0
  46. scitex/dev/plt/demo_plotters/plot_mpl_boxplot.py +28 -0
  47. scitex/dev/plt/demo_plotters/plot_mpl_contour.py +31 -0
  48. scitex/dev/plt/demo_plotters/plot_mpl_contourf.py +31 -0
  49. scitex/dev/plt/demo_plotters/plot_mpl_errorbar.py +30 -0
  50. scitex/dev/plt/demo_plotters/plot_mpl_eventplot.py +28 -0
  51. scitex/dev/plt/demo_plotters/plot_mpl_fill.py +30 -0
  52. scitex/dev/plt/demo_plotters/plot_mpl_fill_between.py +31 -0
  53. scitex/dev/plt/demo_plotters/plot_mpl_hexbin.py +28 -0
  54. scitex/dev/plt/demo_plotters/plot_mpl_hist.py +28 -0
  55. scitex/dev/plt/demo_plotters/plot_mpl_hist2d.py +28 -0
  56. scitex/dev/plt/demo_plotters/plot_mpl_imshow.py +29 -0
  57. scitex/dev/plt/demo_plotters/plot_mpl_pcolormesh.py +31 -0
  58. scitex/dev/plt/demo_plotters/plot_mpl_pie.py +29 -0
  59. scitex/dev/plt/demo_plotters/plot_mpl_plot.py +29 -0
  60. scitex/dev/plt/demo_plotters/plot_mpl_quiver.py +31 -0
  61. scitex/dev/plt/demo_plotters/plot_mpl_scatter.py +28 -0
  62. scitex/dev/plt/demo_plotters/plot_mpl_stackplot.py +31 -0
  63. scitex/dev/plt/demo_plotters/plot_mpl_stem.py +29 -0
  64. scitex/dev/plt/demo_plotters/plot_mpl_step.py +29 -0
  65. scitex/dev/plt/demo_plotters/plot_mpl_violinplot.py +28 -0
  66. scitex/dev/plt/demo_plotters/plot_sns_barplot.py +29 -0
  67. scitex/dev/plt/demo_plotters/plot_sns_boxplot.py +29 -0
  68. scitex/dev/plt/demo_plotters/plot_sns_heatmap.py +28 -0
  69. scitex/dev/plt/demo_plotters/plot_sns_histplot.py +29 -0
  70. scitex/dev/plt/demo_plotters/plot_sns_kdeplot.py +29 -0
  71. scitex/dev/plt/demo_plotters/plot_sns_lineplot.py +31 -0
  72. scitex/dev/plt/demo_plotters/plot_sns_scatterplot.py +29 -0
  73. scitex/dev/plt/demo_plotters/plot_sns_stripplot.py +29 -0
  74. scitex/dev/plt/demo_plotters/plot_sns_swarmplot.py +29 -0
  75. scitex/dev/plt/demo_plotters/plot_sns_violinplot.py +29 -0
  76. scitex/dev/plt/demo_plotters/plot_stx_bar.py +29 -0
  77. scitex/dev/plt/demo_plotters/plot_stx_barh.py +29 -0
  78. scitex/dev/plt/demo_plotters/plot_stx_box.py +28 -0
  79. scitex/dev/plt/demo_plotters/plot_stx_boxplot.py +28 -0
  80. scitex/dev/plt/demo_plotters/plot_stx_conf_mat.py +28 -0
  81. scitex/dev/plt/demo_plotters/plot_stx_contour.py +31 -0
  82. scitex/dev/plt/demo_plotters/plot_stx_ecdf.py +28 -0
  83. scitex/dev/plt/demo_plotters/plot_stx_errorbar.py +30 -0
  84. scitex/dev/plt/demo_plotters/plot_stx_fill_between.py +31 -0
  85. scitex/dev/plt/demo_plotters/plot_stx_fillv.py +28 -0
  86. scitex/dev/plt/demo_plotters/plot_stx_heatmap.py +28 -0
  87. scitex/dev/plt/demo_plotters/plot_stx_image.py +28 -0
  88. scitex/dev/plt/demo_plotters/plot_stx_imshow.py +28 -0
  89. scitex/dev/plt/demo_plotters/plot_stx_joyplot.py +28 -0
  90. scitex/dev/plt/demo_plotters/plot_stx_kde.py +28 -0
  91. scitex/dev/plt/demo_plotters/plot_stx_line.py +28 -0
  92. scitex/dev/plt/demo_plotters/plot_stx_mean_ci.py +28 -0
  93. scitex/dev/plt/demo_plotters/plot_stx_mean_std.py +28 -0
  94. scitex/dev/plt/demo_plotters/plot_stx_median_iqr.py +28 -0
  95. scitex/dev/plt/demo_plotters/plot_stx_raster.py +28 -0
  96. scitex/dev/plt/demo_plotters/plot_stx_rectangle.py +28 -0
  97. scitex/dev/plt/demo_plotters/plot_stx_scatter.py +29 -0
  98. scitex/dev/plt/demo_plotters/plot_stx_shaded_line.py +29 -0
  99. scitex/dev/plt/demo_plotters/plot_stx_violin.py +28 -0
  100. scitex/dev/plt/demo_plotters/plot_stx_violinplot.py +28 -0
  101. scitex/dev/plt/mpl/get_dir_ax.py +46 -0
  102. scitex/dev/plt/mpl/get_signatures.py +176 -0
  103. scitex/dev/plt/mpl/get_signatures_details.py +522 -0
  104. scitex/dev/plt/plot_mpl_axhline.py +0 -0
  105. scitex/dev/plt/plot_mpl_axhspan.py +0 -0
  106. scitex/dev/plt/plot_mpl_axvline.py +0 -0
  107. scitex/dev/plt/plot_mpl_axvspan.py +0 -0
  108. scitex/dev/plt/plot_mpl_bar.py +0 -0
  109. scitex/dev/plt/plot_mpl_barh.py +0 -0
  110. scitex/dev/plt/plot_mpl_boxplot.py +0 -0
  111. scitex/dev/plt/plot_mpl_contour.py +0 -0
  112. scitex/dev/plt/plot_mpl_contourf.py +0 -0
  113. scitex/dev/plt/plot_mpl_errorbar.py +0 -0
  114. scitex/dev/plt/plot_mpl_eventplot.py +0 -0
  115. scitex/dev/plt/plot_mpl_fill.py +0 -0
  116. scitex/dev/plt/plot_mpl_fill_between.py +0 -0
  117. scitex/dev/plt/plot_mpl_hexbin.py +0 -0
  118. scitex/dev/plt/plot_mpl_hist.py +0 -0
  119. scitex/dev/plt/plot_mpl_hist2d.py +0 -0
  120. scitex/dev/plt/plot_mpl_imshow.py +0 -0
  121. scitex/dev/plt/plot_mpl_pcolormesh.py +0 -0
  122. scitex/dev/plt/plot_mpl_pie.py +0 -0
  123. scitex/dev/plt/plot_mpl_plot.py +0 -0
  124. scitex/dev/plt/plot_mpl_quiver.py +0 -0
  125. scitex/dev/plt/plot_mpl_scatter.py +0 -0
  126. scitex/dev/plt/plot_mpl_stackplot.py +0 -0
  127. scitex/dev/plt/plot_mpl_stem.py +0 -0
  128. scitex/dev/plt/plot_mpl_step.py +0 -0
  129. scitex/dev/plt/plot_mpl_violinplot.py +0 -0
  130. scitex/dev/plt/plot_sns_barplot.py +0 -0
  131. scitex/dev/plt/plot_sns_boxplot.py +0 -0
  132. scitex/dev/plt/plot_sns_heatmap.py +0 -0
  133. scitex/dev/plt/plot_sns_histplot.py +0 -0
  134. scitex/dev/plt/plot_sns_kdeplot.py +0 -0
  135. scitex/dev/plt/plot_sns_lineplot.py +0 -0
  136. scitex/dev/plt/plot_sns_scatterplot.py +0 -0
  137. scitex/dev/plt/plot_sns_stripplot.py +0 -0
  138. scitex/dev/plt/plot_sns_swarmplot.py +0 -0
  139. scitex/dev/plt/plot_sns_violinplot.py +0 -0
  140. scitex/dev/plt/plot_stx_bar.py +0 -0
  141. scitex/dev/plt/plot_stx_barh.py +0 -0
  142. scitex/dev/plt/plot_stx_box.py +0 -0
  143. scitex/dev/plt/plot_stx_boxplot.py +0 -0
  144. scitex/dev/plt/plot_stx_conf_mat.py +0 -0
  145. scitex/dev/plt/plot_stx_contour.py +0 -0
  146. scitex/dev/plt/plot_stx_ecdf.py +0 -0
  147. scitex/dev/plt/plot_stx_errorbar.py +0 -0
  148. scitex/dev/plt/plot_stx_fill_between.py +0 -0
  149. scitex/dev/plt/plot_stx_fillv.py +0 -0
  150. scitex/dev/plt/plot_stx_heatmap.py +0 -0
  151. scitex/dev/plt/plot_stx_image.py +0 -0
  152. scitex/dev/plt/plot_stx_imshow.py +0 -0
  153. scitex/dev/plt/plot_stx_joyplot.py +0 -0
  154. scitex/dev/plt/plot_stx_kde.py +0 -0
  155. scitex/dev/plt/plot_stx_line.py +0 -0
  156. scitex/dev/plt/plot_stx_mean_ci.py +0 -0
  157. scitex/dev/plt/plot_stx_mean_std.py +0 -0
  158. scitex/dev/plt/plot_stx_median_iqr.py +0 -0
  159. scitex/dev/plt/plot_stx_raster.py +0 -0
  160. scitex/dev/plt/plot_stx_rectangle.py +0 -0
  161. scitex/dev/plt/plot_stx_scatter.py +0 -0
  162. scitex/dev/plt/plot_stx_shaded_line.py +0 -0
  163. scitex/dev/plt/plot_stx_violin.py +0 -0
  164. scitex/dev/plt/plot_stx_violinplot.py +0 -0
  165. scitex/diagram/README.md +197 -0
  166. scitex/diagram/__init__.py +48 -0
  167. scitex/diagram/_compile.py +312 -0
  168. scitex/diagram/_diagram.py +355 -0
  169. scitex/diagram/_presets.py +173 -0
  170. scitex/diagram/_schema.py +182 -0
  171. scitex/diagram/_split.py +278 -0
  172. scitex/dict/_pop_keys.py +1 -7
  173. scitex/dsp/__init__.py +15 -10
  174. scitex/dsp/add_noise.py +5 -2
  175. scitex/dsp/example.py +35 -22
  176. scitex/dsp/filt.py +8 -3
  177. scitex/dsp/reference.py +3 -2
  178. scitex/dsp/utils/__init__.py +2 -1
  179. scitex/dsp/utils/_differential_bandpass_filters.py +14 -4
  180. scitex/dt/__init__.py +39 -2
  181. scitex/errors.py +82 -521
  182. scitex/fig/__init__.py +4 -4
  183. scitex/fig/editor/__init__.py +5 -2
  184. scitex/fig/editor/_dearpygui_editor.py +1 -1
  185. scitex/fig/editor/_mpl_editor.py +1 -1
  186. scitex/fig/editor/_qt_editor.py +1 -1
  187. scitex/fig/editor/_tkinter_editor.py +1 -1
  188. scitex/fig/editor/edit/__init__.py +50 -0
  189. scitex/fig/editor/edit/backend_detector.py +109 -0
  190. scitex/fig/editor/edit/bundle_resolver.py +240 -0
  191. scitex/fig/editor/edit/editor_launcher.py +239 -0
  192. scitex/fig/editor/edit/manual_handler.py +53 -0
  193. scitex/fig/editor/edit/panel_loader.py +232 -0
  194. scitex/fig/editor/edit/path_resolver.py +67 -0
  195. scitex/fig/editor/flask_editor/_bbox.py +23 -0
  196. scitex/fig/editor/flask_editor/_core.py +908 -103
  197. scitex/fig/editor/flask_editor/_renderer.py +74 -0
  198. scitex/fig/editor/flask_editor/static/css/base/reset.css +41 -0
  199. scitex/fig/editor/flask_editor/static/css/base/typography.css +16 -0
  200. scitex/fig/editor/flask_editor/static/css/base/variables.css +85 -0
  201. scitex/fig/editor/flask_editor/static/css/components/buttons.css +217 -0
  202. scitex/fig/editor/flask_editor/static/css/components/context-menu.css +93 -0
  203. scitex/fig/editor/flask_editor/static/css/components/dropdown.css +57 -0
  204. scitex/fig/editor/flask_editor/static/css/components/forms.css +112 -0
  205. scitex/fig/editor/flask_editor/static/css/components/modal.css +59 -0
  206. scitex/fig/editor/flask_editor/static/css/components/sections.css +212 -0
  207. scitex/fig/editor/flask_editor/static/css/features/canvas.css +176 -0
  208. scitex/fig/editor/flask_editor/static/css/features/element-inspector.css +190 -0
  209. scitex/fig/editor/flask_editor/static/css/features/loading.css +59 -0
  210. scitex/fig/editor/flask_editor/static/css/features/overlay.css +45 -0
  211. scitex/fig/editor/flask_editor/static/css/features/panel-grid.css +95 -0
  212. scitex/fig/editor/flask_editor/static/css/features/selection.css +101 -0
  213. scitex/fig/editor/flask_editor/static/css/features/statistics.css +138 -0
  214. scitex/fig/editor/flask_editor/static/css/index.css +31 -0
  215. scitex/fig/editor/flask_editor/static/css/layout/container.css +7 -0
  216. scitex/fig/editor/flask_editor/static/css/layout/controls.css +56 -0
  217. scitex/fig/editor/flask_editor/static/css/layout/preview.css +78 -0
  218. scitex/fig/editor/flask_editor/static/js/alignment/axis.js +314 -0
  219. scitex/fig/editor/flask_editor/static/js/alignment/basic.js +107 -0
  220. scitex/fig/editor/flask_editor/static/js/alignment/distribute.js +54 -0
  221. scitex/fig/editor/flask_editor/static/js/canvas/canvas.js +172 -0
  222. scitex/fig/editor/flask_editor/static/js/canvas/dragging.js +258 -0
  223. scitex/fig/editor/flask_editor/static/js/canvas/resize.js +48 -0
  224. scitex/fig/editor/flask_editor/static/js/canvas/selection.js +71 -0
  225. scitex/fig/editor/flask_editor/static/js/core/api.js +288 -0
  226. scitex/fig/editor/flask_editor/static/js/core/state.js +143 -0
  227. scitex/fig/editor/flask_editor/static/js/core/utils.js +245 -0
  228. scitex/fig/editor/flask_editor/static/js/dev/element-inspector.js +992 -0
  229. scitex/fig/editor/flask_editor/static/js/editor/bbox.js +339 -0
  230. scitex/fig/editor/flask_editor/static/js/editor/element-drag.js +286 -0
  231. scitex/fig/editor/flask_editor/static/js/editor/overlay.js +371 -0
  232. scitex/fig/editor/flask_editor/static/js/editor/preview.js +293 -0
  233. scitex/fig/editor/flask_editor/static/js/main.js +426 -0
  234. scitex/fig/editor/flask_editor/static/js/shortcuts/context-menu.js +152 -0
  235. scitex/fig/editor/flask_editor/static/js/shortcuts/keyboard.js +265 -0
  236. scitex/fig/editor/flask_editor/static/js/ui/controls.js +184 -0
  237. scitex/fig/editor/flask_editor/static/js/ui/download.js +57 -0
  238. scitex/fig/editor/flask_editor/static/js/ui/help.js +100 -0
  239. scitex/fig/editor/flask_editor/static/js/ui/theme.js +34 -0
  240. scitex/fig/editor/flask_editor/templates/__init__.py +95 -5
  241. scitex/fig/editor/flask_editor/templates/_html.py +27 -9
  242. scitex/fig/editor/flask_editor/templates/_scripts.py +1928 -131
  243. scitex/fig/editor/flask_editor/templates/_styles.py +363 -51
  244. scitex/fig/io/_bundle.py +104 -19
  245. scitex/fts/README.md +262 -0
  246. scitex/fts/TODO.md +66 -0
  247. scitex/fts/__init__.py +90 -0
  248. scitex/fts/_bundle/README_IN_BUNDLE.md +102 -0
  249. scitex/fts/_bundle/_FTS.py +657 -0
  250. scitex/fts/_bundle/__init__.py +38 -0
  251. scitex/fts/_bundle/_children.py +216 -0
  252. scitex/fts/_bundle/_conversion/__init__.py +15 -0
  253. scitex/fts/_bundle/_conversion/_bundle2dict.py +44 -0
  254. scitex/fts/_bundle/_conversion/_dict2bundle.py +50 -0
  255. scitex/fts/_bundle/_dataclasses/_Axes.py +57 -0
  256. scitex/fts/_bundle/_dataclasses/_BBox.py +54 -0
  257. scitex/fts/_bundle/_dataclasses/_ColumnDef.py +72 -0
  258. scitex/fts/_bundle/_dataclasses/_DataFormat.py +40 -0
  259. scitex/fts/_bundle/_dataclasses/_DataInfo.py +135 -0
  260. scitex/fts/_bundle/_dataclasses/_DataSource.py +44 -0
  261. scitex/fts/_bundle/_dataclasses/_Node.py +319 -0
  262. scitex/fts/_bundle/_dataclasses/_NodeRefs.py +45 -0
  263. scitex/fts/_bundle/_dataclasses/_SizeMM.py +38 -0
  264. scitex/fts/_bundle/_dataclasses/__init__.py +35 -0
  265. scitex/fts/_bundle/_extractors/__init__.py +32 -0
  266. scitex/fts/_bundle/_extractors/_extract_bar.py +131 -0
  267. scitex/fts/_bundle/_extractors/_extract_line.py +71 -0
  268. scitex/fts/_bundle/_extractors/_extract_scatter.py +79 -0
  269. scitex/fts/_bundle/_loader.py +134 -0
  270. scitex/fts/_bundle/_mpl_helpers.py +389 -0
  271. scitex/fts/_bundle/_saver.py +269 -0
  272. scitex/fts/_bundle/_storage.py +200 -0
  273. scitex/fts/_bundle/_utils/__init__.py +55 -0
  274. scitex/fts/_bundle/_utils/_const.py +26 -0
  275. scitex/fts/_bundle/_utils/_errors.py +73 -0
  276. scitex/fts/_bundle/_utils/_generate.py +21 -0
  277. scitex/fts/_bundle/_utils/_types.py +76 -0
  278. scitex/fts/_bundle/_validation.py +434 -0
  279. scitex/fts/_bundle/_zipbundle.py +165 -0
  280. scitex/fts/_fig/__init__.py +22 -0
  281. scitex/fts/_fig/_backend/__init__.py +53 -0
  282. scitex/fts/_fig/_backend/_export.py +165 -0
  283. scitex/fts/_fig/_backend/_parser.py +188 -0
  284. scitex/fts/_fig/_backend/_render.py +538 -0
  285. scitex/fts/_fig/_composite.py +345 -0
  286. scitex/fts/_fig/_dataclasses/_ChannelEncoding.py +46 -0
  287. scitex/fts/_fig/_dataclasses/_Encoding.py +82 -0
  288. scitex/fts/_fig/_dataclasses/_Theme.py +441 -0
  289. scitex/fts/_fig/_dataclasses/_TraceEncoding.py +52 -0
  290. scitex/fts/_fig/_dataclasses/__init__.py +47 -0
  291. scitex/fts/_fig/_editor/__init__.py +14 -0
  292. scitex/fts/_fig/_editor/_cui/__init__.py +33 -0
  293. scitex/fts/_fig/_editor/_cui/_backend_detector.py +39 -0
  294. scitex/fts/_fig/_editor/_cui/_bundle_resolver.py +366 -0
  295. scitex/fts/_fig/_editor/_cui/_editor_launcher.py +175 -0
  296. scitex/fts/_fig/_editor/_cui/_manual_handler.py +52 -0
  297. scitex/fts/_fig/_editor/_cui/_panel_loader.py +246 -0
  298. scitex/fts/_fig/_editor/_cui/_path_resolver.py +66 -0
  299. scitex/fts/_fig/_editor/_defaults.py +300 -0
  300. scitex/fts/_fig/_editor/_gui/__init__.py +11 -0
  301. scitex/fts/_fig/_editor/_gui/_flask_editor/__init__.py +20 -0
  302. scitex/fts/_fig/_editor/_gui/_flask_editor/_bbox.py +1339 -0
  303. scitex/fts/_fig/_editor/_gui/_flask_editor/_core.py +1688 -0
  304. scitex/fts/_fig/_editor/_gui/_flask_editor/_plotter.py +664 -0
  305. scitex/fts/_fig/_editor/_gui/_flask_editor/_renderer.py +853 -0
  306. scitex/fts/_fig/_editor/_gui/_flask_editor/_utils.py +79 -0
  307. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/reset.css +41 -0
  308. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/typography.css +16 -0
  309. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/variables.css +85 -0
  310. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/buttons.css +217 -0
  311. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/context-menu.css +93 -0
  312. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/dropdown.css +57 -0
  313. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/forms.css +112 -0
  314. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/modal.css +59 -0
  315. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/sections.css +212 -0
  316. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/canvas.css +176 -0
  317. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/element-inspector.css +190 -0
  318. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/loading.css +59 -0
  319. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/overlay.css +45 -0
  320. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/panel-grid.css +95 -0
  321. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/selection.css +101 -0
  322. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/statistics.css +138 -0
  323. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/index.css +31 -0
  324. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/container.css +7 -0
  325. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/controls.css +56 -0
  326. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/preview.css +78 -0
  327. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/axis.js +314 -0
  328. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/basic.js +107 -0
  329. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/distribute.js +54 -0
  330. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/canvas.js +172 -0
  331. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/dragging.js +258 -0
  332. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/resize.js +48 -0
  333. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/selection.js +71 -0
  334. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/api.js +288 -0
  335. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/state.js +143 -0
  336. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/utils.js +245 -0
  337. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/dev/element-inspector.js +992 -0
  338. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/bbox.js +339 -0
  339. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/element-drag.js +286 -0
  340. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/overlay.js +371 -0
  341. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/preview.js +293 -0
  342. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/main.js +426 -0
  343. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/context-menu.js +152 -0
  344. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/keyboard.js +265 -0
  345. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/controls.js +184 -0
  346. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/download.js +57 -0
  347. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/help.js +100 -0
  348. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/theme.js +34 -0
  349. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/__init__.py +124 -0
  350. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_html.py +851 -0
  351. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_scripts.py +4932 -0
  352. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_styles.py +1657 -0
  353. scitex/fts/_fig/_editor/_gui/_flask_editor.py +36 -0
  354. scitex/fts/_fig/_models/_Annotations.py +115 -0
  355. scitex/fts/_fig/_models/_Axes.py +152 -0
  356. scitex/fts/_fig/_models/_Figure.py +138 -0
  357. scitex/fts/_fig/_models/_Guides.py +104 -0
  358. scitex/fts/_fig/_models/_Plot.py +123 -0
  359. scitex/fts/_fig/_models/_Styles.py +245 -0
  360. scitex/fts/_fig/_models/__init__.py +80 -0
  361. scitex/fts/_fig/_models/_plot_types/__init__.py +156 -0
  362. scitex/fts/_fig/_models/_plot_types/_bar.py +43 -0
  363. scitex/fts/_fig/_models/_plot_types/_box.py +38 -0
  364. scitex/fts/_fig/_models/_plot_types/_distribution.py +36 -0
  365. scitex/fts/_fig/_models/_plot_types/_errorbar.py +60 -0
  366. scitex/fts/_fig/_models/_plot_types/_histogram.py +30 -0
  367. scitex/fts/_fig/_models/_plot_types/_image.py +61 -0
  368. scitex/fts/_fig/_models/_plot_types/_line.py +57 -0
  369. scitex/fts/_fig/_models/_plot_types/_scatter.py +30 -0
  370. scitex/fts/_fig/_models/_plot_types/_seaborn.py +121 -0
  371. scitex/fts/_fig/_models/_plot_types/_violin.py +36 -0
  372. scitex/fts/_fig/_utils/__init__.py +129 -0
  373. scitex/fts/_fig/_utils/_auto_layout.py +127 -0
  374. scitex/fts/_fig/_utils/_calc_bounds.py +111 -0
  375. scitex/fts/_fig/_utils/_const_sizes.py +48 -0
  376. scitex/fts/_fig/_utils/_convert_coords.py +77 -0
  377. scitex/fts/_fig/_utils/_get_template.py +178 -0
  378. scitex/fts/_fig/_utils/_normalize.py +73 -0
  379. scitex/fts/_fig/_utils/_plot_layout.py +397 -0
  380. scitex/fts/_fig/_utils/_validate.py +197 -0
  381. scitex/fts/_kinds/__init__.py +45 -0
  382. scitex/fts/_kinds/_figure/__init__.py +19 -0
  383. scitex/fts/_kinds/_figure/_composite.py +345 -0
  384. scitex/fts/_kinds/_plot/__init__.py +25 -0
  385. scitex/fts/_kinds/_plot/_backend/__init__.py +53 -0
  386. scitex/fts/_kinds/_plot/_backend/_export.py +165 -0
  387. scitex/fts/_kinds/_plot/_backend/_parser.py +188 -0
  388. scitex/fts/_kinds/_plot/_backend/_render.py +538 -0
  389. scitex/fts/_kinds/_plot/_dataclasses/_ChannelEncoding.py +46 -0
  390. scitex/fts/_kinds/_plot/_dataclasses/_Encoding.py +82 -0
  391. scitex/fts/_kinds/_plot/_dataclasses/_Theme.py +441 -0
  392. scitex/fts/_kinds/_plot/_dataclasses/_TraceEncoding.py +52 -0
  393. scitex/fts/_kinds/_plot/_dataclasses/__init__.py +47 -0
  394. scitex/fts/_kinds/_plot/_models/_Annotations.py +115 -0
  395. scitex/fts/_kinds/_plot/_models/_Axes.py +152 -0
  396. scitex/fts/_kinds/_plot/_models/_Figure.py +138 -0
  397. scitex/fts/_kinds/_plot/_models/_Guides.py +104 -0
  398. scitex/fts/_kinds/_plot/_models/_Plot.py +123 -0
  399. scitex/fts/_kinds/_plot/_models/_Styles.py +245 -0
  400. scitex/fts/_kinds/_plot/_models/__init__.py +80 -0
  401. scitex/fts/_kinds/_plot/_models/_plot_types/__init__.py +156 -0
  402. scitex/fts/_kinds/_plot/_models/_plot_types/_bar.py +43 -0
  403. scitex/fts/_kinds/_plot/_models/_plot_types/_box.py +38 -0
  404. scitex/fts/_kinds/_plot/_models/_plot_types/_distribution.py +36 -0
  405. scitex/fts/_kinds/_plot/_models/_plot_types/_errorbar.py +60 -0
  406. scitex/fts/_kinds/_plot/_models/_plot_types/_histogram.py +30 -0
  407. scitex/fts/_kinds/_plot/_models/_plot_types/_image.py +61 -0
  408. scitex/fts/_kinds/_plot/_models/_plot_types/_line.py +57 -0
  409. scitex/fts/_kinds/_plot/_models/_plot_types/_scatter.py +30 -0
  410. scitex/fts/_kinds/_plot/_models/_plot_types/_seaborn.py +121 -0
  411. scitex/fts/_kinds/_plot/_models/_plot_types/_violin.py +36 -0
  412. scitex/fts/_kinds/_plot/_utils/__init__.py +129 -0
  413. scitex/fts/_kinds/_plot/_utils/_auto_layout.py +127 -0
  414. scitex/fts/_kinds/_plot/_utils/_calc_bounds.py +111 -0
  415. scitex/fts/_kinds/_plot/_utils/_const_sizes.py +48 -0
  416. scitex/fts/_kinds/_plot/_utils/_convert_coords.py +77 -0
  417. scitex/fts/_kinds/_plot/_utils/_get_template.py +178 -0
  418. scitex/fts/_kinds/_plot/_utils/_normalize.py +73 -0
  419. scitex/fts/_kinds/_plot/_utils/_plot_layout.py +397 -0
  420. scitex/fts/_kinds/_plot/_utils/_validate.py +197 -0
  421. scitex/fts/_kinds/_shape/__init__.py +141 -0
  422. scitex/fts/_kinds/_stats/__init__.py +56 -0
  423. scitex/fts/_kinds/_stats/_dataclasses/_Stats.py +423 -0
  424. scitex/fts/_kinds/_stats/_dataclasses/__init__.py +48 -0
  425. scitex/fts/_kinds/_table/__init__.py +72 -0
  426. scitex/fts/_kinds/_table/_latex/__init__.py +93 -0
  427. scitex/fts/_kinds/_table/_latex/_editor/__init__.py +11 -0
  428. scitex/fts/_kinds/_table/_latex/_editor/_app.py +725 -0
  429. scitex/fts/_kinds/_table/_latex/_export.py +279 -0
  430. scitex/fts/_kinds/_table/_latex/_figure_exporter.py +153 -0
  431. scitex/fts/_kinds/_table/_latex/_stats_formatter.py +274 -0
  432. scitex/fts/_kinds/_table/_latex/_table_exporter.py +362 -0
  433. scitex/fts/_kinds/_table/_latex/_utils.py +369 -0
  434. scitex/fts/_kinds/_table/_latex/_validator.py +445 -0
  435. scitex/fts/_kinds/_text/__init__.py +77 -0
  436. scitex/fts/_schemas/data_info.schema.json +75 -0
  437. scitex/fts/_schemas/encoding.schema.json +90 -0
  438. scitex/fts/_schemas/node.schema.json +145 -0
  439. scitex/fts/_schemas/render_manifest.schema.json +62 -0
  440. scitex/fts/_schemas/stats.schema.json +132 -0
  441. scitex/fts/_schemas/theme.schema.json +141 -0
  442. scitex/fts/_stats/__init__.py +48 -0
  443. scitex/fts/_stats/_dataclasses/_Stats.py +423 -0
  444. scitex/fts/_stats/_dataclasses/__init__.py +48 -0
  445. scitex/fts/_tables/__init__.py +65 -0
  446. scitex/fts/_tables/_latex/__init__.py +93 -0
  447. scitex/fts/_tables/_latex/_editor/__init__.py +11 -0
  448. scitex/fts/_tables/_latex/_editor/_app.py +725 -0
  449. scitex/fts/_tables/_latex/_export.py +279 -0
  450. scitex/fts/_tables/_latex/_figure_exporter.py +153 -0
  451. scitex/fts/_tables/_latex/_stats_formatter.py +274 -0
  452. scitex/fts/_tables/_latex/_table_exporter.py +362 -0
  453. scitex/fts/_tables/_latex/_utils.py +369 -0
  454. scitex/fts/_tables/_latex/_validator.py +445 -0
  455. scitex/gen/__init__.py +66 -25
  456. scitex/gen/misc.py +28 -0
  457. scitex/io/__init__.py +47 -20
  458. scitex/io/_load.py +87 -36
  459. scitex/io/_load_modules/__init__.py +10 -7
  460. scitex/io/_load_modules/_pandas.py +6 -1
  461. scitex/io/_save.py +299 -1556
  462. scitex/io/_save_modules/__init__.py +76 -19
  463. scitex/io/_save_modules/_figure_utils.py +90 -0
  464. scitex/io/_save_modules/_image_csv.py +497 -0
  465. scitex/io/_save_modules/_legends.py +91 -0
  466. scitex/io/_save_modules/_pltz_bundle.py +356 -0
  467. scitex/io/_save_modules/_pltz_stx.py +536 -0
  468. scitex/io/_save_modules/_stx_bundle.py +104 -0
  469. scitex/io/_save_modules/_symlink.py +96 -0
  470. scitex/io/_save_modules/_yaml.py +1 -1
  471. scitex/io/_save_modules/_zarr.py +64 -18
  472. scitex/io/bundle/README.md +212 -0
  473. scitex/io/bundle/__init__.py +110 -0
  474. scitex/io/{_bundle.py → bundle/_core.py} +219 -89
  475. scitex/io/bundle/_nested.py +713 -0
  476. scitex/io/bundle/_types.py +74 -0
  477. scitex/io/bundle/_zip.py +487 -0
  478. scitex/io/utils/h5_to_zarr.py +1 -1
  479. scitex/logging/__init__.py +108 -13
  480. scitex/logging/_errors.py +508 -0
  481. scitex/logging/_formatters.py +30 -6
  482. scitex/logging/_warnings.py +261 -0
  483. scitex/plt/__init__.py +4 -1
  484. scitex/plt/_figrecipe.py +236 -0
  485. scitex/plt/_subplots/_AxisWrapper.py +6 -0
  486. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/__init__.py +0 -0
  487. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_labels.py +0 -0
  488. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_metadata.py +0 -0
  489. scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_visual.py +0 -0
  490. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +0 -0
  491. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_base.py +0 -0
  492. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_scientific.py +0 -0
  493. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_statistical.py +0 -0
  494. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_stx_aliases.py +0 -0
  495. scitex/plt/_subplots/_AxisWrapperMixins/_RawMatplotlibMixin.py +0 -0
  496. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/__init__.py +0 -0
  497. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_base.py +0 -0
  498. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +0 -0
  499. scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +112 -1
  500. scitex/plt/_subplots/_FigWrapper.py +15 -0
  501. scitex/plt/_subplots/_SubplotsWrapper.py +125 -489
  502. scitex/plt/_subplots/_export_as_csv.py +11 -0
  503. scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +2 -0
  504. scitex/plt/_subplots/_export_as_csv_formatters/_format_pcolormesh.py +66 -0
  505. scitex/plt/_subplots/_export_as_csv_formatters/_format_stackplot.py +62 -0
  506. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_bar.py +0 -0
  507. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_barh.py +0 -0
  508. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_errorbar.py +0 -0
  509. scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter.py +0 -0
  510. scitex/plt/_subplots/_export_as_csv_formatters/test_formatters.py +208 -0
  511. scitex/plt/_subplots/_fonts.py +71 -0
  512. scitex/plt/_subplots/_mm_layout.py +282 -0
  513. scitex/plt/gallery/__init__.py +99 -2
  514. scitex/plt/io/_layered_bundle.py +0 -0
  515. scitex/plt/styles/_plot_postprocess.py +3 -1
  516. scitex/plt/utils/_configure_mpl.py +16 -19
  517. scitex/repro/_RandomStateManager.py +13 -8
  518. scitex/resource/__init__.py +19 -1
  519. scitex/resource/_utils/_get_env_info.py +13 -25
  520. scitex/schema/__init__.py +149 -160
  521. scitex/schema/_encoding.py +273 -0
  522. scitex/schema/_figure_elements.py +406 -0
  523. scitex/schema/_plot.py +0 -0
  524. scitex/schema/_theme.py +360 -0
  525. scitex/schema/_validation.py +0 -98
  526. scitex/scholar/__init__.py +56 -14
  527. scitex/scholar/auth/ScholarAuthManager.py +1 -1
  528. scitex/scholar/auth/__init__.py +11 -2
  529. scitex/scholar/auth/providers/BaseAuthenticator.py +1 -1
  530. scitex/scholar/auth/providers/EZProxyAuthenticator.py +1 -1
  531. scitex/scholar/auth/providers/OpenAthensAuthenticator.py +1 -1
  532. scitex/scholar/auth/providers/ShibbolethAuthenticator.py +1 -1
  533. scitex/scholar/config/ScholarConfig.py +1 -1
  534. scitex/scholar/core/Scholar.py +1 -1
  535. scitex/session/_decorator.py +18 -16
  536. scitex/session/_lifecycle.py +9 -11
  537. scitex/session/template.py +9 -8
  538. scitex/sh/test_sh.py +72 -0
  539. scitex/sh/test_sh_simple.py +61 -0
  540. scitex/stats/__init__.py +221 -97
  541. scitex/stats/_schema.py +21 -22
  542. scitex/stats/descriptive/_circular.py +212 -351
  543. scitex/stats/descriptive/_describe.py +81 -132
  544. scitex/stats/descriptive/_nan.py +205 -433
  545. scitex/stats/descriptive/_real.py +127 -141
  546. scitex/str/_format_plot_text.py +5 -5
  547. scitex/str/_latex.py +26 -84
  548. scitex/str/_latex_fallback.py +53 -47
  549. scitex/web/_search_pubmed.py +5 -4
  550. scitex/writer/tests/test_diff_between.py +451 -0
  551. scitex/writer/tests/test_document_section.py +311 -0
  552. scitex/writer/tests/test_document_workflow.py +393 -0
  553. scitex/writer/tests/test_writer.py +361 -0
  554. scitex/writer/tests/test_writer_integration.py +303 -0
  555. {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/METADATA +364 -181
  556. {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/RECORD +479 -108
  557. scitex/fig/editor/_edit.py +0 -751
  558. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/ARCHITECTURE_EXAMPLE.md +0 -905
  559. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/BULLETIN_BOARD_EXAMPLE.md +0 -99
  560. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/PROJECT_DESCRIPTION_EXAMPLE.md +0 -96
  561. {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/WHEEL +0 -0
  562. {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/entry_points.txt +0 -0
  563. {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,24 +1,17 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
- # Timestamp: "2025-12-13 (ywatanabe)"
4
- # File: /home/ywatanabe/proj/scitex-code/src/scitex/io/_bundle.py
3
+ # Timestamp: "2025-12-16 (ywatanabe)"
4
+ # File: /home/ywatanabe/proj/scitex-code/src/scitex/io/bundle/_core.py
5
5
 
6
6
  """
7
- SciTeX Bundle I/O - Shared utilities for .figz, .pltz, and .statsz formats.
7
+ SciTeX Bundle Core Operations.
8
8
 
9
- This module provides common bundle operations. Domain-specific logic is in:
10
- - scitex.plt.io._bundle (pltz: hitmap, CSV, overview)
11
- - scitex.fig.io._bundle (figz: panel composition, nested pltz)
12
- - scitex.stats.io._bundle (statsz: comparison metadata)
9
+ Provides load, save, copy, pack, unpack, and validate operations for
10
+ .figz, .pltz, and .statsz bundle formats.
13
11
 
14
- Bundle formats:
15
- .figz - Publication Figure Bundle (panels + layout)
16
- .pltz - Reproducible Plot Bundle (data + spec + exports)
17
- .statsz - Statistical Results Bundle (stats + metadata)
18
-
19
- Each bundle can be:
20
- - Directory form: Figure1.figz.d/
21
- - ZIP archive form: Figure1.figz
12
+ Each bundle can exist in two forms:
13
+ - Directory: Figure1.figz.d/
14
+ - ZIP archive: Figure1.figz
22
15
  """
23
16
 
24
17
  import json
@@ -27,59 +20,55 @@ import zipfile
27
20
  from pathlib import Path
28
21
  from typing import Any, Dict, List, Optional, Union
29
22
 
23
+ from ._types import (
24
+ EXTENSIONS,
25
+ BundleNotFoundError,
26
+ BundleType,
27
+ BundleValidationError,
28
+ )
29
+
30
30
  __all__ = [
31
- "is_bundle",
32
- "load_bundle",
33
- "save_bundle",
34
- "pack_bundle",
35
- "unpack_bundle",
36
- "validate_bundle",
31
+ "load",
32
+ "save",
33
+ "copy",
34
+ "pack",
35
+ "unpack",
36
+ "validate",
37
37
  "validate_spec",
38
- "BundleType",
39
- "BundleValidationError",
40
- "BUNDLE_EXTENSIONS",
41
- "get_bundle_type",
38
+ "is_bundle",
39
+ "get_type",
42
40
  "dir_to_zip_path",
43
41
  "zip_to_dir_path",
44
42
  ]
45
43
 
46
- # Bundle extensions
47
- BUNDLE_EXTENSIONS = (".figz", ".pltz", ".statsz")
48
-
49
-
50
- class BundleValidationError(ValueError):
51
- """Error raised when bundle validation fails."""
52
- pass
53
-
54
44
 
55
- class BundleType:
56
- """Bundle type constants."""
57
- FIGZ = "figz"
58
- PLTZ = "pltz"
59
- STATSZ = "statsz"
60
-
61
-
62
- def get_bundle_type(path: Union[str, Path]) -> Optional[str]:
45
+ def get_type(path: Union[str, Path]) -> Optional[str]:
63
46
  """Get bundle type from path.
64
47
 
65
48
  Args:
66
49
  path: Path to bundle (directory or ZIP).
67
50
 
68
51
  Returns:
69
- Bundle type string or None if not a bundle.
52
+ Bundle type string ('figz', 'pltz', 'statsz') or None if not a bundle.
53
+
54
+ Example:
55
+ >>> get_type("Figure1.figz")
56
+ 'figz'
57
+ >>> get_type("plot.pltz.d")
58
+ 'pltz'
70
59
  """
71
60
  p = Path(path)
72
61
 
73
62
  # Directory bundle: ends with .figz.d, .pltz.d, .statsz.d
74
63
  if p.is_dir() and p.suffix == ".d":
75
64
  stem = p.stem # e.g., "Figure1.figz"
76
- for ext in BUNDLE_EXTENSIONS:
65
+ for ext in EXTENSIONS:
77
66
  if stem.endswith(ext):
78
67
  return ext[1:] # Remove leading dot
79
68
  return None
80
69
 
81
70
  # ZIP bundle: ends with .figz, .pltz, .statsz
82
- if p.suffix in BUNDLE_EXTENSIONS:
71
+ if p.suffix in EXTENSIONS:
83
72
  return p.suffix[1:] # Remove leading dot
84
73
 
85
74
  return None
@@ -93,14 +82,22 @@ def is_bundle(path: Union[str, Path]) -> bool:
93
82
 
94
83
  Returns:
95
84
  True if path is a bundle.
85
+
86
+ Example:
87
+ >>> is_bundle("Figure1.figz")
88
+ True
89
+ >>> is_bundle("data.csv")
90
+ False
96
91
  """
97
- return get_bundle_type(path) is not None
92
+ return get_type(path) is not None
98
93
 
99
94
 
100
95
  def dir_to_zip_path(dir_path: Path) -> Path:
101
96
  """Convert directory path to ZIP path.
102
97
 
103
- Example: Figure1.figz.d/ -> Figure1.figz
98
+ Example:
99
+ >>> dir_to_zip_path(Path("Figure1.figz.d"))
100
+ Path('Figure1.figz')
104
101
  """
105
102
  if dir_path.suffix == ".d":
106
103
  return dir_path.with_suffix("")
@@ -110,12 +107,14 @@ def dir_to_zip_path(dir_path: Path) -> Path:
110
107
  def zip_to_dir_path(zip_path: Path) -> Path:
111
108
  """Convert ZIP path to directory path.
112
109
 
113
- Example: Figure1.figz -> Figure1.figz.d/
110
+ Example:
111
+ >>> zip_to_dir_path(Path("Figure1.figz"))
112
+ Path('Figure1.figz.d')
114
113
  """
115
114
  return Path(str(zip_path) + ".d")
116
115
 
117
116
 
118
- def pack_bundle(
117
+ def pack(
119
118
  dir_path: Union[str, Path], output_path: Optional[Union[str, Path]] = None
120
119
  ) -> Path:
121
120
  """Pack a bundle directory into a ZIP archive.
@@ -129,6 +128,10 @@ def pack_bundle(
129
128
 
130
129
  Returns:
131
130
  Path to created ZIP archive.
131
+
132
+ Example:
133
+ >>> pack("plot.pltz.d")
134
+ Path('plot.pltz')
132
135
  """
133
136
  dir_path = Path(dir_path)
134
137
 
@@ -141,15 +144,12 @@ def pack_bundle(
141
144
  output_path = Path(output_path)
142
145
 
143
146
  # Get the directory name to use as top-level folder in ZIP
144
- # e.g., "stx_line.pltz.d" for path "/path/to/stx_line.pltz.d"
145
147
  dir_name = dir_path.name
146
148
 
147
149
  # Create ZIP archive with directory structure preserved
148
150
  with zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED) as zf:
149
151
  for file_path in dir_path.rglob("*"):
150
152
  if file_path.is_file():
151
- # Include directory name in archive path
152
- # e.g., "stx_line.pltz.d/stx_line.csv"
153
153
  rel_path = file_path.relative_to(dir_path)
154
154
  arcname = Path(dir_name) / rel_path
155
155
  zf.write(file_path, arcname)
@@ -157,29 +157,28 @@ def pack_bundle(
157
157
  return output_path
158
158
 
159
159
 
160
- def unpack_bundle(
160
+ def unpack(
161
161
  zip_path: Union[str, Path], output_path: Optional[Union[str, Path]] = None
162
162
  ) -> Path:
163
163
  """Unpack a bundle ZIP archive into a directory.
164
164
 
165
- The ZIP archive contains a top-level .d directory, so extraction goes to
166
- the parent directory. E.g., stx_line.pltz extracts to create stx_line.pltz.d/
167
-
168
165
  Args:
169
166
  zip_path: Path to bundle ZIP (e.g., Figure1.figz).
170
167
  output_path: Output directory path. Auto-generated if None.
171
168
 
172
169
  Returns:
173
170
  Path to created directory.
171
+
172
+ Example:
173
+ >>> unpack("plot.pltz")
174
+ Path('plot.pltz.d')
174
175
  """
175
176
  zip_path = Path(zip_path)
176
177
 
177
178
  if not zip_path.is_file():
178
179
  raise ValueError(f"Not a file: {zip_path}")
179
180
 
180
- # Determine extraction target
181
181
  if output_path is None:
182
- # Extract to same directory as ZIP file (ZIP contains .d folder structure)
183
182
  extract_to = zip_path.parent
184
183
  expected_dir = zip_to_dir_path(zip_path)
185
184
  else:
@@ -187,7 +186,6 @@ def unpack_bundle(
187
186
  extract_to = output_path.parent
188
187
  expected_dir = output_path
189
188
 
190
- # Extract ZIP archive (contains .d directory structure)
191
189
  with zipfile.ZipFile(zip_path, "r") as zf:
192
190
  zf.extractall(extract_to)
193
191
 
@@ -227,12 +225,15 @@ def validate_spec(
227
225
  # Delegate to domain-specific validators
228
226
  if bundle_type == BundleType.FIGZ:
229
227
  from scitex.fig.io._bundle import validate_figz_spec
228
+
230
229
  errors.extend(validate_figz_spec(spec))
231
230
  elif bundle_type == BundleType.PLTZ:
232
231
  from scitex.plt.io._bundle import validate_pltz_spec
232
+
233
233
  errors.extend(validate_pltz_spec(spec))
234
234
  elif bundle_type == BundleType.STATSZ:
235
235
  from scitex.stats.io._bundle import validate_statsz_spec
236
+
236
237
  errors.extend(validate_statsz_spec(spec))
237
238
  else:
238
239
  errors.append(f"Unknown bundle type: {bundle_type}")
@@ -243,9 +244,7 @@ def validate_spec(
243
244
  return errors
244
245
 
245
246
 
246
- def validate_bundle(
247
- path: Union[str, Path], strict: bool = False
248
- ) -> Dict[str, Any]:
247
+ def validate(path: Union[str, Path], strict: bool = False) -> Dict[str, Any]:
249
248
  """Validate a bundle and return validation results.
250
249
 
251
250
  Args:
@@ -271,8 +270,7 @@ def validate_bundle(
271
270
 
272
271
  p = Path(path)
273
272
 
274
- # Check bundle type
275
- bundle_type = get_bundle_type(p)
273
+ bundle_type = get_type(p)
276
274
  if bundle_type is None:
277
275
  result["valid"] = False
278
276
  result["errors"].append(f"Not a valid bundle path: {path}")
@@ -282,9 +280,8 @@ def validate_bundle(
282
280
 
283
281
  result["bundle_type"] = bundle_type
284
282
 
285
- # Try to load and validate
286
283
  try:
287
- bundle = load_bundle(path)
284
+ bundle = load(path)
288
285
  spec = bundle.get("spec")
289
286
  result["spec"] = spec
290
287
 
@@ -307,11 +304,13 @@ def validate_bundle(
307
304
  return result
308
305
 
309
306
 
310
- def load_bundle(path: Union[str, Path]) -> Dict[str, Any]:
307
+ def load(path: Union[str, Path], in_memory: bool = True) -> Dict[str, Any]:
311
308
  """Load bundle from directory or ZIP transparently.
312
309
 
313
310
  Args:
314
311
  path: Path to bundle (directory or ZIP).
312
+ in_memory: If True, load ZIP contents in-memory without extracting.
313
+ If False, extract to temp directory (legacy behavior).
315
314
 
316
315
  Returns:
317
316
  Bundle data as dictionary with:
@@ -320,12 +319,19 @@ def load_bundle(path: Union[str, Path]) -> Dict[str, Any]:
320
319
  - 'path': Original path
321
320
  - 'is_zip': Whether loaded from ZIP
322
321
  - Additional fields based on bundle type
322
+
323
+ Example:
324
+ >>> bundle = load("Figure1.figz")
325
+ >>> bundle['type']
326
+ 'figz'
327
+ >>> bundle['spec']['schema']['name']
328
+ 'scitex.fig'
323
329
  """
324
330
  p = Path(path)
325
- bundle_type = get_bundle_type(p)
331
+ bundle_type = get_type(p)
326
332
 
327
333
  if bundle_type is None:
328
- raise ValueError(f"Not a valid bundle: {path}")
334
+ raise BundleNotFoundError(f"Not a valid bundle: {path}")
329
335
 
330
336
  result = {
331
337
  "type": bundle_type,
@@ -334,36 +340,63 @@ def load_bundle(path: Union[str, Path]) -> Dict[str, Any]:
334
340
  }
335
341
 
336
342
  # Handle ZIP vs directory
337
- if p.is_file() and p.suffix in BUNDLE_EXTENSIONS:
338
- # ZIP archive - extract to temp and load
343
+ if p.is_file() and p.suffix in EXTENSIONS:
339
344
  result["is_zip"] = True
340
- import tempfile
341
- temp_dir = Path(tempfile.mkdtemp())
342
- with zipfile.ZipFile(p, "r") as zf:
343
- zf.extractall(temp_dir)
344
- bundle_dir = temp_dir
345
+
346
+ if in_memory:
347
+ from ._zip import ZipBundle
348
+
349
+ with ZipBundle(p, mode="r") as zb:
350
+ result["_zip_bundle"] = zb
351
+ try:
352
+ result["spec"] = zb.read_json("spec.json")
353
+ except FileNotFoundError:
354
+ result["spec"] = None
355
+ try:
356
+ result["style"] = zb.read_json("style.json")
357
+ except FileNotFoundError:
358
+ result["style"] = None
359
+ try:
360
+ result["data"] = zb.read_csv("data.csv")
361
+ except FileNotFoundError:
362
+ result["data"] = None
363
+
364
+ result["files"] = zb.namelist()
365
+
366
+ return result
367
+ else:
368
+ import tempfile
369
+
370
+ temp_dir = Path(tempfile.mkdtemp())
371
+ with zipfile.ZipFile(p, "r") as zf:
372
+ zf.extractall(temp_dir)
373
+ bundle_dir = temp_dir
345
374
  else:
346
375
  bundle_dir = p
347
376
 
348
377
  # Delegate to domain-specific loaders
349
378
  if bundle_type == BundleType.FIGZ:
350
379
  from scitex.fig.io._bundle import load_figz_bundle
380
+
351
381
  result.update(load_figz_bundle(bundle_dir))
352
382
  elif bundle_type == BundleType.PLTZ:
353
383
  from scitex.plt.io import load_layered_pltz_bundle
384
+
354
385
  result.update(load_layered_pltz_bundle(bundle_dir))
355
386
  elif bundle_type == BundleType.STATSZ:
356
387
  from scitex.stats.io._bundle import load_statsz_bundle
388
+
357
389
  result.update(load_statsz_bundle(bundle_dir))
358
390
 
359
391
  return result
360
392
 
361
393
 
362
- def save_bundle(
394
+ def save(
363
395
  data: Dict[str, Any],
364
396
  path: Union[str, Path],
365
397
  bundle_type: Optional[str] = None,
366
398
  as_zip: bool = False,
399
+ atomic: bool = True,
367
400
  ) -> Path:
368
401
  """Save data as a bundle.
369
402
 
@@ -372,20 +405,24 @@ def save_bundle(
372
405
  path: Output path (with or without .d suffix).
373
406
  bundle_type: Bundle type ('figz', 'pltz', 'statsz'). Auto-detected if None.
374
407
  as_zip: If True, save as ZIP archive.
408
+ atomic: If True, use atomic write (temp file + rename) for ZIP.
375
409
 
376
410
  Returns:
377
411
  Path to saved bundle.
412
+
413
+ Example:
414
+ >>> save({"spec": {...}, "data": df}, "plot.pltz", as_zip=True)
415
+ Path('plot.pltz')
378
416
  """
379
417
  p = Path(path)
380
418
 
381
- # Determine bundle type
382
419
  if bundle_type is None:
383
- bundle_type = get_bundle_type(p)
420
+ bundle_type = get_type(p)
384
421
  if bundle_type is None:
385
422
  raise ValueError(f"Cannot determine bundle type from path: {path}")
386
423
 
387
424
  # Determine if saving as directory or ZIP
388
- if as_zip or (p.suffix in BUNDLE_EXTENSIONS and not str(p).endswith(".d")):
425
+ if as_zip or (p.suffix in EXTENSIONS and not str(p).endswith(".d")):
389
426
  save_as_zip = True
390
427
  if p.suffix == ".d":
391
428
  zip_path = dir_to_zip_path(p)
@@ -399,36 +436,129 @@ def save_bundle(
399
436
  else:
400
437
  dir_path = p
401
438
 
402
- # Create directory
439
+ # For direct ZIP saving with atomic writes
440
+ if save_as_zip and atomic and bundle_type != BundleType.FIGZ:
441
+ from ._zip import ZipBundle
442
+
443
+ with ZipBundle(zip_path, mode="w") as zb:
444
+ if "spec" in data:
445
+ zb.write_json("spec.json", data["spec"])
446
+
447
+ if "style" in data:
448
+ zb.write_json("style.json", data["style"])
449
+
450
+ if "data" in data and data["data"] is not None:
451
+ import pandas as pd
452
+
453
+ if isinstance(data["data"], pd.DataFrame):
454
+ zb.write_csv("data.csv", data["data"])
455
+
456
+ for key in ["png", "svg", "pdf"]:
457
+ if key in data and data[key] is not None:
458
+ export_data = data[key]
459
+ if isinstance(export_data, bytes):
460
+ zb.write_bytes(f"exports/figure.{key}", export_data)
461
+
462
+ return zip_path
463
+
403
464
  dir_path.mkdir(parents=True, exist_ok=True)
404
465
 
405
466
  # Delegate to domain-specific savers
406
467
  if bundle_type == BundleType.FIGZ:
407
468
  from scitex.fig.io._bundle import save_figz_bundle
469
+
408
470
  save_figz_bundle(data, dir_path)
409
471
  elif bundle_type == BundleType.PLTZ:
410
- # Note: This path is only reached when calling save_bundle() directly
411
- # The main stx.io.save() flow uses _save_pltz_bundle() which handles layered format
412
472
  from scitex.plt.io._bundle import save_pltz_bundle
473
+
413
474
  save_pltz_bundle(data, dir_path)
414
475
  elif bundle_type == BundleType.STATSZ:
415
476
  from scitex.stats.io._bundle import save_statsz_bundle
477
+
416
478
  save_statsz_bundle(data, dir_path)
417
479
  else:
418
480
  raise ValueError(f"Unknown bundle type: {bundle_type}")
419
481
 
420
- # Pack to ZIP if requested
421
482
  if save_as_zip:
422
- pack_bundle(dir_path, zip_path)
423
- shutil.rmtree(dir_path) # Remove temp directory
483
+ pack(dir_path, zip_path)
484
+ shutil.rmtree(dir_path)
424
485
  return zip_path
425
486
 
426
487
  return dir_path
427
488
 
428
489
 
429
- # Backward compatibility aliases
430
- _get_bundle_type = get_bundle_type
431
- _dir_to_zip_path = dir_to_zip_path
432
- _zip_to_dir_path = zip_to_dir_path
490
+ def copy(
491
+ src: Union[str, Path],
492
+ dst: Union[str, Path],
493
+ overwrite: bool = False,
494
+ ) -> Path:
495
+ """Copy a bundle from source to destination.
496
+
497
+ Handles both directory (.d) and ZIP formats transparently.
498
+ If source is ZIP, extracts to destination directory.
499
+ If source is directory, copies to destination directory.
500
+
501
+ Args:
502
+ src: Source bundle path (directory or ZIP).
503
+ dst: Destination path (will be created as directory).
504
+ overwrite: If True, overwrite existing destination.
505
+
506
+ Returns:
507
+ Path to copied bundle (directory form).
508
+
509
+ Raises:
510
+ BundleNotFoundError: If source bundle doesn't exist.
511
+ FileExistsError: If destination exists and overwrite=False.
512
+
513
+ Example:
514
+ >>> copy("gallery/line/plot.pltz.d", "my_project/A.pltz.d")
515
+ >>> copy("template.pltz", "output/panel.pltz.d")
516
+ """
517
+ src_path = Path(src)
518
+ dst_path = Path(dst)
519
+
520
+ # Validate source exists
521
+ if not src_path.exists():
522
+ if src_path.suffix in EXTENSIONS:
523
+ alt_path = Path(str(src_path) + ".d")
524
+ if alt_path.exists():
525
+ src_path = alt_path
526
+ else:
527
+ raise BundleNotFoundError(
528
+ f"Bundle not found: {src} (also tried {alt_path})"
529
+ )
530
+ elif str(src_path).endswith(".d"):
531
+ alt_path = Path(str(src_path)[:-2])
532
+ if alt_path.exists():
533
+ src_path = alt_path
534
+ else:
535
+ raise BundleNotFoundError(
536
+ f"Bundle not found: {src} (also tried {alt_path})"
537
+ )
538
+ else:
539
+ raise BundleNotFoundError(f"Bundle not found: {src}")
540
+
541
+ # Handle destination
542
+ if dst_path.exists():
543
+ if overwrite:
544
+ if dst_path.is_dir():
545
+ shutil.rmtree(dst_path)
546
+ else:
547
+ dst_path.unlink()
548
+ else:
549
+ raise FileExistsError(f"Destination exists: {dst}")
550
+
551
+ dst_path.parent.mkdir(parents=True, exist_ok=True)
552
+
553
+ # Copy based on source type
554
+ if src_path.is_dir():
555
+ shutil.copytree(src_path, dst_path)
556
+ elif src_path.suffix in EXTENSIONS or zipfile.is_zipfile(src_path):
557
+ unpack(src_path, dst_path)
558
+ else:
559
+ raise ValueError(f"Unknown bundle format: {src_path}")
560
+
561
+ return dst_path
562
+
433
563
 
434
564
  # EOF