From eff16e11e21d4f7a733c48379e65d5f078ca34f9 Mon Sep 17 00:00:00 2001 From: lorenzo Date: Thu, 12 Nov 2026 14:22:08 +0100 Subject: [PATCH] fix: derive_label/add_table on a read-only container raise a read-only error (#279) --- CHANGELOG.md | 4 ++++ src/ngio/hcs/_plate.py | 2 ++ src/ngio/images/_ome_zarr_container.py | 7 +++++++ tests/unit/hcs/test_plate_edge_cases.py | 2 ++ tests/unit/images/test_container_errors.py | 8 +++++++- 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa7ac2f7..1bd49118 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - Add `set_config()` (exported from `ngio` and `ngio.config`) to replace the global `NgioConfig` from code at any time. It replaces the previous configuration (a zarr setting forwarded earlier stays unless the new config sets it) and immediately applies the `s3fs` retry markers and the `zarr` section, so libraries can configure ngio without setting `NGIO_CONFIG_PATH` before `import ngio`. +### Fixes + +- `derive_label` and `add_table` on a container opened with `mode="r"` that has no `labels`/`tables` group yet raise `NgioValueError("The group is read only. Cannot derive a label.")` (or `... add a table.`) instead of `NgioValidationError("No labels found in the image.")`: the missing group could not be created, and the error said it did not exist. Same for `OmeZarrPlate.add_table`. Reading on such a container (`list_labels`, `labels_container`) still reports "No labels found" ([#279](https://github.com/BioVisionCenter/ngio/issues/279)). + ### Tests - The suite passes under pandas 3.1. The stores test helper no longer calls `set_index(inplace=True)` (deprecated, PDEP-8), and anndata's read of the deprecated `future.infer_string` option is ignored by message in `filterwarnings` until an anndata release carries the upstream fix ([scverse/anndata#2672](https://github.com/scverse/anndata/issues/2672)). The weekly pip CI was red on both jobs ([#266](https://github.com/BioVisionCenter/ngio/issues/266), [#267](https://github.com/BioVisionCenter/ngio/issues/267)). diff --git a/src/ngio/hcs/_plate.py b/src/ngio/hcs/_plate.py index 660ad131..9cf590b6 100644 --- a/src/ngio/hcs/_plate.py +++ b/src/ngio/hcs/_plate.py @@ -1002,6 +1002,8 @@ def add_table( If `backend` is `None` (default), the table's own backend is preserved. """ + if self._group_handler.read_only: + raise NgioValueError("The group is read only. Cannot add a table.") self.tables_container.add( name=name, table=table, backend=backend, overwrite=overwrite ) diff --git a/src/ngio/images/_ome_zarr_container.py b/src/ngio/images/_ome_zarr_container.py index 2e907915..94a73e9d 100644 --- a/src/ngio/images/_ome_zarr_container.py +++ b/src/ngio/images/_ome_zarr_container.py @@ -178,6 +178,11 @@ def refresh(self) -> None: self._tables_container = None self._tables_absent = False + def _require_writable(self, action: str) -> None: + """Raise before a write that needs a group this container cannot create.""" + if self._group_handler.read_only: + raise NgioValueError(f"The group is read only. Cannot {action}.") + @property def images_container(self) -> ImagesContainer: """Return the images container. @@ -820,6 +825,7 @@ def add_table( If `backend` is `None` (default), the table's own backend is preserved. """ + self._require_writable("add a table") self.tables_container.add( name=name, table=table, backend=backend, overwrite=overwrite ) @@ -992,6 +998,7 @@ def derive_label( Label: The new derived label. """ + self._require_writable("derive a label") if ref_image is None: ref_image = self.get_image() return self.labels_container.derive( diff --git a/tests/unit/hcs/test_plate_edge_cases.py b/tests/unit/hcs/test_plate_edge_cases.py index 00cb6c28..39b3384e 100644 --- a/tests/unit/hcs/test_plate_edge_cases.py +++ b/tests/unit/hcs/test_plate_edge_cases.py @@ -154,6 +154,8 @@ def test_tables_container_missing_on_readonly_plate(tmp_path: Path): assert plate.list_tables() == [] with pytest.raises(NgioValidationError, match="No tables found"): _ = plate.tables_container + with pytest.raises(NgioValueError, match="read only"): + plate.add_table("t", RoiTable(rois=[])) def test_typed_table_getters(tmp_path: Path): diff --git a/tests/unit/images/test_container_errors.py b/tests/unit/images/test_container_errors.py index 609575b1..689ffca3 100644 --- a/tests/unit/images/test_container_errors.py +++ b/tests/unit/images/test_container_errors.py @@ -202,5 +202,11 @@ def test_labels_missing_on_read_only_container(tmp_path: Path): # a read-only container without a labels group degrades gracefully assert container.list_labels() == [] - with pytest.raises(NgioValidationError): + with pytest.raises(NgioValidationError, match="No labels found"): _ = container.labels_container + + # a write names the real problem: the group cannot be created (#279) + with pytest.raises(NgioValueError, match="read only"): + container.derive_label("x") + with pytest.raises(NgioValueError, match="read only"): + container.add_table("t", RoiTable(rois=[]))