scitex 2.8.1__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/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/edit/panel_loader.py +1 -1
- scitex/fig/io/_bundle.py +7 -7
- 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 -32
- 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} +168 -97
- scitex/io/bundle/_nested.py +713 -0
- scitex/io/bundle/_types.py +74 -0
- scitex/io/{_zip_bundle.py → bundle/_zip.py} +93 -45
- 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/_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/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/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/_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.8.1.dist-info → scitex-2.10.0.dist-info}/METADATA +364 -181
- {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/RECORD +412 -97
- 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.8.1.dist-info → scitex-2.10.0.dist-info}/WHEEL +0 -0
- {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/entry_points.txt +0 -0
- {scitex-2.8.1.dist-info → scitex-2.10.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2025-12-20
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_bundle/_utils/_types.py
|
|
4
|
+
|
|
5
|
+
"""FTS Node type constants and constraints."""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class NodeType:
|
|
11
|
+
"""Node type constants for FTS bundles.
|
|
12
|
+
|
|
13
|
+
The type is stored in node.json["type"].
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
from scitex.fts import NodeType
|
|
17
|
+
|
|
18
|
+
if bundle.node.type == NodeType.FIGURE:
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
Note:
|
|
22
|
+
Tables are treated as structured figures in academic contexts.
|
|
23
|
+
FTS handles figures, tables, and statistics in a unified way.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
FIGURE = "figure"
|
|
27
|
+
PLOT = "plot"
|
|
28
|
+
TABLE = "table" # Structured data presentation (demographics, results, etc.)
|
|
29
|
+
STATS = "stats"
|
|
30
|
+
IMAGE = "image"
|
|
31
|
+
TEXT = "text"
|
|
32
|
+
SHAPE = "shape"
|
|
33
|
+
SYMBOL = "symbol"
|
|
34
|
+
COMMENT = "comment"
|
|
35
|
+
EQUATION = "equation"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Legacy alias (deprecated)
|
|
39
|
+
BundleType = NodeType
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Type-specific default constraints
|
|
43
|
+
TYPE_DEFAULTS: Dict[str, Dict[str, Any]] = {
|
|
44
|
+
"figure": {"allow_children": True, "max_depth": 3},
|
|
45
|
+
"plot": {"allow_children": False, "max_depth": 1},
|
|
46
|
+
"table": {"allow_children": False, "max_depth": 1}, # Tables are leaf nodes
|
|
47
|
+
"stats": {"allow_children": False, "max_depth": 1},
|
|
48
|
+
"image": {"allow_children": False, "max_depth": 1},
|
|
49
|
+
"text": {"allow_children": False, "max_depth": 1},
|
|
50
|
+
"shape": {"allow_children": False, "max_depth": 1},
|
|
51
|
+
"symbol": {"allow_children": False, "max_depth": 1},
|
|
52
|
+
"comment": {"allow_children": False, "max_depth": 1},
|
|
53
|
+
"equation": {"allow_children": False, "max_depth": 1},
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_default_constraints(node_type: str) -> Dict[str, Any]:
|
|
58
|
+
"""Get default constraints for a node type.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
node_type: Type string (figure, plot, stats, etc.)
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Dict with allow_children and max_depth.
|
|
65
|
+
"""
|
|
66
|
+
return TYPE_DEFAULTS.get(node_type, {"allow_children": False, "max_depth": 1})
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
__all__ = [
|
|
70
|
+
"NodeType",
|
|
71
|
+
"BundleType",
|
|
72
|
+
"TYPE_DEFAULTS",
|
|
73
|
+
"get_default_constraints",
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
# EOF
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2025-12-20
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_bundle/_validation.py
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
FTS Multi-Level Validation System.
|
|
7
|
+
|
|
8
|
+
Three validation levels:
|
|
9
|
+
1. schema - Always ON (init/load/save) - Fast, structural checks
|
|
10
|
+
2. semantic - On demand (save/export) - Cross-reference consistency
|
|
11
|
+
3. strict - Explicit (CI/publication) - Full scientific rigor
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
result = fts.validate(level="schema") # Fast, default at init
|
|
15
|
+
result = fts.validate(level="semantic") # Cross-ref checks
|
|
16
|
+
result = fts.validate(level="strict") # Full validation
|
|
17
|
+
|
|
18
|
+
if result.has_errors:
|
|
19
|
+
raise FTSValidationError(result)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
from dataclasses import dataclass, field
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any, Dict, List, Literal, Optional, Union
|
|
26
|
+
|
|
27
|
+
# Schema directory (now in _schemas/)
|
|
28
|
+
SCHEMA_DIR = Path(__file__).parent.parent / "_schemas"
|
|
29
|
+
|
|
30
|
+
# Schema versions
|
|
31
|
+
SCHEMA_VERSION = "1.0.0"
|
|
32
|
+
|
|
33
|
+
# Loaded schemas (cached)
|
|
34
|
+
_SCHEMAS: Dict[str, Dict[str, Any]] = {}
|
|
35
|
+
|
|
36
|
+
# Validation levels
|
|
37
|
+
ValidationLevel = Literal["schema", "semantic", "strict"]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class ValidationResult:
|
|
42
|
+
"""Result of FTS validation.
|
|
43
|
+
|
|
44
|
+
Attributes
|
|
45
|
+
----------
|
|
46
|
+
level : str
|
|
47
|
+
Validation level used
|
|
48
|
+
errors : list
|
|
49
|
+
Critical errors (must fix)
|
|
50
|
+
warnings : list
|
|
51
|
+
Non-critical issues (should fix)
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
level: str = "schema"
|
|
55
|
+
errors: List[str] = field(default_factory=list)
|
|
56
|
+
warnings: List[str] = field(default_factory=list)
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def has_errors(self) -> bool:
|
|
60
|
+
"""True if validation failed with errors."""
|
|
61
|
+
return len(self.errors) > 0
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def has_warnings(self) -> bool:
|
|
65
|
+
"""True if there are warnings."""
|
|
66
|
+
return len(self.warnings) > 0
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def is_valid(self) -> bool:
|
|
70
|
+
"""True if no errors (warnings OK)."""
|
|
71
|
+
return not self.has_errors
|
|
72
|
+
|
|
73
|
+
def __str__(self) -> str:
|
|
74
|
+
if self.is_valid and not self.has_warnings:
|
|
75
|
+
return f"ValidationResult(level={self.level}, valid=True)"
|
|
76
|
+
parts = [f"ValidationResult(level={self.level}"]
|
|
77
|
+
if self.errors:
|
|
78
|
+
parts.append(f", errors={len(self.errors)}")
|
|
79
|
+
if self.warnings:
|
|
80
|
+
parts.append(f", warnings={len(self.warnings)}")
|
|
81
|
+
parts.append(")")
|
|
82
|
+
return "".join(parts)
|
|
83
|
+
|
|
84
|
+
def raise_if_invalid(self):
|
|
85
|
+
"""Raise BundleValidationError if has errors."""
|
|
86
|
+
if self.has_errors:
|
|
87
|
+
from ._utils import BundleValidationError
|
|
88
|
+
|
|
89
|
+
raise BundleValidationError(
|
|
90
|
+
f"Validation failed ({len(self.errors)} errors): {self.errors[0]}"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def load_schema(name: str) -> Dict[str, Any]:
|
|
95
|
+
"""Load a JSON schema by name."""
|
|
96
|
+
if name not in _SCHEMAS:
|
|
97
|
+
schema_path = SCHEMA_DIR / f"{name}.schema.json"
|
|
98
|
+
if not schema_path.exists():
|
|
99
|
+
raise FileNotFoundError(f"Schema not found: {schema_path}")
|
|
100
|
+
with open(schema_path) as f:
|
|
101
|
+
_SCHEMAS[name] = json.load(f)
|
|
102
|
+
return _SCHEMAS[name]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# =============================================================================
|
|
106
|
+
# Level 1: Schema Validation (Always ON)
|
|
107
|
+
# =============================================================================
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def validate_schema(data: Dict[str, Any], schema_name: str) -> List[str]:
|
|
111
|
+
"""Validate data against JSON schema.
|
|
112
|
+
|
|
113
|
+
Fast structural validation:
|
|
114
|
+
- Required fields present
|
|
115
|
+
- Types correct
|
|
116
|
+
- Enum values valid
|
|
117
|
+
- bbox in range [0, 1]
|
|
118
|
+
"""
|
|
119
|
+
errors = []
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
schema = load_schema(schema_name)
|
|
123
|
+
except FileNotFoundError as e:
|
|
124
|
+
return [str(e)]
|
|
125
|
+
|
|
126
|
+
# Check required fields
|
|
127
|
+
required = schema.get("required", [])
|
|
128
|
+
for fld in required:
|
|
129
|
+
if fld not in data:
|
|
130
|
+
errors.append(f"Missing required field: {fld}")
|
|
131
|
+
|
|
132
|
+
# Check property types
|
|
133
|
+
properties = schema.get("properties", {})
|
|
134
|
+
for fld, value in data.items():
|
|
135
|
+
if fld in properties:
|
|
136
|
+
prop_schema = properties[fld]
|
|
137
|
+
expected_type = prop_schema.get("type")
|
|
138
|
+
|
|
139
|
+
if expected_type:
|
|
140
|
+
type_map = {
|
|
141
|
+
"string": str,
|
|
142
|
+
"number": (int, float),
|
|
143
|
+
"integer": int,
|
|
144
|
+
"boolean": bool,
|
|
145
|
+
"array": list,
|
|
146
|
+
"object": dict,
|
|
147
|
+
}
|
|
148
|
+
expected = type_map.get(expected_type)
|
|
149
|
+
if expected and not isinstance(value, expected):
|
|
150
|
+
errors.append(
|
|
151
|
+
f"Field '{fld}' should be {expected_type}, "
|
|
152
|
+
f"got {type(value).__name__}"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Check enum values
|
|
156
|
+
if "enum" in prop_schema and value not in prop_schema["enum"]:
|
|
157
|
+
errors.append(
|
|
158
|
+
f"Field '{fld}' must be one of {prop_schema['enum']}, got '{value}'"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# Special: bbox range validation
|
|
162
|
+
if "bbox" in data and isinstance(data["bbox"], dict):
|
|
163
|
+
bbox = data["bbox"]
|
|
164
|
+
for key in ["x", "y", "width", "height"]:
|
|
165
|
+
if key in bbox:
|
|
166
|
+
val = bbox[key]
|
|
167
|
+
if isinstance(val, (int, float)) and not (0 <= val <= 1):
|
|
168
|
+
errors.append(f"bbox.{key} must be in range [0, 1], got {val}")
|
|
169
|
+
|
|
170
|
+
return errors
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def validate_node(data: Dict[str, Any]) -> List[str]:
|
|
174
|
+
"""Validate node.json data."""
|
|
175
|
+
return validate_schema(data, "node")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def validate_encoding(data: Dict[str, Any]) -> List[str]:
|
|
179
|
+
"""Validate encoding.json data."""
|
|
180
|
+
return validate_schema(data, "encoding")
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def validate_theme(data: Dict[str, Any]) -> List[str]:
|
|
184
|
+
"""Validate theme.json data."""
|
|
185
|
+
return validate_schema(data, "theme")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def validate_stats(data: Dict[str, Any]) -> List[str]:
|
|
189
|
+
"""Validate stats.json data."""
|
|
190
|
+
return validate_schema(data, "stats")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def validate_data_info(data: Dict[str, Any]) -> List[str]:
|
|
194
|
+
"""Validate data_info.json data."""
|
|
195
|
+
return validate_schema(data, "data_info")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
# =============================================================================
|
|
199
|
+
# Level 2: Semantic Validation (On demand)
|
|
200
|
+
# =============================================================================
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def validate_semantic(
|
|
204
|
+
node: Optional[Dict] = None,
|
|
205
|
+
encoding: Optional[Dict] = None,
|
|
206
|
+
theme: Optional[Dict] = None,
|
|
207
|
+
stats: Optional[Dict] = None,
|
|
208
|
+
data_info: Optional[Dict] = None,
|
|
209
|
+
) -> List[str]:
|
|
210
|
+
"""Validate semantic consistency across bundle components.
|
|
211
|
+
|
|
212
|
+
Cross-reference checks:
|
|
213
|
+
- encoding columns exist in data_info
|
|
214
|
+
- stats references valid data
|
|
215
|
+
- scale/normalization consistency
|
|
216
|
+
"""
|
|
217
|
+
errors = []
|
|
218
|
+
|
|
219
|
+
# Check encoding references data_info columns
|
|
220
|
+
if encoding and data_info:
|
|
221
|
+
columns = set()
|
|
222
|
+
if "columns" in data_info:
|
|
223
|
+
columns = {c.get("name") for c in data_info["columns"] if c.get("name")}
|
|
224
|
+
|
|
225
|
+
traces = encoding.get("traces", [])
|
|
226
|
+
for trace in traces:
|
|
227
|
+
for channel in ["x", "y", "color", "size"]:
|
|
228
|
+
if channel in trace:
|
|
229
|
+
col = trace[channel].get("column")
|
|
230
|
+
if col and columns and col not in columns:
|
|
231
|
+
errors.append(
|
|
232
|
+
f"Encoding references unknown column '{col}' "
|
|
233
|
+
f"(available: {sorted(columns)})"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Check stats references valid data
|
|
237
|
+
if stats and data_info:
|
|
238
|
+
analyses = stats.get("analyses", [])
|
|
239
|
+
columns = set()
|
|
240
|
+
if "columns" in data_info:
|
|
241
|
+
columns = {c.get("name") for c in data_info["columns"] if c.get("name")}
|
|
242
|
+
|
|
243
|
+
for analysis in analyses:
|
|
244
|
+
data_ref = analysis.get("data_ref", {})
|
|
245
|
+
for key in ["group_column", "value_column"]:
|
|
246
|
+
col = data_ref.get(key)
|
|
247
|
+
if col and columns and col not in columns:
|
|
248
|
+
errors.append(f"Stats references unknown column '{col}'")
|
|
249
|
+
|
|
250
|
+
return errors
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# =============================================================================
|
|
254
|
+
# Level 3: Strict Validation (Explicit call)
|
|
255
|
+
# =============================================================================
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def validate_strict(
|
|
259
|
+
node: Optional[Dict] = None,
|
|
260
|
+
encoding: Optional[Dict] = None,
|
|
261
|
+
theme: Optional[Dict] = None,
|
|
262
|
+
stats: Optional[Dict] = None,
|
|
263
|
+
data_info: Optional[Dict] = None,
|
|
264
|
+
) -> List[str]:
|
|
265
|
+
"""Strict validation for publication/CI.
|
|
266
|
+
|
|
267
|
+
All semantic checks plus:
|
|
268
|
+
- Units must be specified
|
|
269
|
+
- Provenance must be complete
|
|
270
|
+
- All metadata fields populated
|
|
271
|
+
"""
|
|
272
|
+
errors = []
|
|
273
|
+
|
|
274
|
+
# Include all semantic errors
|
|
275
|
+
errors.extend(
|
|
276
|
+
validate_semantic(
|
|
277
|
+
node=node,
|
|
278
|
+
encoding=encoding,
|
|
279
|
+
theme=theme,
|
|
280
|
+
stats=stats,
|
|
281
|
+
data_info=data_info,
|
|
282
|
+
)
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# Units must be specified in data_info
|
|
286
|
+
if data_info:
|
|
287
|
+
columns = data_info.get("columns", [])
|
|
288
|
+
for col in columns:
|
|
289
|
+
if col.get("dtype") in ["float64", "int64", "number"]:
|
|
290
|
+
if not col.get("unit"):
|
|
291
|
+
errors.append(f"Column '{col.get('name')}' missing unit specification")
|
|
292
|
+
|
|
293
|
+
# Provenance must be present
|
|
294
|
+
if data_info and not data_info.get("source"):
|
|
295
|
+
errors.append("data_info.source (provenance) is required for publication")
|
|
296
|
+
|
|
297
|
+
# Node must have all metadata
|
|
298
|
+
if node:
|
|
299
|
+
if not node.get("name"):
|
|
300
|
+
errors.append("node.name is required for publication")
|
|
301
|
+
if not node.get("created_at"):
|
|
302
|
+
errors.append("node.created_at is required for publication")
|
|
303
|
+
|
|
304
|
+
return errors
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
# =============================================================================
|
|
308
|
+
# Unified Validation Entry Point
|
|
309
|
+
# =============================================================================
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def validate(
|
|
313
|
+
data: Dict[str, Any],
|
|
314
|
+
schema_name: str,
|
|
315
|
+
level: ValidationLevel = "schema",
|
|
316
|
+
) -> ValidationResult:
|
|
317
|
+
"""Validate data at specified level.
|
|
318
|
+
|
|
319
|
+
Parameters
|
|
320
|
+
----------
|
|
321
|
+
data : dict
|
|
322
|
+
Data to validate
|
|
323
|
+
schema_name : str
|
|
324
|
+
Schema name: node, encoding, theme, stats, data_info
|
|
325
|
+
level : str
|
|
326
|
+
Validation level: schema, semantic, strict
|
|
327
|
+
|
|
328
|
+
Returns
|
|
329
|
+
-------
|
|
330
|
+
ValidationResult
|
|
331
|
+
Validation result with errors and warnings
|
|
332
|
+
"""
|
|
333
|
+
result = ValidationResult(level=level)
|
|
334
|
+
|
|
335
|
+
# Level 1: Schema validation (always)
|
|
336
|
+
schema_errors = validate_schema(data, schema_name)
|
|
337
|
+
result.errors.extend(schema_errors)
|
|
338
|
+
|
|
339
|
+
return result
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def validate_bundle(
|
|
343
|
+
bundle_path: Union[str, Path],
|
|
344
|
+
level: ValidationLevel = "schema",
|
|
345
|
+
) -> Dict[str, ValidationResult]:
|
|
346
|
+
"""Validate all JSON files in a bundle at specified level.
|
|
347
|
+
|
|
348
|
+
Parameters
|
|
349
|
+
----------
|
|
350
|
+
bundle_path : str or Path
|
|
351
|
+
Path to bundle directory or ZIP
|
|
352
|
+
level : str
|
|
353
|
+
Validation level: schema, semantic, strict
|
|
354
|
+
|
|
355
|
+
Returns
|
|
356
|
+
-------
|
|
357
|
+
dict
|
|
358
|
+
Dictionary mapping filename to ValidationResult
|
|
359
|
+
"""
|
|
360
|
+
import zipfile
|
|
361
|
+
|
|
362
|
+
bundle_path = Path(bundle_path)
|
|
363
|
+
results = {}
|
|
364
|
+
|
|
365
|
+
def validate_json_file(name: str, content: str) -> ValidationResult:
|
|
366
|
+
"""Validate a JSON file content."""
|
|
367
|
+
result = ValidationResult(level=level)
|
|
368
|
+
try:
|
|
369
|
+
data = json.loads(content)
|
|
370
|
+
except json.JSONDecodeError as e:
|
|
371
|
+
result.errors.append(f"Invalid JSON: {e}")
|
|
372
|
+
return result
|
|
373
|
+
|
|
374
|
+
# Determine schema from filename
|
|
375
|
+
schema_map = {
|
|
376
|
+
"node.json": "node",
|
|
377
|
+
"encoding.json": "encoding",
|
|
378
|
+
"theme.json": "theme",
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
for suffix, schema in schema_map.items():
|
|
382
|
+
if name.endswith(suffix):
|
|
383
|
+
result.errors.extend(validate_schema(data, schema))
|
|
384
|
+
break
|
|
385
|
+
else:
|
|
386
|
+
if name.endswith("stats.json"):
|
|
387
|
+
result.errors.extend(validate_schema(data, "stats"))
|
|
388
|
+
elif name.endswith("data_info.json"):
|
|
389
|
+
result.errors.extend(validate_schema(data, "data_info"))
|
|
390
|
+
|
|
391
|
+
return result
|
|
392
|
+
|
|
393
|
+
if bundle_path.is_file() and bundle_path.suffix == ".zip":
|
|
394
|
+
with zipfile.ZipFile(bundle_path, "r") as zf:
|
|
395
|
+
for name in zf.namelist():
|
|
396
|
+
if name.endswith(".json"):
|
|
397
|
+
content = zf.read(name).decode("utf-8")
|
|
398
|
+
results[name] = validate_json_file(name, content)
|
|
399
|
+
elif bundle_path.is_dir():
|
|
400
|
+
for json_file in bundle_path.rglob("*.json"):
|
|
401
|
+
rel_path = json_file.relative_to(bundle_path)
|
|
402
|
+
with open(json_file) as f:
|
|
403
|
+
content = f.read()
|
|
404
|
+
results[str(rel_path)] = validate_json_file(str(rel_path), content)
|
|
405
|
+
|
|
406
|
+
return results
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
__all__ = [
|
|
410
|
+
# Version/paths
|
|
411
|
+
"SCHEMA_VERSION",
|
|
412
|
+
"SCHEMA_DIR",
|
|
413
|
+
# Result class
|
|
414
|
+
"ValidationResult",
|
|
415
|
+
"ValidationLevel",
|
|
416
|
+
# Schema loading
|
|
417
|
+
"load_schema",
|
|
418
|
+
# Level 1: Schema
|
|
419
|
+
"validate_schema",
|
|
420
|
+
"validate_node",
|
|
421
|
+
"validate_encoding",
|
|
422
|
+
"validate_theme",
|
|
423
|
+
"validate_stats",
|
|
424
|
+
"validate_data_info",
|
|
425
|
+
# Level 2: Semantic
|
|
426
|
+
"validate_semantic",
|
|
427
|
+
# Level 3: Strict
|
|
428
|
+
"validate_strict",
|
|
429
|
+
# Unified
|
|
430
|
+
"validate",
|
|
431
|
+
"validate_bundle",
|
|
432
|
+
]
|
|
433
|
+
|
|
434
|
+
# EOF
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2025-12-20
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_zipbundle.py
|
|
4
|
+
|
|
5
|
+
"""ZipBundle - Simple ZIP file wrapper for FTS bundles."""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import zipfile
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict, Optional, Union
|
|
11
|
+
|
|
12
|
+
__all__ = ["ZipBundle"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ZipBundle:
|
|
16
|
+
"""Simple ZIP file wrapper for reading/writing bundle data.
|
|
17
|
+
|
|
18
|
+
Provides a context manager interface for working with ZIP files.
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
# Read mode
|
|
22
|
+
with ZipBundle("bundle.zip", mode="r") as bundle:
|
|
23
|
+
spec = bundle.read_json("spec.json")
|
|
24
|
+
data = bundle.read_bytes("data.csv")
|
|
25
|
+
|
|
26
|
+
# Write/append mode
|
|
27
|
+
with ZipBundle("bundle.zip", mode="a") as bundle:
|
|
28
|
+
bundle.write_json("spec.json", {"type": "plot"})
|
|
29
|
+
bundle.write_bytes("data.csv", csv_bytes)
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, path: Union[str, Path], mode: str = "r"):
|
|
33
|
+
"""Initialize ZipBundle.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
path: Path to ZIP file.
|
|
37
|
+
mode: File mode ('r' for read, 'w' for write, 'a' for append).
|
|
38
|
+
"""
|
|
39
|
+
self.path = Path(path)
|
|
40
|
+
self.mode = mode
|
|
41
|
+
self._zf: Optional[zipfile.ZipFile] = None
|
|
42
|
+
|
|
43
|
+
def __enter__(self) -> "ZipBundle":
|
|
44
|
+
"""Enter context manager."""
|
|
45
|
+
self._zf = zipfile.ZipFile(self.path, self.mode)
|
|
46
|
+
return self
|
|
47
|
+
|
|
48
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
49
|
+
"""Exit context manager."""
|
|
50
|
+
if self._zf:
|
|
51
|
+
self._zf.close()
|
|
52
|
+
self._zf = None
|
|
53
|
+
|
|
54
|
+
def read_json(self, name: str) -> Dict[str, Any]:
|
|
55
|
+
"""Read JSON file from ZIP.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
name: File name within ZIP.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Parsed JSON data.
|
|
62
|
+
"""
|
|
63
|
+
if not self._zf:
|
|
64
|
+
raise RuntimeError("ZipBundle not open")
|
|
65
|
+
|
|
66
|
+
# Try direct path first
|
|
67
|
+
try:
|
|
68
|
+
data = self._zf.read(name)
|
|
69
|
+
return json.loads(data.decode("utf-8"))
|
|
70
|
+
except KeyError:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
# Try with .d/ directory prefix
|
|
74
|
+
for item in self._zf.namelist():
|
|
75
|
+
if item.endswith(f"/{name}") or item == name:
|
|
76
|
+
data = self._zf.read(item)
|
|
77
|
+
return json.loads(data.decode("utf-8"))
|
|
78
|
+
|
|
79
|
+
raise FileNotFoundError(f"File not found in ZIP: {name}")
|
|
80
|
+
|
|
81
|
+
def read_bytes(self, name: str) -> bytes:
|
|
82
|
+
"""Read raw bytes from ZIP.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
name: File name within ZIP.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Raw file bytes.
|
|
89
|
+
"""
|
|
90
|
+
if not self._zf:
|
|
91
|
+
raise RuntimeError("ZipBundle not open")
|
|
92
|
+
|
|
93
|
+
# Try direct path first
|
|
94
|
+
try:
|
|
95
|
+
return self._zf.read(name)
|
|
96
|
+
except KeyError:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
# Try with .d/ directory prefix
|
|
100
|
+
for item in self._zf.namelist():
|
|
101
|
+
if item.endswith(f"/{name}") or item == name:
|
|
102
|
+
return self._zf.read(item)
|
|
103
|
+
|
|
104
|
+
raise FileNotFoundError(f"File not found in ZIP: {name}")
|
|
105
|
+
|
|
106
|
+
def write_json(self, name: str, data: Dict[str, Any], indent: int = 2) -> None:
|
|
107
|
+
"""Write JSON file to ZIP.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
name: File name within ZIP.
|
|
111
|
+
data: Data to write.
|
|
112
|
+
indent: JSON indentation level.
|
|
113
|
+
"""
|
|
114
|
+
if not self._zf:
|
|
115
|
+
raise RuntimeError("ZipBundle not open")
|
|
116
|
+
|
|
117
|
+
json_str = json.dumps(data, indent=indent)
|
|
118
|
+
self._zf.writestr(name, json_str.encode("utf-8"))
|
|
119
|
+
|
|
120
|
+
def write_bytes(self, name: str, data: bytes) -> None:
|
|
121
|
+
"""Write raw bytes to ZIP.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
name: File name within ZIP.
|
|
125
|
+
data: Data to write.
|
|
126
|
+
"""
|
|
127
|
+
if not self._zf:
|
|
128
|
+
raise RuntimeError("ZipBundle not open")
|
|
129
|
+
|
|
130
|
+
self._zf.writestr(name, data)
|
|
131
|
+
|
|
132
|
+
def namelist(self) -> list:
|
|
133
|
+
"""List files in ZIP.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
List of file names.
|
|
137
|
+
"""
|
|
138
|
+
if not self._zf:
|
|
139
|
+
raise RuntimeError("ZipBundle not open")
|
|
140
|
+
return self._zf.namelist()
|
|
141
|
+
|
|
142
|
+
def exists(self, name: str) -> bool:
|
|
143
|
+
"""Check if file exists in ZIP.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
name: File name to check.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
True if file exists.
|
|
150
|
+
"""
|
|
151
|
+
if not self._zf:
|
|
152
|
+
raise RuntimeError("ZipBundle not open")
|
|
153
|
+
|
|
154
|
+
if name in self._zf.namelist():
|
|
155
|
+
return True
|
|
156
|
+
|
|
157
|
+
# Check with .d/ directory prefix
|
|
158
|
+
for item in self._zf.namelist():
|
|
159
|
+
if item.endswith(f"/{name}"):
|
|
160
|
+
return True
|
|
161
|
+
|
|
162
|
+
return False
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# EOF
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2025-12-20
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-code/src/scitex/fsb/_fig/__init__.py
|
|
4
|
+
|
|
5
|
+
"""FTS Figure - Encoding and Theme dataclasses."""
|
|
6
|
+
|
|
7
|
+
# Public dataclasses
|
|
8
|
+
from ._dataclasses import (
|
|
9
|
+
ENCODING_VERSION,
|
|
10
|
+
THEME_VERSION,
|
|
11
|
+
Encoding,
|
|
12
|
+
Theme,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"ENCODING_VERSION",
|
|
17
|
+
"THEME_VERSION",
|
|
18
|
+
"Encoding",
|
|
19
|
+
"Theme",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
# EOF
|