scitex 2.7.3__py3-none-any.whl → 2.8.1__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/__version__.py +1 -1
- scitex/dev/plt/__init__.py +0 -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/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 +97 -12
- scitex/io/__init__.py +12 -0
- scitex/io/_bundle.py +69 -10
- scitex/io/_zip_bundle.py +439 -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/_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/io/_layered_bundle.py +0 -0
- scitex/schema/_plot.py +0 -0
- {scitex-2.7.3.dist-info → scitex-2.8.1.dist-info}/METADATA +1 -1
- {scitex-2.7.3.dist-info → scitex-2.8.1.dist-info}/RECORD +78 -22
- scitex/fig/editor/_edit.py +0 -751
- {scitex-2.7.3.dist-info → scitex-2.8.1.dist-info}/WHEEL +0 -0
- {scitex-2.7.3.dist-info → scitex-2.8.1.dist-info}/entry_points.txt +0 -0
- {scitex-2.7.3.dist-info → scitex-2.8.1.dist-info}/licenses/LICENSE +0 -0
scitex/fig/io/_bundle.py
CHANGED
|
@@ -333,31 +333,57 @@ def _copy_nested_pltz_bundles(plots: Dict[str, Any], dir_path: Path) -> None:
|
|
|
333
333
|
|
|
334
334
|
Args:
|
|
335
335
|
plots: Dict mapping panel IDs to either:
|
|
336
|
-
- source_path: Path to existing .pltz.d directory
|
|
336
|
+
- source_path: Path to existing .pltz.d directory or .pltz zip
|
|
337
337
|
- bundle_data: Dict with spec/data (will use save_bundle)
|
|
338
338
|
dir_path: Target figz directory.
|
|
339
339
|
"""
|
|
340
340
|
for panel_id, plot_source in plots.items():
|
|
341
|
-
target_path = dir_path / f"{panel_id}.pltz.d"
|
|
342
|
-
|
|
343
341
|
if isinstance(plot_source, (str, Path)):
|
|
344
342
|
# Direct copy from source path
|
|
345
343
|
source_path = Path(plot_source)
|
|
346
|
-
|
|
344
|
+
|
|
345
|
+
if source_path.is_dir() and str(source_path).endswith('.pltz.d'):
|
|
346
|
+
# Source is .pltz.d directory - copy as directory
|
|
347
|
+
target_path = dir_path / f"{panel_id}.pltz.d"
|
|
347
348
|
if target_path.exists():
|
|
348
349
|
shutil.rmtree(target_path)
|
|
349
350
|
shutil.copytree(source_path, target_path)
|
|
351
|
+
|
|
352
|
+
elif source_path.is_file() and str(source_path).endswith('.pltz'):
|
|
353
|
+
# Source is .pltz zip file - copy as zip file (preserving zip format)
|
|
354
|
+
target_path = dir_path / f"{panel_id}.pltz"
|
|
355
|
+
if target_path.exists():
|
|
356
|
+
target_path.unlink()
|
|
357
|
+
shutil.copy2(source_path, target_path)
|
|
358
|
+
|
|
359
|
+
elif source_path.exists():
|
|
360
|
+
# Unknown format - try to copy as directory
|
|
361
|
+
target_path = dir_path / f"{panel_id}.pltz.d"
|
|
362
|
+
if source_path.is_dir():
|
|
363
|
+
if target_path.exists():
|
|
364
|
+
shutil.rmtree(target_path)
|
|
365
|
+
shutil.copytree(source_path, target_path)
|
|
366
|
+
|
|
350
367
|
elif isinstance(plot_source, dict):
|
|
351
368
|
# Check if it has source_path for direct copy
|
|
352
369
|
if "source_path" in plot_source:
|
|
353
370
|
source_path = Path(plot_source["source_path"])
|
|
354
|
-
if source_path.
|
|
371
|
+
if source_path.is_file() and str(source_path).endswith('.pltz'):
|
|
372
|
+
# .pltz zip file
|
|
373
|
+
target_path = dir_path / f"{panel_id}.pltz"
|
|
374
|
+
if target_path.exists():
|
|
375
|
+
target_path.unlink()
|
|
376
|
+
shutil.copy2(source_path, target_path)
|
|
377
|
+
elif source_path.exists() and source_path.is_dir():
|
|
378
|
+
# .pltz.d directory
|
|
379
|
+
target_path = dir_path / f"{panel_id}.pltz.d"
|
|
355
380
|
if target_path.exists():
|
|
356
381
|
shutil.rmtree(target_path)
|
|
357
382
|
shutil.copytree(source_path, target_path)
|
|
358
383
|
else:
|
|
359
384
|
# Fallback to save_bundle (will lose images)
|
|
360
385
|
from scitex.io._bundle import save_bundle, BundleType
|
|
386
|
+
target_path = dir_path / f"{panel_id}.pltz.d"
|
|
361
387
|
save_bundle(plot_source, target_path, bundle_type=BundleType.PLTZ)
|
|
362
388
|
|
|
363
389
|
|
|
@@ -376,9 +402,33 @@ def _generate_figz_overview(dir_path: Path, spec: Dict, data: Dict, basename: st
|
|
|
376
402
|
from PIL import Image
|
|
377
403
|
import numpy as np
|
|
378
404
|
import warnings
|
|
405
|
+
import tempfile
|
|
406
|
+
import zipfile
|
|
407
|
+
|
|
408
|
+
# Find all panel bundles (both .pltz.d directories and .pltz zip files)
|
|
409
|
+
panel_dirs = []
|
|
410
|
+
temp_dirs_to_cleanup = []
|
|
411
|
+
|
|
412
|
+
for item in dir_path.iterdir():
|
|
413
|
+
if item.is_dir() and str(item).endswith('.pltz.d'):
|
|
414
|
+
panel_dirs.append(item)
|
|
415
|
+
elif item.is_file() and str(item).endswith('.pltz'):
|
|
416
|
+
# Extract .pltz zip to temp directory for overview generation
|
|
417
|
+
temp_dir = tempfile.mkdtemp(prefix=f'scitex_overview_{item.stem}_')
|
|
418
|
+
temp_dirs_to_cleanup.append(temp_dir)
|
|
419
|
+
with zipfile.ZipFile(item, 'r') as zf:
|
|
420
|
+
zf.extractall(temp_dir)
|
|
421
|
+
# Find the extracted .pltz.d directory
|
|
422
|
+
extracted = Path(temp_dir)
|
|
423
|
+
for subitem in extracted.iterdir():
|
|
424
|
+
if subitem.is_dir() and str(subitem).endswith('.pltz.d'):
|
|
425
|
+
panel_dirs.append(subitem)
|
|
426
|
+
break
|
|
427
|
+
else:
|
|
428
|
+
# Use temp dir directly if no .pltz.d subfolder
|
|
429
|
+
panel_dirs.append(extracted)
|
|
379
430
|
|
|
380
|
-
|
|
381
|
-
panel_dirs = sorted(dir_path.glob("*.pltz.d"))
|
|
431
|
+
panel_dirs = sorted(panel_dirs, key=lambda x: x.name)
|
|
382
432
|
n_panels = len(panel_dirs)
|
|
383
433
|
|
|
384
434
|
if n_panels == 0:
|
|
@@ -519,6 +569,13 @@ def _generate_figz_overview(dir_path: Path, spec: Dict, data: Dict, basename: st
|
|
|
519
569
|
fig.savefig(overview_path, dpi=150, bbox_inches="tight", facecolor="white")
|
|
520
570
|
plt.close(fig)
|
|
521
571
|
|
|
572
|
+
# Cleanup temp directories
|
|
573
|
+
for temp_dir in temp_dirs_to_cleanup:
|
|
574
|
+
try:
|
|
575
|
+
shutil.rmtree(temp_dir)
|
|
576
|
+
except Exception:
|
|
577
|
+
pass
|
|
578
|
+
|
|
522
579
|
|
|
523
580
|
def _draw_bboxes_from_geometry(ax, geometry_data: Dict) -> None:
|
|
524
581
|
"""Draw bboxes from geometry data on an axes.
|
|
@@ -745,6 +802,8 @@ def _generate_figz_geometry_cache(dir_path: Path, spec: Dict, basename: str) ->
|
|
|
745
802
|
basename: Base filename for bundle files.
|
|
746
803
|
"""
|
|
747
804
|
from datetime import datetime
|
|
805
|
+
import tempfile
|
|
806
|
+
import zipfile
|
|
748
807
|
|
|
749
808
|
cache_dir = dir_path / "cache"
|
|
750
809
|
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -756,11 +815,30 @@ def _generate_figz_geometry_cache(dir_path: Path, spec: Dict, basename: str) ->
|
|
|
756
815
|
"generated_at": datetime.now().isoformat(),
|
|
757
816
|
}
|
|
758
817
|
|
|
759
|
-
# Find all panel directories
|
|
760
|
-
|
|
818
|
+
# Find all panel bundles (both .pltz.d directories and .pltz zip files)
|
|
819
|
+
panel_sources = []
|
|
820
|
+
temp_dirs_to_cleanup = []
|
|
821
|
+
|
|
822
|
+
for item in dir_path.iterdir():
|
|
823
|
+
if item.is_dir() and str(item).endswith('.pltz.d'):
|
|
824
|
+
panel_sources.append((item.stem.replace('.pltz', ''), item))
|
|
825
|
+
elif item.is_file() and str(item).endswith('.pltz'):
|
|
826
|
+
# Extract .pltz zip to temp directory
|
|
827
|
+
temp_dir = tempfile.mkdtemp(prefix=f'scitex_geom_{item.stem}_')
|
|
828
|
+
temp_dirs_to_cleanup.append(temp_dir)
|
|
829
|
+
with zipfile.ZipFile(item, 'r') as zf:
|
|
830
|
+
zf.extractall(temp_dir)
|
|
831
|
+
extracted = Path(temp_dir)
|
|
832
|
+
for subitem in extracted.iterdir():
|
|
833
|
+
if subitem.is_dir() and str(subitem).endswith('.pltz.d'):
|
|
834
|
+
panel_sources.append((item.stem, subitem))
|
|
835
|
+
break
|
|
836
|
+
else:
|
|
837
|
+
panel_sources.append((item.stem, extracted))
|
|
761
838
|
|
|
762
|
-
|
|
763
|
-
|
|
839
|
+
panel_sources = sorted(panel_sources, key=lambda x: x[0])
|
|
840
|
+
|
|
841
|
+
for panel_id, panel_dir in panel_sources:
|
|
764
842
|
|
|
765
843
|
# Load panel geometry
|
|
766
844
|
panel_geometry_path = panel_dir / "cache" / "geometry_px.json"
|
|
@@ -790,7 +868,7 @@ def _generate_figz_geometry_cache(dir_path: Path, spec: Dict, basename: str) ->
|
|
|
790
868
|
"figure_id": basename,
|
|
791
869
|
"generated_at": datetime.now().isoformat(),
|
|
792
870
|
"size_mm": [size.get("width_mm", 0), size.get("height_mm", 0)],
|
|
793
|
-
"panels_count": len(
|
|
871
|
+
"panels_count": len(panel_sources),
|
|
794
872
|
"schema": spec.get("schema", {}),
|
|
795
873
|
}
|
|
796
874
|
|
|
@@ -798,6 +876,13 @@ def _generate_figz_geometry_cache(dir_path: Path, spec: Dict, basename: str) ->
|
|
|
798
876
|
with open(manifest_path, "w") as f:
|
|
799
877
|
json.dump(manifest, f, indent=2)
|
|
800
878
|
|
|
879
|
+
# Cleanup temp directories
|
|
880
|
+
for temp_dir in temp_dirs_to_cleanup:
|
|
881
|
+
try:
|
|
882
|
+
shutil.rmtree(temp_dir)
|
|
883
|
+
except Exception:
|
|
884
|
+
pass
|
|
885
|
+
|
|
801
886
|
|
|
802
887
|
def _embed_metadata_in_export(
|
|
803
888
|
file_path: Path, spec: Dict[str, Any], fmt: str
|
scitex/io/__init__.py
CHANGED
|
@@ -85,6 +85,14 @@ except ImportError:
|
|
|
85
85
|
embed_metadata = None
|
|
86
86
|
has_metadata = None
|
|
87
87
|
|
|
88
|
+
# Import ZipBundle for in-memory zip access
|
|
89
|
+
try:
|
|
90
|
+
from ._zip_bundle import ZipBundle, open_bundle, create_bundle
|
|
91
|
+
except ImportError:
|
|
92
|
+
ZipBundle = None
|
|
93
|
+
open_bundle = None
|
|
94
|
+
create_bundle = None
|
|
95
|
+
|
|
88
96
|
__all__ = [
|
|
89
97
|
# Primary I/O
|
|
90
98
|
"save",
|
|
@@ -96,6 +104,10 @@ __all__ = [
|
|
|
96
104
|
"reload",
|
|
97
105
|
"flush",
|
|
98
106
|
"cache",
|
|
107
|
+
# Zip bundle access
|
|
108
|
+
"ZipBundle",
|
|
109
|
+
"open_bundle",
|
|
110
|
+
"create_bundle",
|
|
99
111
|
]
|
|
100
112
|
|
|
101
113
|
# EOF
|
scitex/io/_bundle.py
CHANGED
|
@@ -307,11 +307,13 @@ def validate_bundle(
|
|
|
307
307
|
return result
|
|
308
308
|
|
|
309
309
|
|
|
310
|
-
def load_bundle(path: Union[str, Path]) -> Dict[str, Any]:
|
|
310
|
+
def load_bundle(path: Union[str, Path], in_memory: bool = True) -> Dict[str, Any]:
|
|
311
311
|
"""Load bundle from directory or ZIP transparently.
|
|
312
312
|
|
|
313
313
|
Args:
|
|
314
314
|
path: Path to bundle (directory or ZIP).
|
|
315
|
+
in_memory: If True, load ZIP contents in-memory without extracting.
|
|
316
|
+
If False, extract to temp directory (legacy behavior).
|
|
315
317
|
|
|
316
318
|
Returns:
|
|
317
319
|
Bundle data as dictionary with:
|
|
@@ -335,17 +337,42 @@ def load_bundle(path: Union[str, Path]) -> Dict[str, Any]:
|
|
|
335
337
|
|
|
336
338
|
# Handle ZIP vs directory
|
|
337
339
|
if p.is_file() and p.suffix in BUNDLE_EXTENSIONS:
|
|
338
|
-
# ZIP archive - extract to temp and load
|
|
339
340
|
result["is_zip"] = True
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
341
|
+
|
|
342
|
+
if in_memory:
|
|
343
|
+
# In-memory loading using ZipBundle
|
|
344
|
+
from ._zip_bundle import ZipBundle
|
|
345
|
+
with ZipBundle(p, mode="r") as zb:
|
|
346
|
+
result["_zip_bundle"] = zb
|
|
347
|
+
# Load common files in-memory
|
|
348
|
+
try:
|
|
349
|
+
result["spec"] = zb.read_json("spec.json")
|
|
350
|
+
except FileNotFoundError:
|
|
351
|
+
result["spec"] = None
|
|
352
|
+
try:
|
|
353
|
+
result["style"] = zb.read_json("style.json")
|
|
354
|
+
except FileNotFoundError:
|
|
355
|
+
result["style"] = None
|
|
356
|
+
try:
|
|
357
|
+
result["data"] = zb.read_csv("data.csv")
|
|
358
|
+
except FileNotFoundError:
|
|
359
|
+
result["data"] = None
|
|
360
|
+
|
|
361
|
+
# Get file list
|
|
362
|
+
result["files"] = zb.namelist()
|
|
363
|
+
|
|
364
|
+
return result
|
|
365
|
+
else:
|
|
366
|
+
# Legacy: extract to temp and load
|
|
367
|
+
import tempfile
|
|
368
|
+
temp_dir = Path(tempfile.mkdtemp())
|
|
369
|
+
with zipfile.ZipFile(p, "r") as zf:
|
|
370
|
+
zf.extractall(temp_dir)
|
|
371
|
+
bundle_dir = temp_dir
|
|
345
372
|
else:
|
|
346
373
|
bundle_dir = p
|
|
347
374
|
|
|
348
|
-
# Delegate to domain-specific loaders
|
|
375
|
+
# Delegate to domain-specific loaders (for directory bundles or legacy mode)
|
|
349
376
|
if bundle_type == BundleType.FIGZ:
|
|
350
377
|
from scitex.fig.io._bundle import load_figz_bundle
|
|
351
378
|
result.update(load_figz_bundle(bundle_dir))
|
|
@@ -364,6 +391,7 @@ def save_bundle(
|
|
|
364
391
|
path: Union[str, Path],
|
|
365
392
|
bundle_type: Optional[str] = None,
|
|
366
393
|
as_zip: bool = False,
|
|
394
|
+
atomic: bool = True,
|
|
367
395
|
) -> Path:
|
|
368
396
|
"""Save data as a bundle.
|
|
369
397
|
|
|
@@ -372,6 +400,7 @@ def save_bundle(
|
|
|
372
400
|
path: Output path (with or without .d suffix).
|
|
373
401
|
bundle_type: Bundle type ('figz', 'pltz', 'statsz'). Auto-detected if None.
|
|
374
402
|
as_zip: If True, save as ZIP archive.
|
|
403
|
+
atomic: If True, use atomic write (temp file + rename) for ZIP.
|
|
375
404
|
|
|
376
405
|
Returns:
|
|
377
406
|
Path to saved bundle.
|
|
@@ -399,7 +428,37 @@ def save_bundle(
|
|
|
399
428
|
else:
|
|
400
429
|
dir_path = p
|
|
401
430
|
|
|
402
|
-
#
|
|
431
|
+
# For direct ZIP saving with atomic writes, use ZipBundle
|
|
432
|
+
# Note: figz bundles need special handling for nested pltz panels,
|
|
433
|
+
# so they go through the directory-based save_figz_bundle path
|
|
434
|
+
if save_as_zip and atomic and bundle_type != BundleType.FIGZ:
|
|
435
|
+
from ._zip_bundle import ZipBundle
|
|
436
|
+
|
|
437
|
+
with ZipBundle(zip_path, mode="w") as zb:
|
|
438
|
+
# Write spec
|
|
439
|
+
if "spec" in data:
|
|
440
|
+
zb.write_json("spec.json", data["spec"])
|
|
441
|
+
|
|
442
|
+
# Write style
|
|
443
|
+
if "style" in data:
|
|
444
|
+
zb.write_json("style.json", data["style"])
|
|
445
|
+
|
|
446
|
+
# Write CSV data
|
|
447
|
+
if "data" in data and data["data"] is not None:
|
|
448
|
+
import pandas as pd
|
|
449
|
+
if isinstance(data["data"], pd.DataFrame):
|
|
450
|
+
zb.write_csv("data.csv", data["data"])
|
|
451
|
+
|
|
452
|
+
# Write exports (PNG, SVG, etc.)
|
|
453
|
+
for key in ["png", "svg", "pdf"]:
|
|
454
|
+
if key in data and data[key] is not None:
|
|
455
|
+
export_data = data[key]
|
|
456
|
+
if isinstance(export_data, bytes):
|
|
457
|
+
zb.write_bytes(f"exports/figure.{key}", export_data)
|
|
458
|
+
|
|
459
|
+
return zip_path
|
|
460
|
+
|
|
461
|
+
# Create directory for non-ZIP or non-atomic saves
|
|
403
462
|
dir_path.mkdir(parents=True, exist_ok=True)
|
|
404
463
|
|
|
405
464
|
# Delegate to domain-specific savers
|
|
@@ -417,7 +476,7 @@ def save_bundle(
|
|
|
417
476
|
else:
|
|
418
477
|
raise ValueError(f"Unknown bundle type: {bundle_type}")
|
|
419
478
|
|
|
420
|
-
# Pack to ZIP if requested
|
|
479
|
+
# Pack to ZIP if requested (non-atomic path)
|
|
421
480
|
if save_as_zip:
|
|
422
481
|
pack_bundle(dir_path, zip_path)
|
|
423
482
|
shutil.rmtree(dir_path) # Remove temp directory
|