pyadata-cli 0.5.0__tar.gz → 0.5.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/PKG-INFO +2 -2
  2. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/README.md +1 -1
  3. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/pyproject.toml +1 -1
  4. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/storage/__init__.py +62 -5
  5. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/LICENSE +0 -0
  6. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/__init__.py +0 -0
  7. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/cli.py +0 -0
  8. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/commands/__init__.py +0 -0
  9. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/commands/concat.py +0 -0
  10. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/commands/create.py +0 -0
  11. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/commands/export.py +0 -0
  12. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/commands/import_data.py +0 -0
  13. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/commands/info.py +0 -0
  14. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/commands/ls.py +0 -0
  15. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/commands/split.py +0 -0
  16. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/commands/subset.py +0 -0
  17. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/core/__init__.py +0 -0
  18. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/core/concat.py +0 -0
  19. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/core/info.py +0 -0
  20. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/core/query.py +0 -0
  21. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/core/read.py +0 -0
  22. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/core/select.py +0 -0
  23. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/core/subset.py +0 -0
  24. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/elements/__init__.py +0 -0
  25. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/elements/read.py +0 -0
  26. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/elements/spec.py +0 -0
  27. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/elements/strings.py +0 -0
  28. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/elements/write.py +0 -0
  29. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/formats/__init__.py +0 -0
  30. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/formats/array.py +0 -0
  31. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/formats/common.py +0 -0
  32. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/formats/dataframe.py +0 -0
  33. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/formats/image.py +0 -0
  34. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/formats/json_data.py +0 -0
  35. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/formats/sparse.py +0 -0
  36. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/formats/validate.py +0 -0
  37. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/info.py +0 -0
  38. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/py.typed +0 -0
  39. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/read.py +0 -0
  40. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/util/__init__.py +0 -0
  41. {pyadata_cli-0.5.0 → pyadata_cli-0.5.1}/src/adata/util/path.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyadata-cli
3
- Version: 0.5.0
3
+ Version: 0.5.1
4
4
  Summary: Streaming CLI for exploring and editing large AnnData .h5ad and .zarr stores
5
5
  Keywords: anndata,h5ad,zarr,bioinformatics,single-cell,cli
6
6
  Author: Aljes Binkevich
@@ -131,5 +131,5 @@ adata concat per_sample/*.h5ad -o merged.h5ad --join outer --label sample
131
131
  A docker image is available on QUAY: `quay.io/cellgeni/adata-cli:latest`. Pull and run with:
132
132
 
133
133
  ```bash
134
- docker run --rm -it -v /path/to/data:/data quay.io/cellgeni/adata-cli:latest view /data/your_file.h5ad
134
+ docker run --rm -it -v /path/to/data:/data quay.io/cellgeni/adata-cli:latest adata view /data/your_file.h5ad
135
135
  ```
@@ -91,5 +91,5 @@ adata concat per_sample/*.h5ad -o merged.h5ad --join outer --label sample
91
91
  A docker image is available on QUAY: `quay.io/cellgeni/adata-cli:latest`. Pull and run with:
92
92
 
93
93
  ```bash
94
- docker run --rm -it -v /path/to/data:/data quay.io/cellgeni/adata-cli:latest view /data/your_file.h5ad
94
+ docker run --rm -it -v /path/to/data:/data quay.io/cellgeni/adata-cli:latest adata view /data/your_file.h5ad
95
95
  ```
@@ -3,7 +3,7 @@
3
3
  # so the distribution is published as `pyadata-cli`. The import package
4
4
  # and the command are both still `adata`.
5
5
  name = "pyadata-cli"
6
- version = "0.5.0"
6
+ version = "0.5.1"
7
7
  description = "Streaming CLI for exploring and editing large AnnData .h5ad and .zarr stores"
8
8
  readme = "README.md"
9
9
  requires-python = ">=3.12"
@@ -440,12 +440,69 @@ def _is_string_src(src: Any) -> bool:
440
440
  return is_string_dtype(getattr(src, "dtype", None))
441
441
 
442
442
 
443
- def _chunk_step(shape: Sequence[int], chunks: Optional[Sequence[int]]) -> int:
444
- if chunks is not None and len(chunks) > 0 and chunks[0]:
445
- return int(chunks[0])
443
+ #: Byte budget for a single read when streaming a dataset.
444
+ #:
445
+ #: Using a source's chunk height as the read size means inheriting whatever
446
+ #: the writer chose, and a store chunked `(1, n_cols)` is then copied one row
447
+ #: per read. On a local disk that is merely wasteful; on a network filesystem
448
+ #: (Lustre, NFS) every read is a round-trip costing milliseconds, so a
449
+ #: million-row copy spends nearly all of its time waiting. 32 MiB sits
450
+ #: comfortably above a typical 1 MiB Lustre stripe while bounding peak memory.
451
+ TARGET_READ_BYTES = 32 * 1024 * 1024
452
+
453
+ #: Assumed width of a variable-length string element, for read sizing only.
454
+ #: h5py reports `itemsize` 8 for vlen strings because the value is a pointer,
455
+ #: which would overestimate the row count by an order of magnitude and break
456
+ #: the memory bound. Real cell and gene names sit well under this.
457
+ VLEN_ELEMENT_BYTES = 64
458
+
459
+
460
+ def _row_bytes(shape: Sequence[int], dtype: Any) -> int:
461
+ """In-memory size of one row along the first axis."""
462
+ width = 1
463
+ for dim in shape[1:]:
464
+ width *= max(1, int(dim))
465
+
466
+ itemsize = int(getattr(dtype, "itemsize", 0) or 0)
467
+ # 'O' is how h5py spells vlen str, 'T' is numpy StringDType as reported by
468
+ # zarr-python 3. Neither itemsize reflects the bytes actually stored.
469
+ if itemsize <= 0 or getattr(dtype, "kind", None) in ("O", "T"):
470
+ itemsize = VLEN_ELEMENT_BYTES
471
+
472
+ return max(1, width * itemsize)
473
+
474
+
475
+ def _chunk_step(
476
+ shape: Sequence[int], chunks: Optional[Sequence[int]], dtype: Any
477
+ ) -> int:
478
+ """Rows to copy per read, sized for the filesystem rather than the source.
479
+
480
+ The step is grown to `TARGET_READ_BYTES`, then rounded down to a whole
481
+ number of source chunks: reading part of a chunk still costs decompressing
482
+ all of it, so a step that splits one wastes the remainder.
483
+ """
446
484
  if not shape:
447
485
  return 1
448
- return max(1, min(1024, int(shape[0])))
486
+
487
+ n_rows = int(shape[0])
488
+ if n_rows <= 0:
489
+ return 1
490
+
491
+ chunk_rows = 0
492
+ if chunks is not None and len(chunks) > 0 and chunks[0]:
493
+ chunk_rows = max(1, int(chunks[0]))
494
+
495
+ step = max(1, TARGET_READ_BYTES // _row_bytes(shape, dtype))
496
+ if chunk_rows:
497
+ # Never go below a single chunk: a partial read still decompresses the
498
+ # whole thing, so a smaller step costs the same I/O for less data. That
499
+ # makes TARGET_READ_BYTES a target rather than a cap -- a source whose
500
+ # own chunk already exceeds the budget (say 1000 x 1e6 float32, chunked
501
+ # whole) reads that chunk regardless. This matches the previous
502
+ # behaviour, which used the chunk height verbatim.
503
+ step = max(chunk_rows, (step // chunk_rows) * chunk_rows)
504
+
505
+ return min(step, n_rows)
449
506
 
450
507
 
451
508
  def copy_dataset(src: Any, dst_group: Any, name: str) -> Any:
@@ -475,7 +532,7 @@ def copy_dataset(src: Any, dst_group: Any, name: str) -> Any:
475
532
  ds[()] = src[()]
476
533
  return ds
477
534
 
478
- step = _chunk_step(shape, getattr(src, "chunks", None))
535
+ step = _chunk_step(shape, getattr(src, "chunks", None), src.dtype)
479
536
  for start in range(0, shape[0], step):
480
537
  end = min(start + step, shape[0])
481
538
  if len(shape) == 1:
File without changes