feat!: store the whole figpack bundle as the object - #8
MilagrosMarin wants to merge 3 commits into
Conversation
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.
show() served the bundle with Python's stock SimpleHTTPRequestHandler, which has no Range support. figpack packs many small chunks into large consolidated files and the viewer ranges into them, so a browser would have pulled whole files — the difference between a few kilobytes and a gigabyte. figpack's own server subclasses the stock handler to add exactly this; adding it here rather than importing theirs keeps the dependency out of the render path. The first version passed its test only because the sample figure is tiny and fetching index.html needs no range. The new test asks for a prefix range, a suffix range, and one past the end, and asserts 206 / Content-Range / 416. show()'s temp directory is now removed at exit rather than left behind. The dashboard path was never affected: dash-datajoint-components serves through Flask's send_from_directory, and Werkzeug handles Range.
Module, class and encode docstrings still said the codec stores Zarr folders, which described the old design. Also drops the FigpackView type import that removing load() orphaned.
|
Two more commits since the draft went up, from a second review pass.
Also: Five decisions I would rather you made than I did. 1. Version number. Breaking change on a 0.x line — 2. Changelog. This repo has none, and the version is tag-derived, so nothing in-tree records that the release breaks stored objects. Both consumers pin by git tag — the dashboard at 3. PyPI. 4. CI. Nothing runs the 36 tests. 5. The |
Closes #7. Also closes the design half of #4 and closes #3.
A figpack figure is the folder. Storing only
data.zarrand reassembling a viewer at render time made the stored object pure Zarr with afigpackmarker on it, and cost three things:figpackinstalledvalidate()refused them at insertserve_under()had to assume a layout figpack does not promise — a custom view may name its Zarr folder differently, or carry severalSettled with Jeremy Magland (Flatiron Institute) on 2026-09-04.
Changes
encode()value.save()produced, not a subtree of itvalidate()ExtensionView— extension JavaScript travels with the figureserve_under()import figpack, no reach into the privatefigpack-figure-distpath, no synthesized extension manifestshow()load()serve_under()'s layout guard moves fromdata.zarr/.zmetadatatoindex.html, which is what a servable bundle must have at its root. The concurrency race, orphan sweep, idempotence and TTL touch all still hold — they now apply to a downloaded bundle rather than an assembled one.Two decisions worth reviewing
load()is removed rather than left raising. #7 asked for a decision. Its contract was to reconstruct aFigpackViewfrom stored data, and that no longer describes what is stored.is_loadedwent with it: nothing caches a view any more, so it would have reported "not loaded" forever and__repr__would have said so.show()serves over HTTP. A figpack viewer fetches Zarr by range request, so openingindex.htmlfrom the filesystem cannot work. The server runs in a daemon thread on an ephemeral port and lives as long as the process — nothing to clean up, and concurrent shows cannot collide.Storage cost, deliberately accepted
About 2.2 MB per figure for the viewer dist, essentially one JS bundle. Jeremy's read, which #7 records: at realistic figure sizes the data dwarfs the viewer, and separating them only pays for roughly 1 MB figures in the millions. Content-addressed sharing stays an optimization layer to add later — never a change to what the object is.
Tests
Written first, against the old behavior, all five failing before the implementation:
encode()stores the whole bundlevalidate()accepts anExtensionViewserve_under()serves the stored bundle verbatim — the storedindex.htmlis marked, and the mark survives, proving no dist overlayserve_under()imports nofigpack—__import__is patched to raise on itPlus
show()over HTTP, and thatload()is gone. Suite 30 → 35, with the two obsolete rejection tests removed and six existing tests' premises inverted rather than deleted.Mutation-checked: reverting
encode()to store onlydata.zarrbreaks 12 tests; reverting the download to nest intodata.zarrbreaks 9.Net −184/+109 — most of this is deletion.
Breaking, and what follows
Figures written by earlier versions store bare Zarr and will not serve:
serve_under()fails loud with a message naming #7 rather than publishing a bundle with no entry point. Only demo pipelines hold figpack data, so reseed rather than convert.Draft until the sequence after it is agreed: release
0.3.0→ bump the dashboard pin → reseed thedj-inc_rna-sequencingdemo → then the blog's storage prose and the platform captures, which must come from the reseeded demo.Not in this PR, per #7: the
<htmlbundle@store>generalization gets its own issue after this lands.