Skip to content

Commit e801da3

Browse files
authored
Fixes for compatibility with anndata>=0.12.0 (#305)
* Unpin anndata version used for tests * Remove work around for "none" in uns tests Python anndata now stores an empty value instead of nothing * Add read_h5ad_null() helper Conserve dimensions for 1D arrays * Properly transpose in write_h5ad_string_array() * Add write_h5ad_null() helper * Removing skipping of "none" in uns roundtrip tests * Add anndataR.write_null option Allow disabling of writting null values for compatibility with anndata<0.12
1 parent 5a2f99b commit e801da3

5 files changed

Lines changed: 77 additions & 34 deletions

File tree

R/read_h5ad_helpers.R

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ read_h5ad_element <- function(
6464

6565
read_fun <- switch(
6666
type,
67+
"null" = read_h5ad_null,
6768
"array" = read_h5ad_dense_array,
6869
"rec-array" = read_h5ad_rec_array,
6970
"csr_matrix" = read_h5ad_csr_matrix,
@@ -100,6 +101,22 @@ read_h5ad_element <- function(
100101
)
101102
}
102103

104+
#' Read H5AD null
105+
#'
106+
#' Read a null value from an H5AD file
107+
#'
108+
#' @param file Path to a H5AD file or an open H5AD handle
109+
#' @param name Name of the element within the H5AD file
110+
#' @param version Encoding version of the element to read
111+
#'
112+
#' @return `NULL`
113+
#' @noRd
114+
read_h5ad_null <- function(file, name, version = "0.1.0") {
115+
version <- match.arg(version)
116+
117+
NULL
118+
}
119+
103120
#' Read H5AD dense array
104121
#'
105122
#' Read a dense array from an H5AD file
@@ -122,7 +139,7 @@ read_h5ad_dense_array <- function(file, name, version = "0.2.0") {
122139
dim(data) <- length(data)
123140
}
124141

125-
# transpose the matrix if need be
142+
# Transpose the matrix if need be
126143
if (is.matrix(data)) {
127144
data <- t(data)
128145
} else if (is.array(data) && length(dim(data)) > 1) {
@@ -131,8 +148,9 @@ read_h5ad_dense_array <- function(file, name, version = "0.2.0") {
131148

132149
# Reverse {rhdf5} coercion to factors
133150
if (is.factor(data) && all(levels(data) %in% c("TRUE", "FALSE"))) {
151+
dims <- dim(data)
134152
data <- as.logical(data)
135-
dim(data) <- length(data)
153+
dim(data) <- dims
136154
}
137155

138156
data

R/write_h5ad_helpers.R

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
#' Write an element to an H5AD file
88
#'
99
#' @param value The value to write
10-
#' @param file Path to a H5AD file or an open H5AD handle
10+
#' @param file An open H5AD handle
1111
#' @param name Name of the element within the H5AD file
1212
#' @param compression The compression to use when writing the element. Can be
1313
#' one of `"none"`, `"gzip"` or `"lzf"`. Defaults to `"none"`.
@@ -31,11 +31,11 @@ write_h5ad_element <- function(
3131
) {
3232
compression <- match.arg(compression)
3333

34-
# cli::cli_alert_info("Writing {.path {name}} with {.pkg rhdf5}")
35-
3634
# Sparse matrices
3735
write_fun <-
38-
if (inherits(value, "sparseMatrix")) {
36+
if (is.null(value)) {
37+
write_h5ad_null
38+
} else if (inherits(value, "sparseMatrix")) {
3939
# Sparse matrices
4040
write_h5ad_sparse_array
4141
} else if (is.factor(value)) {
@@ -148,12 +148,42 @@ write_h5ad_encoding <- function(file, name, encoding, version) {
148148
)
149149
}
150150

151+
#' Write H5AD null
152+
#'
153+
#' Write a null dataset to an H5AD file
154+
#'
155+
#' @param value Value to write, not used
156+
#' @param file An open H5AD handle
157+
#' @param name Name of the element within the H5AD file
158+
#' @param compression Not used as there is no value
159+
#' @param version Encoding version of the element to write
160+
#'
161+
#' @noRd
162+
write_h5ad_null <- function(value, file, name, compression, version = "0.1.0") {
163+
if (isFALSE(getOption("anndataR.write_null", "TRUE"))) {
164+
return(invisible(NULL))
165+
}
166+
167+
h5s <- rhdf5::H5Screate("H5S_NULL")
168+
on.exit(rhdf5::H5Sclose(h5s), add = TRUE)
169+
170+
h5d <- rhdf5::H5Dcreate(
171+
file,
172+
name = name,
173+
dtype_id = "H5T_IEEE_F32LE",
174+
h5space = h5s
175+
)
176+
on.exit(rhdf5::H5Dclose(h5d), add = TRUE)
177+
178+
write_h5ad_encoding(file, name, "null", version)
179+
}
180+
151181
#' Write H5AD dense array
152182
#'
153183
#' Write a dense array to an H5AD file
154184
#'
155185
#' @param value Value to write
156-
#' @param file Path to a H5AD file or an open H5AD handle
186+
#' @param file An open H5AD handle
157187
#' @param name Name of the element within the H5AD file
158188
#' @param compression The compression to use when writing the element. Can be
159189
#' one of `"none"`, `"gzip"` or `"lzf"`. Defaults to `"none"`.
@@ -222,7 +252,7 @@ write_h5ad_dense_array <- function(
222252
#' @noRd
223253
#'
224254
#' @param value Value to write
225-
#' @param file Path to a H5AD file or an open H5AD handle
255+
#' @param file An open H5AD handle
226256
#' @param name Name of the element within the H5AD file
227257
#' @param compression The compression to use when writing the element. Can be
228258
#' one of `"none"`, `"gzip"` or `"lzf"`. Defaults to `"none"`.
@@ -295,7 +325,7 @@ write_h5ad_sparse_array <- function(
295325
#' @noRd
296326
#'
297327
#' @param value Value to write
298-
#' @param file Path to a H5AD file or an open H5AD handle
328+
#' @param file An open H5AD handle
299329
#' @param name Name of the element within the H5AD file
300330
#' @param compression The compression to use when writing the element. Can be
301331
#' one of `"none"`, `"gzip"` or `"lzf"`. Defaults to `"none"`.
@@ -339,7 +369,7 @@ write_h5ad_nullable_boolean <- function(
339369
#' @noRd
340370
#'
341371
#' @param value Value to write
342-
#' @param file Path to a H5AD file or an open H5AD handle
372+
#' @param file An open H5AD handle
343373
#' @param name Name of the element within the H5AD file
344374
#' @param compression The compression to use when writing the element. Can be
345375
#' one of `"none"`, `"gzip"` or `"lzf"`. Defaults to `"none"`.
@@ -382,7 +412,7 @@ write_h5ad_nullable_integer <- function(
382412
#' @noRd
383413
#'
384414
#' @param value Value to write
385-
#' @param file Path to a H5AD file or an open H5AD handle
415+
#' @param file An open H5AD handle
386416
#' @param name Name of the element within the H5AD file
387417
#' @param compression The compression to use when writing the element. Can be
388418
#' one of `"none"`, `"gzip"` or `"lzf"`. Defaults to `"none"`.
@@ -394,6 +424,14 @@ write_h5ad_string_array <- function(
394424
compression,
395425
version = "0.2.0"
396426
) {
427+
if (!is.vector(value)) {
428+
if (is.matrix(value)) {
429+
value <- t(value)
430+
} else if (is.array(value)) {
431+
value <- aperm(value)
432+
}
433+
}
434+
397435
hdf5_write_dataset(
398436
file = file,
399437
name = name,
@@ -411,7 +449,7 @@ write_h5ad_string_array <- function(
411449
#' @noRd
412450
#'
413451
#' @param value Value to write
414-
#' @param file Path to a H5AD file or an open H5AD handle
452+
#' @param file An open H5AD handle
415453
#' @param name Name of the element within the H5AD file
416454
#' @param compression The compression to use when writing the element. Can be
417455
#' one of `"none"`, `"gzip"` or `"lzf"`. Defaults to `"none"`.
@@ -467,7 +505,7 @@ write_h5ad_categorical <- function(
467505
#' @noRd
468506
#'
469507
#' @param value Value to write
470-
#' @param file Path to a H5AD file or an open H5AD handle
508+
#' @param file An open H5AD handle
471509
#' @param name Name of the element within the H5AD file
472510
#' @param compression The compression to use when writing the element. Can be
473511
#' one of `"none"`, `"gzip"` or `"lzf"`. Defaults to `"none"`.
@@ -496,7 +534,7 @@ write_h5ad_string_scalar <- function(
496534
#' @noRd
497535
#'
498536
#' @param value Value to write
499-
#' @param file Path to a H5AD file or an open H5AD handle
537+
#' @param file An open H5AD handle
500538
#' @param name Name of the element within the H5AD file
501539
#' @param compression The compression to use when writing the element. Can be
502540
#' one of `"none"`, `"gzip"` or `"lzf"`. Defaults to `"none"`.
@@ -535,7 +573,7 @@ write_h5ad_numeric_scalar <- function(
535573
#' @noRd
536574
#'
537575
#' @param value Value to write
538-
#' @param file Path to a H5AD file or an open H5AD handle
576+
#' @param file An open H5AD handle
539577
#' @param name Name of the element within the H5AD file
540578
#' @param compression The compression to use when writing the element. Can be
541579
#' one of `"none"`, `"gzip"` or `"lzf"`. Defaults to `"none"`.
@@ -569,7 +607,7 @@ write_h5ad_mapping <- function(
569607
#' @noRd
570608
#'
571609
#' @param value Value to write
572-
#' @param file Path to a H5AD file or an open H5AD handle
610+
#' @param file An open H5AD handle
573611
#' @param name Name of the element within the H5AD file
574612
#' @param compression The compression to use when writing the element. Can be
575613
#' one of `"none"`, `"gzip"` or `"lzf"`. Defaults to `"none"`.

tests/testthat/helper-skip_if_no_anndata.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ skip_if_no_anndata <- function() {
44
testthat::skip_if_not_installed("reticulate")
55
testthat::skip_if_not_installed("anndata")
66
requireNamespace("reticulate")
7-
reticulate::py_require("anndata<=0.11.4")
7+
reticulate::py_require("anndata")
88
testthat::skip_if_not(
99
reticulate::py_module_available("anndata"),
1010
message = "Python anndata module not available for testing"

tests/testthat/test-roundtrip-uns-nested.R

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,9 @@ for (name in test_names) {
5454
skip_if(!is.null(msg), message = msg)
5555

5656
adata_r <- read_h5ad(file_py, as = "HDF5AnnData")
57-
nested_keys <- if (name == "none") {
58-
list()
59-
} else {
60-
names(adata_r$uns$nested)
61-
}
57+
6258
expect_equal(
63-
nested_keys,
59+
names(adata_r$uns$nested),
6460
bi$list(adata_py$uns$nested$keys())
6561
)
6662

@@ -101,8 +97,6 @@ for (name in test_names) {
10197
gc()
10298

10399
test_that(paste0("Writing an AnnData with uns_nested '", name, "' works"), {
104-
skip_if(name == "none", message = "No value to test for 'none'")
105-
106100
msg <- message_if_known(
107101
backend = "HDF5AnnData",
108102
slot = c("uns_nested"),

tests/testthat/test-roundtrip-uns.R

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,9 @@ for (name in test_names) {
5454
skip_if(!is.null(msg), message = msg)
5555

5656
adata_r <- read_h5ad(file_py, as = "HDF5AnnData")
57-
uns_keys <- if (name == "none") {
58-
list()
59-
} else {
60-
names(adata_r$uns)
61-
}
57+
6258
expect_equal(
63-
uns_keys,
59+
names(adata_r$uns),
6460
bi$list(adata_py$uns$keys())
6561
)
6662

@@ -74,7 +70,6 @@ for (name in test_names) {
7470
test_that(
7571
paste0("Comparing an anndata with uns '", name, "' with reticulate works"),
7672
{
77-
skip_if(name == "none", message = "No value to test for 'none'")
7873
msg <- message_if_known(
7974
backend = "HDF5AnnData",
8075
slot = c("uns"),
@@ -98,8 +93,6 @@ for (name in test_names) {
9893
gc()
9994

10095
test_that(paste0("Writing an AnnData with uns '", name, "' works"), {
101-
skip_if(name == "none", message = "No value to test for 'none'")
102-
10396
msg <- message_if_known(
10497
backend = "HDF5AnnData",
10598
slot = c("uns"),

0 commit comments

Comments
 (0)