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.
- scitex/__init__.py +15 -7
- scitex/__version__.py +1 -2
- scitex/_install_guide.py +250 -0
- scitex/_optional_deps.py +206 -39
- scitex/ai/_gen_ai/_Groq.py +2 -4
- scitex/ai/_gen_ai/_OpenAI.py +5 -2
- scitex/ai/_gen_ai/_Perplexity.py +20 -6
- scitex/audio/__init__.py +24 -15
- scitex/audio/_cross_process_lock.py +139 -0
- scitex/audio/_mcp_handlers.py +256 -0
- scitex/audio/_mcp_tool_schemas.py +203 -0
- scitex/audio/engines/elevenlabs_engine.py +5 -2
- scitex/audio/mcp_server.py +98 -457
- scitex/bridge/__init__.py +30 -19
- scitex/bridge/_figrecipe.py +245 -0
- scitex/bridge/_helpers.py +2 -1
- scitex/bridge/_plt_vis.py +23 -10
- scitex/bridge/_stats_plt.py +18 -5
- scitex/bridge/_stats_vis.py +16 -2
- scitex/browser/__init__.py +84 -44
- scitex/browser/automation/__init__.py +5 -1
- scitex/browser/core/BrowserMixin.py +17 -4
- scitex/browser/core/__init__.py +11 -2
- scitex/browser/remote/CaptchaHandler.py +1 -1
- scitex/browser/remote/ZenRowsAPIClient.py +1 -1
- scitex/capture/grid.py +487 -0
- scitex/capture/mcp_handlers.py +401 -0
- scitex/capture/mcp_tool_defs.py +192 -0
- scitex/capture/mcp_tools.py +241 -0
- scitex/capture/mcp_utils.py +30 -0
- scitex/cli/convert.py +421 -0
- scitex/cli/main.py +6 -4
- scitex/datetime/__init__.py +46 -0
- scitex/datetime/_linspace.py +100 -0
- scitex/datetime/_normalize_timestamp.py +306 -0
- scitex/db/_delete_duplicates.py +4 -4
- scitex/db/_sqlite3/_delete_duplicates.py +11 -2
- scitex/dev/plt/__init__.py +61 -62
- scitex/dev/plt/demo_plotters/__init__.py +0 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axhline.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axhspan.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axvline.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axvspan.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_bar.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_barh.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_boxplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_contour.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_contourf.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_errorbar.py +30 -0
- scitex/dev/plt/demo_plotters/plot_mpl_eventplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_fill.py +30 -0
- scitex/dev/plt/demo_plotters/plot_mpl_fill_between.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hexbin.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hist.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hist2d.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_imshow.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_pcolormesh.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_pie.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_plot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_quiver.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_scatter.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_stackplot.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_stem.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_step.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_violinplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_sns_barplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_boxplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_heatmap.py +28 -0
- scitex/dev/plt/demo_plotters/plot_sns_histplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_kdeplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_lineplot.py +31 -0
- scitex/dev/plt/demo_plotters/plot_sns_scatterplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_stripplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_swarmplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_violinplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_bar.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_barh.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_box.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_boxplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_conf_mat.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_contour.py +31 -0
- scitex/dev/plt/demo_plotters/plot_stx_ecdf.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_errorbar.py +30 -0
- scitex/dev/plt/demo_plotters/plot_stx_fill_between.py +31 -0
- scitex/dev/plt/demo_plotters/plot_stx_fillv.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_heatmap.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_image.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_imshow.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_joyplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_kde.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_line.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_mean_ci.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_mean_std.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_median_iqr.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_raster.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_rectangle.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_scatter.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_shaded_line.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_violin.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_violinplot.py +28 -0
- scitex/dev/plt/mpl/get_dir_ax.py +46 -0
- scitex/dev/plt/mpl/get_signatures.py +176 -0
- scitex/dev/plt/mpl/get_signatures_details.py +522 -0
- scitex/dev/plt/plot_mpl_axhline.py +0 -0
- scitex/dev/plt/plot_mpl_axhspan.py +0 -0
- scitex/dev/plt/plot_mpl_axvline.py +0 -0
- scitex/dev/plt/plot_mpl_axvspan.py +0 -0
- scitex/dev/plt/plot_mpl_bar.py +0 -0
- scitex/dev/plt/plot_mpl_barh.py +0 -0
- scitex/dev/plt/plot_mpl_boxplot.py +0 -0
- scitex/dev/plt/plot_mpl_contour.py +0 -0
- scitex/dev/plt/plot_mpl_contourf.py +0 -0
- scitex/dev/plt/plot_mpl_errorbar.py +0 -0
- scitex/dev/plt/plot_mpl_eventplot.py +0 -0
- scitex/dev/plt/plot_mpl_fill.py +0 -0
- scitex/dev/plt/plot_mpl_fill_between.py +0 -0
- scitex/dev/plt/plot_mpl_hexbin.py +0 -0
- scitex/dev/plt/plot_mpl_hist.py +0 -0
- scitex/dev/plt/plot_mpl_hist2d.py +0 -0
- scitex/dev/plt/plot_mpl_imshow.py +0 -0
- scitex/dev/plt/plot_mpl_pcolormesh.py +0 -0
- scitex/dev/plt/plot_mpl_pie.py +0 -0
- scitex/dev/plt/plot_mpl_plot.py +0 -0
- scitex/dev/plt/plot_mpl_quiver.py +0 -0
- scitex/dev/plt/plot_mpl_scatter.py +0 -0
- scitex/dev/plt/plot_mpl_stackplot.py +0 -0
- scitex/dev/plt/plot_mpl_stem.py +0 -0
- scitex/dev/plt/plot_mpl_step.py +0 -0
- scitex/dev/plt/plot_mpl_violinplot.py +0 -0
- scitex/dev/plt/plot_sns_barplot.py +0 -0
- scitex/dev/plt/plot_sns_boxplot.py +0 -0
- scitex/dev/plt/plot_sns_heatmap.py +0 -0
- scitex/dev/plt/plot_sns_histplot.py +0 -0
- scitex/dev/plt/plot_sns_kdeplot.py +0 -0
- scitex/dev/plt/plot_sns_lineplot.py +0 -0
- scitex/dev/plt/plot_sns_scatterplot.py +0 -0
- scitex/dev/plt/plot_sns_stripplot.py +0 -0
- scitex/dev/plt/plot_sns_swarmplot.py +0 -0
- scitex/dev/plt/plot_sns_violinplot.py +0 -0
- scitex/dev/plt/plot_stx_bar.py +0 -0
- scitex/dev/plt/plot_stx_barh.py +0 -0
- scitex/dev/plt/plot_stx_box.py +0 -0
- scitex/dev/plt/plot_stx_boxplot.py +0 -0
- scitex/dev/plt/plot_stx_conf_mat.py +0 -0
- scitex/dev/plt/plot_stx_contour.py +0 -0
- scitex/dev/plt/plot_stx_ecdf.py +0 -0
- scitex/dev/plt/plot_stx_errorbar.py +0 -0
- scitex/dev/plt/plot_stx_fill_between.py +0 -0
- scitex/dev/plt/plot_stx_fillv.py +0 -0
- scitex/dev/plt/plot_stx_heatmap.py +0 -0
- scitex/dev/plt/plot_stx_image.py +0 -0
- scitex/dev/plt/plot_stx_imshow.py +0 -0
- scitex/dev/plt/plot_stx_joyplot.py +0 -0
- scitex/dev/plt/plot_stx_kde.py +0 -0
- scitex/dev/plt/plot_stx_line.py +0 -0
- scitex/dev/plt/plot_stx_mean_ci.py +0 -0
- scitex/dev/plt/plot_stx_mean_std.py +0 -0
- scitex/dev/plt/plot_stx_median_iqr.py +0 -0
- scitex/dev/plt/plot_stx_raster.py +0 -0
- scitex/dev/plt/plot_stx_rectangle.py +0 -0
- scitex/dev/plt/plot_stx_scatter.py +0 -0
- scitex/dev/plt/plot_stx_shaded_line.py +0 -0
- scitex/dev/plt/plot_stx_violin.py +0 -0
- scitex/dev/plt/plot_stx_violinplot.py +0 -0
- scitex/diagram/README.md +197 -0
- scitex/diagram/__init__.py +48 -0
- scitex/diagram/_compile.py +312 -0
- scitex/diagram/_diagram.py +355 -0
- scitex/diagram/_presets.py +173 -0
- scitex/diagram/_schema.py +182 -0
- scitex/diagram/_split.py +278 -0
- scitex/dict/_pop_keys.py +1 -7
- scitex/dsp/__init__.py +15 -10
- scitex/dsp/add_noise.py +5 -2
- scitex/dsp/example.py +35 -22
- scitex/dsp/filt.py +8 -3
- scitex/dsp/reference.py +3 -2
- scitex/dsp/utils/__init__.py +2 -1
- scitex/dsp/utils/_differential_bandpass_filters.py +14 -4
- scitex/dt/__init__.py +39 -2
- scitex/errors.py +82 -521
- scitex/fig/__init__.py +4 -4
- scitex/fig/editor/__init__.py +5 -2
- scitex/fig/editor/_dearpygui_editor.py +1 -1
- scitex/fig/editor/_mpl_editor.py +1 -1
- scitex/fig/editor/_qt_editor.py +1 -1
- scitex/fig/editor/_tkinter_editor.py +1 -1
- scitex/fig/editor/edit/__init__.py +50 -0
- scitex/fig/editor/edit/backend_detector.py +109 -0
- scitex/fig/editor/edit/bundle_resolver.py +240 -0
- scitex/fig/editor/edit/editor_launcher.py +239 -0
- scitex/fig/editor/edit/manual_handler.py +53 -0
- scitex/fig/editor/edit/panel_loader.py +232 -0
- scitex/fig/editor/edit/path_resolver.py +67 -0
- scitex/fig/editor/flask_editor/_bbox.py +23 -0
- scitex/fig/editor/flask_editor/_core.py +908 -103
- scitex/fig/editor/flask_editor/_renderer.py +74 -0
- scitex/fig/editor/flask_editor/static/css/base/reset.css +41 -0
- scitex/fig/editor/flask_editor/static/css/base/typography.css +16 -0
- scitex/fig/editor/flask_editor/static/css/base/variables.css +85 -0
- scitex/fig/editor/flask_editor/static/css/components/buttons.css +217 -0
- scitex/fig/editor/flask_editor/static/css/components/context-menu.css +93 -0
- scitex/fig/editor/flask_editor/static/css/components/dropdown.css +57 -0
- scitex/fig/editor/flask_editor/static/css/components/forms.css +112 -0
- scitex/fig/editor/flask_editor/static/css/components/modal.css +59 -0
- scitex/fig/editor/flask_editor/static/css/components/sections.css +212 -0
- scitex/fig/editor/flask_editor/static/css/features/canvas.css +176 -0
- scitex/fig/editor/flask_editor/static/css/features/element-inspector.css +190 -0
- scitex/fig/editor/flask_editor/static/css/features/loading.css +59 -0
- scitex/fig/editor/flask_editor/static/css/features/overlay.css +45 -0
- scitex/fig/editor/flask_editor/static/css/features/panel-grid.css +95 -0
- scitex/fig/editor/flask_editor/static/css/features/selection.css +101 -0
- scitex/fig/editor/flask_editor/static/css/features/statistics.css +138 -0
- scitex/fig/editor/flask_editor/static/css/index.css +31 -0
- scitex/fig/editor/flask_editor/static/css/layout/container.css +7 -0
- scitex/fig/editor/flask_editor/static/css/layout/controls.css +56 -0
- scitex/fig/editor/flask_editor/static/css/layout/preview.css +78 -0
- scitex/fig/editor/flask_editor/static/js/alignment/axis.js +314 -0
- scitex/fig/editor/flask_editor/static/js/alignment/basic.js +107 -0
- scitex/fig/editor/flask_editor/static/js/alignment/distribute.js +54 -0
- scitex/fig/editor/flask_editor/static/js/canvas/canvas.js +172 -0
- scitex/fig/editor/flask_editor/static/js/canvas/dragging.js +258 -0
- scitex/fig/editor/flask_editor/static/js/canvas/resize.js +48 -0
- scitex/fig/editor/flask_editor/static/js/canvas/selection.js +71 -0
- scitex/fig/editor/flask_editor/static/js/core/api.js +288 -0
- scitex/fig/editor/flask_editor/static/js/core/state.js +143 -0
- scitex/fig/editor/flask_editor/static/js/core/utils.js +245 -0
- scitex/fig/editor/flask_editor/static/js/dev/element-inspector.js +992 -0
- scitex/fig/editor/flask_editor/static/js/editor/bbox.js +339 -0
- scitex/fig/editor/flask_editor/static/js/editor/element-drag.js +286 -0
- scitex/fig/editor/flask_editor/static/js/editor/overlay.js +371 -0
- scitex/fig/editor/flask_editor/static/js/editor/preview.js +293 -0
- scitex/fig/editor/flask_editor/static/js/main.js +426 -0
- scitex/fig/editor/flask_editor/static/js/shortcuts/context-menu.js +152 -0
- scitex/fig/editor/flask_editor/static/js/shortcuts/keyboard.js +265 -0
- scitex/fig/editor/flask_editor/static/js/ui/controls.js +184 -0
- scitex/fig/editor/flask_editor/static/js/ui/download.js +57 -0
- scitex/fig/editor/flask_editor/static/js/ui/help.js +100 -0
- scitex/fig/editor/flask_editor/static/js/ui/theme.js +34 -0
- scitex/fig/editor/flask_editor/templates/__init__.py +95 -5
- scitex/fig/editor/flask_editor/templates/_html.py +27 -9
- scitex/fig/editor/flask_editor/templates/_scripts.py +1928 -131
- scitex/fig/editor/flask_editor/templates/_styles.py +363 -51
- scitex/fig/io/_bundle.py +104 -19
- scitex/fts/README.md +262 -0
- scitex/fts/TODO.md +66 -0
- scitex/fts/__init__.py +90 -0
- scitex/fts/_bundle/README_IN_BUNDLE.md +102 -0
- scitex/fts/_bundle/_FTS.py +657 -0
- scitex/fts/_bundle/__init__.py +38 -0
- scitex/fts/_bundle/_children.py +216 -0
- scitex/fts/_bundle/_conversion/__init__.py +15 -0
- scitex/fts/_bundle/_conversion/_bundle2dict.py +44 -0
- scitex/fts/_bundle/_conversion/_dict2bundle.py +50 -0
- scitex/fts/_bundle/_dataclasses/_Axes.py +57 -0
- scitex/fts/_bundle/_dataclasses/_BBox.py +54 -0
- scitex/fts/_bundle/_dataclasses/_ColumnDef.py +72 -0
- scitex/fts/_bundle/_dataclasses/_DataFormat.py +40 -0
- scitex/fts/_bundle/_dataclasses/_DataInfo.py +135 -0
- scitex/fts/_bundle/_dataclasses/_DataSource.py +44 -0
- scitex/fts/_bundle/_dataclasses/_Node.py +319 -0
- scitex/fts/_bundle/_dataclasses/_NodeRefs.py +45 -0
- scitex/fts/_bundle/_dataclasses/_SizeMM.py +38 -0
- scitex/fts/_bundle/_dataclasses/__init__.py +35 -0
- scitex/fts/_bundle/_extractors/__init__.py +32 -0
- scitex/fts/_bundle/_extractors/_extract_bar.py +131 -0
- scitex/fts/_bundle/_extractors/_extract_line.py +71 -0
- scitex/fts/_bundle/_extractors/_extract_scatter.py +79 -0
- scitex/fts/_bundle/_loader.py +134 -0
- scitex/fts/_bundle/_mpl_helpers.py +389 -0
- scitex/fts/_bundle/_saver.py +269 -0
- scitex/fts/_bundle/_storage.py +200 -0
- scitex/fts/_bundle/_utils/__init__.py +55 -0
- scitex/fts/_bundle/_utils/_const.py +26 -0
- scitex/fts/_bundle/_utils/_errors.py +73 -0
- scitex/fts/_bundle/_utils/_generate.py +21 -0
- scitex/fts/_bundle/_utils/_types.py +76 -0
- scitex/fts/_bundle/_validation.py +434 -0
- scitex/fts/_bundle/_zipbundle.py +165 -0
- scitex/fts/_fig/__init__.py +22 -0
- scitex/fts/_fig/_backend/__init__.py +53 -0
- scitex/fts/_fig/_backend/_export.py +165 -0
- scitex/fts/_fig/_backend/_parser.py +188 -0
- scitex/fts/_fig/_backend/_render.py +538 -0
- scitex/fts/_fig/_composite.py +345 -0
- scitex/fts/_fig/_dataclasses/_ChannelEncoding.py +46 -0
- scitex/fts/_fig/_dataclasses/_Encoding.py +82 -0
- scitex/fts/_fig/_dataclasses/_Theme.py +441 -0
- scitex/fts/_fig/_dataclasses/_TraceEncoding.py +52 -0
- scitex/fts/_fig/_dataclasses/__init__.py +47 -0
- scitex/fts/_fig/_editor/__init__.py +14 -0
- scitex/fts/_fig/_editor/_cui/__init__.py +33 -0
- scitex/fts/_fig/_editor/_cui/_backend_detector.py +39 -0
- scitex/fts/_fig/_editor/_cui/_bundle_resolver.py +366 -0
- scitex/fts/_fig/_editor/_cui/_editor_launcher.py +175 -0
- scitex/fts/_fig/_editor/_cui/_manual_handler.py +52 -0
- scitex/fts/_fig/_editor/_cui/_panel_loader.py +246 -0
- scitex/fts/_fig/_editor/_cui/_path_resolver.py +66 -0
- scitex/fts/_fig/_editor/_defaults.py +300 -0
- scitex/fts/_fig/_editor/_gui/__init__.py +11 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/__init__.py +20 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_bbox.py +1339 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_core.py +1688 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_plotter.py +664 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_renderer.py +853 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_utils.py +79 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/reset.css +41 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/typography.css +16 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/variables.css +85 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/buttons.css +217 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/context-menu.css +93 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/dropdown.css +57 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/forms.css +112 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/modal.css +59 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/sections.css +212 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/canvas.css +176 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/element-inspector.css +190 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/loading.css +59 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/overlay.css +45 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/panel-grid.css +95 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/selection.css +101 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/statistics.css +138 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/index.css +31 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/container.css +7 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/controls.css +56 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/preview.css +78 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/axis.js +314 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/basic.js +107 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/distribute.js +54 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/canvas.js +172 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/dragging.js +258 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/resize.js +48 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/selection.js +71 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/api.js +288 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/state.js +143 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/utils.js +245 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/dev/element-inspector.js +992 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/bbox.js +339 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/element-drag.js +286 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/overlay.js +371 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/preview.js +293 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/main.js +426 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/context-menu.js +152 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/keyboard.js +265 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/controls.js +184 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/download.js +57 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/help.js +100 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/theme.js +34 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/__init__.py +124 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_html.py +851 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_scripts.py +4932 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_styles.py +1657 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor.py +36 -0
- scitex/fts/_fig/_models/_Annotations.py +115 -0
- scitex/fts/_fig/_models/_Axes.py +152 -0
- scitex/fts/_fig/_models/_Figure.py +138 -0
- scitex/fts/_fig/_models/_Guides.py +104 -0
- scitex/fts/_fig/_models/_Plot.py +123 -0
- scitex/fts/_fig/_models/_Styles.py +245 -0
- scitex/fts/_fig/_models/__init__.py +80 -0
- scitex/fts/_fig/_models/_plot_types/__init__.py +156 -0
- scitex/fts/_fig/_models/_plot_types/_bar.py +43 -0
- scitex/fts/_fig/_models/_plot_types/_box.py +38 -0
- scitex/fts/_fig/_models/_plot_types/_distribution.py +36 -0
- scitex/fts/_fig/_models/_plot_types/_errorbar.py +60 -0
- scitex/fts/_fig/_models/_plot_types/_histogram.py +30 -0
- scitex/fts/_fig/_models/_plot_types/_image.py +61 -0
- scitex/fts/_fig/_models/_plot_types/_line.py +57 -0
- scitex/fts/_fig/_models/_plot_types/_scatter.py +30 -0
- scitex/fts/_fig/_models/_plot_types/_seaborn.py +121 -0
- scitex/fts/_fig/_models/_plot_types/_violin.py +36 -0
- scitex/fts/_fig/_utils/__init__.py +129 -0
- scitex/fts/_fig/_utils/_auto_layout.py +127 -0
- scitex/fts/_fig/_utils/_calc_bounds.py +111 -0
- scitex/fts/_fig/_utils/_const_sizes.py +48 -0
- scitex/fts/_fig/_utils/_convert_coords.py +77 -0
- scitex/fts/_fig/_utils/_get_template.py +178 -0
- scitex/fts/_fig/_utils/_normalize.py +73 -0
- scitex/fts/_fig/_utils/_plot_layout.py +397 -0
- scitex/fts/_fig/_utils/_validate.py +197 -0
- scitex/fts/_kinds/__init__.py +45 -0
- scitex/fts/_kinds/_figure/__init__.py +19 -0
- scitex/fts/_kinds/_figure/_composite.py +345 -0
- scitex/fts/_kinds/_plot/__init__.py +25 -0
- scitex/fts/_kinds/_plot/_backend/__init__.py +53 -0
- scitex/fts/_kinds/_plot/_backend/_export.py +165 -0
- scitex/fts/_kinds/_plot/_backend/_parser.py +188 -0
- scitex/fts/_kinds/_plot/_backend/_render.py +538 -0
- scitex/fts/_kinds/_plot/_dataclasses/_ChannelEncoding.py +46 -0
- scitex/fts/_kinds/_plot/_dataclasses/_Encoding.py +82 -0
- scitex/fts/_kinds/_plot/_dataclasses/_Theme.py +441 -0
- scitex/fts/_kinds/_plot/_dataclasses/_TraceEncoding.py +52 -0
- scitex/fts/_kinds/_plot/_dataclasses/__init__.py +47 -0
- scitex/fts/_kinds/_plot/_models/_Annotations.py +115 -0
- scitex/fts/_kinds/_plot/_models/_Axes.py +152 -0
- scitex/fts/_kinds/_plot/_models/_Figure.py +138 -0
- scitex/fts/_kinds/_plot/_models/_Guides.py +104 -0
- scitex/fts/_kinds/_plot/_models/_Plot.py +123 -0
- scitex/fts/_kinds/_plot/_models/_Styles.py +245 -0
- scitex/fts/_kinds/_plot/_models/__init__.py +80 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/__init__.py +156 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_bar.py +43 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_box.py +38 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_distribution.py +36 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_errorbar.py +60 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_histogram.py +30 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_image.py +61 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_line.py +57 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_scatter.py +30 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_seaborn.py +121 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_violin.py +36 -0
- scitex/fts/_kinds/_plot/_utils/__init__.py +129 -0
- scitex/fts/_kinds/_plot/_utils/_auto_layout.py +127 -0
- scitex/fts/_kinds/_plot/_utils/_calc_bounds.py +111 -0
- scitex/fts/_kinds/_plot/_utils/_const_sizes.py +48 -0
- scitex/fts/_kinds/_plot/_utils/_convert_coords.py +77 -0
- scitex/fts/_kinds/_plot/_utils/_get_template.py +178 -0
- scitex/fts/_kinds/_plot/_utils/_normalize.py +73 -0
- scitex/fts/_kinds/_plot/_utils/_plot_layout.py +397 -0
- scitex/fts/_kinds/_plot/_utils/_validate.py +197 -0
- scitex/fts/_kinds/_shape/__init__.py +141 -0
- scitex/fts/_kinds/_stats/__init__.py +56 -0
- scitex/fts/_kinds/_stats/_dataclasses/_Stats.py +423 -0
- scitex/fts/_kinds/_stats/_dataclasses/__init__.py +48 -0
- scitex/fts/_kinds/_table/__init__.py +72 -0
- scitex/fts/_kinds/_table/_latex/__init__.py +93 -0
- scitex/fts/_kinds/_table/_latex/_editor/__init__.py +11 -0
- scitex/fts/_kinds/_table/_latex/_editor/_app.py +725 -0
- scitex/fts/_kinds/_table/_latex/_export.py +279 -0
- scitex/fts/_kinds/_table/_latex/_figure_exporter.py +153 -0
- scitex/fts/_kinds/_table/_latex/_stats_formatter.py +274 -0
- scitex/fts/_kinds/_table/_latex/_table_exporter.py +362 -0
- scitex/fts/_kinds/_table/_latex/_utils.py +369 -0
- scitex/fts/_kinds/_table/_latex/_validator.py +445 -0
- scitex/fts/_kinds/_text/__init__.py +77 -0
- scitex/fts/_schemas/data_info.schema.json +75 -0
- scitex/fts/_schemas/encoding.schema.json +90 -0
- scitex/fts/_schemas/node.schema.json +145 -0
- scitex/fts/_schemas/render_manifest.schema.json +62 -0
- scitex/fts/_schemas/stats.schema.json +132 -0
- scitex/fts/_schemas/theme.schema.json +141 -0
- scitex/fts/_stats/__init__.py +48 -0
- scitex/fts/_stats/_dataclasses/_Stats.py +423 -0
- scitex/fts/_stats/_dataclasses/__init__.py +48 -0
- scitex/fts/_tables/__init__.py +65 -0
- scitex/fts/_tables/_latex/__init__.py +93 -0
- scitex/fts/_tables/_latex/_editor/__init__.py +11 -0
- scitex/fts/_tables/_latex/_editor/_app.py +725 -0
- scitex/fts/_tables/_latex/_export.py +279 -0
- scitex/fts/_tables/_latex/_figure_exporter.py +153 -0
- scitex/fts/_tables/_latex/_stats_formatter.py +274 -0
- scitex/fts/_tables/_latex/_table_exporter.py +362 -0
- scitex/fts/_tables/_latex/_utils.py +369 -0
- scitex/fts/_tables/_latex/_validator.py +445 -0
- scitex/gen/__init__.py +66 -25
- scitex/gen/misc.py +28 -0
- scitex/io/__init__.py +47 -20
- scitex/io/_load.py +87 -36
- scitex/io/_load_modules/__init__.py +10 -7
- scitex/io/_load_modules/_pandas.py +6 -1
- scitex/io/_save.py +299 -1556
- scitex/io/_save_modules/__init__.py +76 -19
- scitex/io/_save_modules/_figure_utils.py +90 -0
- scitex/io/_save_modules/_image_csv.py +497 -0
- scitex/io/_save_modules/_legends.py +91 -0
- scitex/io/_save_modules/_pltz_bundle.py +356 -0
- scitex/io/_save_modules/_pltz_stx.py +536 -0
- scitex/io/_save_modules/_stx_bundle.py +104 -0
- scitex/io/_save_modules/_symlink.py +96 -0
- scitex/io/_save_modules/_yaml.py +1 -1
- scitex/io/_save_modules/_zarr.py +64 -18
- scitex/io/bundle/README.md +212 -0
- scitex/io/bundle/__init__.py +110 -0
- scitex/io/{_bundle.py → bundle/_core.py} +219 -89
- scitex/io/bundle/_nested.py +713 -0
- scitex/io/bundle/_types.py +74 -0
- scitex/io/bundle/_zip.py +487 -0
- scitex/io/utils/h5_to_zarr.py +1 -1
- scitex/logging/__init__.py +108 -13
- scitex/logging/_errors.py +508 -0
- scitex/logging/_formatters.py +30 -6
- scitex/logging/_warnings.py +261 -0
- scitex/plt/__init__.py +4 -1
- scitex/plt/_figrecipe.py +236 -0
- scitex/plt/_subplots/_AxisWrapper.py +6 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/__init__.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_labels.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_metadata.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_AdjustmentMixin/_visual.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_base.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_scientific.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_statistical.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/_stx_aliases.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_RawMatplotlibMixin.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/__init__.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_base.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin/_wrappers.py +0 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +112 -1
- scitex/plt/_subplots/_FigWrapper.py +15 -0
- scitex/plt/_subplots/_SubplotsWrapper.py +125 -489
- scitex/plt/_subplots/_export_as_csv.py +11 -0
- scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +2 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_pcolormesh.py +66 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stackplot.py +62 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_bar.py +0 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_barh.py +0 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_errorbar.py +0 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stx_scatter.py +0 -0
- scitex/plt/_subplots/_export_as_csv_formatters/test_formatters.py +208 -0
- scitex/plt/_subplots/_fonts.py +71 -0
- scitex/plt/_subplots/_mm_layout.py +282 -0
- scitex/plt/gallery/__init__.py +99 -2
- scitex/plt/io/_layered_bundle.py +0 -0
- scitex/plt/styles/_plot_postprocess.py +3 -1
- scitex/plt/utils/_configure_mpl.py +16 -19
- scitex/repro/_RandomStateManager.py +13 -8
- scitex/resource/__init__.py +19 -1
- scitex/resource/_utils/_get_env_info.py +13 -25
- scitex/schema/__init__.py +149 -160
- scitex/schema/_encoding.py +273 -0
- scitex/schema/_figure_elements.py +406 -0
- scitex/schema/_plot.py +0 -0
- scitex/schema/_theme.py +360 -0
- scitex/schema/_validation.py +0 -98
- scitex/scholar/__init__.py +56 -14
- scitex/scholar/auth/ScholarAuthManager.py +1 -1
- scitex/scholar/auth/__init__.py +11 -2
- scitex/scholar/auth/providers/BaseAuthenticator.py +1 -1
- scitex/scholar/auth/providers/EZProxyAuthenticator.py +1 -1
- scitex/scholar/auth/providers/OpenAthensAuthenticator.py +1 -1
- scitex/scholar/auth/providers/ShibbolethAuthenticator.py +1 -1
- scitex/scholar/config/ScholarConfig.py +1 -1
- scitex/scholar/core/Scholar.py +1 -1
- scitex/session/_decorator.py +18 -16
- scitex/session/_lifecycle.py +9 -11
- scitex/session/template.py +9 -8
- scitex/sh/test_sh.py +72 -0
- scitex/sh/test_sh_simple.py +61 -0
- scitex/stats/__init__.py +221 -97
- scitex/stats/_schema.py +21 -22
- scitex/stats/descriptive/_circular.py +212 -351
- scitex/stats/descriptive/_describe.py +81 -132
- scitex/stats/descriptive/_nan.py +205 -433
- scitex/stats/descriptive/_real.py +127 -141
- scitex/str/_format_plot_text.py +5 -5
- scitex/str/_latex.py +26 -84
- scitex/str/_latex_fallback.py +53 -47
- scitex/web/_search_pubmed.py +5 -4
- scitex/writer/tests/test_diff_between.py +451 -0
- scitex/writer/tests/test_document_section.py +311 -0
- scitex/writer/tests/test_document_workflow.py +393 -0
- scitex/writer/tests/test_writer.py +361 -0
- scitex/writer/tests/test_writer_integration.py +303 -0
- {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/METADATA +364 -181
- {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/RECORD +479 -108
- scitex/fig/editor/_edit.py +0 -751
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/ARCHITECTURE_EXAMPLE.md +0 -905
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/BULLETIN_BOARD_EXAMPLE.md +0 -99
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/PROJECT_DESCRIPTION_EXAMPLE.md +0 -96
- {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/WHEEL +0 -0
- {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/entry_points.txt +0 -0
- {scitex-2.7.3.dist-info → scitex-2.10.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Timestamp: "2025-12-16 (ywatanabe)"
|
|
4
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/io/bundle/_nested.py
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
SciTeX Nested Bundle Access - Transparent access to nested bundles.
|
|
8
|
+
|
|
9
|
+
Provides unified API to access nested bundles (pltz inside figz) regardless
|
|
10
|
+
of whether they are stored as:
|
|
11
|
+
- ZIP files (.figz, .pltz)
|
|
12
|
+
- Directories (.figz.d, .pltz.d)
|
|
13
|
+
- Nested paths (Figure1.figz/A.pltz.d or Figure1.figz.d/A.pltz.d)
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
from scitex.io.bundle import nested
|
|
17
|
+
|
|
18
|
+
# Get pltz bundle from inside figz (works with both ZIP and directory)
|
|
19
|
+
pltz_data = nested.resolve("Figure1.figz/A.pltz.d")
|
|
20
|
+
pltz_data = nested.resolve("Figure1.figz.d/A.pltz.d")
|
|
21
|
+
|
|
22
|
+
# Get specific file from nested bundle
|
|
23
|
+
png_bytes = nested.get_file("Figure1.figz/A.pltz.d/exports/plot.png")
|
|
24
|
+
spec = nested.get_json("Figure1.figz/A.pltz.d/spec.json")
|
|
25
|
+
|
|
26
|
+
# Get preview image (common use case)
|
|
27
|
+
preview_bytes = nested.get_preview("Figure1.figz/A.pltz.d")
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import json
|
|
31
|
+
import zipfile
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
34
|
+
|
|
35
|
+
from ._types import NestedBundleNotFoundError
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"resolve",
|
|
39
|
+
"get_file",
|
|
40
|
+
"get_json",
|
|
41
|
+
"get_preview",
|
|
42
|
+
"put_file",
|
|
43
|
+
"put_json",
|
|
44
|
+
"list_files",
|
|
45
|
+
"parse_path",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def parse_path(
|
|
50
|
+
path: Union[str, Path]
|
|
51
|
+
) -> Tuple[Optional[Path], Optional[str], Optional[str]]:
|
|
52
|
+
"""Parse a path to identify parent bundle, nested bundle, and file.
|
|
53
|
+
|
|
54
|
+
Handles paths like:
|
|
55
|
+
- Figure1.figz/A.pltz.d
|
|
56
|
+
- Figure1.figz.d/A.pltz.d
|
|
57
|
+
- Figure1.figz/A.pltz.d/exports/plot.png
|
|
58
|
+
- Figure1.figz.d/A.pltz.d/spec.json
|
|
59
|
+
- A.pltz.d (standalone)
|
|
60
|
+
- A.pltz (standalone ZIP)
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
path: Path to parse. Can be absolute or relative.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Tuple of (parent_bundle_path, nested_bundle_name, file_path_within_nested):
|
|
67
|
+
- parent_bundle_path: Path to .figz or .figz.d, or None if standalone
|
|
68
|
+
- nested_bundle_name: Name of nested bundle (e.g., "A.pltz.d"), or None
|
|
69
|
+
- file_path_within_nested: Path within nested bundle, or None
|
|
70
|
+
"""
|
|
71
|
+
p = Path(path)
|
|
72
|
+
parts = p.parts
|
|
73
|
+
|
|
74
|
+
parent_bundle = None
|
|
75
|
+
nested_bundle = None
|
|
76
|
+
file_within = None
|
|
77
|
+
|
|
78
|
+
# Find the first .figz or .figz.d component
|
|
79
|
+
figz_idx = None
|
|
80
|
+
for i, part in enumerate(parts):
|
|
81
|
+
if part.endswith(".figz") or part.endswith(".figz.d"):
|
|
82
|
+
figz_idx = i
|
|
83
|
+
break
|
|
84
|
+
|
|
85
|
+
if figz_idx is not None:
|
|
86
|
+
parent_bundle = Path(*parts[: figz_idx + 1])
|
|
87
|
+
remaining = parts[figz_idx + 1 :]
|
|
88
|
+
|
|
89
|
+
if remaining:
|
|
90
|
+
if remaining[0].endswith(".pltz.d") or remaining[0].endswith(".pltz"):
|
|
91
|
+
nested_bundle = remaining[0]
|
|
92
|
+
if len(remaining) > 1:
|
|
93
|
+
file_within = str(Path(*remaining[1:]))
|
|
94
|
+
else:
|
|
95
|
+
file_within = str(Path(*remaining))
|
|
96
|
+
else:
|
|
97
|
+
for i, part in enumerate(parts):
|
|
98
|
+
if part.endswith(".pltz.d") or part.endswith(".pltz"):
|
|
99
|
+
nested_bundle = part
|
|
100
|
+
parent_bundle = Path(*parts[:i]) if i > 0 else None
|
|
101
|
+
if i + 1 < len(parts):
|
|
102
|
+
file_within = str(Path(*parts[i + 1 :]))
|
|
103
|
+
break
|
|
104
|
+
else:
|
|
105
|
+
return None, None, str(p)
|
|
106
|
+
|
|
107
|
+
return parent_bundle, nested_bundle, file_within
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _find_bundle_path(base_path: Path, prefer_directory: bool = True) -> Optional[Path]:
|
|
111
|
+
"""Find the actual bundle path (ZIP or directory).
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
base_path: Path to search for (with or without .d extension).
|
|
115
|
+
prefer_directory: If True, prefer .d directory over ZIP when both exist.
|
|
116
|
+
This is important for figz bundles where panels may be in the directory
|
|
117
|
+
while the ZIP is an older export.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Path to the bundle (directory or ZIP), or None if not found.
|
|
121
|
+
"""
|
|
122
|
+
# Check for .d directory variant
|
|
123
|
+
if base_path.suffix in (".figz", ".pltz", ".statsz"):
|
|
124
|
+
dir_path = Path(str(base_path) + ".d")
|
|
125
|
+
if prefer_directory and dir_path.exists():
|
|
126
|
+
return dir_path
|
|
127
|
+
if base_path.exists():
|
|
128
|
+
return base_path
|
|
129
|
+
if dir_path.exists():
|
|
130
|
+
return dir_path
|
|
131
|
+
|
|
132
|
+
# Check for ZIP variant
|
|
133
|
+
if str(base_path).endswith(".d"):
|
|
134
|
+
zip_path = Path(str(base_path)[:-2])
|
|
135
|
+
if not prefer_directory and zip_path.exists():
|
|
136
|
+
return zip_path
|
|
137
|
+
if base_path.exists():
|
|
138
|
+
return base_path
|
|
139
|
+
if zip_path.exists():
|
|
140
|
+
return zip_path
|
|
141
|
+
|
|
142
|
+
# Direct path
|
|
143
|
+
if base_path.exists():
|
|
144
|
+
return base_path
|
|
145
|
+
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _read_from_zip(zip_path: Path, internal_path: str) -> bytes:
|
|
150
|
+
"""Read file from ZIP archive, dynamically searching for nested bundles."""
|
|
151
|
+
with zipfile.ZipFile(zip_path, "r") as zf:
|
|
152
|
+
namelist = zf.namelist()
|
|
153
|
+
|
|
154
|
+
if internal_path in namelist:
|
|
155
|
+
return zf.read(internal_path)
|
|
156
|
+
|
|
157
|
+
for name in namelist:
|
|
158
|
+
if name.endswith("/" + internal_path) or name.endswith(internal_path):
|
|
159
|
+
return zf.read(name)
|
|
160
|
+
|
|
161
|
+
if ".pltz.d/" in internal_path:
|
|
162
|
+
pltz_dir_name, file_in_pltz = internal_path.split(".pltz.d/", 1)
|
|
163
|
+
pltz_dir = pltz_dir_name + ".pltz.d"
|
|
164
|
+
|
|
165
|
+
for name in namelist:
|
|
166
|
+
if f"/{pltz_dir}/" in name or name.startswith(pltz_dir + "/"):
|
|
167
|
+
if name.endswith("/" + file_in_pltz) or name.endswith(file_in_pltz):
|
|
168
|
+
return zf.read(name)
|
|
169
|
+
|
|
170
|
+
base_name = pltz_dir_name
|
|
171
|
+
for name in namelist:
|
|
172
|
+
if name.endswith(".pltz") and base_name in name:
|
|
173
|
+
pltz_data = zf.read(name)
|
|
174
|
+
return _read_from_nested_zip(pltz_data, file_in_pltz, name)
|
|
175
|
+
|
|
176
|
+
raise NestedBundleNotFoundError(
|
|
177
|
+
f"File not found in {zip_path}: {internal_path}\n"
|
|
178
|
+
f"Available: {namelist[:10]}{'...' if len(namelist) > 10 else ''}"
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _read_from_nested_zip(zip_data: bytes, internal_path: str, zip_name: str = "") -> bytes:
|
|
183
|
+
"""Read file from a ZIP archive stored as bytes (nested ZIP)."""
|
|
184
|
+
import io
|
|
185
|
+
|
|
186
|
+
with zipfile.ZipFile(io.BytesIO(zip_data), "r") as nested_zf:
|
|
187
|
+
namelist = nested_zf.namelist()
|
|
188
|
+
|
|
189
|
+
if internal_path in namelist:
|
|
190
|
+
return nested_zf.read(internal_path)
|
|
191
|
+
|
|
192
|
+
base_name = zip_name.replace(".pltz", "") if zip_name else ""
|
|
193
|
+
d_prefix = f"{base_name}.pltz.d/"
|
|
194
|
+
|
|
195
|
+
full_path = d_prefix + internal_path
|
|
196
|
+
if full_path in namelist:
|
|
197
|
+
return nested_zf.read(full_path)
|
|
198
|
+
|
|
199
|
+
for name in namelist:
|
|
200
|
+
if name.endswith("/" + internal_path) or name.endswith(internal_path):
|
|
201
|
+
return nested_zf.read(name)
|
|
202
|
+
|
|
203
|
+
raise NestedBundleNotFoundError(
|
|
204
|
+
f"File not found in nested ZIP {zip_name}: {internal_path}\n"
|
|
205
|
+
f"Available: {namelist[:10]}{'...' if len(namelist) > 10 else ''}"
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _read_from_directory(dir_path: Path, internal_path: str) -> bytes:
|
|
210
|
+
"""Read file from directory structure."""
|
|
211
|
+
file_path = dir_path / internal_path
|
|
212
|
+
if file_path.exists():
|
|
213
|
+
return file_path.read_bytes()
|
|
214
|
+
|
|
215
|
+
if ".pltz/" in internal_path:
|
|
216
|
+
alt_path = internal_path.replace(".pltz/", ".pltz.d/")
|
|
217
|
+
alt_file_path = dir_path / alt_path
|
|
218
|
+
if alt_file_path.exists():
|
|
219
|
+
return alt_file_path.read_bytes()
|
|
220
|
+
|
|
221
|
+
raise NestedBundleNotFoundError(f"File not found: {file_path}")
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def get_file(path: Union[str, Path]) -> bytes:
|
|
225
|
+
"""Get file bytes from a nested bundle path.
|
|
226
|
+
|
|
227
|
+
Transparently handles both ZIP and directory bundles.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
path: Full path to file, e.g.:
|
|
231
|
+
- "Figure1.figz/A.pltz.d/exports/plot.png"
|
|
232
|
+
- "Figure1.figz.d/A.pltz.d/exports/plot.png"
|
|
233
|
+
- "/abs/path/Figure1.figz/A.pltz.d/spec.json"
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
File contents as bytes.
|
|
237
|
+
|
|
238
|
+
Raises:
|
|
239
|
+
NestedBundleNotFoundError: If file or bundle not found.
|
|
240
|
+
"""
|
|
241
|
+
parent_bundle, nested_bundle, file_within = parse_path(path)
|
|
242
|
+
|
|
243
|
+
if parent_bundle is None and nested_bundle is None:
|
|
244
|
+
p = Path(path)
|
|
245
|
+
if p.exists():
|
|
246
|
+
return p.read_bytes()
|
|
247
|
+
raise NestedBundleNotFoundError(f"File not found: {path}")
|
|
248
|
+
|
|
249
|
+
if parent_bundle:
|
|
250
|
+
actual_parent = _find_bundle_path(parent_bundle)
|
|
251
|
+
if actual_parent is None:
|
|
252
|
+
raise NestedBundleNotFoundError(f"Parent bundle not found: {parent_bundle}")
|
|
253
|
+
|
|
254
|
+
if nested_bundle and file_within:
|
|
255
|
+
internal_path = f"{nested_bundle}/{file_within}"
|
|
256
|
+
elif nested_bundle:
|
|
257
|
+
internal_path = nested_bundle
|
|
258
|
+
else:
|
|
259
|
+
internal_path = file_within or ""
|
|
260
|
+
|
|
261
|
+
if actual_parent.is_file():
|
|
262
|
+
return _read_from_zip(actual_parent, internal_path)
|
|
263
|
+
else:
|
|
264
|
+
return _read_from_directory(actual_parent, internal_path)
|
|
265
|
+
|
|
266
|
+
if nested_bundle:
|
|
267
|
+
bundle_path = _find_bundle_path(Path(nested_bundle))
|
|
268
|
+
if bundle_path is None:
|
|
269
|
+
raise NestedBundleNotFoundError(f"Bundle not found: {nested_bundle}")
|
|
270
|
+
|
|
271
|
+
if file_within:
|
|
272
|
+
if bundle_path.is_file():
|
|
273
|
+
return _read_from_zip(bundle_path, file_within)
|
|
274
|
+
else:
|
|
275
|
+
return _read_from_directory(bundle_path, file_within)
|
|
276
|
+
|
|
277
|
+
raise NestedBundleNotFoundError(f"Cannot resolve path: {path}")
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def get_json(path: Union[str, Path]) -> Dict[str, Any]:
|
|
281
|
+
"""Get JSON from a nested bundle path.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
path: Path to JSON file within bundle.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Parsed JSON as dictionary.
|
|
288
|
+
"""
|
|
289
|
+
data = get_file(path)
|
|
290
|
+
return json.loads(data.decode("utf-8"))
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def put_file(path: Union[str, Path], data: bytes) -> None:
|
|
294
|
+
"""Write file bytes to a nested bundle path.
|
|
295
|
+
|
|
296
|
+
Transparently handles both ZIP and directory bundles.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
path: Full path to file, e.g.:
|
|
300
|
+
- "Figure1.figz/A.pltz.d/exports/plot.png"
|
|
301
|
+
- "Figure1.figz.d/A.pltz.d/exports/plot.png"
|
|
302
|
+
data: File contents as bytes.
|
|
303
|
+
|
|
304
|
+
Raises:
|
|
305
|
+
NestedBundleNotFoundError: If bundle not found.
|
|
306
|
+
"""
|
|
307
|
+
parent_bundle, nested_bundle, file_within = parse_path(path)
|
|
308
|
+
|
|
309
|
+
if parent_bundle is None and nested_bundle is None:
|
|
310
|
+
p = Path(path)
|
|
311
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
312
|
+
p.write_bytes(data)
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
if parent_bundle:
|
|
316
|
+
actual_parent = _find_bundle_path(parent_bundle)
|
|
317
|
+
if actual_parent is None:
|
|
318
|
+
raise NestedBundleNotFoundError(f"Parent bundle not found: {parent_bundle}")
|
|
319
|
+
|
|
320
|
+
if nested_bundle and file_within:
|
|
321
|
+
internal_path = f"{nested_bundle}/{file_within}"
|
|
322
|
+
elif nested_bundle:
|
|
323
|
+
internal_path = nested_bundle
|
|
324
|
+
else:
|
|
325
|
+
internal_path = file_within or ""
|
|
326
|
+
|
|
327
|
+
if actual_parent.is_file():
|
|
328
|
+
_write_to_zip(actual_parent, internal_path, data)
|
|
329
|
+
else:
|
|
330
|
+
_write_to_directory(actual_parent, internal_path, data)
|
|
331
|
+
return
|
|
332
|
+
|
|
333
|
+
if nested_bundle:
|
|
334
|
+
bundle_path = _find_bundle_path(Path(nested_bundle))
|
|
335
|
+
if bundle_path is None:
|
|
336
|
+
raise NestedBundleNotFoundError(f"Bundle not found: {nested_bundle}")
|
|
337
|
+
|
|
338
|
+
if file_within:
|
|
339
|
+
if bundle_path.is_file():
|
|
340
|
+
_write_to_zip(bundle_path, file_within, data)
|
|
341
|
+
else:
|
|
342
|
+
_write_to_directory(bundle_path, file_within, data)
|
|
343
|
+
return
|
|
344
|
+
|
|
345
|
+
raise NestedBundleNotFoundError(f"Cannot resolve path for writing: {path}")
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def put_json(path: Union[str, Path], data: Dict[str, Any]) -> None:
|
|
349
|
+
"""Write JSON to a nested bundle path.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
path: Path to JSON file within bundle.
|
|
353
|
+
data: Dictionary to write as JSON.
|
|
354
|
+
"""
|
|
355
|
+
json_bytes = json.dumps(data, indent=2).encode("utf-8")
|
|
356
|
+
put_file(path, json_bytes)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _write_to_zip(zip_path: Path, internal_path: str, data: bytes) -> None:
|
|
360
|
+
"""Write file to a ZIP archive."""
|
|
361
|
+
with zipfile.ZipFile(zip_path, "r") as zf:
|
|
362
|
+
namelist = zf.namelist()
|
|
363
|
+
contents = {name: zf.read(name) for name in namelist}
|
|
364
|
+
|
|
365
|
+
target_path = None
|
|
366
|
+
for name in namelist:
|
|
367
|
+
if name.endswith("/" + internal_path) or name == internal_path:
|
|
368
|
+
target_path = name
|
|
369
|
+
break
|
|
370
|
+
if f"/{internal_path.split('/')[0]}/" in name:
|
|
371
|
+
idx = name.find(f"/{internal_path.split('/')[0]}/")
|
|
372
|
+
prefix = name[: idx + 1]
|
|
373
|
+
candidate = prefix + internal_path
|
|
374
|
+
if candidate in namelist:
|
|
375
|
+
target_path = candidate
|
|
376
|
+
break
|
|
377
|
+
|
|
378
|
+
if target_path is None:
|
|
379
|
+
for name in namelist:
|
|
380
|
+
if internal_path.split("/")[0] + "/" in name:
|
|
381
|
+
idx = name.find(internal_path.split("/")[0] + "/")
|
|
382
|
+
if idx > 0:
|
|
383
|
+
target_path = name[:idx] + internal_path
|
|
384
|
+
break
|
|
385
|
+
if target_path is None:
|
|
386
|
+
target_path = internal_path
|
|
387
|
+
|
|
388
|
+
contents[target_path] = data
|
|
389
|
+
|
|
390
|
+
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
391
|
+
for name, file_data in contents.items():
|
|
392
|
+
zf.writestr(name, file_data)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _write_to_directory(dir_path: Path, internal_path: str, data: bytes) -> None:
|
|
396
|
+
"""Write file to directory structure."""
|
|
397
|
+
file_path = dir_path / internal_path
|
|
398
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
399
|
+
file_path.write_bytes(data)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def get_preview(
|
|
403
|
+
bundle_path: Union[str, Path],
|
|
404
|
+
filename: str = None,
|
|
405
|
+
) -> bytes:
|
|
406
|
+
"""Get preview PNG from a nested pltz bundle.
|
|
407
|
+
|
|
408
|
+
Handles .pltz and .pltz.d interchangeably. Does not assume the PNG
|
|
409
|
+
filename matches the bundle name (e.g., B.pltz.d may contain stx_scatter.png).
|
|
410
|
+
|
|
411
|
+
Search order:
|
|
412
|
+
1. Specific filename if provided
|
|
413
|
+
2. exports/{basename}.png (bundle name)
|
|
414
|
+
3. exports/plot.png
|
|
415
|
+
4. {basename}.png
|
|
416
|
+
5. plot.png
|
|
417
|
+
6. Any PNG in exports/ not containing 'hitmap' or 'overview'
|
|
418
|
+
7. Any PNG in bundle not containing 'hitmap' or 'overview'
|
|
419
|
+
|
|
420
|
+
Args:
|
|
421
|
+
bundle_path: Path to pltz bundle (can be nested in figz).
|
|
422
|
+
Handles both .pltz and .pltz.d extensions interchangeably.
|
|
423
|
+
filename: Specific filename to look for (optional).
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
PNG image bytes.
|
|
427
|
+
"""
|
|
428
|
+
parent_bundle, nested_bundle, file_within = parse_path(bundle_path)
|
|
429
|
+
|
|
430
|
+
# Build base path, handling .pltz ↔ .pltz.d interchangeably
|
|
431
|
+
if parent_bundle and nested_bundle:
|
|
432
|
+
base_path = f"{parent_bundle}/{nested_bundle}"
|
|
433
|
+
elif nested_bundle:
|
|
434
|
+
base_path = nested_bundle
|
|
435
|
+
else:
|
|
436
|
+
base_path = str(bundle_path)
|
|
437
|
+
|
|
438
|
+
# Try alternate extension if list_files fails
|
|
439
|
+
def try_with_fallback(path: str) -> List[str]:
|
|
440
|
+
"""Try to list files, with .pltz ↔ .pltz.d fallback."""
|
|
441
|
+
try:
|
|
442
|
+
return list_files(path)
|
|
443
|
+
except NestedBundleNotFoundError:
|
|
444
|
+
# Try alternate extension
|
|
445
|
+
if path.endswith(".pltz"):
|
|
446
|
+
alt_path = path + ".d"
|
|
447
|
+
elif path.endswith(".pltz.d"):
|
|
448
|
+
alt_path = path[:-2] # Remove .d
|
|
449
|
+
else:
|
|
450
|
+
raise
|
|
451
|
+
return list_files(alt_path)
|
|
452
|
+
|
|
453
|
+
# Find actual working path
|
|
454
|
+
working_path = base_path
|
|
455
|
+
try:
|
|
456
|
+
files = try_with_fallback(base_path)
|
|
457
|
+
# Determine which path worked
|
|
458
|
+
if base_path.endswith(".pltz"):
|
|
459
|
+
alt_path = base_path + ".d"
|
|
460
|
+
try:
|
|
461
|
+
list_files(base_path)
|
|
462
|
+
except NestedBundleNotFoundError:
|
|
463
|
+
working_path = alt_path
|
|
464
|
+
elif base_path.endswith(".pltz.d"):
|
|
465
|
+
alt_path = base_path[:-2]
|
|
466
|
+
try:
|
|
467
|
+
list_files(base_path)
|
|
468
|
+
except NestedBundleNotFoundError:
|
|
469
|
+
working_path = alt_path
|
|
470
|
+
except NestedBundleNotFoundError:
|
|
471
|
+
files = []
|
|
472
|
+
|
|
473
|
+
bundle_name = Path(working_path).stem.replace(".pltz", "")
|
|
474
|
+
|
|
475
|
+
# Standard locations to try
|
|
476
|
+
locations = [
|
|
477
|
+
f"exports/{bundle_name}.png",
|
|
478
|
+
"exports/plot.png",
|
|
479
|
+
f"{bundle_name}.png",
|
|
480
|
+
"plot.png",
|
|
481
|
+
]
|
|
482
|
+
|
|
483
|
+
if filename:
|
|
484
|
+
locations.insert(0, filename)
|
|
485
|
+
|
|
486
|
+
# Try standard locations first
|
|
487
|
+
for loc in locations:
|
|
488
|
+
try:
|
|
489
|
+
return get_file(f"{working_path}/{loc}")
|
|
490
|
+
except NestedBundleNotFoundError:
|
|
491
|
+
continue
|
|
492
|
+
|
|
493
|
+
# Fallback: find ANY suitable PNG in the bundle
|
|
494
|
+
# Prioritize exports/ directory, then root
|
|
495
|
+
exports_pngs = []
|
|
496
|
+
root_pngs = []
|
|
497
|
+
|
|
498
|
+
for f in files:
|
|
499
|
+
if f.endswith(".png") and "hitmap" not in f and "overview" not in f:
|
|
500
|
+
if f.startswith("exports/"):
|
|
501
|
+
exports_pngs.append(f)
|
|
502
|
+
else:
|
|
503
|
+
root_pngs.append(f)
|
|
504
|
+
|
|
505
|
+
# Try exports/ PNGs first
|
|
506
|
+
for f in exports_pngs:
|
|
507
|
+
try:
|
|
508
|
+
return get_file(f"{working_path}/{f}")
|
|
509
|
+
except NestedBundleNotFoundError:
|
|
510
|
+
continue
|
|
511
|
+
|
|
512
|
+
# Then try root PNGs
|
|
513
|
+
for f in root_pngs:
|
|
514
|
+
try:
|
|
515
|
+
return get_file(f"{working_path}/{f}")
|
|
516
|
+
except NestedBundleNotFoundError:
|
|
517
|
+
continue
|
|
518
|
+
|
|
519
|
+
raise NestedBundleNotFoundError(
|
|
520
|
+
f"No preview image found in {bundle_path}. "
|
|
521
|
+
f"Tried: {locations}, then searched {len(files)} files"
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def list_files(bundle_path: Union[str, Path]) -> List[str]:
|
|
526
|
+
"""List files in a nested bundle.
|
|
527
|
+
|
|
528
|
+
Args:
|
|
529
|
+
bundle_path: Path to bundle (nested or standalone).
|
|
530
|
+
|
|
531
|
+
Returns:
|
|
532
|
+
List of file paths relative to bundle root.
|
|
533
|
+
"""
|
|
534
|
+
parent_bundle, nested_bundle, _ = parse_path(bundle_path)
|
|
535
|
+
|
|
536
|
+
if parent_bundle:
|
|
537
|
+
actual_parent = _find_bundle_path(parent_bundle)
|
|
538
|
+
if actual_parent is None:
|
|
539
|
+
raise NestedBundleNotFoundError(f"Bundle not found: {parent_bundle}")
|
|
540
|
+
|
|
541
|
+
if actual_parent.is_file():
|
|
542
|
+
with zipfile.ZipFile(actual_parent, "r") as zf:
|
|
543
|
+
namelist = zf.namelist()
|
|
544
|
+
|
|
545
|
+
if nested_bundle:
|
|
546
|
+
files = []
|
|
547
|
+
|
|
548
|
+
for name in namelist:
|
|
549
|
+
if f"/{nested_bundle}/" in name or name.startswith(
|
|
550
|
+
nested_bundle + "/"
|
|
551
|
+
):
|
|
552
|
+
if name.startswith(nested_bundle + "/"):
|
|
553
|
+
rel = name[len(nested_bundle) + 1 :]
|
|
554
|
+
else:
|
|
555
|
+
idx = name.find(f"/{nested_bundle}/")
|
|
556
|
+
rel = name[idx + len(nested_bundle) + 2 :]
|
|
557
|
+
if rel and not rel.endswith("/"):
|
|
558
|
+
files.append(rel)
|
|
559
|
+
|
|
560
|
+
if files:
|
|
561
|
+
return files
|
|
562
|
+
|
|
563
|
+
if nested_bundle.endswith(".pltz.d"):
|
|
564
|
+
base_name = nested_bundle[:-7]
|
|
565
|
+
for name in namelist:
|
|
566
|
+
if name.endswith(".pltz") and base_name in name:
|
|
567
|
+
pltz_data = zf.read(name)
|
|
568
|
+
return _list_nested_zip_files(pltz_data, name)
|
|
569
|
+
|
|
570
|
+
return files
|
|
571
|
+
else:
|
|
572
|
+
return [n for n in namelist if not n.endswith("/")]
|
|
573
|
+
else:
|
|
574
|
+
target = actual_parent
|
|
575
|
+
if nested_bundle:
|
|
576
|
+
# Prefer .d directory over ZIP when both exist
|
|
577
|
+
if nested_bundle.endswith(".pltz"):
|
|
578
|
+
dir_target = actual_parent / (nested_bundle + ".d")
|
|
579
|
+
if dir_target.exists():
|
|
580
|
+
target = dir_target
|
|
581
|
+
else:
|
|
582
|
+
target = actual_parent / nested_bundle
|
|
583
|
+
elif nested_bundle.endswith(".pltz.d"):
|
|
584
|
+
target = actual_parent / nested_bundle
|
|
585
|
+
if not target.exists():
|
|
586
|
+
# Try without .d
|
|
587
|
+
zip_target = actual_parent / nested_bundle[:-2]
|
|
588
|
+
if zip_target.exists():
|
|
589
|
+
target = zip_target
|
|
590
|
+
else:
|
|
591
|
+
target = actual_parent / nested_bundle
|
|
592
|
+
|
|
593
|
+
if not target.exists():
|
|
594
|
+
raise NestedBundleNotFoundError(f"Bundle not found: {target}")
|
|
595
|
+
|
|
596
|
+
return [str(f.relative_to(target)) for f in target.rglob("*") if f.is_file()]
|
|
597
|
+
|
|
598
|
+
bundle = Path(bundle_path)
|
|
599
|
+
actual = _find_bundle_path(bundle)
|
|
600
|
+
if actual is None:
|
|
601
|
+
raise NestedBundleNotFoundError(f"Bundle not found: {bundle_path}")
|
|
602
|
+
|
|
603
|
+
if actual.is_file():
|
|
604
|
+
with zipfile.ZipFile(actual, "r") as zf:
|
|
605
|
+
return [n for n in zf.namelist() if not n.endswith("/")]
|
|
606
|
+
else:
|
|
607
|
+
return [str(f.relative_to(actual)) for f in actual.rglob("*") if f.is_file()]
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def _list_nested_zip_files(zip_data: bytes, zip_name: str = "") -> List[str]:
|
|
611
|
+
"""List files in a nested ZIP archive."""
|
|
612
|
+
import io
|
|
613
|
+
|
|
614
|
+
with zipfile.ZipFile(io.BytesIO(zip_data), "r") as nested_zf:
|
|
615
|
+
namelist = nested_zf.namelist()
|
|
616
|
+
|
|
617
|
+
base_name = zip_name.replace(".pltz", "") if zip_name else ""
|
|
618
|
+
d_prefix = f"{base_name}.pltz.d/"
|
|
619
|
+
|
|
620
|
+
files = []
|
|
621
|
+
for name in namelist:
|
|
622
|
+
if name.startswith(d_prefix):
|
|
623
|
+
rel = name[len(d_prefix) :]
|
|
624
|
+
if rel and not rel.endswith("/"):
|
|
625
|
+
files.append(rel)
|
|
626
|
+
elif not name.endswith("/"):
|
|
627
|
+
files.append(name)
|
|
628
|
+
|
|
629
|
+
return files
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
def resolve(
|
|
633
|
+
bundle_path: Union[str, Path],
|
|
634
|
+
extract_to: Optional[Path] = None,
|
|
635
|
+
) -> Dict[str, Any]:
|
|
636
|
+
"""Load a nested bundle's data.
|
|
637
|
+
|
|
638
|
+
Transparently handles:
|
|
639
|
+
- Standalone .pltz or .pltz.d
|
|
640
|
+
- Nested .pltz.d inside .figz
|
|
641
|
+
- Nested .pltz.d inside .figz.d
|
|
642
|
+
|
|
643
|
+
Args:
|
|
644
|
+
bundle_path: Path to bundle, e.g.:
|
|
645
|
+
- "Figure1.figz/A.pltz.d"
|
|
646
|
+
- "Figure1.figz.d/A.pltz.d"
|
|
647
|
+
- "A.pltz.d"
|
|
648
|
+
- "A.pltz"
|
|
649
|
+
extract_to: If provided, extract ZIP contents to this directory.
|
|
650
|
+
|
|
651
|
+
Returns:
|
|
652
|
+
Dictionary with bundle data:
|
|
653
|
+
- 'spec': Parsed spec.json
|
|
654
|
+
- 'style': Parsed style.json (if exists)
|
|
655
|
+
- 'data': CSV data as DataFrame (if exists)
|
|
656
|
+
- 'path': Original path
|
|
657
|
+
- 'is_nested': Whether bundle is nested in another
|
|
658
|
+
- 'files': List of files in bundle
|
|
659
|
+
"""
|
|
660
|
+
result = {
|
|
661
|
+
"path": str(bundle_path),
|
|
662
|
+
"is_nested": False,
|
|
663
|
+
"spec": None,
|
|
664
|
+
"style": None,
|
|
665
|
+
"data": None,
|
|
666
|
+
"files": [],
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
parent_bundle, nested_bundle, _ = parse_path(bundle_path)
|
|
670
|
+
result["is_nested"] = parent_bundle is not None and nested_bundle is not None
|
|
671
|
+
|
|
672
|
+
try:
|
|
673
|
+
result["files"] = list_files(bundle_path)
|
|
674
|
+
except Exception:
|
|
675
|
+
result["files"] = []
|
|
676
|
+
|
|
677
|
+
try:
|
|
678
|
+
result["spec"] = get_json(f"{bundle_path}/spec.json")
|
|
679
|
+
except NestedBundleNotFoundError:
|
|
680
|
+
bundle_name = Path(bundle_path).stem.replace(".pltz", "")
|
|
681
|
+
try:
|
|
682
|
+
result["spec"] = get_json(f"{bundle_path}/{bundle_name}.json")
|
|
683
|
+
except NestedBundleNotFoundError:
|
|
684
|
+
pass
|
|
685
|
+
|
|
686
|
+
try:
|
|
687
|
+
result["style"] = get_json(f"{bundle_path}/style.json")
|
|
688
|
+
except NestedBundleNotFoundError:
|
|
689
|
+
pass
|
|
690
|
+
|
|
691
|
+
try:
|
|
692
|
+
import io
|
|
693
|
+
|
|
694
|
+
import pandas as pd
|
|
695
|
+
|
|
696
|
+
csv_bytes = get_file(f"{bundle_path}/data.csv")
|
|
697
|
+
result["data"] = pd.read_csv(io.BytesIO(csv_bytes))
|
|
698
|
+
except (NestedBundleNotFoundError, ImportError):
|
|
699
|
+
bundle_name = Path(bundle_path).stem.replace(".pltz", "")
|
|
700
|
+
try:
|
|
701
|
+
import io
|
|
702
|
+
|
|
703
|
+
import pandas as pd
|
|
704
|
+
|
|
705
|
+
csv_bytes = get_file(f"{bundle_path}/{bundle_name}.csv")
|
|
706
|
+
result["data"] = pd.read_csv(io.BytesIO(csv_bytes))
|
|
707
|
+
except (NestedBundleNotFoundError, ImportError):
|
|
708
|
+
pass
|
|
709
|
+
|
|
710
|
+
return result
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
# EOF
|