scitex 2.10.3__py3-none-any.whl → 2.11.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 +1 -4
- scitex/__version__.py +1 -1
- scitex/_install_guide.py +14 -2
- scitex/bridge/_figrecipe.py +1 -1
- scitex/bridge/_helpers.py +1 -1
- scitex/bridge/_plt_vis.py +1 -1
- scitex/bridge/_stats_plt.py +1 -1
- scitex/bridge/_stats_vis.py +2 -2
- scitex/{fig → canvas}/__init__.py +84 -96
- scitex/{fig → canvas}/backend/_parser.py +1 -1
- scitex/{fig → canvas}/canvas.py +13 -14
- scitex/{fts/_fig/_editor → canvas/editor}/_defaults.py +2 -2
- scitex/{fig → canvas}/editor/edit/__init__.py +11 -14
- scitex/{fig → canvas}/editor/edit/bundle_resolver.py +56 -48
- scitex/{fig → canvas}/editor/edit/editor_launcher.py +79 -26
- scitex/{fts/_fig/_editor/_cui/_panel_loader.py → canvas/editor/edit/panel_loader.py} +8 -8
- scitex/{fts/_fig/_editor/_gui/_flask_editor → canvas/editor/flask_editor}/_bbox.py +2 -1
- scitex/{fts/_fig/_editor/_gui/_flask_editor → canvas/editor/flask_editor}/_core.py +84 -84
- scitex/{fts/_fig/_editor/_gui/_flask_editor → canvas/editor/flask_editor}/_renderer.py +7 -6
- scitex/{fts/_fig/_editor/_gui/_flask_editor → canvas/editor/flask_editor}/static/css/features/canvas.css +2 -2
- scitex/{fig → canvas}/editor/flask_editor/static/css/features/panel-grid.css +1 -1
- scitex/{fig → canvas}/editor/flask_editor/static/js/core/api.js +3 -4
- scitex/{fig → canvas}/editor/flask_editor/static/js/editor/preview.js +5 -5
- scitex/{fig → canvas}/editor/flask_editor/templates/_html.py +3 -3
- scitex/{fig → canvas}/editor/flask_editor/templates/_scripts.py +10 -10
- scitex/{fig → canvas}/editor/flask_editor/templates/_styles.py +3 -3
- scitex/{fig → canvas}/io/__init__.py +32 -38
- scitex/{fig → canvas}/io/_bundle.py +217 -154
- scitex/{fig → canvas}/io/_canvas.py +1 -1
- scitex/{fig → canvas}/io/_data.py +1 -1
- scitex/{fig → canvas}/io/_export.py +1 -1
- scitex/{fig → canvas}/io/_load.py +1 -1
- scitex/{fig → canvas}/io/_panel.py +1 -1
- scitex/{fig → canvas}/io/_save.py +1 -1
- scitex/{fig → canvas}/model/__init__.py +1 -1
- scitex/{fig → canvas}/model/_annotations.py +1 -1
- scitex/{fig → canvas}/model/_axes.py +1 -1
- scitex/{fig → canvas}/model/_figure.py +1 -1
- scitex/{fig → canvas}/model/_guides.py +1 -1
- scitex/{fig → canvas}/model/_plot.py +1 -1
- scitex/{fig → canvas}/model/_styles.py +1 -1
- scitex/{fig → canvas}/utils/__init__.py +1 -1
- scitex/cli/convert.py +10 -6
- scitex/diagram/README.md +7 -7
- scitex/io/__init__.py +7 -19
- scitex/io/_load.py +15 -19
- scitex/io/_load_modules/_canvas.py +2 -2
- scitex/io/_load_modules/_con.py +5 -5
- scitex/io/_load_modules/_eeg.py +16 -12
- scitex/io/_save.py +11 -16
- scitex/io/_save_modules/__init__.py +6 -10
- scitex/io/_save_modules/_canvas.py +3 -3
- scitex/io/_save_modules/_plot_bundle.py +112 -0
- scitex/io/_save_modules/{_pltz_stx.py → _plot_scitex.py} +7 -7
- scitex/io/_save_modules/_stx_bundle.py +16 -16
- scitex/io/bundle/README.md +89 -80
- scitex/{fts/_bundle/_FTS.py → io/bundle/_Bundle.py} +197 -95
- scitex/io/bundle/__init__.py +67 -35
- scitex/{fts/_bundle → io/bundle}/_children.py +32 -40
- scitex/io/bundle/_core.py +184 -97
- scitex/{fts/_bundle/_dataclasses/_Node.py → io/bundle/_dataclasses/_Spec.py} +29 -23
- scitex/{fts/_bundle/_dataclasses/_NodeRefs.py → io/bundle/_dataclasses/_SpecRefs.py} +6 -6
- scitex/{fts/_bundle → io/bundle}/_dataclasses/__init__.py +4 -4
- scitex/{fts/_bundle → io/bundle}/_loader.py +19 -19
- scitex/io/bundle/_manifest.py +99 -0
- scitex/{fts/_bundle → io/bundle}/_mpl_helpers.py +119 -28
- scitex/io/bundle/_nested.py +113 -100
- scitex/{fts/_bundle → io/bundle}/_saver.py +13 -14
- scitex/{fts/_bundle → io/bundle}/_storage.py +3 -3
- scitex/io/bundle/_types.py +41 -16
- scitex/{fts/_bundle → io/bundle}/_validation.py +20 -18
- scitex/io/bundle/_zip.py +21 -31
- scitex/{fts/_kinds → io/bundle/kinds}/_plot/_backend/_parser.py +1 -1
- scitex/{fts/_kinds → io/bundle/kinds}/_plot/_models/_Annotations.py +1 -1
- scitex/{fts/_kinds → io/bundle/kinds}/_plot/_models/_Axes.py +1 -1
- scitex/{fts/_kinds → io/bundle/kinds}/_plot/_models/_Figure.py +1 -1
- scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_Guides.py +1 -1
- scitex/{fts/_kinds → io/bundle/kinds}/_plot/_models/_Plot.py +1 -1
- scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_Styles.py +1 -1
- scitex/{fts/_kinds → io/bundle/kinds}/_plot/_utils/_plot_layout.py +1 -1
- scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/__init__.py +1 -1
- scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_editor/_app.py +1 -1
- scitex/{fts/_tables → io/bundle/kinds/_table}/_latex/_export.py +1 -1
- scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_figure_exporter.py +1 -1
- scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_table_exporter.py +1 -1
- scitex/io/bundle/schemas/__init__.py +30 -0
- scitex/parallel/_run.py +5 -4
- scitex/path/_find.py +60 -83
- scitex/path/_get_module_path.py +23 -21
- scitex/path/_get_spath.py +6 -27
- scitex/path/_getsize.py +23 -9
- scitex/path/_increment_version.py +31 -38
- scitex/path/_mk_spath.py +26 -29
- scitex/path/_path.py +5 -12
- scitex/path/_split.py +27 -15
- scitex/path/_this_path.py +23 -9
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin/__init__.py +2 -1
- scitex/plt/_subplots/_AxisWrapperMixins/__init__.py +2 -2
- scitex/plt/gallery/_generate.py +76 -50
- scitex/plt/io/__init__.py +17 -19
- scitex/plt/io/_bundle.py +99 -52
- scitex/plt/io/_layered_bundle.py +303 -168
- scitex/plt/utils/_csv_column_naming.py +250 -118
- scitex/schema/__init__.py +69 -73
- scitex/schema/_canvas.py +1 -1
- scitex/schema/_stats.py +2 -2
- scitex/stats/__init__.py +30 -33
- scitex/stats/_schema.py +1 -1
- scitex/stats/io/__init__.py +10 -11
- scitex/stats/io/_bundle.py +16 -16
- {scitex-2.10.3.dist-info → scitex-2.11.0.dist-info}/METADATA +190 -73
- {scitex-2.10.3.dist-info → scitex-2.11.0.dist-info}/RECORD +237 -360
- scitex/fig/editor/_defaults.py +0 -300
- scitex/fig/editor/edit/panel_loader.py +0 -232
- scitex/fig/editor/flask_editor/_bbox.py +0 -1299
- scitex/fig/editor/flask_editor/_core.py +0 -1429
- scitex/fig/editor/flask_editor/_renderer.py +0 -813
- scitex/fig/editor/flask_editor/static/css/features/canvas.css +0 -176
- scitex/fts/README.md +0 -262
- scitex/fts/TODO.md +0 -66
- scitex/fts/__init__.py +0 -90
- scitex/fts/_bundle/README_IN_BUNDLE.md +0 -102
- scitex/fts/_bundle/__init__.py +0 -38
- scitex/fts/_bundle/_utils/__init__.py +0 -55
- scitex/fts/_bundle/_utils/_const.py +0 -26
- scitex/fts/_bundle/_utils/_errors.py +0 -73
- scitex/fts/_bundle/_utils/_generate.py +0 -21
- scitex/fts/_bundle/_utils/_types.py +0 -76
- scitex/fts/_bundle/_zipbundle.py +0 -165
- scitex/fts/_fig/__init__.py +0 -22
- scitex/fts/_fig/_backend/_parser.py +0 -188
- scitex/fts/_fig/_editor/__init__.py +0 -14
- scitex/fts/_fig/_editor/_cui/__init__.py +0 -33
- scitex/fts/_fig/_editor/_cui/_backend_detector.py +0 -39
- scitex/fts/_fig/_editor/_cui/_bundle_resolver.py +0 -366
- scitex/fts/_fig/_editor/_cui/_editor_launcher.py +0 -175
- scitex/fts/_fig/_editor/_cui/_manual_handler.py +0 -52
- scitex/fts/_fig/_editor/_cui/_path_resolver.py +0 -66
- scitex/fts/_fig/_editor/_gui/__init__.py +0 -11
- scitex/fts/_fig/_editor/_gui/_flask_editor/__init__.py +0 -20
- scitex/fts/_fig/_editor/_gui/_flask_editor/_plotter.py +0 -664
- scitex/fts/_fig/_editor/_gui/_flask_editor/_utils.py +0 -79
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/reset.css +0 -41
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/typography.css +0 -16
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/variables.css +0 -85
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/buttons.css +0 -217
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/context-menu.css +0 -93
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/dropdown.css +0 -57
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/forms.css +0 -112
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/modal.css +0 -59
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/sections.css +0 -212
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/element-inspector.css +0 -190
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/loading.css +0 -59
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/overlay.css +0 -45
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/panel-grid.css +0 -95
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/selection.css +0 -101
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/statistics.css +0 -138
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/index.css +0 -31
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/container.css +0 -7
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/controls.css +0 -56
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/preview.css +0 -78
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/axis.js +0 -314
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/basic.js +0 -107
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/distribute.js +0 -54
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/canvas.js +0 -172
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/dragging.js +0 -258
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/resize.js +0 -48
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/selection.js +0 -71
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/api.js +0 -288
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/state.js +0 -143
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/utils.js +0 -245
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/dev/element-inspector.js +0 -992
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/bbox.js +0 -339
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/element-drag.js +0 -286
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/overlay.js +0 -371
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/preview.js +0 -293
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/main.js +0 -426
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/context-menu.js +0 -152
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/keyboard.js +0 -265
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/controls.js +0 -184
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/download.js +0 -57
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/help.js +0 -100
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/theme.js +0 -34
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/__init__.py +0 -124
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_html.py +0 -851
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_scripts.py +0 -4932
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_styles.py +0 -1657
- scitex/fts/_fig/_editor/_gui/_flask_editor.py +0 -36
- scitex/fts/_fig/_models/_Annotations.py +0 -115
- scitex/fts/_fig/_models/_Axes.py +0 -152
- scitex/fts/_fig/_models/_Figure.py +0 -138
- scitex/fts/_fig/_models/_Plot.py +0 -123
- scitex/fts/_fig/_utils/_plot_layout.py +0 -397
- scitex/fts/_kinds/_figure/_composite.py +0 -345
- scitex/fts/_kinds/_plot/_backend/__init__.py +0 -53
- scitex/fts/_kinds/_plot/_backend/_export.py +0 -165
- scitex/fts/_kinds/_plot/_backend/_render.py +0 -538
- scitex/fts/_kinds/_plot/_dataclasses/_ChannelEncoding.py +0 -46
- scitex/fts/_kinds/_plot/_dataclasses/_Encoding.py +0 -82
- scitex/fts/_kinds/_plot/_dataclasses/_Theme.py +0 -441
- scitex/fts/_kinds/_plot/_dataclasses/_TraceEncoding.py +0 -52
- scitex/fts/_kinds/_plot/_dataclasses/__init__.py +0 -47
- scitex/fts/_kinds/_plot/_models/_Guides.py +0 -104
- scitex/fts/_kinds/_plot/_models/_Styles.py +0 -245
- scitex/fts/_kinds/_plot/_models/__init__.py +0 -80
- scitex/fts/_kinds/_plot/_models/_plot_types/__init__.py +0 -156
- scitex/fts/_kinds/_plot/_models/_plot_types/_bar.py +0 -43
- scitex/fts/_kinds/_plot/_models/_plot_types/_box.py +0 -38
- scitex/fts/_kinds/_plot/_models/_plot_types/_distribution.py +0 -36
- scitex/fts/_kinds/_plot/_models/_plot_types/_errorbar.py +0 -60
- scitex/fts/_kinds/_plot/_models/_plot_types/_histogram.py +0 -30
- scitex/fts/_kinds/_plot/_models/_plot_types/_image.py +0 -61
- scitex/fts/_kinds/_plot/_models/_plot_types/_line.py +0 -57
- scitex/fts/_kinds/_plot/_models/_plot_types/_scatter.py +0 -30
- scitex/fts/_kinds/_plot/_models/_plot_types/_seaborn.py +0 -121
- scitex/fts/_kinds/_plot/_models/_plot_types/_violin.py +0 -36
- scitex/fts/_kinds/_plot/_utils/__init__.py +0 -129
- scitex/fts/_kinds/_plot/_utils/_auto_layout.py +0 -127
- scitex/fts/_kinds/_plot/_utils/_calc_bounds.py +0 -111
- scitex/fts/_kinds/_plot/_utils/_const_sizes.py +0 -48
- scitex/fts/_kinds/_plot/_utils/_convert_coords.py +0 -77
- scitex/fts/_kinds/_plot/_utils/_get_template.py +0 -178
- scitex/fts/_kinds/_plot/_utils/_normalize.py +0 -73
- scitex/fts/_kinds/_plot/_utils/_validate.py +0 -197
- scitex/fts/_kinds/_table/_latex/_export.py +0 -279
- scitex/fts/_stats/__init__.py +0 -48
- scitex/fts/_stats/_dataclasses/_Stats.py +0 -423
- scitex/fts/_stats/_dataclasses/__init__.py +0 -48
- scitex/fts/_tables/__init__.py +0 -65
- scitex/fts/_tables/_latex/__init__.py +0 -93
- scitex/fts/_tables/_latex/_editor/__init__.py +0 -11
- scitex/fts/_tables/_latex/_editor/_app.py +0 -725
- scitex/fts/_tables/_latex/_figure_exporter.py +0 -153
- scitex/fts/_tables/_latex/_stats_formatter.py +0 -274
- scitex/fts/_tables/_latex/_table_exporter.py +0 -362
- scitex/fts/_tables/_latex/_utils.py +0 -369
- scitex/fts/_tables/_latex/_validator.py +0 -445
- scitex/io/_save_modules/_pltz_bundle.py +0 -356
- /scitex/{fig → canvas}/README.md +0 -0
- /scitex/{fig → canvas}/backend/__init__.py +0 -0
- /scitex/{fig → canvas}/backend/_export.py +0 -0
- /scitex/{fig → canvas}/backend/_render.py +0 -0
- /scitex/{fig → canvas}/docs/CANVAS_ARCHITECTURE.md +0 -0
- /scitex/{fig → canvas}/editor/__init__.py +0 -0
- /scitex/{fig → canvas}/editor/_dearpygui_editor.py +0 -0
- /scitex/{fig → canvas}/editor/_flask_editor.py +0 -0
- /scitex/{fig → canvas}/editor/_mpl_editor.py +0 -0
- /scitex/{fig → canvas}/editor/_qt_editor.py +0 -0
- /scitex/{fig → canvas}/editor/_tkinter_editor.py +0 -0
- /scitex/{fig → canvas}/editor/edit/backend_detector.py +0 -0
- /scitex/{fig → canvas}/editor/edit/manual_handler.py +0 -0
- /scitex/{fig → canvas}/editor/edit/path_resolver.py +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/__init__.py +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/_plotter.py +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/_utils.py +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/base/reset.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/base/typography.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/base/variables.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/components/buttons.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/components/context-menu.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/components/dropdown.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/components/forms.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/components/modal.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/components/sections.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/features/element-inspector.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/features/loading.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/features/overlay.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/features/selection.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/features/statistics.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/index.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/layout/container.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/layout/controls.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/css/layout/preview.css +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/alignment/axis.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/alignment/basic.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/alignment/distribute.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/canvas/canvas.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/canvas/dragging.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/canvas/resize.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/canvas/selection.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/core/state.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/core/utils.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/dev/element-inspector.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/editor/bbox.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/editor/element-drag.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/editor/overlay.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/main.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/shortcuts/context-menu.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/shortcuts/keyboard.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/ui/controls.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/ui/download.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/ui/help.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/static/js/ui/theme.js +0 -0
- /scitex/{fig → canvas}/editor/flask_editor/templates/__init__.py +0 -0
- /scitex/{fig → canvas}/io/_directory.py +0 -0
- /scitex/{fig → canvas}/model/_plot_types.py +0 -0
- /scitex/{fig → canvas}/utils/_defaults.py +0 -0
- /scitex/{fig → canvas}/utils/_validate.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_conversion/__init__.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_conversion/_bundle2dict.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_conversion/_dict2bundle.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_dataclasses/_Axes.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_dataclasses/_BBox.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_dataclasses/_ColumnDef.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_dataclasses/_DataFormat.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_dataclasses/_DataInfo.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_dataclasses/_DataSource.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_dataclasses/_SizeMM.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_extractors/__init__.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_extractors/_extract_bar.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_extractors/_extract_line.py +0 -0
- /scitex/{fts/_bundle → io/bundle}/_extractors/_extract_scatter.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/__init__.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_figure/__init__.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_figure}/_composite.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_plot/__init__.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_backend/__init__.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_backend/_export.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_backend/_render.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/_ChannelEncoding.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/_Encoding.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/_Theme.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/_TraceEncoding.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_dataclasses/__init__.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/__init__.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/__init__.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_bar.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_box.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_distribution.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_errorbar.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_histogram.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_image.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_line.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_scatter.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_seaborn.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_models/_plot_types/_violin.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/__init__.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_auto_layout.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_calc_bounds.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_const_sizes.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_convert_coords.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_get_template.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_normalize.py +0 -0
- /scitex/{fts/_fig → io/bundle/kinds/_plot}/_utils/_validate.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_shape/__init__.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_stats/__init__.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_stats/_dataclasses/_Stats.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_stats/_dataclasses/__init__.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_table/__init__.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_editor/__init__.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_stats_formatter.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_utils.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_table/_latex/_validator.py +0 -0
- /scitex/{fts/_kinds → io/bundle/kinds}/_text/__init__.py +0 -0
- /scitex/{fts/_schemas → io/bundle/schemas}/data_info.schema.json +0 -0
- /scitex/{fts/_schemas → io/bundle/schemas}/encoding.schema.json +0 -0
- /scitex/{fts/_schemas → io/bundle/schemas}/node.schema.json +0 -0
- /scitex/{fts/_schemas → io/bundle/schemas}/render_manifest.schema.json +0 -0
- /scitex/{fts/_schemas → io/bundle/schemas}/stats.schema.json +0 -0
- /scitex/{fts/_schemas → io/bundle/schemas}/theme.schema.json +0 -0
- {scitex-2.10.3.dist-info → scitex-2.11.0.dist-info}/WHEEL +0 -0
- {scitex-2.10.3.dist-info → scitex-2.11.0.dist-info}/entry_points.txt +0 -0
- {scitex-2.10.3.dist-info → scitex-2.11.0.dist-info}/licenses/LICENSE +0 -0
scitex/io/bundle/_core.py
CHANGED
|
@@ -1,31 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
# File: /home/ywatanabe/proj/scitex-code/src/scitex/io/bundle/_core.py
|
|
2
|
+
# Timestamp: 2026-01-07
|
|
3
|
+
# File: src/scitex/io/bundle/_core.py
|
|
5
4
|
|
|
6
5
|
"""
|
|
7
6
|
SciTeX Bundle Core Operations.
|
|
8
7
|
|
|
9
8
|
Provides load, save, copy, pack, unpack, and validate operations for
|
|
10
|
-
.
|
|
9
|
+
.figure.zip, .plot.zip, .stats.zip bundle formats.
|
|
11
10
|
|
|
12
11
|
Each bundle can exist in two forms:
|
|
13
|
-
- Directory: Figure1.
|
|
14
|
-
- ZIP archive: Figure1.
|
|
12
|
+
- Directory: Figure1.figure/, plot.plot/, results.stats/
|
|
13
|
+
- ZIP archive: Figure1.figure.zip, plot.plot.zip, results.stats.zip
|
|
15
14
|
"""
|
|
16
15
|
|
|
17
|
-
import json
|
|
18
16
|
import shutil
|
|
19
17
|
import zipfile
|
|
20
18
|
from pathlib import Path
|
|
21
19
|
from typing import Any, Dict, List, Optional, Union
|
|
22
20
|
|
|
23
|
-
from ._types import
|
|
24
|
-
EXTENSIONS,
|
|
25
|
-
BundleNotFoundError,
|
|
26
|
-
BundleType,
|
|
27
|
-
BundleValidationError,
|
|
28
|
-
)
|
|
21
|
+
from ._types import EXTENSIONS, BundleNotFoundError, BundleType, BundleValidationError
|
|
29
22
|
|
|
30
23
|
__all__ = [
|
|
31
24
|
"load",
|
|
@@ -45,31 +38,99 @@ __all__ = [
|
|
|
45
38
|
def get_type(path: Union[str, Path]) -> Optional[str]:
|
|
46
39
|
"""Get bundle type from path.
|
|
47
40
|
|
|
41
|
+
Checks manifest.json first for reliable type detection, then falls back
|
|
42
|
+
to extension-based detection.
|
|
43
|
+
|
|
48
44
|
Args:
|
|
49
45
|
path: Path to bundle (directory or ZIP).
|
|
50
46
|
|
|
51
47
|
Returns:
|
|
52
|
-
Bundle type string ('
|
|
48
|
+
Bundle type string ('figure', 'plot', 'stats') or None if not a bundle.
|
|
53
49
|
|
|
54
50
|
Example:
|
|
55
|
-
>>> get_type("Figure1.
|
|
56
|
-
'
|
|
57
|
-
>>> get_type("plot.
|
|
58
|
-
'
|
|
51
|
+
>>> get_type("Figure1.figure.zip")
|
|
52
|
+
'figure'
|
|
53
|
+
>>> get_type("plot.plot")
|
|
54
|
+
'plot'
|
|
59
55
|
"""
|
|
60
56
|
p = Path(path)
|
|
61
57
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
58
|
+
if not p.exists():
|
|
59
|
+
# Fall back to extension-based detection for non-existent paths
|
|
60
|
+
return _get_type_from_extension(p)
|
|
61
|
+
|
|
62
|
+
# Try manifest.json first (most reliable)
|
|
63
|
+
manifest_type = _get_type_from_manifest(p)
|
|
64
|
+
if manifest_type:
|
|
65
|
+
return manifest_type
|
|
66
|
+
|
|
67
|
+
# Fall back to extension-based detection
|
|
68
|
+
return _get_type_from_extension(p)
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
|
|
71
|
+
def _get_type_from_manifest(path: Path) -> Optional[str]:
|
|
72
|
+
"""Get bundle type from manifest.json.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
path: Path to bundle (directory or ZIP).
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Bundle type from manifest or None if not found.
|
|
79
|
+
"""
|
|
80
|
+
import json
|
|
81
|
+
|
|
82
|
+
from ._types import EXTENSIONS
|
|
83
|
+
|
|
84
|
+
if path.is_dir():
|
|
85
|
+
# Directory bundle - read manifest.json directly
|
|
86
|
+
manifest_path = path / "manifest.json"
|
|
87
|
+
if manifest_path.exists():
|
|
88
|
+
try:
|
|
89
|
+
with open(manifest_path) as f:
|
|
90
|
+
manifest = json.load(f)
|
|
91
|
+
return manifest.get("scitex", {}).get("type")
|
|
92
|
+
except (OSError, json.JSONDecodeError):
|
|
93
|
+
pass
|
|
94
|
+
elif path.is_file():
|
|
95
|
+
# Check if it's a ZIP bundle by extension
|
|
96
|
+
name = path.name.lower()
|
|
97
|
+
is_zip_bundle = any(name.endswith(ext) for ext in EXTENSIONS)
|
|
98
|
+
if is_zip_bundle:
|
|
99
|
+
try:
|
|
100
|
+
with zipfile.ZipFile(path, "r") as zf:
|
|
101
|
+
if "manifest.json" in zf.namelist():
|
|
102
|
+
content = zf.read("manifest.json")
|
|
103
|
+
manifest = json.loads(content)
|
|
104
|
+
return manifest.get("scitex", {}).get("type")
|
|
105
|
+
except (zipfile.BadZipFile, OSError, json.JSONDecodeError):
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _get_type_from_extension(path: Path) -> Optional[str]:
|
|
112
|
+
"""Get bundle type from file extension (fallback method).
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
path: Path to bundle.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Bundle type from extension or None.
|
|
119
|
+
"""
|
|
120
|
+
from ._types import DIR_EXTENSIONS, EXTENSIONS
|
|
121
|
+
|
|
122
|
+
name = path.name.lower()
|
|
123
|
+
|
|
124
|
+
# Check ZIP extensions (.figure.zip, .plot.zip, .stats.zip)
|
|
125
|
+
for ext in EXTENSIONS:
|
|
126
|
+
if name.endswith(ext):
|
|
127
|
+
# Return type without dots: 'figure', 'plot', 'stats'
|
|
128
|
+
return ext.split(".")[1]
|
|
129
|
+
|
|
130
|
+
# Check directory extensions (.figure, .plot, .stats)
|
|
131
|
+
for ext in DIR_EXTENSIONS:
|
|
132
|
+
if name.endswith(ext) and (not path.exists() or path.is_dir()):
|
|
133
|
+
return ext[1:] # Remove leading dot
|
|
73
134
|
|
|
74
135
|
return None
|
|
75
136
|
|
|
@@ -84,7 +145,7 @@ def is_bundle(path: Union[str, Path]) -> bool:
|
|
|
84
145
|
True if path is a bundle.
|
|
85
146
|
|
|
86
147
|
Example:
|
|
87
|
-
>>> is_bundle("Figure1.
|
|
148
|
+
>>> is_bundle("Figure1.figure.zip")
|
|
88
149
|
True
|
|
89
150
|
>>> is_bundle("data.csv")
|
|
90
151
|
False
|
|
@@ -96,22 +157,25 @@ def dir_to_zip_path(dir_path: Path) -> Path:
|
|
|
96
157
|
"""Convert directory path to ZIP path.
|
|
97
158
|
|
|
98
159
|
Example:
|
|
99
|
-
>>> dir_to_zip_path(Path("Figure1.
|
|
100
|
-
Path('Figure1.
|
|
160
|
+
>>> dir_to_zip_path(Path("Figure1.figure"))
|
|
161
|
+
Path('Figure1.figure.zip')
|
|
101
162
|
"""
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return dir_path
|
|
163
|
+
# .figure -> .figure.zip, .plot -> .plot.zip, .stats -> .stats.zip
|
|
164
|
+
return Path(str(dir_path) + ".zip")
|
|
105
165
|
|
|
106
166
|
|
|
107
167
|
def zip_to_dir_path(zip_path: Path) -> Path:
|
|
108
168
|
"""Convert ZIP path to directory path.
|
|
109
169
|
|
|
110
170
|
Example:
|
|
111
|
-
>>> zip_to_dir_path(Path("Figure1.
|
|
112
|
-
Path('Figure1.
|
|
171
|
+
>>> zip_to_dir_path(Path("Figure1.figure.zip"))
|
|
172
|
+
Path('Figure1.figure')
|
|
113
173
|
"""
|
|
114
|
-
|
|
174
|
+
# .figure.zip -> .figure, .plot.zip -> .plot, .stats.zip -> .stats
|
|
175
|
+
s = str(zip_path)
|
|
176
|
+
if s.endswith(".zip"):
|
|
177
|
+
return Path(s[:-4])
|
|
178
|
+
return zip_path
|
|
115
179
|
|
|
116
180
|
|
|
117
181
|
def pack(
|
|
@@ -119,19 +183,16 @@ def pack(
|
|
|
119
183
|
) -> Path:
|
|
120
184
|
"""Pack a bundle directory into a ZIP archive.
|
|
121
185
|
|
|
122
|
-
The ZIP archive includes the .d directory as a top-level folder so that
|
|
123
|
-
standard unzip extracts to: fname.pltz.d/fname.csv, fname.pltz.d/fname.json, etc.
|
|
124
|
-
|
|
125
186
|
Args:
|
|
126
|
-
dir_path: Path to bundle directory (e.g., Figure1.
|
|
187
|
+
dir_path: Path to bundle directory (e.g., Figure1.figure/).
|
|
127
188
|
output_path: Output ZIP path. Auto-generated if None.
|
|
128
189
|
|
|
129
190
|
Returns:
|
|
130
191
|
Path to created ZIP archive.
|
|
131
192
|
|
|
132
193
|
Example:
|
|
133
|
-
>>> pack("plot.
|
|
134
|
-
Path('plot.
|
|
194
|
+
>>> pack("plot.plot")
|
|
195
|
+
Path('plot.plot.zip')
|
|
135
196
|
"""
|
|
136
197
|
dir_path = Path(dir_path)
|
|
137
198
|
|
|
@@ -163,15 +224,15 @@ def unpack(
|
|
|
163
224
|
"""Unpack a bundle ZIP archive into a directory.
|
|
164
225
|
|
|
165
226
|
Args:
|
|
166
|
-
zip_path: Path to bundle ZIP (e.g., Figure1.
|
|
227
|
+
zip_path: Path to bundle ZIP (e.g., Figure1.figure.zip).
|
|
167
228
|
output_path: Output directory path. Auto-generated if None.
|
|
168
229
|
|
|
169
230
|
Returns:
|
|
170
231
|
Path to created directory.
|
|
171
232
|
|
|
172
233
|
Example:
|
|
173
|
-
>>> unpack("plot.
|
|
174
|
-
Path('plot.
|
|
234
|
+
>>> unpack("plot.plot.zip")
|
|
235
|
+
Path('plot.plot')
|
|
175
236
|
"""
|
|
176
237
|
zip_path = Path(zip_path)
|
|
177
238
|
|
|
@@ -199,7 +260,7 @@ def validate_spec(
|
|
|
199
260
|
|
|
200
261
|
Args:
|
|
201
262
|
spec: The specification dictionary to validate.
|
|
202
|
-
bundle_type: Bundle type ('
|
|
263
|
+
bundle_type: Bundle type ('figure', 'plot', 'stats').
|
|
203
264
|
strict: If True, raise BundleValidationError on failure.
|
|
204
265
|
|
|
205
266
|
Returns:
|
|
@@ -223,18 +284,18 @@ def validate_spec(
|
|
|
223
284
|
errors.append("schema.version is required")
|
|
224
285
|
|
|
225
286
|
# Delegate to domain-specific validators
|
|
226
|
-
if bundle_type == BundleType.
|
|
227
|
-
from scitex.
|
|
287
|
+
if bundle_type == BundleType.FIGURE:
|
|
288
|
+
from scitex.canvas.io._bundle import validate_figure_spec
|
|
228
289
|
|
|
229
|
-
errors.extend(
|
|
230
|
-
elif bundle_type == BundleType.
|
|
231
|
-
from scitex.plt.io._bundle import
|
|
290
|
+
errors.extend(validate_figure_spec(spec))
|
|
291
|
+
elif bundle_type == BundleType.PLOT:
|
|
292
|
+
from scitex.plt.io._bundle import validate_plot_spec
|
|
232
293
|
|
|
233
|
-
errors.extend(
|
|
234
|
-
elif bundle_type == BundleType.
|
|
235
|
-
from scitex.stats.io._bundle import
|
|
294
|
+
errors.extend(validate_plot_spec(spec))
|
|
295
|
+
elif bundle_type == BundleType.STATS:
|
|
296
|
+
from scitex.stats.io._bundle import validate_stats_spec
|
|
236
297
|
|
|
237
|
-
errors.extend(
|
|
298
|
+
errors.extend(validate_stats_spec(spec))
|
|
238
299
|
else:
|
|
239
300
|
errors.append(f"Unknown bundle type: {bundle_type}")
|
|
240
301
|
|
|
@@ -314,18 +375,18 @@ def load(path: Union[str, Path], in_memory: bool = True) -> Dict[str, Any]:
|
|
|
314
375
|
|
|
315
376
|
Returns:
|
|
316
377
|
Bundle data as dictionary with:
|
|
317
|
-
- 'type': Bundle type ('
|
|
378
|
+
- 'type': Bundle type ('figure', 'plot', 'stats')
|
|
318
379
|
- 'spec': Parsed JSON specification
|
|
319
380
|
- 'path': Original path
|
|
320
381
|
- 'is_zip': Whether loaded from ZIP
|
|
321
382
|
- Additional fields based on bundle type
|
|
322
383
|
|
|
323
384
|
Example:
|
|
324
|
-
>>> bundle = load("Figure1.
|
|
385
|
+
>>> bundle = load("Figure1.figure.zip")
|
|
325
386
|
>>> bundle['type']
|
|
326
|
-
'
|
|
387
|
+
'figure'
|
|
327
388
|
>>> bundle['spec']['schema']['name']
|
|
328
|
-
'scitex.
|
|
389
|
+
'scitex.canvas'
|
|
329
390
|
"""
|
|
330
391
|
p = Path(path)
|
|
331
392
|
bundle_type = get_type(p)
|
|
@@ -340,7 +401,7 @@ def load(path: Union[str, Path], in_memory: bool = True) -> Dict[str, Any]:
|
|
|
340
401
|
}
|
|
341
402
|
|
|
342
403
|
# Handle ZIP vs directory
|
|
343
|
-
if p.is_file() and p.
|
|
404
|
+
if p.is_file() and any(str(p).lower().endswith(ext) for ext in EXTENSIONS):
|
|
344
405
|
result["is_zip"] = True
|
|
345
406
|
|
|
346
407
|
if in_memory:
|
|
@@ -375,18 +436,18 @@ def load(path: Union[str, Path], in_memory: bool = True) -> Dict[str, Any]:
|
|
|
375
436
|
bundle_dir = p
|
|
376
437
|
|
|
377
438
|
# Delegate to domain-specific loaders
|
|
378
|
-
if bundle_type == BundleType.
|
|
379
|
-
from scitex.
|
|
439
|
+
if bundle_type == BundleType.FIGURE:
|
|
440
|
+
from scitex.canvas.io._bundle import load_figure_bundle
|
|
380
441
|
|
|
381
|
-
result.update(
|
|
382
|
-
elif bundle_type == BundleType.
|
|
383
|
-
from scitex.plt.io import
|
|
442
|
+
result.update(load_figure_bundle(bundle_dir))
|
|
443
|
+
elif bundle_type == BundleType.PLOT:
|
|
444
|
+
from scitex.plt.io._bundle import load_plot_bundle
|
|
384
445
|
|
|
385
|
-
result.update(
|
|
386
|
-
elif bundle_type == BundleType.
|
|
387
|
-
from scitex.stats.io._bundle import
|
|
446
|
+
result.update(load_plot_bundle(bundle_dir))
|
|
447
|
+
elif bundle_type == BundleType.STATS:
|
|
448
|
+
from scitex.stats.io._bundle import load_stats_bundle
|
|
388
449
|
|
|
389
|
-
result.update(
|
|
450
|
+
result.update(load_stats_bundle(bundle_dir))
|
|
390
451
|
|
|
391
452
|
return result
|
|
392
453
|
|
|
@@ -402,8 +463,8 @@ def save(
|
|
|
402
463
|
|
|
403
464
|
Args:
|
|
404
465
|
data: Bundle data dictionary.
|
|
405
|
-
path: Output path (
|
|
406
|
-
bundle_type: Bundle type ('
|
|
466
|
+
path: Output path (e.g., 'plot.plot' or 'plot.plot.zip').
|
|
467
|
+
bundle_type: Bundle type ('figure', 'plot', 'stats'). Auto-detected if None.
|
|
407
468
|
as_zip: If True, save as ZIP archive.
|
|
408
469
|
atomic: If True, use atomic write (temp file + rename) for ZIP.
|
|
409
470
|
|
|
@@ -411,8 +472,8 @@ def save(
|
|
|
411
472
|
Path to saved bundle.
|
|
412
473
|
|
|
413
474
|
Example:
|
|
414
|
-
>>> save({"spec": {...}, "data": df}, "plot.
|
|
415
|
-
Path('plot.
|
|
475
|
+
>>> save({"spec": {...}, "data": df}, "plot.plot.zip", as_zip=True)
|
|
476
|
+
Path('plot.plot.zip')
|
|
416
477
|
"""
|
|
417
478
|
p = Path(path)
|
|
418
479
|
|
|
@@ -422,25 +483,36 @@ def save(
|
|
|
422
483
|
raise ValueError(f"Cannot determine bundle type from path: {path}")
|
|
423
484
|
|
|
424
485
|
# Determine if saving as directory or ZIP
|
|
425
|
-
|
|
486
|
+
from ._types import DIR_EXTENSIONS
|
|
487
|
+
|
|
488
|
+
path_str = str(p).lower()
|
|
489
|
+
is_zip_path = any(path_str.endswith(ext) for ext in EXTENSIONS)
|
|
490
|
+
is_dir_path = any(path_str.endswith(ext) for ext in DIR_EXTENSIONS)
|
|
491
|
+
|
|
492
|
+
if as_zip or is_zip_path:
|
|
426
493
|
save_as_zip = True
|
|
427
|
-
if
|
|
494
|
+
if is_dir_path:
|
|
428
495
|
zip_path = dir_to_zip_path(p)
|
|
429
496
|
else:
|
|
430
497
|
zip_path = p
|
|
431
498
|
dir_path = zip_to_dir_path(zip_path)
|
|
432
499
|
else:
|
|
433
500
|
save_as_zip = False
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
501
|
+
dir_path = p
|
|
502
|
+
|
|
503
|
+
# Normalize bundle type for manifest (use new names: plot, figure, stats)
|
|
504
|
+
manifest_type = BundleType.normalize(bundle_type)
|
|
438
505
|
|
|
439
506
|
# For direct ZIP saving with atomic writes
|
|
440
|
-
if save_as_zip and atomic and bundle_type != BundleType.
|
|
507
|
+
if save_as_zip and atomic and bundle_type != BundleType.FIGURE:
|
|
508
|
+
from ._manifest import create_manifest
|
|
441
509
|
from ._zip import ZipBundle
|
|
442
510
|
|
|
443
511
|
with ZipBundle(zip_path, mode="w") as zb:
|
|
512
|
+
# Write manifest.json first
|
|
513
|
+
manifest = create_manifest(manifest_type)
|
|
514
|
+
zb.write_json("manifest.json", manifest)
|
|
515
|
+
|
|
444
516
|
if "spec" in data:
|
|
445
517
|
zb.write_json("spec.json", data["spec"])
|
|
446
518
|
|
|
@@ -464,21 +536,26 @@ def save(
|
|
|
464
536
|
dir_path.mkdir(parents=True, exist_ok=True)
|
|
465
537
|
|
|
466
538
|
# Delegate to domain-specific savers
|
|
467
|
-
if bundle_type == BundleType.
|
|
468
|
-
from scitex.
|
|
539
|
+
if bundle_type == BundleType.FIGURE:
|
|
540
|
+
from scitex.canvas.io._bundle import save_figure_bundle
|
|
469
541
|
|
|
470
|
-
|
|
471
|
-
elif bundle_type == BundleType.
|
|
472
|
-
from scitex.plt.io._bundle import
|
|
542
|
+
save_figure_bundle(data, dir_path)
|
|
543
|
+
elif bundle_type == BundleType.PLOT:
|
|
544
|
+
from scitex.plt.io._bundle import save_plot_bundle
|
|
473
545
|
|
|
474
|
-
|
|
475
|
-
elif bundle_type == BundleType.
|
|
476
|
-
from scitex.stats.io._bundle import
|
|
546
|
+
save_plot_bundle(data, dir_path)
|
|
547
|
+
elif bundle_type == BundleType.STATS:
|
|
548
|
+
from scitex.stats.io._bundle import save_stats_bundle
|
|
477
549
|
|
|
478
|
-
|
|
550
|
+
save_stats_bundle(data, dir_path)
|
|
479
551
|
else:
|
|
480
552
|
raise ValueError(f"Unknown bundle type: {bundle_type}")
|
|
481
553
|
|
|
554
|
+
# Write manifest.json for directory bundles
|
|
555
|
+
from ._manifest import write_manifest
|
|
556
|
+
|
|
557
|
+
write_manifest(dir_path, manifest_type)
|
|
558
|
+
|
|
482
559
|
if save_as_zip:
|
|
483
560
|
pack(dir_path, zip_path)
|
|
484
561
|
shutil.rmtree(dir_path)
|
|
@@ -511,24 +588,32 @@ def copy(
|
|
|
511
588
|
FileExistsError: If destination exists and overwrite=False.
|
|
512
589
|
|
|
513
590
|
Example:
|
|
514
|
-
>>> copy("gallery/line/plot.
|
|
515
|
-
>>> copy("template.
|
|
591
|
+
>>> copy("gallery/line/plot.plot", "my_project/A.plot")
|
|
592
|
+
>>> copy("template.plot.zip", "output/panel.plot")
|
|
516
593
|
"""
|
|
594
|
+
from ._types import DIR_EXTENSIONS
|
|
595
|
+
|
|
517
596
|
src_path = Path(src)
|
|
518
597
|
dst_path = Path(dst)
|
|
598
|
+
src_str = str(src_path).lower()
|
|
599
|
+
|
|
600
|
+
# Check if path is a ZIP or directory bundle
|
|
601
|
+
is_zip_path = any(src_str.endswith(ext) for ext in EXTENSIONS)
|
|
602
|
+
is_dir_path = any(src_str.endswith(ext) for ext in DIR_EXTENSIONS)
|
|
519
603
|
|
|
520
604
|
# Validate source exists
|
|
521
605
|
if not src_path.exists():
|
|
522
|
-
|
|
523
|
-
|
|
606
|
+
# Try alternate form (ZIP <-> directory)
|
|
607
|
+
if is_zip_path:
|
|
608
|
+
alt_path = zip_to_dir_path(src_path)
|
|
524
609
|
if alt_path.exists():
|
|
525
610
|
src_path = alt_path
|
|
526
611
|
else:
|
|
527
612
|
raise BundleNotFoundError(
|
|
528
613
|
f"Bundle not found: {src} (also tried {alt_path})"
|
|
529
614
|
)
|
|
530
|
-
elif
|
|
531
|
-
alt_path =
|
|
615
|
+
elif is_dir_path:
|
|
616
|
+
alt_path = dir_to_zip_path(src_path)
|
|
532
617
|
if alt_path.exists():
|
|
533
618
|
src_path = alt_path
|
|
534
619
|
else:
|
|
@@ -553,7 +638,9 @@ def copy(
|
|
|
553
638
|
# Copy based on source type
|
|
554
639
|
if src_path.is_dir():
|
|
555
640
|
shutil.copytree(src_path, dst_path)
|
|
556
|
-
elif
|
|
641
|
+
elif any(
|
|
642
|
+
str(src_path).lower().endswith(ext) for ext in EXTENSIONS
|
|
643
|
+
) or zipfile.is_zipfile(src_path):
|
|
557
644
|
unpack(src_path, dst_path)
|
|
558
645
|
else:
|
|
559
646
|
raise ValueError(f"Unknown bundle format: {src_path}")
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# Timestamp: 2025-12-21
|
|
3
|
-
# File: /home/ywatanabe/proj/scitex-code/src/scitex/
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/io/bundle/_dataclasses/_Spec.py
|
|
4
4
|
|
|
5
|
-
"""
|
|
5
|
+
"""Spec - Core bundle specification model with kind-based constraints."""
|
|
6
6
|
|
|
7
7
|
from dataclasses import dataclass, field
|
|
8
8
|
from datetime import datetime
|
|
@@ -10,13 +10,13 @@ from typing import Any, ClassVar, Dict, List, Optional, Set
|
|
|
10
10
|
|
|
11
11
|
from ._Axes import Axes
|
|
12
12
|
from ._BBox import BBox
|
|
13
|
-
from ._NodeRefs import NodeRefs
|
|
14
13
|
from ._SizeMM import SizeMM
|
|
14
|
+
from ._SpecRefs import SpecRefs
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
@dataclass
|
|
18
18
|
class TextContent:
|
|
19
|
-
"""Text content for kind=text
|
|
19
|
+
"""Text content for kind=text specs."""
|
|
20
20
|
|
|
21
21
|
content: str = ""
|
|
22
22
|
fontsize: Optional[float] = None
|
|
@@ -45,7 +45,7 @@ class TextContent:
|
|
|
45
45
|
|
|
46
46
|
@dataclass
|
|
47
47
|
class ShapeParams:
|
|
48
|
-
"""Shape parameters for kind=shape
|
|
48
|
+
"""Shape parameters for kind=shape specs."""
|
|
49
49
|
|
|
50
50
|
shape_type: str = "rectangle" # "rectangle" | "ellipse" | "arrow" | "line"
|
|
51
51
|
color: str = "#000000"
|
|
@@ -71,15 +71,15 @@ class ShapeParams:
|
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
@dataclass
|
|
74
|
-
class
|
|
75
|
-
"""Core
|
|
74
|
+
class Spec:
|
|
75
|
+
"""Core bundle specification model with kind-based constraints.
|
|
76
76
|
|
|
77
|
-
The central structural element of
|
|
77
|
+
The central structural element of a bundle.
|
|
78
78
|
Stored in canonical/spec.json.
|
|
79
79
|
|
|
80
80
|
Kind categories:
|
|
81
81
|
- Data leaf kinds (plot, table, stats): require payload data files
|
|
82
|
-
- Annotation leaf kinds (text, shape): no payload required, params in
|
|
82
|
+
- Annotation leaf kinds (text, shape): no payload required, params in spec
|
|
83
83
|
- Image leaf kinds (image): require payload image file
|
|
84
84
|
- Composite kinds (figure): contain children, no payload
|
|
85
85
|
|
|
@@ -95,7 +95,7 @@ class Node:
|
|
|
95
95
|
kind: str # "figure" | "plot" | "table" | "stats" | "text" | "shape" | "image"
|
|
96
96
|
|
|
97
97
|
# Schema versioning for forward compatibility
|
|
98
|
-
scitex_schema: str = "scitex.
|
|
98
|
+
scitex_schema: str = "scitex.io.bundle.spec"
|
|
99
99
|
scitex_schema_version: str = "1.0.0"
|
|
100
100
|
|
|
101
101
|
# Children and layout (for composite kinds)
|
|
@@ -116,7 +116,7 @@ class Node:
|
|
|
116
116
|
shape: Optional[ShapeParams] = None # For kind=shape
|
|
117
117
|
|
|
118
118
|
# References and timestamps
|
|
119
|
-
refs:
|
|
119
|
+
refs: SpecRefs = field(default_factory=SpecRefs)
|
|
120
120
|
created_at: Optional[str] = None
|
|
121
121
|
modified_at: Optional[str] = None
|
|
122
122
|
|
|
@@ -128,7 +128,9 @@ class Node:
|
|
|
128
128
|
# Image leaf kinds: require payload image file, forbid children
|
|
129
129
|
IMAGE_LEAF_KINDS: ClassVar[Set[str]] = {"image"}
|
|
130
130
|
# All leaf kinds (for convenience)
|
|
131
|
-
LEAF_KINDS: ClassVar[Set[str]] =
|
|
131
|
+
LEAF_KINDS: ClassVar[Set[str]] = (
|
|
132
|
+
DATA_LEAF_KINDS | ANNOTATION_LEAF_KINDS | IMAGE_LEAF_KINDS
|
|
133
|
+
)
|
|
132
134
|
# Composite kinds: allow children, forbid payload
|
|
133
135
|
COMPOSITE_KINDS: ClassVar[Set[str]] = {"figure"}
|
|
134
136
|
# All valid kinds
|
|
@@ -136,10 +138,10 @@ class Node:
|
|
|
136
138
|
|
|
137
139
|
# Payload schema -> required file mapping
|
|
138
140
|
PAYLOAD_REQUIRED_FILES: ClassVar[Dict[str, str]] = {
|
|
139
|
-
"scitex.
|
|
140
|
-
"scitex.
|
|
141
|
-
"scitex.
|
|
142
|
-
"scitex.
|
|
141
|
+
"scitex.io.bundle.payload.plot@1": "payload/data.csv",
|
|
142
|
+
"scitex.io.bundle.payload.table@1": "payload/table.csv",
|
|
143
|
+
"scitex.io.bundle.payload.stats@1": "payload/stats.json",
|
|
144
|
+
"scitex.io.bundle.payload.image@1": "payload/image.png",
|
|
143
145
|
}
|
|
144
146
|
|
|
145
147
|
def __post_init__(self):
|
|
@@ -186,7 +188,9 @@ class Node:
|
|
|
186
188
|
|
|
187
189
|
# Check: kind is valid
|
|
188
190
|
if self.kind not in self.ALL_KINDS:
|
|
189
|
-
errors.append(
|
|
191
|
+
errors.append(
|
|
192
|
+
f"Unknown kind: {self.kind}. Valid kinds: {sorted(self.ALL_KINDS)}"
|
|
193
|
+
)
|
|
190
194
|
return errors # Early return - other checks don't make sense
|
|
191
195
|
|
|
192
196
|
# Check: children list has no duplicates
|
|
@@ -216,7 +220,9 @@ class Node:
|
|
|
216
220
|
# Check: panel child references must be subset of children
|
|
217
221
|
for child_ref in panel_children:
|
|
218
222
|
if child_ref not in self.children:
|
|
219
|
-
errors.append(
|
|
223
|
+
errors.append(
|
|
224
|
+
f"layout.panels references unknown child: {child_ref}"
|
|
225
|
+
)
|
|
220
226
|
|
|
221
227
|
# Check: no duplicate panel child references
|
|
222
228
|
if len(panel_children) != len(set(panel_children)):
|
|
@@ -259,8 +265,8 @@ class Node:
|
|
|
259
265
|
return result
|
|
260
266
|
|
|
261
267
|
@classmethod
|
|
262
|
-
def from_dict(cls, data: Dict[str, Any]) -> "
|
|
263
|
-
"""Create
|
|
268
|
+
def from_dict(cls, data: Dict[str, Any]) -> "Spec":
|
|
269
|
+
"""Create Spec from dictionary."""
|
|
264
270
|
# Handle legacy 'type' field
|
|
265
271
|
kind = data.get("kind") or data.get("type", "plot")
|
|
266
272
|
|
|
@@ -293,7 +299,7 @@ class Node:
|
|
|
293
299
|
return cls(
|
|
294
300
|
id=data.get("id", "unknown"),
|
|
295
301
|
kind=kind,
|
|
296
|
-
scitex_schema=data.get("scitex_schema", "scitex.
|
|
302
|
+
scitex_schema=data.get("scitex_schema", "scitex.io.bundle.spec"),
|
|
297
303
|
scitex_schema_version=data.get("scitex_schema_version", "1.0.0"),
|
|
298
304
|
children=data.get("children", []),
|
|
299
305
|
layout=data.get("layout"),
|
|
@@ -304,7 +310,7 @@ class Node:
|
|
|
304
310
|
axes=Axes.from_dict(data["axes"]) if "axes" in data else None,
|
|
305
311
|
text=text,
|
|
306
312
|
shape=shape,
|
|
307
|
-
refs=
|
|
313
|
+
refs=SpecRefs.from_dict(data.get("refs", {})),
|
|
308
314
|
created_at=data.get("created_at"),
|
|
309
315
|
modified_at=data.get("modified_at"),
|
|
310
316
|
)
|
|
@@ -314,6 +320,6 @@ class Node:
|
|
|
314
320
|
self.modified_at = datetime.utcnow().isoformat() + "Z"
|
|
315
321
|
|
|
316
322
|
|
|
317
|
-
__all__ = ["
|
|
323
|
+
__all__ = ["Spec", "TextContent", "ShapeParams"]
|
|
318
324
|
|
|
319
325
|
# EOF
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
# Timestamp: 2025-12-
|
|
3
|
-
# File: /home/ywatanabe/proj/scitex-code/src/scitex/
|
|
2
|
+
# Timestamp: 2025-12-21
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/io/bundle/_dataclasses/_SpecRefs.py
|
|
4
4
|
|
|
5
|
-
"""
|
|
5
|
+
"""SpecRefs - References to associated files within the bundle."""
|
|
6
6
|
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from typing import Any, Dict, Optional
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
@dataclass
|
|
12
|
-
class
|
|
12
|
+
class SpecRefs:
|
|
13
13
|
"""References to associated files within the bundle.
|
|
14
14
|
|
|
15
15
|
All paths are relative to the bundle root.
|
|
@@ -31,7 +31,7 @@ class NodeRefs:
|
|
|
31
31
|
return result
|
|
32
32
|
|
|
33
33
|
@classmethod
|
|
34
|
-
def from_dict(cls, data: Dict[str, Any]) -> "
|
|
34
|
+
def from_dict(cls, data: Dict[str, Any]) -> "SpecRefs":
|
|
35
35
|
return cls(
|
|
36
36
|
encoding=data.get("encoding", "encoding.json"),
|
|
37
37
|
theme=data.get("theme", "theme.json"),
|
|
@@ -40,6 +40,6 @@ class NodeRefs:
|
|
|
40
40
|
)
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
__all__ = ["
|
|
43
|
+
__all__ = ["SpecRefs"]
|
|
44
44
|
|
|
45
45
|
# EOF
|