==================== File-Based Archivers ==================== :py:class:`~herostools.actor.archiver.ArrayArchiver`, :py:class:`~herostools.actor.archiver.ZarrArchiver`, and :py:class:`~herostools.actor.archiver.JsonArchiver` all write payloads to the local filesystem. The output path (or store path) for each event is resolved at runtime by rendering a `Jinja2 `_ template against the merged metadata. Jinja2 path templates --------------------- Every file-based archiver accepts one or more template strings. Templates are rendered against a metadata dict that is the merge of ``default_metadata`` (static defaults set at configuration time) and the per-event metadata received with each payload. ``default_metadata`` values act as fallbacks; per-event values take precedence on key collision. Any Python expression valid inside a Jinja2 ``{{ }}`` block can be used, including arithmetic and filter pipelines: .. code:: text {{ file_path }}/shot-{{ '%04d' % (frame / 2) | round(0, 'floor') }}-{{ frame % 2 }}.npy This produces paths such as:: /mnt/storage/shot-0000-0.npy /mnt/storage/shot-0000-1.npy /mnt/storage/shot-0001-0.npy when ``file_path`` is ``/mnt/storage`` and ``frame`` is a running integer supplied by the event source. Parent directories are created automatically if they do not exist. ArrayArchiver ------------- Saves each numpy array payload as a ``.npy`` file. **Parameters** ``save_template`` Jinja2 template rendered to the output file path. ``split_data_array`` *(default: False)* When ``True``, each row of a 2-D array is written as a separate file. The special variable ``{{ _split_index }}`` is injected into the metadata before rendering so that row number can appear in the filename. **BOSS configuration example** .. code:: json { "_id": "my-camera-capturer", "classname": "herostools.actor.archiver.ArrayArchiver", "arguments": { "object_selector": "my-camera", "event_name": "acquisition_data", "default_metadata": { "file_path": "/mnt/storage/images" }, "save_template": "{{ file_path }}/frame-{{ '%04d' % frame }}.npy" } } ZarrArchiver ------------ Stores numpy array payloads inside a `zarr `_ directory store. A zarr store is a directory (e.g. ``shot_0001.zarr/``) — not a single file. Its internal structure (chunk files, ``.zarray`` metadata) is managed by zarr and should be treated as opaque. The store is opened in append mode (``'a'``) so successive writes to the same store path do not erase existing arrays. **Parameters** ``store_template`` Jinja2 template rendered to the zarr store directory path. ``array_path_template`` Jinja2 template rendered to the array key within the store. Nested keys (e.g. ``sensors/spectrum``) create a group hierarchy inside the store. **BOSS configuration example** .. code:: json { "_id": "my-zarr-archiver", "classname": "herostools.actor.archiver.ZarrArchiver", "arguments": { "object_selector": "my-instrument", "event_name": "frame_data", "default_metadata": { "store_root": "/mnt/storage/zarr" }, "store_template": "{{ store_root }}/{{ run_id }}.zarr", "array_path_template": "{{ identifier }}/data" } } With ``run_id = "run_01"`` and ``identifier = "shot_001"`` this writes the array to ``/mnt/storage/zarr/run_01.zarr/shot_001/data``. To read the array back: .. code:: python import zarr group = zarr.open_group("/mnt/storage/zarr/run_01.zarr", mode="r") arr = group["shot_001/data"][:] JsonArchiver ------------ Saves each dict payload as a ``.json`` file. **Parameters** ``save_template`` Jinja2 template rendered to the output file path. ``merge_metadata`` *(default: False)* When ``True``, the metadata dict is embedded in the saved JSON under the key ``"metadata"``. **BOSS configuration example** .. code:: json { "_id": "my-json-archiver", "classname": "herostools.actor.archiver.JsonArchiver", "arguments": { "object_selector": "my-device", "event_name": "result_data", "default_metadata": { "output_dir": "/mnt/storage/results" }, "save_template": "{{ output_dir }}/{{ identifier }}.json", "merge_metadata": true } }