Skip to content

Commit

Permalink
Merge branch 'main' into ig/zarr_v3
Browse files Browse the repository at this point in the history
  • Loading branch information
ilan-gold authored Jan 24, 2025
2 parents ae18de3 + 3d0105b commit a7d3bf7
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.1
rev: v0.9.2
hooks:
- id: ruff
args: ["--fix"]
Expand Down
2 changes: 2 additions & 0 deletions docs/release-notes/999.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{data}`None` values can now be serialized to `.h5ad` and `.zarr`,
preserving e.g. {attr}`~anndata.AnnData.uns` structure through saving and loading {user}`flying-sheep`
24 changes: 24 additions & 0 deletions src/anndata/_io/specs/methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,30 @@ def write_raw(
_writer.write_elem(g, "varm", dict(raw.varm), dataset_kwargs=dataset_kwargs)


########
# Null #
########


@_REGISTRY.register_read(H5Array, IOSpec("null", "0.1.0"))
@_REGISTRY.register_read(ZarrArray, IOSpec("null", "0.1.0"))
def read_null(_elem, _reader) -> None:
return None


@_REGISTRY.register_write(H5Group, type(None), IOSpec("null", "0.1.0"))
def write_null_h5py(f, k, _v, _writer, dataset_kwargs=MappingProxyType({})):
f.create_dataset(k, data=h5py.Empty("f"), **dataset_kwargs)


@_REGISTRY.register_write(ZarrGroup, type(None), IOSpec("null", "0.1.0"))
def write_null_zarr(f, k, _v, _writer, dataset_kwargs=MappingProxyType({})):
import zarr

# zarr has no first-class null dataset
f.create_dataset(k, data=zarr.empty(()), **dataset_kwargs)


############
# Mappings #
############
Expand Down
3 changes: 0 additions & 3 deletions src/anndata/_io/specs/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,9 +336,6 @@ def write_elem(

dest_type = type(store)

if elem is None:
return lambda *_, **__: None

# Normalize k to absolute path
if (
(isinstance(store, ZarrGroup) and is_zarr_v2())
Expand Down
1 change: 1 addition & 0 deletions tests/test_io_elementwise.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ def create_sparse_store(
@pytest.mark.parametrize(
("value", "encoding_type"),
[
pytest.param(None, "null", id="none"),
pytest.param("hello world", "string", id="py_str"),
pytest.param(np.str_("hello world"), "string", id="np_str"),
pytest.param(np.array([1, 2, 3]), "array", id="np_arr_int"),
Expand Down
22 changes: 22 additions & 0 deletions tests/test_readwrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,28 @@ def test_adata_in_uns(tmp_path, diskfmt, roundtrip):
assert_equal(orig, curr)


@pytest.mark.parametrize(
"uns_val",
[
pytest.param(dict(base=None), id="dict_val"),
pytest.param(
pd.DataFrame(dict(col_0=["string", None])).convert_dtypes(), id="df"
),
],
)
def test_none_dict_value_in_uns(diskfmt, tmp_path, roundtrip, uns_val):
pth = tmp_path / f"adata_dtype.{diskfmt}"

orig = ad.AnnData(np.ones((3, 4)), uns=dict(val=uns_val))
with ad.settings.override(allow_write_nullable_strings=True):
curr = roundtrip(orig, pth)

if isinstance(orig.uns["val"], pd.DataFrame):
pd.testing.assert_frame_equal(curr.uns["val"], orig.uns["val"])
else:
assert curr.uns["val"] == orig.uns["val"]


def test_io_dtype(tmp_path, diskfmt, dtype, roundtrip):
pth = tmp_path / f"adata_dtype.{diskfmt}"

Expand Down

0 comments on commit a7d3bf7

Please sign in to comment.