From e94e6cd01b2576cda74868002e3c15c07c495ebc Mon Sep 17 00:00:00 2001 From: MilagrosMarin Date: Wed, 16 Sep 2026 14:21:50 +0200 Subject: [PATCH 1/3] feat!: store the whole figpack bundle as the object MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A figpack figure is the folder. Storing only data.zarr and reassembling a viewer at render time made the stored object pure Zarr with a figpack marker on it, and cost three things: the serving container needed a compatible figpack installed, extension views had nowhere to keep their JavaScript and were refused at insert, and serve_under() had to assume a layout figpack does not promise — a custom view may name its Zarr folder differently or carry several. Settled with Jeremy Magland (Flatiron Institute) on 2026-09-04. Closes #7, the design half of #4, and #3. - encode() uploads the bundle value.save() produced, not a subtree of it. - validate() no longer rejects ExtensionView; extension JavaScript now travels with the figure. A round-trip test proves the JS survives. - serve_under() downloads and publishes the stored bundle. No viewer overlay, no import figpack, no reach into the private figpack-figure-dist path, no synthesized extension manifest. Its guard moves from data.zarr/.zmetadata to index.html, which is what a servable bundle must have at its root. - show() serves the bundle over HTTP on an ephemeral port and returns the URL. A figpack viewer fetches Zarr chunks by range request, so file:// cannot work. - load() is removed. Its contract was to reconstruct a FigpackView from stored data, which no longer describes what is stored; is_loaded went with it, since nothing caches a view any more. Storage grows by the viewer dist, about 2.2 MB per figure. Deliberately accepted: at realistic figure sizes the data dwarfs it, and content-addressed sharing is an optimization layer to add later if a pipeline shows the small-figure, high-count profile — never a change to what the object is. Breaking: figures written by earlier versions store bare Zarr and will not serve. Only demo pipelines hold figpack data; reseed rather than convert. --- README.md | 27 +++--- src/dj_figpack_codecs/codec.py | 29 +++--- src/dj_figpack_codecs/ref.py | 130 +++++++++++---------------- tests/test_bundle_storage.py | 160 +++++++++++++++++++++++++++++++++ tests/test_codec.py | 67 ++------------ tests/test_config_threading.py | 2 +- tests/test_serve_under.py | 38 ++++---- 7 files changed, 269 insertions(+), 184 deletions(-) create mode 100644 tests/test_bundle_storage.py diff --git a/README.md b/README.md index 173e161..21e29a1 100644 --- a/README.md +++ b/README.md @@ -69,11 +69,8 @@ ref = (RasterPlot & key).fetch1('visualization') print(ref.title) # "Spike Raster" print(ref.description) # "Unit activity over time" -# Display in browser +# Serve it over HTTP and open it in a browser ref.show() - -# Or load the full FigpackView -view = ref.load() ``` ### Jupyter Integration @@ -86,7 +83,11 @@ ref # Shows title, description, and action hints ### Storage Structure -Visualizations are stored as Zarr folders under a **schema-addressed path chosen by the +A figpack figure is its bundle — viewer, data and extension manifest together — and the +whole folder is what gets stored. That is what lets a figure be served without `figpack` +installed, and what gives an extension view somewhere to keep its JavaScript. + +Bundles are stored under a **schema-addressed path chosen by the framework** (DataJoint's `build_object_path`): it mirrors the schema/table structure, encodes primary keys as `attr=value` segments, and ends in a tokenized filename (`{attribute}_{token}.zarr`), subject to the store's prefix/partitioning configuration — @@ -96,6 +97,10 @@ for example: {store_location}/demo_showcase/fluorescence_figpack/session_id=4/fig_NPhczfGY.zarr/ ``` +The stored folder holds `index.html`, `assets/`, `data.zarr/` and +`extension_manifest.json` — exactly what `FigpackView.save()` produced. Treat it as +opaque: a custom view may name its data folder differently or carry several. + The layout is browsable but framework-owned — do not hand-build or rely on exact paths; the database column's metadata (`path`, `store`) is the source of truth. @@ -114,18 +119,16 @@ Lazy reference returned when fetching `` attributes. - `description` - Visualization description (no I/O) - `path` - Storage path - `store` - Store name -- `is_loaded` - Whether data has been cached **Methods:** -- `load()` - Download and return the `FigpackView` -- `show(**kwargs)` - Download and display in browser -- `serve_under(base_dir)` - Materialize a servable viewer bundle; returns its relative URL +- `show(open_browser=True)` - Serve the figure over HTTP; returns its URL +- `serve_under(base_dir)` - Publish the stored bundle; returns its relative URL ### Serving a figure in a dashboard -`FigpackRef.serve_under(base_dir)` materializes a self-contained, servable viewer -bundle (figpack's viewer + the stored `data.zarr`) under `base_dir//` and returns -the relative URL `//index.html`. Dashboards (e.g. dash-datajoint-components' +`FigpackRef.serve_under(base_dir)` downloads the stored bundle into `base_dir//` +and returns the relative URL `//index.html`. Nothing is assembled and `figpack` +need not be installed in the serving process. Dashboards (e.g. dash-datajoint-components' `PlotGrid`) serve `base_dir` over HTTP and embed the URL in an `