diff --git a/.github/scripts/test_sgkit_bgen.py b/.github/scripts/test_sgkit_bgen.py deleted file mode 100644 index 1d0a334c0..000000000 --- a/.github/scripts/test_sgkit_bgen.py +++ /dev/null @@ -1,11 +0,0 @@ -import urllib.request - -from sgkit.io.bgen import read_bgen - -if __name__ == "__main__": - urllib.request.urlretrieve( - "https://github.com/sgkit-dev/sgkit/raw/main/sgkit/tests/io/bgen/data/example.bgen", - "example.bgen", - ) - ds = read_bgen("example.bgen") - print(ds) diff --git a/.github/workflows/build-gpu.yml b/.github/workflows/build-gpu.yml index fcb021b89..d9f30a0a2 100644 --- a/.github/workflows/build-gpu.yml +++ b/.github/workflows/build-gpu.yml @@ -49,7 +49,7 @@ jobs: run: | conda install -c nvidia cudatoolkit # Avoid building SciPy from source in CI. - uv pip install --only-binary scipy --python "$CONDA_PREFIX/bin/python" -e ".[bgen,plink]" --group test + uv pip install --only-binary scipy --python "$CONDA_PREFIX/bin/python" -e ".[plink]" --group test - name: Numba Information run: | diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b3c21f06c..e688b7587 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,14 +41,11 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} - test-zarr-version: - name: Test Zarr Python v3 + test-xarray-minimum: + name: Test minimum Xarray # Scheduled runs only on the origin org if: (github.event_name == 'schedule' && github.repository_owner == 'sgkit-dev') || (github.event_name != 'schedule') runs-on: ubuntu-latest - strategy: - matrix: - zarr: [">=3"] steps: - uses: actions/checkout@v7 - uses: actions/setup-python@v7 @@ -63,10 +60,9 @@ jobs: - name: Install dependencies run: | uv sync --locked --all-extras --group test --no-default-groups --python 3.11 - - name: Install zarr${{ matrix.zarr }} + - name: Install minimum supported Xarray run: | - uv pip install --python .venv --pre 'zarr${{ matrix.zarr }}' - uv pip uninstall --python .venv bio2zarr # TODO: remove when bio2zarr supports Zarr Python 3 + uv pip install --python .venv 'xarray==2026.9.0' - name: Run tests run: | uv run --no-sync pytest diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 8450fc622..b725e1420 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -81,9 +81,8 @@ jobs: # Avoid building SciPy from source in CI. wheel=$(ls artifact/sgkit-*.whl) uv venv --python "${{ matrix.python-version }}" - uv pip install --python .venv --only-binary scipy "${wheel}[bgen,plink]" + uv pip install --python .venv --only-binary scipy "${wheel}[plink]" uv run --no-sync python sgkit-copy/.github/scripts/test_sgkit.py - uv run --no-sync python sgkit-copy/.github/scripts/test_sgkit_bgen.py uv run --no-sync python sgkit-copy/.github/scripts/test_sgkit_plink.py windows-test: @@ -120,9 +119,8 @@ jobs: # Avoid building SciPy from source in CI. $wheel = (Get-ChildItem artifact/sgkit-*.whl).FullName uv venv --python "${{ matrix.python-version }}" - uv pip install --python .venv --only-binary scipy "${wheel}[bgen,plink]" + uv pip install --python .venv --only-binary scipy "${wheel}[plink]" uv run --no-sync python sgkit-copy/.github/scripts/test_sgkit.py - uv run --no-sync python sgkit-copy/.github/scripts/test_sgkit_bgen.py uv run --no-sync python sgkit-copy/.github/scripts/test_sgkit_plink.py diff --git a/README.md b/README.md index d27828743..89ee63c08 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,14 @@ general-purpose frameworks such as [Xarray](http://xarray.pydata.org/en/stable/) For more information on using sgkit, see the [documentation](https://sgkit-dev.github.io/sgkit/). +**Genetic file conversion:** Use [bio2zarr](https://sgkit-dev.github.io/bio2zarr/) +to convert genetic file formats to Zarr, then load them with `sgkit.load_dataset`. +The BGEN reader, conversion functions and `bgen` extra have been removed; +sgkit no longer depends on cbgen. Use a bio2zarr version with BGEN support +(tracked in [bio2zarr issue 417](https://github.com/sgkit-dev/bio2zarr/issues/417)). +See the [BGEN migration guide](docs/user_guide.rst#bgen) for details and +`unpack_variables`, retained only for existing legacy packed Zarr stores. + [//]: # (numfocus-fiscal-sponsor-attribution) The sgkit project uses a [custom governance model](./GOVERNANCE.md) diff --git a/docs/api.rst b/docs/api.rst index 512a3ccda..85a8c519b 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -9,16 +9,24 @@ IO/imports and exports See :ref:`reading_and_writing_genetic_data` +.. important:: + + **Use bio2zarr for genetic file format conversion to Zarr**, then load the + converted dataset with :func:`sgkit.load_dataset`. The BGEN reader and + conversion functions have been removed. See :ref:`bgen_conversion_migration` + for migration and legacy-store unpacking guidance. + BGEN ----- +Only the helper for existing legacy packed stores remains; it does not read +BGEN files or require an optional backend. + .. currentmodule:: sgkit.io.bgen .. autosummary:: :toctree: generated/ - bgen_to_zarr - read_bgen - rechunk_bgen + unpack_variables PLINK ----- diff --git a/docs/changelog.rst b/docs/changelog.rst index 91b574ec8..923727779 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -11,19 +11,36 @@ Changelog .. New Features .. ~~~~~~~~~~~~ -.. Breaking changes -.. ~~~~~~~~~~~~~~~~ +Breaking changes +~~~~~~~~~~~~~~~~ + +- Require Zarr Python >= 3 and Xarray >= 2026.9.0. The default on-disk Zarr + format remains 2. (:issue:`1355`) +- **Remove BGEN reading and conversion**: ``sgkit.io.bgen.read_bgen``, + ``sgkit.io.bgen.bgen_to_zarr`` and ``sgkit.io.bgen.rechunk_bgen``, along with + the ``bgen`` extra and the cbgen and rechunker dependencies. + **Use bio2zarr for genetic file format conversion to Zarr**, and load the + result with :func:`load_dataset`. BGEN input requires a bio2zarr version + with BGEN support + (`bio2zarr issue 417 `_). + The dependency-free :func:`sgkit.io.bgen.unpack_variables` helper remains + for existing legacy packed stores. See :ref:`bgen_conversion_migration`. + (:pr:`1325`, :issue:`1355`) Deprecations ~~~~~~~~~~~~ +- BGEN reading and conversion have moved out of sgkit. Use + `bio2zarr `_ for BGEN-to-Zarr conversion + and :func:`load_dataset` for analysis. Unlike the deprecated PLINK functions + below, ``read_bgen``, ``bgen_to_zarr`` and ``rechunk_bgen`` are already removed + in this release; see Breaking changes above and + :ref:`bgen_conversion_migration`. Only the dependency-free + :func:`sgkit.io.bgen.unpack_variables` helper remains for legacy packed stores. + (:pr:`1325`, :issue:`1331`, :issue:`1355`) - Functions for reading and writing plink are deprecated, please use the `bio2zarr `_ and `vcztools `_ packages. (:user:`tomwhite`, :pr:`1324`, :issue:`1318`) -- Using rechunker for rechunking in sgkit is deprecated, please use the the - `Xarray chunk `_ - function directly. - (:user:`tomwhite`, :pr:`1325`) .. Improvements .. ~~~~~~~~~~~~ @@ -31,6 +48,12 @@ Deprecations Bug fixes ~~~~~~~~~ +- Unpacking legacy BGEN-derived Zarr datasets no longer imports or requires + cbgen. Use :func:`sgkit.io.bgen.unpack_variables`; the original + ``sgkit.io.bgen.bgen_reader.unpack_variables`` import is also preserved. + Its documentation explains the legacy packing convention and when unpacking + is needed; ordinary unpacked datasets and new bio2zarr output do not need it. + (:issue:`1331`) - Fixed bug causing high memory usage in :func:`pedigree_kinship` when evaluating a chunked relationship matrix with the Hamilton-Kerr method. (:user:`timothymillar`, :pr:`1335`, :issue:`1333`) diff --git a/docs/conf.py b/docs/conf.py index a71360b68..eead605b9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -117,7 +117,11 @@ def filter(self, record: pylogging.LogRecord) -> bool: autosummary_generate = True -nitpick_ignore = [("py:class", "sgkit.display.GenotypeDisplay")] +nitpick_ignore = [ + ("py:class", "sgkit.display.GenotypeDisplay"), + # Zarr's StoreLike alias is rendered as an undocumented TypeAliasType. + ("py:class", "zarr.storage._common.TypeAliasType"), +] # FIXME: Workaround for linking xarray module @@ -130,6 +134,7 @@ def filter(self, record: pylogging.LogRecord) -> bool: intersphinx_mapping = dict( dask=("https://docs.dask.org/en/stable/", None), + fsspec=("https://filesystem-spec.readthedocs.io/en/latest/", None), xarray=("https://docs.xarray.dev/en/stable/", None), zarr=("https://zarr.readthedocs.io/en/stable", None), numpy=("https://numpy.org/doc/stable/", None), diff --git a/docs/user_guide.rst b/docs/user_guide.rst index d5b246980..9f8e079b5 100644 --- a/docs/user_guide.rst +++ b/docs/user_guide.rst @@ -13,24 +13,32 @@ User Guide Reading and writing genetic data ================================ +.. important:: + + **Use bio2zarr for genetic file format conversion to Zarr**, then use + :func:`sgkit.load_dataset` for analysis in sgkit. See the + `bio2zarr documentation `_ for + installation and conversion instructions. + + BGEN reading and conversion have been removed from sgkit, along with the + optional cbgen dependency. See :ref:`bgen_conversion_migration` for conversion + guidance and support for existing legacy packed stores. + .. _installation: Installation ------------ -Sgkit can read standard genetic file formats, including PLINK and BGEN. For reading VCF, -please use the `bio2zarr `_ package. +Sgkit reads Zarr datasets directly. For VCF, BGEN and PLINK conversion, use +`bio2zarr `_. The deprecated direct PLINK +reader remains available with optional dependencies. -If sgkit has been installed using conda, support for reading BGEN and PLINK is included. +If sgkit has been installed using conda, support for reading PLINK is included. -If sgkit has been installed using pip, then support for reading these formats is +If sgkit has been installed using pip, then support for reading PLINK is not included, and requires additional dependencies, which can be installed as an "extra" feature using pip, as follows. -To install sgkit with BGEN support:: - - $ pip install 'sgkit[bgen]' - To install sgkit with PLINK support:: $ pip install 'sgkit[plink]' @@ -38,18 +46,12 @@ To install sgkit with PLINK support:: Converting genetic data to Zarr ------------------------------- -There are broadly two ways of loading genetic file format ``X`` for use in sgkit: - -1. a ``read_X`` function to read the file directly -2. a ``X_to_zarr`` function to convert to Zarr first - -Which one to use depends on the size of the input, as well as the file format (not all file -formats have both options). - -Generally speaking, the ``read_X`` functions are convenience functions that are suitable -for small input files, or where the cost of conversion is small. The ``X_to_zarr`` functions -are more appropriate for large datasets, since the expensive conversion step need only be -paid once. +Genetic file format conversion belongs in +`bio2zarr `__, rather than sgkit. +Convert inputs with bio2zarr before analysis, so the expensive conversion step +need only be paid once. BGEN input requires a bio2zarr version with BGEN support +(see :ref:`bgen_conversion_migration`). The remaining direct PLINK functions +are deprecated convenience functions for small inputs. After converting a file to Zarr format, it can be loaded with sgkit's :func:`sgkit.load_dataset`. This is like Xarray's :func:`xarray.open_zarr` function, with some useful defaults applied for you. @@ -58,22 +60,76 @@ Similarly, the function :func:`sgkit.save_dataset` can be used to save the datas This can be used to save a newly-loaded dataset, or a dataset with new variables that are the result of running genetic methods on the data, so it can be loaded again later. -By default, Zarr datasets created by sgkit (and Xarray) have `consolidated metadata `_, +Sgkit requires Zarr Python >= 3 and Xarray >= 2026.9.0, but +:func:`sgkit.save_dataset` continues to write **Zarr format 2** by default. +The Python package version does not determine the on-disk format. +Pass ``zarr_format=3`` to opt into format 3; :func:`sgkit.load_dataset` +can read either format. +The Xarray minimum includes support for the native NumPy string dtype returned +by recent Zarr releases, preserving string-array chunking on load. + +By default, Zarr format 2 datasets created by sgkit have `consolidated metadata `_, which makes opening the dataset faster. +.. _bgen_conversion_migration: + BGEN ---- -The :func:`sgkit.io.bgen.read_bgen` function loads a single BGEN dataset as Dask -arrays within an :class:`xarray.Dataset` from a ``bgen`` file. +.. warning:: + + ``sgkit.io.bgen.read_bgen``, ``sgkit.io.bgen.bgen_to_zarr`` and + ``sgkit.io.bgen.rechunk_bgen`` have been removed, together with the ``bgen`` + extra. Sgkit no longer reads BGEN files or depends on cbgen. + +Use bio2zarr for BGEN conversion, then load the resulting Zarr dataset with +:func:`sgkit.load_dataset`. Install a bio2zarr version that includes BGEN support; +availability is tracked in +`bio2zarr issue 417 `_. +An older bio2zarr release without BGEN support cannot replace the removed reader. +Sgkit does not provide an alternative BGEN conversion path. -The :func:`sgkit.io.bgen.bgen_to_zarr` function converts a ``bgen`` file to Zarr -files stored in sgkit's Xarray data representation, which can then be opened -as a :class:`xarray.Dataset`. +Legacy packed stores +~~~~~~~~~~~~~~~~~~~~ + +Some older sgkit BGEN converters packed variables to reduce storage size: +they saved only the heterozygous and homozygous-alternate probabilities, +omitted dosage, and used one missingness flag per variant/sample pair. +For these **known legacy packed stores only**, use +:func:`sgkit.io.bgen.unpack_variables` after loading and before methods that +expect complete genotype probabilities or dosage. This helper operates on an +existing Xarray dataset and needs no BGEN backend:: + + import sgkit as sg + from sgkit.io.bgen import unpack_variables + + packed = sg.load_dataset("legacy-packed.zarr") + ds = unpack_variables(packed) + +The expected signature is ``call_genotype_probability`` with dimensions +``(variants, samples, genotypes)`` and **two** stored probabilities, plus +``call_genotype_probability_mask`` with dimensions ``(variants, samples)``. +Unpacking restores three diploid, biallelic genotype probabilities, computes +alternate-allele dosage, and expands the probability mask. For example, +``[0.25, 0.5]`` becomes ``[0.25, 0.25, 0.5]`` with dosage ``1.25``. +Missing probabilities remain NaN. The input is unchanged and Dask arrays +remain lazy; lost quantization precision cannot be recovered. + +**Do not unpack ordinary datasets, new bio2zarr output, or an already-unpacked +store.** A two-state probability array alone is not evidence of legacy packing: +other ploidies or allele counts can also produce two states. The helper is not +a general BGEN decoder or schema adapter. :func:`sgkit.load_dataset` does not +automatically detect or unpack legacy stores. +The original ``sgkit.io.bgen.bgen_reader.unpack_variables`` import remains +supported as a compatibility alias; that module no longer contains a reader. PLINK ----- +Use `bio2zarr `__ for PLINK-to-Zarr +conversion and `vcztools `_ for export. +The following sgkit PLINK functions are deprecated. + The :func:`sgkit.io.plink.read_plink` function loads a single PLINK dataset as Dask arrays within an :class:`xarray.Dataset` from ``bed``, ``bim``, and ``fam`` files. diff --git a/pyproject.toml b/pyproject.toml index 8dde5ed1d..a0cd46283 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,10 +22,10 @@ requires-python = ">=3.11" dependencies = [ "numpy >= 2.3.5; sys_platform == \"darwin\"", # Apple ARM chip error "numpy >= 2.0; sys_platform != \"darwin\"", - "xarray < 2025.03.1", + "xarray >= 2026.9.0", "dask[array,dataframe] >= 2022.01.0, <= 2024.8.0", "scipy < 1.16", - "zarr >= 2.10.0, != 2.11.0, != 2.11.1, != 2.11.2, < 3", + "zarr >= 3", "numba", "fsspec != 2021.6.*", "scikit-learn", @@ -41,7 +41,6 @@ content-type = "text/x-rst" [project.optional-dependencies] plink = ["partd", "bed-reader"] -bgen = ["rechunker", "cbgen > 1.0.5"] [dependency-groups] test = [ @@ -55,14 +54,14 @@ test = [ "scikit-allel", "statsmodels", "msprime>=1.0", - "bio2zarr[vcf]; platform_system != \"Windows\"", + "bio2zarr[vcf] >= 0.2; platform_system != \"Windows\"", "yarl", "matplotlib", "networkx", "aiohttp", "requests", "graphviz", - "sgkit[plink,bgen]", + "sgkit[plink]", ] docs = [ "graphviz", @@ -189,14 +188,6 @@ ignore_missing_imports = true module = ["sklearn.*"] ignore_missing_imports = true -[[tool.mypy.overrides]] -module = ["cbgen.*"] -ignore_missing_imports = true - -[[tool.mypy.overrides]] -module = ["rechunker.*"] -ignore_missing_imports = true - [[tool.mypy.overrides]] module = ["bed_reader.*"] ignore_missing_imports = true diff --git a/sgkit/io/bgen/__init__.py b/sgkit/io/bgen/__init__.py index 8d57ccd82..abaaba5bd 100644 --- a/sgkit/io/bgen/__init__.py +++ b/sgkit/io/bgen/__init__.py @@ -1,11 +1,3 @@ -try: - from .bgen_reader import bgen_to_zarr, read_bgen, rechunk_bgen +from .utils import unpack_variables - __all__ = ["read_bgen", "bgen_to_zarr", "rechunk_bgen"] -except ImportError as e: # pragma: no cover - msg = ( - "sgkit bgen requirements are not installed.\n\n" - "Please install them via pip :\n\n" - " pip install 'sgkit[bgen]'" - ) - raise ImportError(str(e) + "\n\n" + msg) from e +__all__ = ["unpack_variables"] diff --git a/sgkit/io/bgen/bgen_reader.py b/sgkit/io/bgen/bgen_reader.py index 6fa44bfe3..72857bc8d 100644 --- a/sgkit/io/bgen/bgen_reader.py +++ b/sgkit/io/bgen/bgen_reader.py @@ -1,629 +1,9 @@ -"""BGEN reader implementation (using bgen_reader)""" -import logging -import tempfile -import time -import warnings -from pathlib import Path -from typing import ( - Any, - Dict, - Hashable, - List, - Mapping, - MutableMapping, - Optional, - Tuple, - Union, -) +"""Compatibility import for unpacking legacy stores; BGEN reading was removed. -import dask -import dask.array as da -import dask.dataframe as dd -import numcodecs -import numpy as np -import pandas as pd -import xarray as xr -import zarr -from cbgen import bgen_file, bgen_metafile -from rechunker import api as rechunker_api -from xarray import Dataset +Use bio2zarr to convert BGEN inputs and :func:`sgkit.load_dataset` to load Zarr. +New code should import :func:`sgkit.io.bgen.unpack_variables` directly. +""" -from sgkit import create_genotype_dosage_dataset, variables -from sgkit.io.utils import dataframe_to_dict, encode_contigs -from sgkit.typing import ArrayLike, DType, NDArray, PathType +from .utils import unpack_variables -logger = logging.getLogger(__name__) - -GT_DATA_VARS = [ - variables.call_genotype_probability, - variables.call_genotype_probability_mask, - variables.call_dosage, - variables.call_dosage_mask, -] - -METAFILE_DTYPE = dict( - [ - ("id", "S"), - ("rsid", "S"), - ("chrom", "S"), - ("pos", "int32"), - ("a1", "S"), - ("a2", "S"), - ("offset", "int64"), - ] -) - - -class BgenReader: - name = "bgen_reader" - - def __init__( - self, - path: PathType, - metafile_path: Optional[PathType] = None, - dtype: DType = "float32", - ) -> None: - self.path = Path(path) - self.metafile_path = ( - Path(metafile_path) if metafile_path else self.path.with_suffix(".metafile") - ) - - with bgen_file(self.path) as bgen: - self.n_variants = bgen.nvariants - self.n_samples = bgen.nsamples - - if not self.metafile_path.exists(): - start = time.time() - logger.info( - f"Generating BGEN metafile for '{self.path}' (this may take a while)" - ) - bgen.create_metafile(self.metafile_path, verbose=False) - stop = time.time() - logger.info( - f"BGEN metafile generation complete ({stop - start:.0f} seconds)" - ) - - with bgen_metafile(self.metafile_path) as mf: - assert self.n_variants == mf.nvariants - self.npartitions = mf.npartitions - self.partition_size = mf.partition_size - - self.shape = (self.n_variants, self.n_samples, 3) - self.dtype = np.dtype(dtype) - self.precision = 64 if self.dtype.itemsize >= 8 else 32 - self.ndim = 3 - - def __getitem__(self, idx: Any) -> NDArray: - if not isinstance(idx, tuple): - raise IndexError(f"Indexer must be tuple (received {type(idx)})") - if len(idx) != self.ndim: - raise IndexError( - f"Indexer must have {self.ndim} items (received {len(idx)} slices)" - ) - if not all(isinstance(i, slice) or isinstance(i, int) for i in idx): - raise IndexError( - f"Indexer must contain only slices or ints (received types {[type(i) for i in idx]})" - ) - # Determine which dims should have unit size in result - squeeze_dims = tuple(i for i in range(len(idx)) if isinstance(idx[i], int)) - # Convert all indexers to slices - idx = tuple(slice(i, i + 1) if isinstance(i, int) else i for i in idx) - - if idx[0].start == idx[0].stop: - return np.empty((0,) * self.ndim, dtype=self.dtype) - - # Determine start and end partitions that correspond to the - # given variant dimension indexer - start_partition = idx[0].start // self.partition_size - start_partition_offset = idx[0].start % self.partition_size - end_partition = (idx[0].stop - 1) // self.partition_size - end_partition_offset = (idx[0].stop - 1) % self.partition_size - - # Create a list of all offsets into the underlying file at which - # data for each variant begins - all_vaddr = [] - with bgen_metafile(self.metafile_path) as mf: - for i in range(start_partition, end_partition + 1): - partition = mf.read_partition(i) - start_offset = start_partition_offset if i == start_partition else 0 - end_offset = ( - end_partition_offset + 1 - if i == end_partition - else self.partition_size - ) - vaddr = partition.variants.offset - all_vaddr.extend(vaddr[start_offset:end_offset].tolist()) - - # Read the probabilities for each variant, apply indexer for - # samples dimension to give probabilities for all genotypes, - # and then apply final genotype dimension indexer - with bgen_file(self.path) as bgen: - res = None - for i, vaddr in enumerate(all_vaddr): - probs = bgen.read_probability(vaddr, precision=self.precision)[idx[1]] - assert len(probs.shape) == 2 and probs.shape[1] == 3 - if res is None: - res = np.zeros((len(all_vaddr), len(probs), 3), dtype=self.dtype) - res[i] = probs - res = res[..., idx[2]] # type: ignore[index] - return np.squeeze(res, axis=squeeze_dims) # type: ignore[arg-type] - - -def _split_alleles(allele_ids: bytes) -> List[bytes]: - alleles = allele_ids.split(b",") - if len(alleles) != 2: - raise NotImplementedError( - f"Bgen reads only supported for biallelic variants (found non-biallelic variant '{str(allele_ids)}')" - ) - return alleles - - -def _read_metafile_partition(path: Path, partition: int) -> pd.DataFrame: - with bgen_metafile(path) as mf: - part = mf.read_partition(partition) - v = part.variants - allele_ids = np.array([_split_alleles(aid) for aid in v.allele_ids]) - data = { - "id": v.id, - "rsid": v.rsid, - "chrom": v.chromosome, - "pos": v.position, - "a1": allele_ids[:, 0], - "a2": allele_ids[:, 1], - "offset": v.offset, - } - return pd.DataFrame(data).astype(METAFILE_DTYPE) - - -def read_metafile(path: PathType) -> dd.DataFrame: - """Read cbgen metafile containing partitioned variant info""" - with bgen_metafile(path) as mf: - divisions = [mf.partition_size * i for i in range(mf.npartitions)] + [ - mf.nvariants - 1 - ] - dfs = [ - dask.delayed(_read_metafile_partition)(path, i) - for i in range(mf.npartitions) - ] - meta = dd.utils.make_meta(METAFILE_DTYPE) - return dd.from_delayed(dfs, meta=meta, divisions=divisions, verify_meta=False) - - -def read_samples(path: PathType) -> pd.DataFrame: - """Read BGEN .sample file""" - df = pd.read_csv(path, sep=" ", skiprows=[1], usecols=[0]) - df.columns = ["sample_id"] - return df - - -def read_bgen( - path: PathType, - metafile_path: Optional[PathType] = None, - sample_path: Optional[PathType] = None, - chunks: Union[str, int, Tuple[int, int, int]] = "auto", - lock: bool = False, - persist: bool = True, - contig_dtype: DType = "str", - gp_dtype: DType = "float32", -) -> Dataset: - """Read BGEN dataset. - - Loads a single BGEN dataset as dask arrays within a Dataset - from a ``.bgen`` file. - - Parameters - ---------- - path - Path to BGEN file. - metafile_path - Path to companion index file used to determine BGEN byte offsets. - Defaults to ``path`` + ".metafile" if not provided. - This file is necessary for reading BGEN genotype probabilities and it will be - generated the first time the file is read if it does not already exist. - If it needs to be created, it can make the first call to this function - much slower than subsequent calls. - sample_path - Path to ``.sample`` file, by default None. This is used to fetch sample identifiers - and when provided it is preferred over sample identifiers embedded in the ``.bgen`` file. - chunks - Chunk size for genotype probability data (3 dimensions), - by default "auto". - lock - Whether or not to synchronize concurrent reads of - file blocks, by default False. This is passed through to - [dask.array.from_array](https://docs.dask.org/en/latest/array-api.html#dask.array.from_array). - persist - Whether or not to persist variant information in memory, by default True. - This is an important performance consideration as the metadata file for this data will - be read multiple times when False. - contig_dtype - Data type for contig names, by default "str". - This may also be an integer type (e.g. "int"), but will fail if any of the contig names - cannot be converted to integers. - gp_dtype - Data type for genotype probabilities, by default "float32". - - Warnings - -------- - Only bi-allelic, diploid BGEN files are currently supported. - - Returns - ------- - A dataset containing the following variables: - - - :data:`sgkit.variables.variant_id_spec` (variants) - - :data:`sgkit.variables.variant_contig_spec` (variants) - - :data:`sgkit.variables.variant_position_spec` (variants) - - :data:`sgkit.variables.variant_allele_spec` (variants) - - :data:`sgkit.variables.sample_id_spec` (samples) - - :data:`sgkit.variables.call_dosage_spec` (variants, samples) - - :data:`sgkit.variables.call_dosage_mask_spec` (variants, samples) - - :data:`sgkit.variables.call_genotype_probability_spec` (variants, samples, genotypes) - - :data:`sgkit.variables.call_genotype_probability_mask_spec` (variants, samples, genotypes) - - """ - if isinstance(chunks, tuple) and len(chunks) != 3: - raise ValueError(f"`chunks` must be tuple with 3 items, not {chunks}") - if not np.issubdtype(gp_dtype, np.floating): - raise ValueError( - f"`gp_dtype` must be a floating point data type, not {gp_dtype}" - ) - if not np.issubdtype(contig_dtype, np.integer) and np.dtype( - contig_dtype - ).kind not in {"U", "S"}: - raise ValueError( - f"`contig_dtype` must be of string or int type, not {contig_dtype}" - ) - - with dask.config.set({"dataframe.convert-string": False}): - path = Path(path) - sample_path = Path(sample_path) if sample_path else path.with_suffix(".sample") - - if sample_path.exists(): - sample_id = read_samples(sample_path).sample_id.values.astype("U") - else: - sample_id = _default_sample_ids(path) - - bgen_reader = BgenReader(path, metafile_path=metafile_path, dtype=gp_dtype) - - df = read_metafile(bgen_reader.metafile_path) - if persist: - df = df.persist() - arrs = dataframe_to_dict(df, METAFILE_DTYPE) - - variant_id = arrs["id"] - variant_contig: ArrayLike = arrs["chrom"].astype(contig_dtype) - variant_contig, variant_contig_names = encode_contigs(variant_contig) - variant_contig_names = list(variant_contig_names) - variant_position = arrs["pos"] - variant_allele = da.hstack( - (arrs["a1"][:, np.newaxis], arrs["a2"][:, np.newaxis]) - ) - - call_genotype_probability = da.from_array( - bgen_reader, - chunks=chunks, - lock=lock, - fancy=False, - asarray=False, - name=f"{bgen_reader.name}:read_bgen:{path}", - ) - call_dosage = _to_dosage(call_genotype_probability) - - ds: Dataset = create_genotype_dosage_dataset( - variant_contig_names=variant_contig_names, - variant_contig=variant_contig, - variant_position=variant_position, - variant_allele=variant_allele, - sample_id=sample_id, - call_dosage=call_dosage, - call_genotype_probability=call_genotype_probability, - variant_id=variant_id, - ) - - return ds - - -def _default_sample_ids(path: PathType) -> ArrayLike: - """Fetch or generate sample ids""" - with bgen_file(path) as bgen: - if bgen.contain_samples: - return bgen.read_samples() - else: - return np.char.add(b"sample_", np.arange(bgen.nsamples).astype("S")) # type: ignore[no-untyped-call] - - -def _to_dosage(probs: ArrayLike) -> ArrayLike: - """Calculate the dosage from genotype likelihoods (probabilities)""" - assert ( - probs.shape[-1] == 3 - ), f"Expecting genotype (trailing) dimension of size 3, got array of shape {probs.shape}" - return probs[..., 1] + 2 * probs[..., 2] - - -######################## -# Rechunking Functions # -######################## - - -def encode_variables( - ds: Dataset, - chunk_length: int, - chunk_width: int, - compressor: Optional[Any] = numcodecs.Blosc(cname="zstd", clevel=7, shuffle=2), - probability_dtype: Optional[Any] = "uint8", -) -> Dict[Hashable, Dict[str, Any]]: - encoding = {} - for v in ds: - e = {} - if compressor is not None: - e.update({"compressor": compressor}) - if v in GT_DATA_VARS: - e.update({"chunks": (chunk_length, chunk_width) + ds[v].shape[2:]}) - if probability_dtype is not None and v == "call_genotype_probability": - dtype = np.dtype(probability_dtype) - # Xarray will decode into float32 so any int greater than - # 16 bits will cause overflow/underflow - # See https://en.wikipedia.org/wiki/Floating-point_arithmetic#Internal_representation - # *bits precision column for single precision floats - if dtype not in [np.uint8, np.uint16]: # type: ignore[comparison-overlap] - raise ValueError( - "Probability integer dtype invalid, must " - f"be uint8 or uint16 not {probability_dtype}" - ) - divisor = np.iinfo(dtype).max - 1 - e.update( - { - "dtype": probability_dtype, - "add_offset": -1.0 / divisor, - "scale_factor": 1.0 / divisor, - "_FillValue": 0, - } - ) - if e: - encoding[v] = e - return encoding - - -def pack_variables(ds: Dataset) -> Dataset: - # Remove dosage as it is unnecessary and should be redefined - # based on encoded probabilities later (w/ reduced precision) - ds = ds.drop_vars(["call_dosage", "call_dosage_mask"], errors="ignore") - - # Remove homozygous reference GP and redefine mask - gp = ds["call_genotype_probability"][..., 1:] - gp_mask = ds["call_genotype_probability_mask"].any(dim="genotypes") - ds = ds.drop_vars(["call_genotype_probability", "call_genotype_probability_mask"]) - ds = ds.assign(call_genotype_probability=gp, call_genotype_probability_mask=gp_mask) - return ds - - -def unpack_variables(ds: Dataset, dtype: DType = "float32") -> Dataset: - # Restore homozygous reference GP - gp = ds["call_genotype_probability"].astype(dtype) - if gp.sizes["genotypes"] != 2: - raise ValueError( - "Expecting variable 'call_genotype_probability' to have genotypes " - f"dimension of size 2 (received sizes = {dict(gp.sizes)})" - ) - ds = ds.drop_vars("call_genotype_probability") - ds["call_genotype_probability"] = xr.concat( - [1 - gp.sum(dim="genotypes", skipna=False), gp], dim="genotypes" - ) - - # Restore dosage - ds["call_dosage"] = gp[..., 0] + 2 * gp[..., 1] - ds["call_dosage_mask"] = ds["call_genotype_probability_mask"] - ds["call_genotype_probability_mask"] = ds[ - "call_genotype_probability_mask" - ].broadcast_like(ds["call_genotype_probability"]) - return ds - - -def rechunk_bgen( - ds: Dataset, - output: Union[PathType, MutableMapping[str, bytes]], - *, - chunk_length: int = 10_000, - chunk_width: int = 1_000, - compressor: Optional[Any] = numcodecs.Blosc(cname="zstd", clevel=7, shuffle=2), - probability_dtype: Optional[DType] = "uint8", - max_mem: str = "4GB", - pack: bool = True, - tempdir: Optional[PathType] = None, -) -> Dataset: - """Rechunk BGEN dataset as Zarr. - - This function will use the algorithm https://rechunker.readthedocs.io/en/latest/ - to rechunk certain fields in a provided Dataset for better downstream performance. - Depending on the system memory available (and the `max_mem` setting) this - rechunking may occur without the need of any intermediate data store. Otherwise, - approximately as much disk space is required as was needed to store the original - BGEN data. Experiments show that this Zarr representation is ~20% larger even - with all available optimizations and fairly aggressive compression (i.e. the - default `clevel` 7). - - Note that this function is not evaluated lazily. The rechunking algorithm - will run inline so calls to it may be slow. The resulting Dataset is - generated based on the final, serialized Zarr data. - - .. deprecated:: 0.11.0 - Using rechunker for rechunking in sgkit is deprecated, please use the the - `Xarray chunk `_ - function directly. - - Parameters - ---------- - ds - Dataset to rechunk, typically the result from `read_bgen`. - output - Zarr store or path to directory in file system. - chunk_length - Length (number of variants) of chunks in which data are stored, by default 10_000. - chunk_width - Width (number of samples) to use when storing chunks in output, by default 1_000. - compressor - Zarr compressor, no compression is used when set as None. - probability_dtype - Data type used to encode genotype probabilities, must be either uint8 or uint16. - Setting this parameter results in a loss of precision. If None, probabilities - will not be altered when stored. - max_mem - The amount of memory (in bytes) that workers are allowed to use. A string - (e.g. 100MB) can also be used. - pack - Whether or not to optimize variable representations by removing unnecessary - dimensions and elements. This includes storing 2 genotypes instead of 3, omitting - dosage and collapsing the genotype probability mask to 2 dimensions. All of - the above are restored in the resulting Dataset at the expense of extra - computations on read. - tempdir - Temporary directory where intermediate files are stored. The default None means - use the system default temporary directory. - - Warnings - -------- - - This functional is only applicable to diploid, bi-allelic BGEN datasets. - - Returns - ------- - - Dataset - The rechunked dataset. - """ - warnings.warn( - "Using rechunker for rechunking in sgkit is deprecated, please use the the Xarray 'chunk' function directly.", - DeprecationWarning, - stacklevel=2, - ) - - if isinstance(output, Path): - output = str(output) - - chunk_length = min(chunk_length, ds.sizes["variants"]) - chunk_width = min(chunk_width, ds.sizes["samples"]) - - if pack: - ds = pack_variables(ds) - - encoding = encode_variables( - ds, - chunk_length=chunk_length, - chunk_width=chunk_width, - compressor=compressor, - probability_dtype=probability_dtype, - ) - target_chunks = { - var: encoding[var]["chunks"] for var in encoding if "chunks" in encoding[var] - } - target_options = { - var: {k: v for k, v in encoding[var].items() if k != "chunks"} - for var in encoding - } - with tempfile.TemporaryDirectory( - prefix="bgen_to_zarr_", suffix=".zarr", dir=tempdir - ) as tmpdir: - rechunked = rechunker_api.rechunk( - ds, - max_mem=max_mem, - target_chunks=target_chunks, - target_store=output, - target_options=target_options, - temp_store=tmpdir, - executor="dask", - ) - rechunked.execute() - - zarr.consolidate_metadata(output) - - ds = xr.open_zarr(output, concat_characters=False) # type: ignore[no-untyped-call] - if pack: - ds = unpack_variables(ds) - - return ds - - -def bgen_to_zarr( - input: PathType, - output: Union[PathType, MutableMapping[str, bytes]], - region: Optional[Mapping[Hashable, Any]] = None, - chunk_length: int = 10_000, - chunk_width: int = 1_000, - temp_chunk_length: int = 100, - compressor: Optional[Any] = numcodecs.Blosc(cname="zstd", clevel=7, shuffle=2), - probability_dtype: Optional[DType] = "uint8", - max_mem: str = "4GB", - pack: bool = True, - tempdir: Optional[PathType] = None, -) -> Dataset: - """Convert a BGEN file to a Zarr on-disk store. - - This function is a convenience for calling :func:`read_bgen` followed by - :func:`rechunk_bgen`. - - Parameters - ---------- - input - Path to local BGEN dataset. - output - Zarr store or path to directory in file system. - region - Indexers on dataset dimensions used to define a subset of data to convert. - Must be None or a dict with keys matching dimension names and values - equal to integers or slice objects. This is passed directly to `Dataset.isel` - so it has the same semantics. - chunk_length - Length (number of variants) of chunks in which data are stored, by default 10_000. - chunk_width - Width (number of samples) to use when storing chunks in output, by default 1_000. - temp_chunk_length - Length of chunks used in raw BGEN read, by default 100. This defines the vertical - chunking (i.e. in the variants dimension) used when reading the raw data and because - there is no horizontal chunking at this phase (i.e. in the samples dimension), this - value should be much smaller than the target `chunk_length`. - compressor - Zarr compressor, by default Blosc + zstd with compression level 7. No compression - is used when set as None. - probability_dtype - Data type used to encode genotype probabilities, must be either uint8 or uint16. - Setting this parameter results in a loss of precision. If None, probabilities - will not be altered when stored. - max_mem - The amount of memory (in bytes) that workers are allowed to use. A string - (e.g. 100MB) can also be used. - pack - Whether or not to optimize variable representations by removing unnecessary - dimensions and elements. This includes storing 2 genotypes instead of 3, omitting - dosage and collapsing the genotype probability mask to 2 dimensions. All of - the above are restored in the resulting Dataset at the expense of extra - computations on read. - tempdir - Temporary directory where intermediate files are stored. The default None means - use the system default temporary directory. - - Warnings - -------- - - This functional is only applicable to diploid, bi-allelic BGEN datasets. - - Returns - ------- - - Dataset - The rechunked dataset. - """ - ds = read_bgen(input, chunks=(temp_chunk_length, -1, -1)) - if region is not None: - ds = ds.isel(indexers=region) - return rechunk_bgen( - ds, - output, - chunk_length=chunk_length, - chunk_width=chunk_width, - compressor=compressor, - probability_dtype=probability_dtype, - max_mem=max_mem, - pack=pack, - tempdir=tempdir, - ) +__all__ = ["unpack_variables"] diff --git a/sgkit/io/bgen/utils.py b/sgkit/io/bgen/utils.py new file mode 100644 index 000000000..bb042dea1 --- /dev/null +++ b/sgkit/io/bgen/utils.py @@ -0,0 +1,88 @@ +import xarray as xr +from xarray import Dataset + +from sgkit.typing import DType + + +def unpack_variables(ds: Dataset, dtype: DType = "float32") -> Dataset: + """Restore probabilities and dosages from a legacy packed BGEN-derived dataset. + + Use this only for stores written with variable packing by sgkit's removed + ``bgen_to_zarr`` or ``rechunk_bgen`` functions. Packing saved space by + omitting the homozygous-reference probability, storing a single missingness + flag per call, and omitting dosage (which can be derived from probabilities). + Unpack such a dataset before using methods that expect full genotype + probabilities or dosage. + + This is not a BGEN reader, a general probability decoder, or a required step + after loading Zarr. Do not apply it to ordinary unpacked datasets or new + bio2zarr output. A ``genotypes`` dimension of length two is meaningful here + only for this legacy diploid, biallelic packing convention, not for haploid + or multiallelic data. + + Parameters + ---------- + ds + Legacy packed dataset with ``call_genotype_probability`` dimensions + ``(variants, samples, genotypes)``, where ``genotypes`` has length two + and stores the heterozygous and homozygous-alternate probabilities, + in that order. ``call_genotype_probability_mask`` must have dimensions + ``(variants, samples)`` and be True for missing calls; the stored + probabilities for those calls are NaN. + dtype + Floating-point dtype for restored probabilities and dosage, by default + float32. This cannot recover precision lost during legacy quantization. + + Returns + ------- + Dataset + New dataset with three genotype probabilities ordered as + homozygous-reference, heterozygous, homozygous-alternate; alternate-allele + ``call_dosage``; and their masks. Other variables and attributes are + preserved. The input dataset is not modified. + + Raises + ------ + ValueError + If the stored ``genotypes`` dimension does not have length two. An + already-unpacked dataset must not be unpacked a second time. + + Notes + ----- + The omitted probability is reconstructed as ``1 - p_het - p_hom_alt``. + Dosage is ``p_het + 2 * p_hom_alt``. Missing probabilities remain NaN, + and the per-call mask is broadcast across all three genotypes. No hard + genotype calls are inferred. Operations remain lazy for Dask-backed arrays + and require no optional BGEN backend. + + Examples + -------- + Load a known legacy packed store and unpack it once before analysis:: + + import sgkit as sg + from sgkit.io.bgen import unpack_variables + + packed = sg.load_dataset("legacy-packed.zarr") + ds = unpack_variables(packed) + + For example, stored probabilities ``[0.25, 0.5]`` become + ``[0.25, 0.25, 0.5]`` and alternate-allele dosage ``1.25``. Loading with + :func:`sgkit.load_dataset` alone does not perform this reconstruction. + """ + gp = ds["call_genotype_probability"].astype(dtype) + if gp.sizes["genotypes"] != 2: + raise ValueError( + "Expecting variable 'call_genotype_probability' to have genotypes " + f"dimension of size 2 (received sizes = {dict(gp.sizes)})" + ) + ds = ds.drop_vars("call_genotype_probability") + ds["call_genotype_probability"] = xr.concat( + [1 - gp.sum(dim="genotypes", skipna=False), gp], dim="genotypes" + ) + + ds["call_dosage"] = gp[..., 0] + 2 * gp[..., 1] + ds["call_dosage_mask"] = ds["call_genotype_probability_mask"] + ds["call_genotype_probability_mask"] = ds[ + "call_genotype_probability_mask" + ].broadcast_like(ds["call_genotype_probability"]) + return ds diff --git a/sgkit/io/dataset.py b/sgkit/io/dataset.py index 4f5226168..514007202 100644 --- a/sgkit/io/dataset.py +++ b/sgkit/io/dataset.py @@ -1,16 +1,14 @@ -from typing import Any, Dict, MutableMapping, Optional, Union +from typing import Any, Dict, Optional import numcodecs import xarray as xr from xarray import Dataset - -from sgkit.typing import PathType -from sgkit.utils import has_keyword +from zarr.storage import StoreLike def save_dataset( ds: Dataset, - store: Union[PathType, MutableMapping[str, bytes]], + store: StoreLike, storage_options: Optional[Dict[str, str]] = None, auto_rechunk: Optional[bool] = None, zarr_format: int = 2, @@ -32,6 +30,9 @@ def save_dataset( auto_rechunk: If True, automatically rechunk the dataset to uniform chunks before saving, if necessary. This is required for Zarr, but can be expensive. Defaults to False. + zarr_format: + On-disk Zarr format, either 2 or 3. Defaults to 2 for compatibility with + existing datasets, even though sgkit requires Zarr Python 3. kwargs Additional arguments to pass to :meth:`xarray.Dataset.to_zarr`. """ @@ -41,13 +42,14 @@ def save_dataset( # Workaround for https://github.com/pydata/xarray/issues/4380 ds[v].encoding.pop("chunks", None) - # Remove VLenUTF8 from filters to avoid double encoding error https://github.com/pydata/xarray/issues/3476 - filters = ds[v].encoding.get("filters", None) - var_len_str_codec = numcodecs.VLenUTF8() - if filters is not None and var_len_str_codec in filters: - filters = list(filters) - filters.remove(var_len_str_codec) - ds[v].encoding["filters"] = filters + # Xarray writes native NumPy strings as fixed-width Unicode in format 2. + # A VLenUTF8 filter inherited from a variable-length array is then invalid. + dtype = ds[v].encoding.get("dtype", ds[v].dtype) + filters = ds[v].encoding.get("filters") + if getattr(dtype, "kind", None) == "T" and filters is not None: + ds[v].encoding["filters"] = [ + f for f in filters if f != numcodecs.VLenUTF8() + ] if auto_rechunk: # This logic for checking if rechunking is necessary is @@ -66,9 +68,9 @@ def save_dataset( # Catch unequal chunking errors to provide a more helpful error message try: - if has_keyword(ds.to_zarr, "zarr_format"): # from xarray v2024.10.0 - kwargs["zarr_format"] = zarr_format - ds.to_zarr(store, storage_options=storage_options, **kwargs) + ds.to_zarr( + store, storage_options=storage_options, zarr_format=zarr_format, **kwargs + ) except ValueError as e: if "Zarr requires uniform chunk sizes" in str( e @@ -82,7 +84,7 @@ def save_dataset( def load_dataset( - store: Union[PathType, MutableMapping[str, bytes]], + store: StoreLike, storage_options: Optional[Dict[str, str]] = None, **kwargs: Any, ) -> Dataset: diff --git a/sgkit/io/plink/plink_reader.py b/sgkit/io/plink/plink_reader.py index b6da1c012..63d6d5ee1 100644 --- a/sgkit/io/plink/plink_reader.py +++ b/sgkit/io/plink/plink_reader.py @@ -1,6 +1,6 @@ """PLINK 1.9 reader implementation""" from pathlib import Path -from typing import Any, Dict, List, MutableMapping, Optional, Tuple, Union +from typing import Any, Dict, List, Optional, Tuple, Union import dask import dask.array as da @@ -9,6 +9,7 @@ from bed_reader import open_bed from dask.dataframe import DataFrame from xarray import Dataset +from zarr.storage import StoreLike from sgkit import create_genotype_call_dataset, save_dataset from sgkit.io.utils import dataframe_to_dict @@ -317,7 +318,7 @@ def plink_to_zarr( bed_path: Optional[PathType] = None, bim_path: Optional[PathType] = None, fam_path: Optional[PathType] = None, - output: Union[PathType, MutableMapping[str, bytes]], + output: StoreLike, chunks: Union[str, int, tuple] = "auto", fam_sep: str = " ", bim_sep: str = "\t", diff --git a/sgkit/io/plink/plink_writer.py b/sgkit/io/plink/plink_writer.py index 42939b492..6c380b930 100644 --- a/sgkit/io/plink/plink_writer.py +++ b/sgkit/io/plink/plink_writer.py @@ -1,8 +1,9 @@ -from typing import MutableMapping, Optional, Union +from typing import Optional import numpy as np from bed_reader import to_bed from xarray import Dataset +from zarr.storage import StoreLike from sgkit import load_dataset from sgkit.accelerate import numba_guvectorize @@ -137,7 +138,7 @@ def write_plink( def zarr_to_plink( - input: Union[PathType, MutableMapping[str, bytes]], + input: StoreLike, *, path: Optional[PathType] = None, bed_path: Optional[PathType] = None, diff --git a/sgkit/io/utils.py b/sgkit/io/utils.py index 49f2df8ec..48e6206c1 100644 --- a/sgkit/io/utils.py +++ b/sgkit/io/utils.py @@ -1,10 +1,10 @@ -from typing import Mapping, Optional, Tuple +from typing import Mapping, Optional import dask.dataframe as dd import numpy as np from ..typing import ArrayLike, DType -from ..utils import encode_array, max_str_len +from ..utils import max_str_len INT_MISSING, INT_FILL = -1, -2 @@ -37,16 +37,3 @@ def dataframe_to_dict( dt = f"{kind}{max_len}" arrs[c] = a.astype(dt) return arrs - - -def encode_contigs(contig: ArrayLike) -> Tuple[ArrayLike, ArrayLike]: - # TODO: test preservation of int16 - # If contigs are already integers, use them as-is - if np.issubdtype(contig.dtype, np.integer): - ids = contig - names = np.unique(np.asarray(ids)).astype(str) # type: ignore[no-untyped-call] - # Otherwise create index for contig names based - # on order of appearance in underlying file - else: - ids, names = encode_array(np.asarray(contig, dtype=str)) - return ids, names diff --git a/sgkit/stats/conversion.py b/sgkit/stats/conversion.py index 3d2ebf529..ce5aaeeb9 100644 --- a/sgkit/stats/conversion.py +++ b/sgkit/stats/conversion.py @@ -137,7 +137,8 @@ def convert_probability_to_call( Parameters ---------- ds - Dataset containing genotype probabilities, such as from :func:`sgkit.io.bgen.read_bgen`. + Dataset containing diploid biallelic genotype probabilities. For legacy + packed BGEN-derived stores, first use :func:`sgkit.io.bgen.unpack_variables`. call_genotype_probability Genotype probability variable to be converted as defined by :data:`sgkit.variables.call_genotype_probability_spec`. diff --git a/sgkit/tests/io/bgen/__init__.py b/sgkit/tests/io/bgen/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/sgkit/tests/io/bgen/data/.gitignore b/sgkit/tests/io/bgen/data/.gitignore deleted file mode 100644 index 4685de1c2..000000000 --- a/sgkit/tests/io/bgen/data/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.metadata2.mmm -*.metafile diff --git a/sgkit/tests/io/bgen/data/example-no-samples.bgen b/sgkit/tests/io/bgen/data/example-no-samples.bgen deleted file mode 100644 index e8b88ed70..000000000 Binary files a/sgkit/tests/io/bgen/data/example-no-samples.bgen and /dev/null differ diff --git a/sgkit/tests/io/bgen/data/example-separate-samples.bgen b/sgkit/tests/io/bgen/data/example-separate-samples.bgen deleted file mode 100644 index 77a3ac705..000000000 Binary files a/sgkit/tests/io/bgen/data/example-separate-samples.bgen and /dev/null differ diff --git a/sgkit/tests/io/bgen/data/example-separate-samples.sample b/sgkit/tests/io/bgen/data/example-separate-samples.sample deleted file mode 100644 index c6e7b1b26..000000000 --- a/sgkit/tests/io/bgen/data/example-separate-samples.sample +++ /dev/null @@ -1,7 +0,0 @@ -ID -0 -s1 -s2 -s3 -s4 -s5 diff --git a/sgkit/tests/io/bgen/data/example.bgen b/sgkit/tests/io/bgen/data/example.bgen deleted file mode 100644 index a7d0e1c9a..000000000 Binary files a/sgkit/tests/io/bgen/data/example.bgen and /dev/null differ diff --git a/sgkit/tests/io/bgen/data/samples b/sgkit/tests/io/bgen/data/samples deleted file mode 100644 index 44dbc30ea..000000000 --- a/sgkit/tests/io/bgen/data/samples +++ /dev/null @@ -1,5 +0,0 @@ -sample_001 -sample_002 -sample_003 -sample_004 -sample_005 diff --git a/sgkit/tests/io/bgen/test_bgen_reader.py b/sgkit/tests/io/bgen/test_bgen_reader.py deleted file mode 100644 index be8c2bb57..000000000 --- a/sgkit/tests/io/bgen/test_bgen_reader.py +++ /dev/null @@ -1,300 +0,0 @@ -from pathlib import Path -from typing import Any, Tuple - -import numpy as np -import numpy.testing as npt -import pytest -import xarray as xr -import zarr -from packaging.version import Version - -pytestmark = pytest.mark.skipif( - Version(zarr.__version__).major >= 3, reason="Rechunking fails for Zarr Python 3" -) - -from sgkit.io.bgen.bgen_reader import ( - GT_DATA_VARS, - BgenReader, - _split_alleles, - bgen_to_zarr, - read_bgen, - rechunk_bgen, - unpack_variables, -) - -CHUNKS = [ - (100, 200, 3), - (100, 200, 1), - (100, 500, 3), - (199, 500, 3), - ((100, 99), 500, 2), - "auto", -] -INDEXES = [0, 10, 20, 100, -1] - -# Expectations below generated using bgen-reader directly, ex: -# > from bgen_reader import open_bgen -# > bgen = open_bgen('sgkit_bgen/tests/data/example.bgen', verbose=False) -# > bgen.read(-1)[0] # Probabilities for last variant, first sample -# array([[0.0133972 , 0.98135378, 0.00524902]] -# > bgen.allele_expectation(-1)[0, 0, -1] # Dosage for last variant, first sample -# 0.9918518217727197 -EXPECTED_PROBABILITIES = np.array( - [ # Generated using bgen-reader directly - [np.nan, np.nan, np.nan], - [0.007, 0.966, 0.0259], - [0.993, 0.002, 0.003], - [0.916, 0.007, 0.0765], - [0.013, 0.981, 0.0052], - ] -) -EXPECTED_DOSAGES = np.array( - [np.nan, 1.018, 0.010, 0.160, 0.991] # Generated using bgen-reader directly -) - -EXPECTED_DIMS = dict(variants=199, samples=500, genotypes=3, alleles=2) - - -def _shape(*dims: str) -> Tuple[int, ...]: - return tuple(EXPECTED_DIMS[d] for d in dims) - - -@pytest.mark.parametrize("chunks", CHUNKS) -def test_read_bgen(shared_datadir, chunks): - path = shared_datadir / "example.bgen" - ds = read_bgen(path, chunks=chunks) - - # check some of the data (in different chunks) - assert ds["call_dosage"].shape == _shape("variants", "samples") - npt.assert_almost_equal(ds["call_dosage"].values[1][0], 1.987, decimal=3) - npt.assert_almost_equal(ds["call_dosage"].values[100][0], 0.160, decimal=3) - npt.assert_array_equal(ds["call_dosage_mask"].values[0, 0], [True]) - npt.assert_array_equal(ds["call_dosage_mask"].values[0, 1], [False]) - assert ds["call_genotype_probability"].shape == _shape( - "variants", "samples", "genotypes" - ) - npt.assert_almost_equal( - ds["call_genotype_probability"].values[1][0], [0.005, 0.002, 0.992], decimal=3 - ) - npt.assert_almost_equal( - ds["call_genotype_probability"].values[100][0], [0.916, 0.007, 0.076], decimal=3 - ) - npt.assert_array_equal( - ds["call_genotype_probability_mask"].values[0, 0], [True] * 3 - ) - npt.assert_array_equal( - ds["call_genotype_probability_mask"].values[0, 1], [False] * 3 - ) - - -def test_read_bgen__with_sample_file(shared_datadir): - # The example file was generated using - # qctool -g sgkit_bgen/tests/data/example.bgen -og sgkit_bgen/tests/data/example-separate-samples.bgen -os sgkit_bgen/tests/data/example-separate-samples.sample -incl-samples sgkit_bgen/tests/data/samples - # Then editing example-separate-samples.sample to change the sample IDs - path = shared_datadir / "example-separate-samples.bgen" - ds = read_bgen(path) - # Check the sample IDs are the ones from the .sample file - assert ds["sample_id"].values.tolist() == ["s1", "s2", "s3", "s4", "s5"] - - -def test_read_bgen__with_no_samples(shared_datadir): - # The example file was generated using - # qctool -g sgkit_bgen/tests/data/example.bgen -og sgkit_bgen/tests/data/example-no-samples.bgen -os sgkit_bgen/tests/data/example-no-samples.sample -bgen-omit-sample-identifier-block -incl-samples sgkit_bgen/tests/data/samples - # Then deleting example-no-samples.sample - path = shared_datadir / "example-no-samples.bgen" - ds = read_bgen(path) - # Check the sample IDs are generated - assert ds["sample_id"].values.tolist() == [ - b"sample_0", - b"sample_1", - b"sample_2", - b"sample_3", - b"sample_4", - ] - - -@pytest.mark.parametrize("dtype", ["f2", "f4", "f8"]) -def test_read_bgen__gp_dtype(shared_datadir, dtype): - path = shared_datadir / "example.bgen" - ds = read_bgen(path, gp_dtype=dtype) - dtype = np.dtype(dtype) - assert ds["call_genotype_probability"].dtype == dtype - assert ds["call_dosage"].dtype == dtype - - -@pytest.mark.parametrize("dtype", ["c8", "i8", "str"]) -def test_read_bgen__invalid_gp_dtype(shared_datadir, dtype): - path = shared_datadir / "example.bgen" - with pytest.raises( - ValueError, match="`gp_dtype` must be a floating point data type" - ): - read_bgen(path, gp_dtype=dtype) - - -@pytest.mark.parametrize("dtype", ["U", "S", "u1", "u2", "i8", "int"]) -def test_read_bgen__contig_dtype(shared_datadir, dtype): - path = shared_datadir / "example.bgen" - ds = read_bgen(path, contig_dtype=dtype) - dtype = np.dtype(dtype) - if dtype.kind in {"U", "S"}: - assert ds["variant_contig"].dtype == np.int64 # type: ignore[comparison-overlap] - else: - assert ds["variant_contig"].dtype == dtype - - -@pytest.mark.parametrize("dtype", ["c8", "M", "f4"]) -def test_read_bgen__invalid_contig_dtype(shared_datadir, dtype): - path = shared_datadir / "example.bgen" - with pytest.raises( - ValueError, match="`contig_dtype` must be of string or int type" - ): - read_bgen(path, contig_dtype=dtype) - - -@pytest.mark.parametrize("chunks", CHUNKS) -def test_read_bgen__fancy_index(shared_datadir, chunks): - path = shared_datadir / "example.bgen" - ds = read_bgen(path, chunks=chunks) - npt.assert_almost_equal( - ds["call_genotype_probability"][INDEXES, 0], EXPECTED_PROBABILITIES, decimal=3 - ) - npt.assert_almost_equal(ds["call_dosage"][INDEXES, 0], EXPECTED_DOSAGES, decimal=3) - - -@pytest.mark.parametrize("chunks", CHUNKS) -def test_read_bgen__scalar_index(shared_datadir, chunks): - path = shared_datadir / "example.bgen" - ds = read_bgen(path, chunks=chunks) - for i, ix in enumerate(INDEXES): - npt.assert_almost_equal( - ds["call_genotype_probability"][ix, 0], EXPECTED_PROBABILITIES[i], decimal=3 - ) - npt.assert_almost_equal( - ds["call_dosage"][ix, 0], EXPECTED_DOSAGES[i], decimal=3 - ) - for j in range(3): - npt.assert_almost_equal( - ds["call_genotype_probability"][ix, 0, j], - EXPECTED_PROBABILITIES[i, j], - decimal=3, - ) - - -def test_read_bgen__raise_on_invalid_indexers(shared_datadir): - path = shared_datadir / "example.bgen" - reader = BgenReader(path) - with pytest.raises(IndexError, match="Indexer must be tuple"): - reader[[0]] - with pytest.raises(IndexError, match="Indexer must have 3 items"): - reader[(slice(None),)] - with pytest.raises(IndexError, match="Indexer must contain only slices or ints"): - reader[([0], [0], [0])] - - -def test_split_alleles__raise_on_multiallelic(): - with pytest.raises( - NotImplementedError, match="Bgen reads only supported for biallelic variants" - ): - _split_alleles(b"A,B,C") - - -def test_read_bgen__invalid_chunks(shared_datadir): - path = shared_datadir / "example.bgen" - with pytest.raises(ValueError, match="`chunks` must be tuple with 3 items"): - read_bgen(path, chunks=(100, -1)) # type: ignore[arg-type] - - -def _rechunk_bgen( - shared_datadir: Path, tmp_path: Path, **kwargs: Any -) -> Tuple[xr.Dataset, xr.Dataset, str]: - path = shared_datadir / "example.bgen" - ds = read_bgen(path, chunks=(10, -1, -1)) - store = tmp_path / "example.zarr" - dsr = rechunk_bgen(ds, store, **kwargs) - return ds, dsr, str(store) - - -def _open_zarr(store: str, **kwargs: Any) -> xr.Dataset: - # Force concat_characters False to avoid to avoid https://github.com/pydata/xarray/issues/4405 - return xr.open_zarr(store, concat_characters=False, **kwargs) # type: ignore[no-any-return,no-untyped-call] - - -@pytest.mark.parametrize("target_chunks", [(10, 10), (50, 50), (100, 50), (50, 100)]) -def test_rechunk_bgen__target_chunks(shared_datadir, tmp_path, target_chunks): - _, dsr, store = _rechunk_bgen( - shared_datadir, - tmp_path, - chunk_length=target_chunks[0], - chunk_width=target_chunks[1], - pack=False, - ) - for v in GT_DATA_VARS: - assert dsr[v].data.chunksize[:2] == target_chunks - - -def test_rechunk_from_zarr__self_consistent(shared_datadir, tmp_path): - # With no probability dtype or packing, rechunk_{to,from}_zarr is a noop - ds, dsr, store = _rechunk_bgen( - shared_datadir, tmp_path, probability_dtype=None, pack=False - ) - xr.testing.assert_allclose(ds.compute(), dsr.compute()) - - -@pytest.mark.parametrize("dtype", ["uint8", "uint16"]) -def test_rechunk_bgen__probability_encoding(shared_datadir, tmp_path, dtype): - ds, _, store = _rechunk_bgen( - shared_datadir, tmp_path, probability_dtype=dtype, pack=False - ) - dsr = _open_zarr(store, mask_and_scale=False) - v = "call_genotype_probability" - assert dsr[v].shape == ds[v].shape - assert dsr[v].dtype == dtype - dsr = _open_zarr(store, mask_and_scale=True) - # There are two missing calls which equates to - # 6 total nan values across 3 possible genotypes - assert np.isnan(dsr[v].values).sum() == 6 - tolerance = 1.0 / (np.iinfo(dtype).max - 1) - np.testing.assert_allclose(ds[v], dsr[v], atol=tolerance) - - -def test_rechunk_bgen__variable_packing(shared_datadir, tmp_path): - ds, dsr, store = _rechunk_bgen( - shared_datadir, tmp_path, probability_dtype=None, pack=True - ) - # A minor tolerance is necessary here when packing is enabled - # because one of the genotype probabilities is constructed from the others - xr.testing.assert_allclose(ds.compute(), dsr.compute(), atol=1e-6) - - -@pytest.mark.parametrize("dtype", ["uint32", "int8", "float32"]) -def test_rechunk_bgen__invalid_probability_type(shared_datadir, tmp_path, dtype): - with pytest.raises(ValueError, match="Probability integer dtype invalid"): - _rechunk_bgen(shared_datadir, tmp_path, probability_dtype=dtype) - - -def test_unpack_variables__invalid_gp_dims(shared_datadir, tmp_path): - # Validate that an error is thrown when variables are - # unpacked without being packed in the first place - _, dsr, store = _rechunk_bgen(shared_datadir, tmp_path, pack=False) - with pytest.raises( - ValueError, - match="Expecting variable 'call_genotype_probability' to have genotypes dimension of size 2", - ): - unpack_variables(dsr) - - -@pytest.mark.parametrize( - "region", [None, dict(variants=slice(0, 100)), dict(samples=slice(0, 50))] -) -def test_bgen_to_zarr(shared_datadir, tmp_path, region): - input = shared_datadir / "example.bgen" - output = tmp_path / "example.zarr" - ds = bgen_to_zarr(input, output, region=region) - expected_dims = { - k: EXPECTED_DIMS[k] - if region is None or k not in region - else region[k].stop - region[k].start - for k in EXPECTED_DIMS - } - actual_dims = {k: v for k, v in ds.sizes.items() if k in expected_dims} - assert actual_dims == expected_dims diff --git a/sgkit/tests/io/test_bgen_utils.py b/sgkit/tests/io/test_bgen_utils.py new file mode 100644 index 000000000..7ca2c1fb3 --- /dev/null +++ b/sgkit/tests/io/test_bgen_utils.py @@ -0,0 +1,145 @@ +import subprocess +import sys +import textwrap + +import numpy as np +import pytest +import xarray as xr + +from sgkit import load_dataset, save_dataset +from sgkit.io import bgen +from sgkit.io.bgen import unpack_variables +from sgkit.io.bgen.bgen_reader import unpack_variables as legacy_unpack_variables +from sgkit.io.bgen.utils import unpack_variables as utility_unpack_variables + + +def test_unpack_variables_import_paths(): + assert unpack_variables is legacy_unpack_variables + assert unpack_variables is utility_unpack_variables + + +def test_bgen_reader_removed(): + from sgkit.io.bgen import bgen_reader + + assert bgen.__all__ == ["unpack_variables"] + assert bgen_reader.__all__ == ["unpack_variables"] + for module in (bgen, bgen_reader): + for name in ("read_bgen", "BgenReader", "bgen_to_zarr", "rechunk_bgen"): + assert not hasattr(module, name) + + +def test_unpack_variables_without_cbgen(): + code = textwrap.dedent( + """ + import importlib.abc + import sys + + class BlockCbgen(importlib.abc.MetaPathFinder): + def find_spec(self, fullname, path=None, target=None): + if fullname == "cbgen" or fullname.startswith("cbgen."): + raise AssertionError("Unpacking must not import cbgen") + + sys.meta_path.insert(0, BlockCbgen()) + + import numpy as np + import xarray as xr + from sgkit.io.bgen import unpack_variables + from sgkit.io.bgen.bgen_reader import unpack_variables as legacy_unpack + from sgkit.io.bgen.utils import unpack_variables as utility_unpack + + assert unpack_variables is legacy_unpack is utility_unpack + ds = xr.Dataset({ + "call_genotype_probability": ( + ("variants", "samples", "genotypes"), + [[[0.25, 0.5], [np.nan, np.nan]]], + ), + "call_genotype_probability_mask": ( + ("variants", "samples"), [[False, True]] + ), + }).chunk({"variants": 1, "samples": 1}) + result = unpack_variables(ds) + assert result.call_genotype_probability.chunks is not None + np.testing.assert_allclose( + result.call_genotype_probability.compute(), + [[[0.25, 0.25, 0.5], [np.nan, np.nan, np.nan]]], + ) + np.testing.assert_allclose(result.call_dosage.compute(), [[1.25, np.nan]]) + np.testing.assert_array_equal(result.call_dosage_mask, [[False, True]]) + np.testing.assert_array_equal( + result.call_genotype_probability_mask, + [[[False, False, False], [True, True, True]]], + ) + assert "cbgen" not in sys.modules + """ + ) + subprocess.run( + [sys.executable, "-c", code], check=True, capture_output=True, text=True + ) + + +@pytest.fixture +def packed_dataset(): + return ( + xr.Dataset( + { + "call_genotype_probability": ( + ("variants", "samples", "genotypes"), + [[[0.25, 0.5], [np.nan, np.nan]], [[0.0, 1.0], [0.0, 0.0]]], + ), + "call_genotype_probability_mask": ( + ("variants", "samples"), + [[False, True], [False, False]], + ), + "variant_position": ("variants", [10, 20]), + }, + attrs={"source": "legacy packed BGEN store"}, + ) + .chunk({"variants": 1, "samples": 1}) + .assign(sample_id=("samples", np.array(["a", "b"], dtype=object))) + ) + + +@pytest.mark.parametrize("dtype", ["float32", "float64"]) +@pytest.mark.parametrize("zarr_format", [2, 3]) +def test_unpack_variables_legacy_store(packed_dataset, tmp_path, dtype, zarr_format): + store = tmp_path / "legacy.zarr" + consolidated = zarr_format == 2 + save_dataset( + packed_dataset, store, zarr_format=zarr_format, consolidated=consolidated + ) + packed = load_dataset(store, consolidated=consolidated) + before = packed.copy(deep=True) + restored = unpack_variables(packed, dtype=dtype) + assert restored.call_genotype_probability.chunks is not None + assert restored.call_dosage.chunks is not None + assert restored.call_genotype_probability.dtype == dtype + assert restored.call_dosage.dtype == dtype + np.testing.assert_allclose( + restored.call_genotype_probability.compute(), + [ + [[0.25, 0.25, 0.5], [np.nan, np.nan, np.nan]], + [[0.0, 0.0, 1.0], [1.0, 0.0, 0.0]], + ], + ) + np.testing.assert_allclose(restored.call_dosage.compute(), [[1.25, np.nan], [2, 0]]) + xr.testing.assert_equal( + restored.call_dosage_mask, + packed.call_genotype_probability_mask.rename("call_dosage_mask"), + ) + np.testing.assert_array_equal( + restored.call_genotype_probability_mask, + [[[False] * 3, [True] * 3], [[False] * 3, [False] * 3]], + ) + xr.testing.assert_identical(restored.sample_id, packed.sample_id) + xr.testing.assert_identical(restored.variant_position, packed.variant_position) + assert restored.attrs == packed.attrs + xr.testing.assert_identical(packed, before) + + +def test_unpack_variables_rejects_unpacked_dataset(packed_dataset): + restored = unpack_variables(packed_dataset) + with pytest.raises( + ValueError, + match="Expecting variable 'call_genotype_probability' to have genotypes dimension of size 2", + ): + unpack_variables(restored) diff --git a/sgkit/tests/io/test_dataset.py b/sgkit/tests/io/test_dataset.py index ce3ce6210..ba95c073d 100644 --- a/sgkit/tests/io/test_dataset.py +++ b/sgkit/tests/io/test_dataset.py @@ -1,10 +1,9 @@ -from typing import MutableMapping - +import numcodecs import pytest import xarray as xr import zarr -from packaging.version import Version from xarray import Dataset +from zarr.storage import MemoryStore from sgkit import load_dataset, save_dataset from sgkit.testing import simulate_genotype_call_dataset @@ -26,6 +25,7 @@ def test_save_and_load_dataset(tmp_path, is_path): path = str(path) ds = simulate_genotype_call_dataset(n_variant=10, n_sample=10) save_dataset(ds, path) + assert zarr.open_group(path, mode="r").metadata.zarr_format == 2 ds2 = load_dataset(path) assert_identical(ds, ds2) @@ -37,30 +37,64 @@ def test_save_and_load_dataset(tmp_path, is_path): assert_identical(ds, load_dataset(path2)) -def test_save_and_load_dataset__mutable_mapping(): - store: MutableMapping[str, bytes] = {} +@pytest.mark.parametrize("store_factory", [dict, MemoryStore]) +def test_save_and_load_dataset__memory_store(store_factory): + store = store_factory() ds = simulate_genotype_call_dataset(n_variant=10, n_sample=10) save_dataset(ds, store) ds2 = load_dataset(store) assert_identical(ds, ds2) # save and load again to test https://github.com/pydata/xarray/issues/4386 - store2: MutableMapping[str, bytes] = {} + store2 = store_factory() save_dataset(ds2, store2) assert_identical(ds, load_dataset(store2)) -def test_save_unequal_chunks_error(): +def test_save_and_load_dataset__format_3(tmp_path): + ds = xr.Dataset( + { + "value": ("variants", [0, 1, -1]), + "value_mask": ("variants", [False, False, True]), + } + ).chunk({"variants": 2}) + store = tmp_path / "ds.zarr" + save_dataset(ds, store, zarr_format=3, consolidated=False) + assert zarr.open_group(store, mode="r").metadata.zarr_format == 3 + assert_identical(ds, load_dataset(store, consolidated=False)) + + +@pytest.mark.parametrize("dtype", [object, "T"]) +def test_save_dataset__vlen_utf8_filter(dtype): + ds = xr.Dataset({"text": ("variants", ["a", "abc"])}).astype(dtype) + ds["text"].encoding["filters"] = [numcodecs.VLenUTF8()] + store = MemoryStore() + save_dataset(ds, store) + assert ds["text"].encoding["filters"] == ( + [] if dtype == "T" else [numcodecs.VLenUTF8()] + ) + xr.testing.assert_identical(ds, load_dataset(store)) + + +def test_save_dataset__invalid_encoding(): + ds = simulate_genotype_call_dataset(n_variant=10, n_sample=10) + with pytest.raises(ValueError, match="unexpected encoding parameters"): + save_dataset(ds, {}, encoding={"variant_position": {"invalid": True}}) + + +def test_save_unequal_chunks_error(tmp_path): # Make all dimensions the same size for ease of testing ds = simulate_genotype_call_dataset( n_variant=10, n_sample=10, n_ploidy=10, n_allele=10, n_contig=10 ) # Normal zarr errors shouldn't be caught + store = tmp_path / "array.zarr" + zarr.create_array(store=store, shape=(1,), dtype="i1", zarr_format=2) with pytest.raises( (FileExistsError, ValueError), match="(path '' contains an array|is not empty|contains data)", ): - save_dataset(ds, {".zarray": ""}) + save_dataset(ds, store) # Make the dataset have unequal chunk sizes across all dimensions ds = ds.chunk({dim: (1, 3, 5, 1) for dim in ds.sizes}) @@ -79,16 +113,14 @@ def test_save_unequal_chunks_error(): save_dataset(ds, {}) -@pytest.mark.skipif( - Version(zarr.__version__).major >= 3, reason="Fails for Zarr Python 3" -) -def test_save_auto_rechunk(): +@pytest.mark.parametrize("chunks", [(1, 3, 5, 1), (4, 6)]) +def test_save_auto_rechunk(chunks): # Make all dimensions the same size for ease of testing ds = simulate_genotype_call_dataset( n_variant=10, n_sample=10, n_ploidy=10, n_allele=10, n_contig=10 ) # Make the dataset have unequal chunk sizes across all dimensions - ds = ds.chunk({dim: (1, 3, 5, 1) for dim in ds.sizes}) + ds = ds.chunk({dim: chunks for dim in ds.sizes}) # Default is to not rechunk with pytest.raises( diff --git a/sgkit/tests/io/test_vcf2zarr_compat.py b/sgkit/tests/io/test_vcf2zarr_compat.py index 7f185763e..be0b62b57 100644 --- a/sgkit/tests/io/test_vcf2zarr_compat.py +++ b/sgkit/tests/io/test_vcf2zarr_compat.py @@ -1,5 +1,6 @@ import numpy as np import pytest +import xarray as xr pytest.importorskip("bio2zarr") from bio2zarr import vcf @@ -15,7 +16,6 @@ from sgkit import load_dataset, save_dataset from sgkit.model import get_contigs, get_filters, num_contigs -from sgkit.tests.io.test_dataset import assert_identical @pytest.mark.filterwarnings("ignore::xarray.coding.variables.SerializationWarning") @@ -29,9 +29,10 @@ def test_vcf2zarr_compat(shared_datadir, tmp_path): variants_chunk_size=5, samples_chunk_size=2, worker_processes=0, + zarr_format=2, ) - ds = load_dataset(vcz_path) + ds = load_dataset(vcz_path, consolidated=False) assert_array_equal(ds["filter_id"], ["PASS", "s50", "q10"]) assert_array_equal(get_filters(ds), ["PASS", "s50", "q10"]) # utility function @@ -164,13 +165,13 @@ def test_vcf2zarr_compat(shared_datadir, tmp_path): ], ) assert ds["variant_allele"].chunks[0][0] == 5 - assert ds["variant_allele"].dtype == "O" + assert ds["variant_allele"].dtype.kind in ("O", "T") assert_array_equal( ds["variant_id"].values.tolist(), [sm, sm, "rs6054257", sm, "rs6040355", sm, "microsat1", sm, "rsTest"], ) assert ds["variant_id"].chunks[0][0] == 5 - assert ds["variant_id"].dtype == "O" + assert ds["variant_id"].dtype.kind in ("O", "T") assert_array_equal( ds["variant_id_mask"], [True, True, False, True, False, True, False, True, False], @@ -246,4 +247,11 @@ def test_vcf2zarr_compat(shared_datadir, tmp_path): # save and load again to test https://github.com/pydata/xarray/issues/3476 path2 = tmp_path / "ds2.zarr" save_dataset(ds, path2) - assert_identical(ds, load_dataset(path2)) + ds2 = load_dataset(path2) + xr.testing.assert_identical(ds, ds2) + for name in ds: + if ds[name].dtype.kind == "T": + # Recent Xarray writes NumPy StringDType as fixed-width Unicode. + assert ds2[name].dtype.kind in ("T", "U") + else: + assert ds[name].dtype == ds2[name].dtype diff --git a/sgkit/tests/test_association.py b/sgkit/tests/test_association.py index aad1bef19..4ccc8f4d9 100644 --- a/sgkit/tests/test_association.py +++ b/sgkit/tests/test_association.py @@ -8,11 +8,7 @@ import xarray as xr from pandas import DataFrame from xarray import Dataset - -try: - from zarr.storage import ZipStore # v3 -except ImportError: # pragma: no cover - from zarr import ZipStore +from zarr.storage import ZipStore import sgkit.distarray as da from sgkit.stats.association import ( diff --git a/sgkit/tests/test_regenie.py b/sgkit/tests/test_regenie.py index 76ad69abb..8b1c650ef 100644 --- a/sgkit/tests/test_regenie.py +++ b/sgkit/tests/test_regenie.py @@ -16,11 +16,7 @@ from hypothesis.strategies import data as st_data from pandas import DataFrame from xarray import Dataset - -try: - from zarr.storage import ZipStore # v3 -except ImportError: # pragma: no cover - from zarr import ZipStore +from zarr.storage import ZipStore from sgkit.stats.association import LinearRegressionResult, linear_regression from sgkit.stats.regenie import ( diff --git a/sgkit/utils.py b/sgkit/utils.py index f60db1f91..ee9bbfd3f 100644 --- a/sgkit/utils.py +++ b/sgkit/utils.py @@ -1,4 +1,3 @@ -import inspect import warnings from itertools import product from typing import Any, Callable, Hashable, List, Mapping, Optional, Set, Tuple, Union @@ -426,10 +425,3 @@ def smallest_numpy_int_dtype(value: int) -> Optional[DType]: if np.iinfo(dtype).min <= value <= np.iinfo(dtype).max: return dtype raise OverflowError(f"Value {value} cannot be stored in np.int64") - - -def has_keyword(func, keyword): - try: - return keyword in inspect.signature(func).parameters - except Exception: # pragma: no cover - return False diff --git a/uv.lock b/uv.lock index 170bddde4..acd9c31d9 100644 --- a/uv.lock +++ b/uv.lock @@ -191,15 +191,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, ] -[[package]] -name = "appdirs" -version = "1.4.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/d8/05696357e0311f5b5c316d7b95f46c669dd9c15aaeecbb48c7d0aeb88c40/appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", size = 13470, upload-time = "2020-05-11T07:59:51.037Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128", size = 9566, upload-time = "2020-05-11T07:59:49.499Z" }, -] - [[package]] name = "appnope" version = "1.0.0" @@ -209,12 +200,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/c7/6b687cb0f83d2a51017d47953f2ee430ffad6fec2a8ebc964e0718a33eb1/appnope-1.0.0-py3-none-any.whl", hash = "sha256:6fe0c04218aab65c54c4ff81638cdbf848d89f5653b74d68638a137f200dd16e", size = 4158, upload-time = "2026-08-20T21:36:12.444Z" }, ] -[[package]] -name = "asciitree" -version = "0.3.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2d/6a/885bc91484e1aa8f618f6f0228d76d0e67000b0fdd6090673b777e311913/asciitree-0.3.3.tar.gz", hash = "sha256:4aa4b9b649f85e3fcb343363d97564aa1fb62e249677f2e18a96765145cc0f6e", size = 3951, upload-time = "2016-09-05T19:10:42.681Z" } - [[package]] name = "asttokens" version = "3.0.2" @@ -297,22 +282,22 @@ wheels = [ [[package]] name = "bio2zarr" -version = "0.1.7" +version = "0.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "coloredlogs" }, { name = "humanfriendly" }, - { name = "numcodecs", extra = ["msgpack"] }, { name = "numpy" }, { name = "pandas" }, { name = "tabulate" }, { name = "tqdm" }, - { name = "zarr" }, + { name = "zarr", version = "3.1.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "zarr", version = "3.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/8f/9f79bfdde3dc98228dc0030bad8f668199c811111f5842bd3b3ff5c752d3/bio2zarr-0.1.7.tar.gz", hash = "sha256:237aa8af904bb29172a0cb8a525527d0ad8e5a855d214d8a5be29b39b4334b2c", size = 262655, upload-time = "2026-02-03T15:44:52.011Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/98/96c14b30e96c581362513ea29520efdf6cfa2b9f222deb0a87377ab2be33/bio2zarr-0.2.1.tar.gz", hash = "sha256:5bbab214c7b12ec99459e9e08a7575f21bc5415b31d6385cf80a7ff55e097b63", size = 405736, upload-time = "2026-06-04T11:20:04.196Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/34/b827ccb81a4ed1a4e90cd968dfa94efc339b9e32dc354602a302d199a935/bio2zarr-0.1.7-py3-none-any.whl", hash = "sha256:95eabaada6a2fae02c4c2ea52fb7e4684de3b45bc3b521a13855353a8e50d1c6", size = 61768, upload-time = "2026-02-03T15:44:50.564Z" }, + { url = "https://files.pythonhosted.org/packages/11/2e/d69c09b3727630169361827a9cd7ef10aae7a9edfc91ae01c8ba6f474e51/bio2zarr-0.2.1-py3-none-any.whl", hash = "sha256:989d993fef83d64a8eff54770625601a4a4108f86149fc7a7bfbbd6b6e2af010", size = 63715, upload-time = "2026-06-04T11:20:02.904Z" }, ] [package.optional-dependencies] @@ -320,91 +305,12 @@ vcf = [ { name = "cyvcf2" }, ] -[[package]] -name = "bokeh" -version = "3.9.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.12' and sys_platform == 'darwin'", - "python_full_version < '3.12' and sys_platform != 'darwin'", -] -dependencies = [ - { name = "contourpy" }, - { name = "jinja2" }, - { name = "narwhals" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "pillow" }, - { name = "pyyaml" }, - { name = "tornado", marker = "sys_platform != 'emscripten'" }, - { name = "xyzservices" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/dd/6f/262b649ad2fdd5ed23341452eebfa669a73b0e0c739a369204d02ee2e201/bokeh-3.9.2.tar.gz", hash = "sha256:5be30d105b329d7d2ab1cb92f51e6bd44e23f0fe21ab8d49594b220b2ab11632", size = 5751825, upload-time = "2026-07-25T16:57:56.207Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/0c/06a99ee998b1babf95074376cbf6ede5a86af726b12cbf47c4b84fa2803a/bokeh-3.9.2-py3-none-any.whl", hash = "sha256:448e07d5ee78231f5bdece3be020024bb98696c0d6b127e0e2df0b8ba8fa9765", size = 6409477, upload-time = "2026-07-25T16:57:54.62Z" }, -] - -[[package]] -name = "bokeh" -version = "3.10.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'darwin'", - "python_full_version >= '3.14' and sys_platform != 'darwin'", - "python_full_version == '3.13.*' and sys_platform == 'darwin'", - "python_full_version == '3.13.*' and sys_platform != 'darwin'", - "python_full_version == '3.12.*' and sys_platform == 'darwin'", - "python_full_version == '3.12.*' and sys_platform != 'darwin'", -] -dependencies = [ - { name = "jinja2" }, - { name = "narwhals" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "pillow" }, - { name = "pyyaml" }, - { name = "tornado", marker = "sys_platform != 'emscripten'" }, - { name = "xyzservices" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4c/e8/93d270023eb987348f9b5a2aef0103f903511b934519cd2bbefde451868d/bokeh-3.10.0.tar.gz", hash = "sha256:73f0b0c06d7925ca2b6d52b2dc51c2533a11f667cc6944f1f06c039ccc57c5ce", size = 5982193, upload-time = "2026-08-18T17:47:09.943Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/0a/1423bb61b5f06376fe6ccf79c8781e2bb5e36c78c1814c78913b7425950f/bokeh-3.10.0-py3-none-any.whl", hash = "sha256:6bb8dbd4a555f71a921be5bd45c3bfaa15b390257fbadae76901927bd58629b7", size = 6649702, upload-time = "2026-08-18T17:47:07.287Z" }, -] - [[package]] name = "callee" version = "0.3.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7c/a9/cc2ee5fb2686292610e743324854059a6da6780479fa38999d725bd729ab/callee-0.3.1.tar.gz", hash = "sha256:056f95d7760c87ce470aa4ab83bab5f9bdf090d4ecf77d52efe9c4559c0aefd3", size = 19824, upload-time = "2019-12-24T14:58:38.579Z" } -[[package]] -name = "cbgen" -version = "1.0.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "appdirs" }, - { name = "cffi" }, - { name = "numpy" }, - { name = "pooch" }, - { name = "pytest" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a0/97/cc69e0d0accb46965332ae247f3289832e546f8d30cb77218d6eb0f47b48/cbgen-1.0.6.tar.gz", hash = "sha256:edcf856c9b0cafae7f6e999ed5d08aacdf1e09dadec766b79990fe5861e8cf7e", size = 12387, upload-time = "2024-06-17T15:27:30.503Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/05/360448e10fbe7bed04450d2e2f9df6c8ddfa6e86beb367e426eb6b06fb49/cbgen-1.0.6-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:b97ab995397d56fa007dfd4e3d01f0ee7bc02ee5685b4e174161615af6a1dad0", size = 419458, upload-time = "2024-06-17T15:27:02.719Z" }, - { url = "https://files.pythonhosted.org/packages/81/71/260966997bdad39615e3430375fbca020ca103c1f601ec95f969e0b02296/cbgen-1.0.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a0ddcaa25f19ea383931605d4627da06339662d0ef5bc07fda706d2592a11b31", size = 288999, upload-time = "2024-06-17T15:27:04.516Z" }, - { url = "https://files.pythonhosted.org/packages/ad/cc/0a735cc723989189177d00a57937e44184b57ca42245570b0b8e86720d57/cbgen-1.0.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85560c53ea769902e9516b86d6bd2f3e6718dcac8ce4c3d7c0ac9fb43863b19f", size = 180643, upload-time = "2024-06-17T15:27:06.253Z" }, - { url = "https://files.pythonhosted.org/packages/1a/aa/7b37e68dda0f95b5fde96d20aab5163c61f35268914d4df110923953fbb4/cbgen-1.0.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c04f5860e7c35296777af14b0749c3b0c0210825644ac98cae9f169822a40bd", size = 578915, upload-time = "2024-06-17T15:27:08.26Z" }, - { url = "https://files.pythonhosted.org/packages/b8/7b/10cff399b0255d1426a1498aeb2eda7a94167a034fbd9f0bca5da2c66fdd/cbgen-1.0.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9beb0fe67968020344b47c0f5f648418202ead39b1cc599adc481a49daace99b", size = 345395, upload-time = "2024-06-17T15:27:09.476Z" }, - { url = "https://files.pythonhosted.org/packages/c4/bb/fa9ed88dca04be210707e00bf329a337b78cbfc154c4cfa0250a7f589b0b/cbgen-1.0.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fc40d9e917155125057adbad5477537a3974a7908080b60715449298f5c686d7", size = 729100, upload-time = "2024-06-17T15:27:10.633Z" }, - { url = "https://files.pythonhosted.org/packages/40/84/f0660542361cfe3a97e553d61e91d92bb26ea04a87cdd7d06522f1852dcf/cbgen-1.0.6-cp311-cp311-win_amd64.whl", hash = "sha256:733a11d2eb2b06adf4ef0dd7675ce18e011c12df99e42fea93b3222efad5293b", size = 243941, upload-time = "2024-06-17T15:27:12.426Z" }, - { url = "https://files.pythonhosted.org/packages/09/ce/6abc744f0b17d24ce47fd177b81aafc0a8afe005439b18e57db6caa564f3/cbgen-1.0.6-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:e05037fabfd12711836b0cd26da961fc271b56080871e3508177c6c08b1247b1", size = 554262, upload-time = "2024-06-17T15:27:14.2Z" }, - { url = "https://files.pythonhosted.org/packages/c0/15/7c42414b690d937ca6c4c75d08964e1e7bb19d9bb2fc06b5698ee18e53b8/cbgen-1.0.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:07d8d9209d24005f8f492b82f3793ac4ac8596263b07c5f68b7ab3e57eb0bc83", size = 380328, upload-time = "2024-06-17T15:27:15.918Z" }, - { url = "https://files.pythonhosted.org/packages/a5/36/3e8550e6d05baea8eb2eae8c27887e81ea0085f8246d13e030d73f9f87fd/cbgen-1.0.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e95a3b83d58343f5a7026dcee9dfce7ab9845406feb8b1c065151636b9fbefc", size = 767497, upload-time = "2024-06-17T15:27:17.117Z" }, - { url = "https://files.pythonhosted.org/packages/a9/24/6613c31e2346bd583ebbd4f32eccca4cd894364daf751ed0b632665013b3/cbgen-1.0.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e1279d9881844018d27d6cd3ec8475e08d68136a1862c8eadf41745b373393d1", size = 914945, upload-time = "2024-06-17T15:27:18.309Z" }, - { url = "https://files.pythonhosted.org/packages/ec/eb/493f97d94704a5078770477d03924c6bd74c2d98d041d25848f4b735034d/cbgen-1.0.6-cp312-cp312-win_amd64.whl", hash = "sha256:e56d6a01fb71d5c7fbf7039cc23089edd7a749fafb9f2898efffeacbd14a2bba", size = 319686, upload-time = "2024-06-17T15:27:20.306Z" }, -] - [[package]] name = "certifi" version = "2026.7.22" @@ -1036,11 +942,6 @@ dataframe = [ { name = "numpy" }, { name = "pandas" }, ] -diagnostics = [ - { name = "bokeh", version = "3.9.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "bokeh", version = "3.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "jinja2" }, -] [[package]] name = "dask-expr" @@ -1125,21 +1026,24 @@ wheels = [ ] [[package]] -name = "executing" -version = "2.2.1" +name = "donfig" +version = "0.8.1.post1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/71/80cc718ff6d7abfbabacb1f57aaa42e9c1552bfdd01e64ddd704e4a03638/donfig-0.8.1.post1.tar.gz", hash = "sha256:3bef3413a4c1c601b585e8d297256d0c1470ea012afa6e8461dc28bfb7c23f52", size = 19506, upload-time = "2024-05-23T14:14:31.513Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d5/c5db1ea3394c6e1732fb3286b3bd878b59507a8f77d32a2cebda7d7b7cd4/donfig-0.8.1.post1-py3-none-any.whl", hash = "sha256:2a3175ce74a06109ff9307d90a230f81215cbac9a751f4d1c6194644b8204f9d", size = 21592, upload-time = "2024-05-23T14:13:55.283Z" }, ] [[package]] -name = "fasteners" -version = "0.20" +name = "executing" +version = "2.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2d/18/7881a99ba5244bfc82f06017316ffe93217dbbbcfa52b887caa1d4f2a6d3/fasteners-0.20.tar.gz", hash = "sha256:55dce8792a41b56f727ba6e123fcaee77fd87e638a6863cec00007bfea84c8d8", size = 25087, upload-time = "2025-08-11T10:19:37.785Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/ac/e5d886f892666d2d1e5cb8c1a41146e1d79ae8896477b1153a21711d3b44/fasteners-0.20-py3-none-any.whl", hash = "sha256:9422c40d1e350e4259f509fb2e608d6bc43c0136f79a00db1b49046029d0b3b7", size = 18702, upload-time = "2025-08-11T10:19:35.716Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, ] [[package]] @@ -1367,6 +1271,39 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/3c/6a2bf344106328fd04963664a60b9bb6496fc25df8e962fcdc1367285fb9/fsspec-2026.7.0-py3-none-any.whl", hash = "sha256:b57ddbafedfaef7018c1ecab32aa200a9d7ca26b77965f64e48b70061249d279", size = 206583, upload-time = "2026-07-28T16:34:49.538Z" }, ] +[[package]] +name = "google-crc32c" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/25/9cb0c1c31c45b893eb8f11ae70b3f4309432d59b5acaebca5dbe791729a4/google_crc32c-1.9.0.tar.gz", hash = "sha256:7b8c84c3d159ab6817fe3f74e6e6cef099c3f95dcec3abc0d8afb1404642efbe", size = 14857, upload-time = "2026-09-24T21:39:32.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/55/a2f07f15e624f0de79359b1a6c1deb59ec5061bd3b38744b3b2849400662/google_crc32c-1.9.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:457d0d9a4718fd52b1494eac5c200ad25beeadbdc91843d550a003910838589f", size = 31958, upload-time = "2026-09-24T21:19:00.994Z" }, + { url = "https://files.pythonhosted.org/packages/f8/b3/923743597b774bbcf12a7c3e00e48d745e15fd616ad7489a40a63fff8f2f/google_crc32c-1.9.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:ccfe40021fd6afe23361175cf7551e3cef5fd34dc1ebe319f14993a83579e0eb", size = 31805, upload-time = "2026-09-24T21:22:25.019Z" }, + { url = "https://files.pythonhosted.org/packages/df/a6/4d0352fe889663e0d81cea7fc664ec9158727384de4a44ab10e9967a7682/google_crc32c-1.9.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fbef61a3794e011c65fb4396a196cf123a7f474fe5a443db8e5dd7d751b9e6d4", size = 38119, upload-time = "2026-09-24T21:38:06.634Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e3/26685384e4b66ff0928d9566ef6110a7df76029175a1842329d7e3515f10/google_crc32c-1.9.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:86764b99e7a607830d93cb5b75e0ec3ff6cb06d3c274624418473cee701900d4", size = 36677, upload-time = "2026-09-24T21:38:08.082Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ce/4e90102e84880e97d3cf935f2672ecd29191bdeacf57f01740f92debda00/google_crc32c-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:43a2dc26f9be213fbe0b4fc4a1088c5d45cbfcb3247420ccc820f0fc3edeea86", size = 35096, upload-time = "2026-09-24T21:39:28.201Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5d/0730e1b3a14d054d1466f2fec88dadf978509c749a3d96d8b069cc56d38a/google_crc32c-1.9.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:53fdafef58e230d0c946ab5f8446d123d9f548230a73b29c8b41c9546f268bc1", size = 31961, upload-time = "2026-09-24T21:19:01.724Z" }, + { url = "https://files.pythonhosted.org/packages/dd/32/d085abaf2fd907121975b92245bb3480fb8be40c37d03f9d6c41857f84c3/google_crc32c-1.9.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:8b91f41645b15a720357183fa5716682ada441873e3c462c15f9714be36f146b", size = 31804, upload-time = "2026-09-24T21:22:25.81Z" }, + { url = "https://files.pythonhosted.org/packages/94/78/dd1935432337e5da7af391a6fc9f161c1c8e9b9002a402b9190135fe1b59/google_crc32c-1.9.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:16865b477d7941712cb0e0aad8ad4815e984fb5fc16d3fdaef7d986e26e53c95", size = 37775, upload-time = "2026-09-24T21:38:09.249Z" }, + { url = "https://files.pythonhosted.org/packages/9e/43/9db03635bb10188d93dcbab9baa2a8670a0da4e868b4370cdbd98d65fed8/google_crc32c-1.9.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3abb18297d9ef0ab120531838be0e6d68c9fa876570e11c229c48f2edac23ce7", size = 36347, upload-time = "2026-09-24T21:38:10.141Z" }, + { url = "https://files.pythonhosted.org/packages/cf/eb/94dee516c846bd9382c3f566d8f8e5fb9e90599e45afeb697f9fc2533528/google_crc32c-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fb63a8d7fa2e95dcff1ca16af2f4d88b526fa5ff72d1696285884ac2d49b6963", size = 35097, upload-time = "2026-09-24T21:39:28.934Z" }, + { url = "https://files.pythonhosted.org/packages/3f/34/cb484e8b6174f130f8c6dc79c733a9dd8869b410ad6511fb6104c46b973a/google_crc32c-1.9.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:f1dc17d987ddcc5eba12a7ce48f0eb93141dea236b170c1101151396edf2f0cf", size = 31960, upload-time = "2026-09-24T21:19:02.454Z" }, + { url = "https://files.pythonhosted.org/packages/af/25/3e8e567bd48448e225ea27318ccf2b94e05124e7b8b97b13eaec9e127199/google_crc32c-1.9.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:f894a2877650b56201d26a012a257b76d54a68834dc3913a93830ca8a047b075", size = 31803, upload-time = "2026-09-24T21:22:27.008Z" }, + { url = "https://files.pythonhosted.org/packages/f0/18/bee0dd59ae622482dc6463636c79e4bde7c954d061c859c9256362c9931a/google_crc32c-1.9.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4488f1553a9ab7e86cdedc833374a7e904031803b995dc0bd0be48c271fa6556", size = 37776, upload-time = "2026-09-24T21:38:11.056Z" }, + { url = "https://files.pythonhosted.org/packages/fd/b6/e76e80fed5f2558273c7839e622f98095c9b36c719c7147e38e3c055cb70/google_crc32c-1.9.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0568b17ed90ac596f29400d99e243fd0cc6276766183def888d1bf8d1dc13827", size = 36350, upload-time = "2026-09-24T21:38:12.138Z" }, + { url = "https://files.pythonhosted.org/packages/87/34/165542bfa99dfef91a76471cc48cce74b8ff4e295722896087ab2b8e8611/google_crc32c-1.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:8583ec21d56b565d68ab2963cc7e21b3b271247c29b04286068255ef65f221bd", size = 35090, upload-time = "2026-09-24T21:39:29.764Z" }, + { url = "https://files.pythonhosted.org/packages/8f/eb/43ea41f4061a1cad87b2b6559c98e960e45bf551fe66f83d833b98aaf0c9/google_crc32c-1.9.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:6a3b2c8a343c570ed8100a7627c20badfd92c6caa2067093a86be45af27f5b1b", size = 31963, upload-time = "2026-09-24T21:19:03.208Z" }, + { url = "https://files.pythonhosted.org/packages/45/d2/a968c0c29ccd2b0c980ff4f9e3f7035cee28c23a1c57541825cc8221858c/google_crc32c-1.9.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:13179f7e3282617923e957b8e54b8f9c3968030f48640a9f47fd7c5c38c4a215", size = 31805, upload-time = "2026-09-24T21:22:27.917Z" }, + { url = "https://files.pythonhosted.org/packages/03/73/388e493d6c3e252e37165d22efe5a1361f872a24425391b999822861b23a/google_crc32c-1.9.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:265233aff33d835f5b909584fe36ab29647b598c271b661a300001099109e53e", size = 37925, upload-time = "2026-09-24T21:38:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/98/36/190d32caa363ef25d685f422ed1bbf93ff1140fb22fd4d90f24cec209977/google_crc32c-1.9.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dee799544cae42a42b17a88e38b59cf2c271051dc001da2117a8ff240ffa0548", size = 36495, upload-time = "2026-09-24T21:38:14.211Z" }, + { url = "https://files.pythonhosted.org/packages/d3/fd/81cefea6adae7bd92abb23d4567d199f6485a20ec0a305ca5fa04c52b9c5/google_crc32c-1.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:af73200fa9791ccd380f3598235dba8d82b8af0905df045b3dc60b59836e8ddd", size = 35535, upload-time = "2026-09-24T21:39:30.52Z" }, + { url = "https://files.pythonhosted.org/packages/c5/18/19d4f17f3f33f8fdffcb3e1e69219d6f7ec2c359c160867b04dac1d0a64d/google_crc32c-1.9.0-cp315-cp315-macosx_12_0_arm64.whl", hash = "sha256:e6e8be8a94436079cb5340f6d495d9d7ba30124d8b952703994c739c7c06e236", size = 31977, upload-time = "2026-09-24T21:19:03.976Z" }, + { url = "https://files.pythonhosted.org/packages/81/b4/8010372c4b46f2ee2352dfdb630c397570cd85522a315df024ad2f9459aa/google_crc32c-1.9.0-cp315-cp315-macosx_12_0_x86_64.whl", hash = "sha256:f2b64641bca27497b986b9d87883014035aa904cb4fa333407c6752b3afee9ba", size = 31809, upload-time = "2026-09-24T21:22:29.1Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f8/7e33845d6b90ce1cf37cfabf25cb859277c7d3533ef1b6b1e1ca58581549/google_crc32c-1.9.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f97c3806dcea41c29c04965347b0e12481561b75e0045dc7a4f69d75dec5d9b1", size = 38359, upload-time = "2026-09-24T21:38:14.983Z" }, + { url = "https://files.pythonhosted.org/packages/36/ff/556b2423f449a7515af6b8222a4d7833cbe09ff3e8d2f0b80471f5f6d02e/google_crc32c-1.9.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0abe7e202c25909869c35672ab0f2fe748a7acf276eb78577332a7c38999740f", size = 36858, upload-time = "2026-09-24T21:38:15.799Z" }, + { url = "https://files.pythonhosted.org/packages/40/71/4733f1b7c921d04a2bb9b9916cf66498bf7ad0860a06289413830da83192/google_crc32c-1.9.0-cp315-cp315-win_amd64.whl", hash = "sha256:5695c8b9327e040b2aba12c6659b0acb5995314ef0af0192da66e662e011103b", size = 35534, upload-time = "2026-09-24T21:39:31.337Z" }, +] + [[package]] name = "graphviz" version = "0.21" @@ -2336,67 +2273,82 @@ wheels = [ ] [[package]] -name = "msgpack" -version = "1.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6d/44/ea2100ec54d30c46ee9dba10a3bfb79b655e96c6df237238a3234c75869b/msgpack-1.2.2.tar.gz", hash = "sha256:9eb0b0e602064527a045ea28c4f174ed69383587e29cebe28947e3b84106eb2a", size = 187025, upload-time = "2026-08-27T10:03:47.793Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/53/fd/a05ba8f84c5951c9aec2a19c1c81f6c4a67b8bec80af604ac5b23ccfa019/msgpack-1.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d7fb25b4442fae0cb2590272d06ab4f6caa526ee36a994edb81e946b874813e", size = 83498, upload-time = "2026-08-27T10:01:50.62Z" }, - { url = "https://files.pythonhosted.org/packages/0f/df/e20bcf5c149890545334743b212eb4b82e1a25fe0a34f99753a1755bfab5/msgpack-1.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7fe374ba76eb0ecca13a1703daa8fa85825a6ddddbb52d4c1a732fa524194683", size = 83896, upload-time = "2026-08-27T10:01:51.898Z" }, - { url = "https://files.pythonhosted.org/packages/ac/c3/00dcd902d66a641b9ba350783feb482ea5c1ca4a7ff6629db0c10c0ea982/msgpack-1.2.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9b0c1f2aa7b0026b4bd50718100e8b04175e4f36e160aa852502377b5e572e7", size = 413259, upload-time = "2026-08-27T10:01:53.296Z" }, - { url = "https://files.pythonhosted.org/packages/93/15/17374efe9793f5332c7d4727ab40539f95a1dc9df653531795daca8c4281/msgpack-1.2.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f11e09f10210a91c169e39c7a5a1f9090eaa73ad75555fafad5023c3053c47ba", size = 422907, upload-time = "2026-08-27T10:01:54.786Z" }, - { url = "https://files.pythonhosted.org/packages/d3/af/2b567d684f912fedcefe3f7c37de604716ffa99336bd432688f9f040df92/msgpack-1.2.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8b1415d02e9bf722672af8a90f90813265a0cd0b14163187261e54a5592bc949", size = 389248, upload-time = "2026-08-27T10:01:56.55Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fd/d8533fed473cc3e309a701e851d0e5fe36ada5552a3899025f5c69fbe877/msgpack-1.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:42fd9260416885b4815caca5bdd14dfd5dda6cdade732d6c09104ef8f6228761", size = 407099, upload-time = "2026-08-27T10:01:58.357Z" }, - { url = "https://files.pythonhosted.org/packages/d6/1b/57906337bfee0ead554571dc203ea17c3fad26d51e5eca6271ecd983f73b/msgpack-1.2.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:336525cc2688e43ea77dfb1a4ce012c8cde561835913801dbfcfdcf4111d8abb", size = 387201, upload-time = "2026-08-27T10:02:00.109Z" }, - { url = "https://files.pythonhosted.org/packages/de/0f/5d1e6d68e516621697a9262b24917d678793e838cf3f331ed4656b3e959d/msgpack-1.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cdb6cc6e1127d15879c47a8b3270716243da82d3e7feab1f5946872c75b3d60f", size = 420765, upload-time = "2026-08-27T10:02:01.573Z" }, - { url = "https://files.pythonhosted.org/packages/31/78/90c15bebb1a72667349ca62d4507e9d9369e7f8f76b95f490b823d3622e5/msgpack-1.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a4348705be86e029d04e741cf9ed0dfe03e942d7d3b92e838fa80d3aa2c3ebc", size = 84275, upload-time = "2026-08-27T10:02:07.106Z" }, - { url = "https://files.pythonhosted.org/packages/88/88/c2b6d8e81571da87aa232c0e34a3f3a0e618e6235892065ec82d1d81fc7a/msgpack-1.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a652ceeededf71d3fa40c303a02a149d42338d310162367b91c539d4bd6e0a3", size = 83970, upload-time = "2026-08-27T10:02:08.488Z" }, - { url = "https://files.pythonhosted.org/packages/da/c0/d3ede9f5d16acb4c05a9281859f1e99ef9f877a928eb78454c37f70db001/msgpack-1.2.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90986cc9aab9d7d1d8f38bcbf65d3f7ac83bdd90c35765db7d691b4829698cba", size = 409401, upload-time = "2026-08-27T10:02:09.877Z" }, - { url = "https://files.pythonhosted.org/packages/41/f0/29f591bea185616cf417645ac03bd3ad9b317483ad8572160e325f7fe777/msgpack-1.2.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:77c2e018417dc1d66f235e383877ee885b60ade9d29e494dd581e08af2cb1923", size = 420619, upload-time = "2026-08-27T10:02:11.526Z" }, - { url = "https://files.pythonhosted.org/packages/4e/8e/c70c8c9180c5ddf4440eb8658ebead98e22e7686fbf84f6b165031430750/msgpack-1.2.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e91332144f69bc3018c91232fac26da580ef748fb8eaddd7914d4458001cc4f", size = 379747, upload-time = "2026-08-27T10:02:13.345Z" }, - { url = "https://files.pythonhosted.org/packages/50/9a/f10ce11fa62700c9ab87a22e65b9ca272f7f673ddd31aeb2de6ae272ad35/msgpack-1.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e915d390d7068b257ca8b62f3fc59fad135c8631d1017ab03b0b924b07c5367", size = 398944, upload-time = "2026-08-27T10:02:15.006Z" }, - { url = "https://files.pythonhosted.org/packages/82/fe/d7be978456ff8552e69a8e270d882e7530e01513c096b293d83df03753ea/msgpack-1.2.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c522420d78db2431887d45b518e304d86e27b9ad0b30f24e3806a6ad5d8bdbfc", size = 373979, upload-time = "2026-08-27T10:02:16.618Z" }, - { url = "https://files.pythonhosted.org/packages/be/af/91b0d8d3fb3063e259daee3ea8515cea6282f68f4b0e5f0b6fea25762c6e/msgpack-1.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4b554d8164ebb526892194f71dcd96ef1fefe0c250087498785d3ffc04a80be3", size = 417781, upload-time = "2026-08-27T10:02:18.293Z" }, - { url = "https://files.pythonhosted.org/packages/1f/eb/42f31c5a48811787ff59a9869721f70a49654d65ab6c455f4463c39b044e/msgpack-1.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8b2a281b556f120a43e591ea39915741b7ad54d4727b9c4350a0a11692252533", size = 83911, upload-time = "2026-08-27T10:02:24.06Z" }, - { url = "https://files.pythonhosted.org/packages/33/54/10c6c16ddba8a5112e3680176b838e3694e4aad7284f9daa6d6d70d98817/msgpack-1.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e8cdd1f3e7cc52c751092a9bf740e81e6919ab109cd376ae2d965dad0bbae34", size = 83734, upload-time = "2026-08-27T10:02:25.613Z" }, - { url = "https://files.pythonhosted.org/packages/d7/75/35823e4419df8792191b2a17ae3fe71b41d02c162b2c491c94d1a87f0caa/msgpack-1.2.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1814f92306ae7862908e9ece7cfd90e0dc87ded3e89b6ae7ffdd1175d6376fdc", size = 405635, upload-time = "2026-08-27T10:02:27.012Z" }, - { url = "https://files.pythonhosted.org/packages/6e/d3/6592e4064619b04f2dd0054c5fa13e37e3d55eb26044483d871fadb2f46b/msgpack-1.2.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d24b38a825bcca41bb956de50eb98451ef291304a8607fad99e619043d3e79b9", size = 417332, upload-time = "2026-08-27T10:02:28.776Z" }, - { url = "https://files.pythonhosted.org/packages/e3/a1/b21c6818a545e9a4a976ac954a5c250eecde9a02e0ec82f415473dab1324/msgpack-1.2.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34e83e345194a2a51d8bd447dea9de2104f91e75b247f4735f14f04529f0746b", size = 374378, upload-time = "2026-08-27T10:02:30.678Z" }, - { url = "https://files.pythonhosted.org/packages/03/8b/7ada15c7b64151d6dbb562d1b091520efb2c37acf2403b1d4ae13797b27d/msgpack-1.2.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:682804bf31e43d46e51a9a33bd575b51e839d715ce6bd5612c055f7b28ad637b", size = 395809, upload-time = "2026-08-27T10:02:32.322Z" }, - { url = "https://files.pythonhosted.org/packages/bb/f7/96283e50f7020df4dfeacc55612b7a210c8cdf0dda48bc262f1f9b3e4c49/msgpack-1.2.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9b659d77f8726fa5e7038967dda6b68d53cf34472c094cfa5b845454713b90d5", size = 373495, upload-time = "2026-08-27T10:02:33.832Z" }, - { url = "https://files.pythonhosted.org/packages/cc/fe/1548dede9d9ca482f2d424a2e110a9705d4e02627a16b8bc8d10ce0208a2/msgpack-1.2.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4d9a562aec0a92fe536da2e533d313b3d2a6b929157b1dec7ff623446dc0a8ab", size = 414360, upload-time = "2026-08-27T10:02:35.396Z" }, - { url = "https://files.pythonhosted.org/packages/5e/50/3e92c403346652cabd08cb8faceef847bae917ea3b3c81b64a5b6d09ed41/msgpack-1.2.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e497ee34e8a3342bbde51b27c22d8db05a651df3361dd3daef5b3ab0d66f3e04", size = 84315, upload-time = "2026-08-27T10:02:41.181Z" }, - { url = "https://files.pythonhosted.org/packages/b6/dc/8efe6dd96a12ab043930cb4cffb40b6e7f061491d6ec7a3d2b75ef1fda42/msgpack-1.2.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0dd9173c5ebaf5ecc5ca86e7ae1db92934e1d57b856f3dd90698941431f4fd77", size = 84634, upload-time = "2026-08-27T10:02:42.621Z" }, - { url = "https://files.pythonhosted.org/packages/1b/89/996573095bf7b038c04dd65ddbc4f1a4d381b0f7a44ff9186f3c7b8325c2/msgpack-1.2.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8dc4487097571f7311188c3eca2a3e86cd1f1db4c37c7a017bcc3fd38486cbfe", size = 404194, upload-time = "2026-08-27T10:02:44.096Z" }, - { url = "https://files.pythonhosted.org/packages/b6/4e/46f5a5d949dbd054dab60cb15aac7ac6ae6774c134532893414689bf2f53/msgpack-1.2.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73b0e05c32c3cfc3cd84994908e57430c0ebc6813abf905d3f18ff115d54df3f", size = 412343, upload-time = "2026-08-27T10:02:45.747Z" }, - { url = "https://files.pythonhosted.org/packages/da/e8/739a94197358a313307e6e9e7d8d22ef66add39222de911a44161aa96920/msgpack-1.2.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aa1120c653b76d8eafa50423b5eba06b5c9737f8692c74fa3afe03e84b8978ea", size = 372620, upload-time = "2026-08-27T10:02:47.578Z" }, - { url = "https://files.pythonhosted.org/packages/03/d4/09b92e1fcdccea9466bfae45455367ac52362ae445d96a602e51b7a8df73/msgpack-1.2.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccfd880988f8438d1c91c77d7edc58e70f4d2012e999167bc154c64c6f06ea6b", size = 394603, upload-time = "2026-08-27T10:02:49.172Z" }, - { url = "https://files.pythonhosted.org/packages/47/db/d11bd6f258a60703dcdc7a3772818ad0c2f602ee4c2acfb24088c6c3ebc3/msgpack-1.2.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:6195257a107bf25872ef84aab7295078271eea3ac6413f0506b631f6c9586ed5", size = 372666, upload-time = "2026-08-27T10:02:50.886Z" }, - { url = "https://files.pythonhosted.org/packages/71/cf/fbbbac0c6e5fbb9d51abc23e3b5fe8620f5c01e0588797cf664a623bb9e1/msgpack-1.2.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b8dd6c71d20c28d2d0eb0c51e7cccf3584afde3b1364f6629596186c9025bd54", size = 410889, upload-time = "2026-08-27T10:02:52.51Z" }, - { url = "https://files.pythonhosted.org/packages/64/58/7e764b957bae80ae281a9cb28761068c8bae8d5c6ac0873e43cc69d176c7/msgpack-1.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f466049b8e1ec0854287bbe9a074316826fe0e08dcf707245f98b1ae49e92650", size = 86594, upload-time = "2026-08-27T10:02:57.796Z" }, - { url = "https://files.pythonhosted.org/packages/ae/f0/250f5985b6ee533e60d357571a808aaae03c54118294dc3db7158e27feb1/msgpack-1.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f6b6f8deb07d49090e1808c6ef9cb7d23ca17bef3aa6ed3e5e03df16606e60c", size = 87374, upload-time = "2026-08-27T10:02:59.256Z" }, - { url = "https://files.pythonhosted.org/packages/bd/2c/126ec8f187877c5f688631c543d1d3a3d75b2e66b83fb9de3ed7c13a39b6/msgpack-1.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b542ffc0a5c531eedc40419f291f1bd659aa8d4223408a5b51c88a2796083fd3", size = 428157, upload-time = "2026-08-27T10:03:00.9Z" }, - { url = "https://files.pythonhosted.org/packages/95/21/d2d81d50aaedb14147d01f22094185794db3ad8a8791b60afacba0627c89/msgpack-1.2.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d095df2627e5dd59ac7b0c5ad627a671c76e6020171e03cbe4621a61f0562c3", size = 426669, upload-time = "2026-08-27T10:03:02.457Z" }, - { url = "https://files.pythonhosted.org/packages/c6/fc/f7d484ee5b572719608e7ffad569bea22ff11309a96ca2fae85eec94226b/msgpack-1.2.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ffdd2f4950daf7815490f23087963e3420175b9609520b7ff5df64d351159c22", size = 380625, upload-time = "2026-08-27T10:03:04.244Z" }, - { url = "https://files.pythonhosted.org/packages/0a/c4/b924cbd5516676f4e612329f18602a833bd055ffbe27f808eeba0f01bfea/msgpack-1.2.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:652d1bf13d01bac8fd569def0fe76745e55bcda01e30aa6332d5947ea3788839", size = 411328, upload-time = "2026-08-27T10:03:05.869Z" }, - { url = "https://files.pythonhosted.org/packages/27/9d/0c1d9683a951a80f270c3b7dac1022c18b9307617344dd44d904135d5e12/msgpack-1.2.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9bf452ff4d4981f25a18e9476e002bcc9263e7928024aa4d7148e25f7be3f929", size = 377892, upload-time = "2026-08-27T10:03:07.37Z" }, - { url = "https://files.pythonhosted.org/packages/06/bb/bf22338cdd22e0b40c8f28468cea5f3d9c320244c095d8303364bc012c41/msgpack-1.2.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:55faa6f8395e23b848c535ad5dcb96b3462f37f5e7f4ac500d500434f7345da7", size = 419426, upload-time = "2026-08-27T10:03:09Z" }, - { url = "https://files.pythonhosted.org/packages/3b/92/4b44bc8f3243ef8cf9cb5368c17a299d45b9df858f6dfdd98a0482dbbb37/msgpack-1.2.2-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:e1b99ad34613d5f8477fa5cf99bc4eaeaf27965588007c102370cd9a78fe9de5", size = 84293, upload-time = "2026-08-27T10:03:14.718Z" }, - { url = "https://files.pythonhosted.org/packages/80/05/c992bb65744665a41b5bf531fc0e1619bae0901f57738228ded90023c151/msgpack-1.2.2-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:0fbc1bed8a535389b41882cfae66376e248cd1680eaa94fd83193c73e1d24986", size = 84490, upload-time = "2026-08-27T10:03:16.12Z" }, - { url = "https://files.pythonhosted.org/packages/d7/bf/7f53b9e6709a4df7f9b9b81dc65f9dfaa32caf65bee94986ec2cb8fa07f1/msgpack-1.2.2-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:06d95f61de7afe4f4ff908a6feebfcb070d0582ac87c9cf3cedf8551cf634516", size = 405332, upload-time = "2026-08-27T10:03:17.692Z" }, - { url = "https://files.pythonhosted.org/packages/d2/5a/305c4dca14b50d0b51fb88ef04ec125b8f0be3e2ce730dcc62dbaa651cc5/msgpack-1.2.2-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b5c696ae7cd7166b3657261adb855b461ff31f07823fdbae9de8bf80adfccc21", size = 416798, upload-time = "2026-08-27T10:03:19.389Z" }, - { url = "https://files.pythonhosted.org/packages/7a/df/a645102b4cdfd9a94201cac4e900e9c1429fc16d86aa311c06eef82528c9/msgpack-1.2.2-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0708afbf6a9587f0bfe479a9825c141d14d91e2f6a5c8103cf28bc96f4edb5d9", size = 377312, upload-time = "2026-08-27T10:03:20.928Z" }, - { url = "https://files.pythonhosted.org/packages/f4/26/c56d8d086d3fb1077bb48092b158b5ea2eee08b279e10c191275f13bc980/msgpack-1.2.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:226a62ffe99fe54c5c61d910ec64c3449b7766c3280bd286bf6c94838dde239a", size = 395182, upload-time = "2026-08-27T10:03:22.571Z" }, - { url = "https://files.pythonhosted.org/packages/4d/b5/3d46ba367a565e536d8d2a61eebcee71b1dc803da3ce74a22313b573d6fa/msgpack-1.2.2-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:9fd7f32e2f0fb334e7ecc5adb5cf0458785bd3a9d9d86f950e1715f101cebce5", size = 377945, upload-time = "2026-08-27T10:03:24.151Z" }, - { url = "https://files.pythonhosted.org/packages/a5/2c/d5d2df273ed5306357da25b69400fd8d7a53c4d87d8976604b677484d61c/msgpack-1.2.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:9db1ba1c1e6a84245a9dd866265b56b8a1e9461549cc72ed296d8cbfbd32961b", size = 413341, upload-time = "2026-08-27T10:03:25.85Z" }, - { url = "https://files.pythonhosted.org/packages/cd/20/12751ca0d8ec874701b54c392c2b19f51af8dd1de40a92a10e356f0aaf58/msgpack-1.2.2-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:d886baa46b2532135e7320067e6a44edb09ba5883a6096b0f9c044533984b8a8", size = 86462, upload-time = "2026-08-27T10:03:31.348Z" }, - { url = "https://files.pythonhosted.org/packages/91/4c/cf6d12a3d709fe5f9771dd917c35e6ebcd55597a5b792287382fde056c95/msgpack-1.2.2-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53679573c75cce5f82359e0bd4e6a97809a6b9a9b7a48fd1ba592f4a82cddc84", size = 87412, upload-time = "2026-08-27T10:03:32.74Z" }, - { url = "https://files.pythonhosted.org/packages/ca/0d/0aac5752d1708dcb458f8754db34a4999514db3df2d2b798b9381293f638/msgpack-1.2.2-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d3c247d457ae9079974c7ce3c665396754a6d2baff7eaa51332212a8a5a3f13b", size = 422057, upload-time = "2026-08-27T10:03:34.124Z" }, - { url = "https://files.pythonhosted.org/packages/81/30/70f281a3685b04aaf235a5237da11b978a02a865a5a479186205177ad676/msgpack-1.2.2-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352ed831042549cca8be23780e1fe7c9177e65ff02bf183509c4b4d33f671782", size = 422696, upload-time = "2026-08-27T10:03:35.862Z" }, - { url = "https://files.pythonhosted.org/packages/8a/6d/f76e8425efb0aa38988cd778ae290bfa120491d80d26872d88bb52fedb3f/msgpack-1.2.2-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f80361592c13d7226b4379c8941529b63fe1a9d0e05d2de8f3306b70e522b53f", size = 376495, upload-time = "2026-08-27T10:03:37.339Z" }, - { url = "https://files.pythonhosted.org/packages/95/77/0809aa9b52b2868f7d01862dc14073708f0440421a65197b48453480034c/msgpack-1.2.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:68df2947921d449f6dcfeafd86cb2cdde13327a8b447534bbe4ee5aaf32a5695", size = 404683, upload-time = "2026-08-27T10:03:38.87Z" }, - { url = "https://files.pythonhosted.org/packages/02/d2/4e5ac915ba120172d210ef00165c5e6276c8a65db3a4a5cf36e946b83e23/msgpack-1.2.2-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:51dd39d23cfdea0400ed3ff2d29d1e83bd951d3aea79dc89be5b701a09edfe23", size = 375087, upload-time = "2026-08-27T10:03:40.486Z" }, - { url = "https://files.pythonhosted.org/packages/cb/e3/8051d53e5495c87c6cf27eb42fb680361017037f87f322bdaf525f71e4a2/msgpack-1.2.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b13b59e66f107cca1ba708dd5307179870ca1b15b19fcee7ccf722e5308d9212", size = 414421, upload-time = "2026-08-27T10:03:42.308Z" }, +name = "msgspec" +version = "0.22.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/e6/6dcf9306ff3c5e486578f3bf29ed11dfbdbbc2a8bf0caf7e07d392887fda/msgspec-0.22.0.tar.gz", hash = "sha256:0a13624a4969159fe35d8c2a3d377b2b61bbd8585e327440d5e52725affcce38", size = 343188, upload-time = "2026-09-29T14:14:11.422Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/22/45c17acb1a85360b10afb95f66777f76bc2634993c66db8b7833832bd343/msgspec-0.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb1e129b81ac8fcf9ec649b081c6c8da1c7ea6f87cab336d46386abc2cd855c1", size = 198231, upload-time = "2026-09-29T14:12:23.016Z" }, + { url = "https://files.pythonhosted.org/packages/34/79/1cf725694125051e866066d74e6199206838d1465cbfc35081dc29b6e366/msgspec-0.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dce29a04966e31abf9b83b697c6d672486526dc5d03fcd6970cb56d5dc1fbeea", size = 190911, upload-time = "2026-09-29T14:12:24.636Z" }, + { url = "https://files.pythonhosted.org/packages/bc/b2/e0ace038031a2988aa2e85c431c4d7aef734fbba4749ace6bc5bf310b769/msgspec-0.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b962000e11dd34fb210a5a2c57a8a62b2d92b381c8cb3b05c075a83e38f8d645", size = 220343, upload-time = "2026-09-29T14:12:26.111Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e6/16ddb09185d79dc00177994cf0bdb1cd8e5cc44a1d1bfba61bdda5f382cb/msgspec-0.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6db3806b3b76ca78064255eac6fa101a8a64fe6f698d80fbaf81fdfa21217d4", size = 225251, upload-time = "2026-09-29T14:12:27.559Z" }, + { url = "https://files.pythonhosted.org/packages/16/c2/a6af0d38fb0e72f02851ed084c4b8175140cfaf3eaf48b38da0c3941db26/msgspec-0.22.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a88d939d3fe4b8c7314645ebcd6e86c8c8a512ea7820d6550355973e803bc0f1", size = 233488, upload-time = "2026-09-29T14:12:28.996Z" }, + { url = "https://files.pythonhosted.org/packages/0b/9b/b1c4208cdf487e2ba7af145f721b279444ff76af05a9f8fce992ed0588ee/msgspec-0.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0b31746da07cba0e330c6433a94a4699ad77d3aeb9638d1a320a7686b69f6249", size = 225688, upload-time = "2026-09-29T14:12:30.351Z" }, + { url = "https://files.pythonhosted.org/packages/83/54/b9240d908674ef7c41d02cb909731ad6d9931c23bd6a27d8d10776c6f964/msgspec-0.22.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:6ae370f92f3517f0e6f209ba7cc649c957b444868439197e046be07154667551", size = 234250, upload-time = "2026-09-29T14:12:31.887Z" }, + { url = "https://files.pythonhosted.org/packages/df/c0/d498798aaab3bd191a33955de47b40f07fae7667d86a33b705443a7e9491/msgspec-0.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a696f23f7c1ffb31fae308502e01a3965c3891d5c400f01d0d1096dbe77519e", size = 228337, upload-time = "2026-09-29T14:12:33.365Z" }, + { url = "https://files.pythonhosted.org/packages/fa/51/5e9ae5a5ddc254e15435749328161e95598750e5df644bb00fa9e2297122/msgspec-0.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:024138c51afd335d0b4dce401be33902caafac2b64f8c9f2509a378986175d98", size = 190962, upload-time = "2026-09-29T14:12:34.847Z" }, + { url = "https://files.pythonhosted.org/packages/12/38/fb64a18543bcbebc53a375cb00b1c93bf264a0b6c7bbe9e38b37cc5f0768/msgspec-0.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:4600dbec738ed74e4c9bd35503e84701200ea7db344cfdeda80677b3ee53eb64", size = 189458, upload-time = "2026-09-29T14:12:36.277Z" }, + { url = "https://files.pythonhosted.org/packages/a4/87/3e017dca361d09ed1cd09dc981a6df21b32e830fbec3470f7486d38b6be5/msgspec-0.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab1e9e7531e353653b906cdd12a0220cc288a1e8e3436aabc65f4508d91b14d9", size = 201301, upload-time = "2026-09-29T14:12:38.048Z" }, + { url = "https://files.pythonhosted.org/packages/fb/02/109165edaafb895668d87177972a32ade9126a54f3736123d8e44be9096d/msgspec-0.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b60b43425a47eb9cfe987f6874e354ca7c760e58e295b4e2273ff03574df28a1", size = 193044, upload-time = "2026-09-29T14:12:39.46Z" }, + { url = "https://files.pythonhosted.org/packages/54/a5/65de05f8804492f76ea121b21a125cdf1d97ec461c677bfa0ba354d6fbdd/msgspec-0.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5a169b5b03f0f2c7a296c002647db1dab75d2cd501bca34e32b71cab0261b56", size = 224035, upload-time = "2026-09-29T14:12:40.876Z" }, + { url = "https://files.pythonhosted.org/packages/4a/cc/aa1a47f8c92280d37498a5ea56a2a36606d034383e3e6472d64cbb56cf85/msgspec-0.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99c401861c5bb3a57f7d6423ea7ed4352cd57aa3f04f4fbe9f3e3e4564a10f08", size = 230377, upload-time = "2026-09-29T14:12:42.796Z" }, + { url = "https://files.pythonhosted.org/packages/61/50/f8bcdb3d613a4a4b92704297a12eba5c985cf572a64ee1a004d265759c69/msgspec-0.22.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08826f5e5b0fa2f7a88592c396a243cfcc63d37e19f9d4fbe3b3f1be2fbdc404", size = 237390, upload-time = "2026-09-29T14:12:44.282Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8a/473fa423f8fdd1b810b8652594323d7301df6920b62844d860daa0feff34/msgspec-0.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21460f54cee9208239b1a8421fdf25bffc77293e1daba88f585711ad839b9758", size = 227733, upload-time = "2026-09-29T14:12:45.839Z" }, + { url = "https://files.pythonhosted.org/packages/03/1d/272ce23adae6c71b3f763aed3ee6e115cccc56124ed8ee0e3e3d2681e2c8/msgspec-0.22.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cfc3d9557de9c806318725b702f3e664db33167bb42892079b693c69893fd33b", size = 236783, upload-time = "2026-09-29T14:12:47.234Z" }, + { url = "https://files.pythonhosted.org/packages/f6/26/29e0b9a8605c8819a3c718158e345a616ac42c092dd7d7ab248c2f2b0a72/msgspec-0.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0b25dcbc108783cb72503ed705b9fbb8c3cb02ee5801923f44b5f038c91cc365", size = 232728, upload-time = "2026-09-29T14:12:48.792Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a6/99597c281d716da6c662b48dcc3f734669f716b41d5df2af367dac9e7c21/msgspec-0.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:6ad64f5c260866b0d543f89f50cee43628989c1433c5de7ce820281fa28a2611", size = 192885, upload-time = "2026-09-29T14:12:50.274Z" }, + { url = "https://files.pythonhosted.org/packages/46/80/85fff923d448b886ec3a85900c578d9367f08dad54fe48879495b4c6d055/msgspec-0.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:0922714feff5300aacd8ecd65fa828317ce4bf5212b3139258c0bfc0253cd80e", size = 191223, upload-time = "2026-09-29T14:12:51.699Z" }, + { url = "https://files.pythonhosted.org/packages/7f/62/5374fba2ede0408f4bd8b9b3a6c8464f8d0ea7ae9a2a064bd81ca492bd1e/msgspec-0.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f13c127a945479bc9db057eb253b8851075c8e1ae07ffc967bfa1c5676203a86", size = 201355, upload-time = "2026-09-29T14:12:53.145Z" }, + { url = "https://files.pythonhosted.org/packages/cc/e3/357baa8d2a9164a98dfd7ef9d3a58125df0ed981be909945bdd337be7194/msgspec-0.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5aa24eb475d070ecbbe5b21080fc3ce4b0b76c60de25cfe0c9678d8fb44bb42f", size = 193097, upload-time = "2026-09-29T14:12:54.52Z" }, + { url = "https://files.pythonhosted.org/packages/fa/1b/9cc07718d1dee8ed5e89a265801d565bc0f15ead435ccb198f9c7bf92574/msgspec-0.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:627bfdfe5a4b3d916b3360b30f4cddeee3a084f56593e33527c6872fa8322ff9", size = 224112, upload-time = "2026-09-29T14:12:55.983Z" }, + { url = "https://files.pythonhosted.org/packages/46/64/f33fdfe95aca76601194a7064d14816c7c22c4eccc1b03a5335785895fa3/msgspec-0.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6c310ef83e7e291b01a63298828f848348bb99e84a1098c4b3923c05674d032", size = 230472, upload-time = "2026-09-29T14:12:57.648Z" }, + { url = "https://files.pythonhosted.org/packages/8e/b3/8ceaa9981c230adf43c45a6e8da25da23a381eddc7ed05aeaca1d5e7928b/msgspec-0.22.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7c1e76c6bd523141b9c05c2f8a70979cd0efedbd68855a66f292f8892c0b8fc7", size = 237382, upload-time = "2026-09-29T14:12:59.414Z" }, + { url = "https://files.pythonhosted.org/packages/88/a6/7b5c4fb39e0bf2dabc8be923c33c39b07ba769a0ce6f0afbbdfaadb1f2f2/msgspec-0.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc374dedd5f85a5f4de2386dc5f737894ccb8c1ac18e9566ce66fd9839e6285d", size = 227717, upload-time = "2026-09-29T14:13:00.88Z" }, + { url = "https://files.pythonhosted.org/packages/b8/5b/2334ee638880e756c8bc54a1177bd65877c786433693a43594ef5ecbe2d8/msgspec-0.22.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:feafe612034d49e9144340c0b5168ee4e22c2af4aaa2c1db11ae84e1aac9543b", size = 236781, upload-time = "2026-09-29T14:13:02.468Z" }, + { url = "https://files.pythonhosted.org/packages/6c/e5/b4c5323b17ecfce45350695d40fc93e16856db957a53cbcf2f53007d6e12/msgspec-0.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f48317f05312bfdf78248f53933f830f07ab75cc1c813ac3ca4220cb3b5b019", size = 232777, upload-time = "2026-09-29T14:13:04.025Z" }, + { url = "https://files.pythonhosted.org/packages/01/33/e591f9d3d8d6c9cfc02ae95f3e3c44920f2d18050f3f252c244e0f293a0e/msgspec-0.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:0739b068f31f2004a364f97679ba91f2f5ecd6ec2a5b4b890188ab5c57d20672", size = 192829, upload-time = "2026-09-29T14:13:05.519Z" }, + { url = "https://files.pythonhosted.org/packages/d1/cd/a011a5b8732cd781e2ea6da5b38d71ae4a9a329338411d1f008a58f5edbf/msgspec-0.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:508278300dd4efbd21cd3a4b2b016160a5feac98bc880d3673f6c06697baaf62", size = 191258, upload-time = "2026-09-29T14:13:06.909Z" }, + { url = "https://files.pythonhosted.org/packages/53/f9/ac027b35477e6b83bcee32b3d9675b37abfa130f098dd6500fa67d768852/msgspec-0.22.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:221cbcbfa4478152b91d37dcfd4830e2be92773e8139e883f43773450ebacef8", size = 201276, upload-time = "2026-09-29T14:13:08.311Z" }, + { url = "https://files.pythonhosted.org/packages/13/6b/2bffffa31662b1353a62e672442865d51c291ad778352fd490de16361dc6/msgspec-0.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dd9568695911055440d2bb7099ed9098fc181d335daa772d0eb3fe8f31ba4efb", size = 193233, upload-time = "2026-09-29T14:13:09.943Z" }, + { url = "https://files.pythonhosted.org/packages/14/bc/4066416ff6aa918d1ef9295edee0041e4629e4079ad3839bdd8a68fd87f0/msgspec-0.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f039ef5207b847f075a0a43020ee6140cd47505f890e47e157f2deb485c2dc96", size = 225101, upload-time = "2026-09-29T14:13:11.391Z" }, + { url = "https://files.pythonhosted.org/packages/63/ba/a8d390d5bd4c7d9ccde87c95cf071ada934cc9ca2c6af4d3d50b38f2d718/msgspec-0.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4f7e09cceac7dbf4c0761b8ae7df51c55b5df5e9af7aff2c895aac1ebea015", size = 230505, upload-time = "2026-09-29T14:13:12.869Z" }, + { url = "https://files.pythonhosted.org/packages/9c/89/979664fdc913c624ef88a139b40e3a95ddf2a47c89e8b5c4147f69ee9c48/msgspec-0.22.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:614e2c827e0a3f934f3cf0cf4ba65210df8132b75a69a8a1f51bb3b2caf0ac5a", size = 237382, upload-time = "2026-09-29T14:13:14.317Z" }, + { url = "https://files.pythonhosted.org/packages/07/3f/7d44c614376ae008ac6099be5f589b322c4ad44e32c6dbb0edd256215028/msgspec-0.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3689b9dfcc663358ef23ba4299d7460f01108515b041a7d30d05908ac9c32f", size = 228962, upload-time = "2026-09-29T14:13:15.763Z" }, + { url = "https://files.pythonhosted.org/packages/0b/59/bf8504e6f63f6769d01fb66f8bd856cf0ed39a07fde354f440d711640054/msgspec-0.22.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d2f950239ff1fc7322c6f9634807310265149cb168270d3ddcdda5b6ada13a28", size = 236691, upload-time = "2026-09-29T14:13:17.195Z" }, + { url = "https://files.pythonhosted.org/packages/2b/40/5a9d2bde12af16a22ddbf371990a81d3e3c0dcd4bb4ef3b3f9616b033c14/msgspec-0.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3c789b5ccd07c0a3c09767108ee06e089b2875f2309a4569c2648f30a8d31dfa", size = 232750, upload-time = "2026-09-29T14:13:18.691Z" }, + { url = "https://files.pythonhosted.org/packages/75/5d/c0e6bdb81a87f6bd56a663a330c271af7670490c80d8d635d9fa21ad1adf/msgspec-0.22.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:a66b1766311e42371e509c996c3933b161c7ae0eabdf361af5316dec197e1022", size = 136814, upload-time = "2026-09-29T14:13:20.415Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c0/b0cfc6d33608e5ea8871f3be31f9146c56699e737a7d8862bf018484f278/msgspec-0.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:749899563d26b211379f142b8ffd7e2d7da149a51717798f0ce994dce50324f0", size = 197097, upload-time = "2026-09-29T14:13:21.869Z" }, + { url = "https://files.pythonhosted.org/packages/42/1f/571f7fe7c725380605d680fc4c0084212b23d2dfcf6be0f2277f14462c56/msgspec-0.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:10d0d1d464960d99a949f7ca01ef8928e51c472433a5f5ab74b2d695fb830652", size = 196779, upload-time = "2026-09-29T14:13:23.62Z" }, + { url = "https://files.pythonhosted.org/packages/ab/f3/3c87372bac651b37911e0dc6926c3958949d3fcb8cec1016adbc44d948b2/msgspec-0.22.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:e79725246291516a7359caad5fb743ddc0ec66ed40d2381fb846325b5031504e", size = 205214, upload-time = "2026-09-29T14:13:25.158Z" }, + { url = "https://files.pythonhosted.org/packages/43/4c/fbccd6e0fbbdf10c4d9b6bac8a26148dd5483b3ffff6d6c5a376ff1f5cb1/msgspec-0.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:38f7022fbe91954b31afe3888a0af1b652e0f370fafdeb1d425f4a814d789c9f", size = 196941, upload-time = "2026-09-29T14:13:26.637Z" }, + { url = "https://files.pythonhosted.org/packages/55/04/8db7186d3ae8818356bc623cc132db8b77da37ce4b1345f35719c8ad5726/msgspec-0.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b6d3ca19a8ff28d0a67a1824e2bff7ec649ec795c80a265f20ade4caa63080de", size = 229934, upload-time = "2026-09-29T14:13:28.285Z" }, + { url = "https://files.pythonhosted.org/packages/17/24/a249f3491cabbe77cc65a1a6f87c128582aa39357227149be61cac8e554f/msgspec-0.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8b98ae215a102cbf6635f7df45f5c4af12f77fad1f7b71b9808fcf868a5735d", size = 234378, upload-time = "2026-09-29T14:13:29.821Z" }, + { url = "https://files.pythonhosted.org/packages/87/ee/6dbcb1b5de8e9d47e8f0fde9a288628dc178c1749a570b98251218fa10c4/msgspec-0.22.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0aa0cc3f18c35bab79bd7b87fde95d6274a9deddeebd1ea541f8066a5073165", size = 243118, upload-time = "2026-09-29T14:13:31.544Z" }, + { url = "https://files.pythonhosted.org/packages/79/03/7dd2d0ca988600e01fc00ad0cf20d1d44bc59369a913c988654c65f6582b/msgspec-0.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8c8e84789918fbc15a503b92a829115ddd7567ecd3e4778bd418c56abbb86c11", size = 234557, upload-time = "2026-09-29T14:13:33.068Z" }, + { url = "https://files.pythonhosted.org/packages/74/e2/43f3c63bff1650efcaaea31466246e28b46927323fc9ff416c68cc6e4047/msgspec-0.22.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:3ca7d4cd69fbb66bd2da6211d3e79d40542d196c16c6d99bf838f76767ad35be", size = 241288, upload-time = "2026-09-29T14:13:34.532Z" }, + { url = "https://files.pythonhosted.org/packages/8b/70/11b93815a59674f33182dc3e873d343ca0b37e25be52ecb28f52092f1fed/msgspec-0.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:28f53f3604dd3e70225f7563c831628dbb03299b428f8e62aadb4b628e386874", size = 236432, upload-time = "2026-09-29T14:13:36.083Z" }, + { url = "https://files.pythonhosted.org/packages/b7/82/7aad0f033f8dcb3f23868773c2ede803ae162a784828ccde75aa3f9b2f9d/msgspec-0.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:7293dee54de040cfa225c22151cc3d72f17cd674b5ebcb52f38fb9f5701592e6", size = 202062, upload-time = "2026-09-29T14:13:37.955Z" }, + { url = "https://files.pythonhosted.org/packages/e3/45/cf52577926d73e2369e25927e389cb4ea1461169c489f46d3248159b5be7/msgspec-0.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:c3c510aba9015c085e514b75a9b3f1ed7c4591ae5e379655821b8bba51f30cc7", size = 201686, upload-time = "2026-09-29T14:13:39.42Z" }, + { url = "https://files.pythonhosted.org/packages/c8/63/d93937e2aae34ff1ea33b62799d1963cacc1bf432d196d6130039657a122/msgspec-0.22.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:263e110955ed76fe0af2d79f819903b50a70dc0e7a752eb7aabe79d2e0a084fb", size = 202241, upload-time = "2026-09-29T14:13:40.919Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e2/46ece11a244cd56432eb2362ffbb8014f3f02963136d84d941f71fdc2a3f/msgspec-0.22.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:c6f06576eced70462179a4b4638e84cf69fdbba37f44d13a64a21739c131a830", size = 194232, upload-time = "2026-09-29T14:13:42.454Z" }, + { url = "https://files.pythonhosted.org/packages/cf/b1/1c385f2f93006cdc2af1511cc512c347cb22e2d4f11952c205230aedf586/msgspec-0.22.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8d67582478b0eaabb899f2fb255c878ee7de57dff80eb73ab24f1865524ec441", size = 226524, upload-time = "2026-09-29T14:13:43.876Z" }, + { url = "https://files.pythonhosted.org/packages/dc/fb/c80c8842d40347cacf89a60a4986b849dae1a6dfd25830441efdd6faa65b/msgspec-0.22.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:71cbbdb39631064e2f2f9e9ac2b1b69931d72276eb5f9da4ed025726296bdbb6", size = 231816, upload-time = "2026-09-29T14:13:45.329Z" }, + { url = "https://files.pythonhosted.org/packages/73/ac/90bbcfd890b4bda90c93f7e1b7fc24e84b270420486d9d43ae31443d15ab/msgspec-0.22.0-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8f0a5c25516e2034b2db7767081759ff8996e214def9c43b3055f61e1be1caad", size = 244241, upload-time = "2026-09-29T14:13:46.851Z" }, + { url = "https://files.pythonhosted.org/packages/72/9a/eabdb5f1b5e6013b0e2f9f2a95790587f6864aa9ca37f9d7dece65b53878/msgspec-0.22.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:a1dab6a99c759d1391ab2993388c1892746a697254f4b5dc6c059ca6e3bfbc8b", size = 230198, upload-time = "2026-09-29T14:13:48.296Z" }, + { url = "https://files.pythonhosted.org/packages/e9/89/9f080532d4ac52f416dd7318e55c2053cc071853d17d58e24897a5b553bf/msgspec-0.22.0-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:a52eba5c9528fd181fcec39d22b67aaa1dccc6cfe8e24d3f5d41130e6d04289d", size = 242949, upload-time = "2026-09-29T14:13:49.829Z" }, + { url = "https://files.pythonhosted.org/packages/11/df/6baf9b2f3523ebe2b820820c7929fd72ec5f483a93147130338ecc353fac/msgspec-0.22.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:1e547966017265c0d23342bcf2e027305dde40ea042d16694a9b96b4f696a052", size = 233914, upload-time = "2026-09-29T14:13:51.5Z" }, + { url = "https://files.pythonhosted.org/packages/bb/37/9cf650779c8c1e53291ef184c838703930a4cabb1fb37e222c85a7d49fa9/msgspec-0.22.0-cp315-cp315-win_amd64.whl", hash = "sha256:0067057df265795f742658b15dbe53f3b6f21d19dcfa53676db11088cfa41e0a", size = 197910, upload-time = "2026-09-29T14:13:53.071Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ce/2f78c93d4f69e0167a19c2d40d4fbf7bbd6f074e1047536735832a4368ee/msgspec-0.22.0-cp315-cp315-win_arm64.whl", hash = "sha256:05dbc8268e50c9232ec72b9af1c7b13049aade4d1197764e38c427048706e046", size = 197590, upload-time = "2026-09-29T14:13:54.47Z" }, + { url = "https://files.pythonhosted.org/packages/3f/bf/282e9a443058b85b8f706c9a651e2d8cdd11cc09d16e8fa347b6c57b75bb/msgspec-0.22.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:b3113ebcceeb7693a915183c73d92c10bf5c62851dd187cab43bd025fb587419", size = 206298, upload-time = "2026-09-29T14:13:55.913Z" }, + { url = "https://files.pythonhosted.org/packages/ef/2d/2e694fa46f55319007f72013b17341ea3868be1c77e7a597176b202dda92/msgspec-0.22.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:0dfadea8bdcfafc614bd031de55a8ede22b43445cfff6d8b77cc0c07d3edc8a8", size = 198145, upload-time = "2026-09-29T14:13:57.412Z" }, + { url = "https://files.pythonhosted.org/packages/5b/2e/2fa279cb57cb47175ae604d572787f903d4ad3f0afa867201bbd99e6647e/msgspec-0.22.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7a738826936c72348c613061d260446f13c82b6fd7d5d7705b6911ab8dca2f3", size = 232362, upload-time = "2026-09-29T14:13:58.817Z" }, + { url = "https://files.pythonhosted.org/packages/a0/58/a7e759b11b28441c27f803b29d9b5f4b5ad85150c89354b5ede1baca9258/msgspec-0.22.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f2ddea9d78d09460f06c26a7a508adcd049761c3208776162b8eb79b8a032cff", size = 235885, upload-time = "2026-09-29T14:14:00.381Z" }, + { url = "https://files.pythonhosted.org/packages/86/56/8d7ee098e94cbd9f35fa643dc497e06a4a6307b9f562cfbe48103fc3b209/msgspec-0.22.0-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:884c28c80b0a511595b29a9b04a3a230c3797369e4a033e6d5c6d9b5427f8e09", size = 248155, upload-time = "2026-09-29T14:14:01.945Z" }, + { url = "https://files.pythonhosted.org/packages/b9/6d/1cabb4b8a5dbf696e2b24df9e482b2e0333bb3b1b13ebb5433813e6616ec/msgspec-0.22.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:f7a923bcde480065c8e25967464cfb2a687ee67000bb43157e2d57e40eca7305", size = 236416, upload-time = "2026-09-29T14:14:03.363Z" }, + { url = "https://files.pythonhosted.org/packages/ba/43/8bf0f558eb369f1f2d494b3d5ab9d0ae0907d07ecc0cdbe11b6768b02867/msgspec-0.22.0-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:65eea14bc65ccfeb8f3af62cb204841871e2961f002d7fa87dbe0f79dacf1c1c", size = 247292, upload-time = "2026-09-29T14:14:04.829Z" }, + { url = "https://files.pythonhosted.org/packages/81/33/2fbaadf98b5510cac4bb56d2b03937e0b1fb4bfcd1ae6aba20361f299583/msgspec-0.22.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:0666a1520cab86796612e794e71107e0fbf5e8ff3ddcdfcfff8f1d94b860d2f1", size = 238220, upload-time = "2026-09-29T14:14:06.408Z" }, + { url = "https://files.pythonhosted.org/packages/f1/cc/b6be6041098ab859a8472983ccc2c08339fc2ef53f28d4f5fe7f4f34276b/msgspec-0.22.0-cp315-cp315t-win_amd64.whl", hash = "sha256:885c6e0c89d6103648525fe62aa78d600054dedf7b3713d23b15d7ddb6d66a13", size = 202939, upload-time = "2026-09-29T14:14:08.079Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c1/664578dd98be70cd4ab1a9dcf3a181b1376b83c65ec41ee162130b58c8c0/msgspec-0.22.0-cp315-cp315t-win_arm64.whl", hash = "sha256:268594d0bae5510572599a6ab0364dd9de43c867d24a30856cd9f5edb63d8dc6", size = 202117, upload-time = "2026-09-29T14:14:09.891Z" }, ] [[package]] @@ -2546,15 +2498,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, ] -[[package]] -name = "mypy-extensions" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, -] - [[package]] name = "myst-nb" version = "1.4.0" @@ -2739,6 +2682,10 @@ wheels = [ name = "numcodecs" version = "0.15.1" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12' and sys_platform == 'darwin'", + "python_full_version < '3.12' and sys_platform != 'darwin'", +] dependencies = [ { name = "deprecated" }, { name = "numpy" }, @@ -2759,9 +2706,44 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c3/32/233e5ede6568bdb044e6f99aaa9fa39827ff3109c6487fc137315f733586/numcodecs-0.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:a4f7bdb26f1b34423cb56d48e75821223be38040907c9b5954eeb7463e7eb03c", size = 831955, upload-time = "2025-02-10T10:23:30.601Z" }, ] -[package.optional-dependencies] -msgpack = [ - { name = "msgpack" }, +[[package]] +name = "numcodecs" +version = "0.17.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'darwin'", + "python_full_version >= '3.14' and sys_platform != 'darwin'", + "python_full_version == '3.13.*' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and sys_platform != 'darwin'", + "python_full_version == '3.12.*' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and sys_platform != 'darwin'", +] +dependencies = [ + { name = "numpy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dd/ec/260cdb6304868de6db14eb31064bd2735c0200bcb3331d6b4c9e9be02a03/numcodecs-0.17.0.tar.gz", hash = "sha256:e8db2e337bdafd3bb5f891a2543b53b2b36a509ce9d587af2846db3715b6c8b9", size = 6288352, upload-time = "2026-09-17T18:12:42.262Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/e8/28cc96c77078ffcd08579211297cbf1f8ca6e76b4b53c8fbc029b879aaa8/numcodecs-0.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2e29732c5e3a83663e51b40007819d8fd0aae16a2322f7044ce13a2460a99e23", size = 1171637, upload-time = "2026-09-17T18:12:12.765Z" }, + { url = "https://files.pythonhosted.org/packages/96/59/1cde6df2f9baa26a1a21c36ac10312062acace29e5c95e029d8da9cf7c3d/numcodecs-0.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d30c69b4bdb1755af1022fa913e184eaadc4fc0cd38f736e483e8ad205e130d1", size = 978974, upload-time = "2026-09-17T18:12:14.496Z" }, + { url = "https://files.pythonhosted.org/packages/ef/86/15e1cc4e6644d7e33be613d17bb7cc939b1862ccd975fa2ce1055a1e3045/numcodecs-0.17.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1837d4d1d646cecd3ab2d1ba22956295d709edea0bddc952737c647bec1d03c4", size = 1384196, upload-time = "2026-09-17T18:12:16.327Z" }, + { url = "https://files.pythonhosted.org/packages/73/ca/b784745f189a12ccef60517c0c8526d579b40f35da4463c30c07a4677366/numcodecs-0.17.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ebd63cdb8985c66257bc037fcdff5f38637aff72d7ef62612ec46f2299e8749", size = 1436978, upload-time = "2026-09-17T18:12:17.731Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b0/f8b3852828c6712eae36e031d763cd52c2777290406066eae0b2a527c05f/numcodecs-0.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:ecd0f6a10e3f8afbbb16ecc999d2b06aa2a31a2946f1c1a85d15d91a1ebcfef3", size = 1496300, upload-time = "2026-09-17T18:12:19.319Z" }, + { url = "https://files.pythonhosted.org/packages/11/f1/1d3d2bcb1240e5000f6647b5b0fd465b2b51ecef180bfa797a85df48cf2f/numcodecs-0.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de2c66db238e74e66fe9be7e02b7e0129b75d3f812d38e4019eb0102cc2dcdf0", size = 1170875, upload-time = "2026-09-17T18:12:20.638Z" }, + { url = "https://files.pythonhosted.org/packages/64/81/64e2472a8b3a9fa26bccfc7d5fa876770a9027bd5cd77e5b4a7b807a0785/numcodecs-0.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:69b9b4685097c4d478a0c829debf4470555ec63e92cdd2c6b5f195460f1dc888", size = 976128, upload-time = "2026-09-17T18:12:21.856Z" }, + { url = "https://files.pythonhosted.org/packages/25/ea/2ab25f7e674cf1e78f123c5c2689d8a7dc85475554af0619bcce05cb32a9/numcodecs-0.17.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7065b3349b73d54785aa89e00d0b97d80f664e9056757929d28151f9208dc04c", size = 1379341, upload-time = "2026-09-17T18:12:23.159Z" }, + { url = "https://files.pythonhosted.org/packages/9d/96/b3bf9a31978d936654a73f2bb1036b92b6515164f170092d162419eb771c/numcodecs-0.17.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c3342d91ed7cf59c1be84396edd364e936bb0ec9e366d24bb69689748d19625", size = 1430551, upload-time = "2026-09-17T18:12:24.97Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ec/47515bea31725376aa6f061c335326cc7437f863ab704b8e167e80734bfd/numcodecs-0.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:a854e9c89f58eeeb2453f3c1637d1916797edb6eaff26bc186a6cdb09d187092", size = 1492769, upload-time = "2026-09-17T18:12:26.702Z" }, + { url = "https://files.pythonhosted.org/packages/cc/e0/be0a4df898bd2cca26cf8071552925aa66601210c0e35be9c4070ede6467/numcodecs-0.17.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0fc125d1c726c1937cde346e109e3662a2b4ff6be073289da7d124d172aceda5", size = 1172207, upload-time = "2026-09-17T18:12:28.061Z" }, + { url = "https://files.pythonhosted.org/packages/54/0f/9da01fd25953fc37273d7bab7a2174ff74520450a8d77abb837de5d5f040/numcodecs-0.17.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6f1293581326e92293b142bd05b389f6682ed1ce333f36f116344bca340cfd10", size = 978019, upload-time = "2026-09-17T18:12:29.597Z" }, + { url = "https://files.pythonhosted.org/packages/31/ee/e306a14295a67f9852b9856077ed5ab6b67fae5797559ccdd22a3aa6d853/numcodecs-0.17.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a62e5a821ccfbe425bbdd9a079f8b6c41b7e796ff3c99324530561193a53047", size = 1387252, upload-time = "2026-09-17T18:12:31.065Z" }, + { url = "https://files.pythonhosted.org/packages/d9/13/107244147a8b2edd43ffc3f8bf229f07220aed200022d2e0b1dd761b68f8/numcodecs-0.17.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1cce4bf2278ed74841c2088acfd38e67c3e5aa77e3bc1962ef0fa2931becbb12", size = 1432514, upload-time = "2026-09-17T18:12:32.327Z" }, + { url = "https://files.pythonhosted.org/packages/dc/88/9460630aa3517f1745da87201a96ba84169f2ebee9558b223c0e5a35ab44/numcodecs-0.17.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f43ba0d834ce012ed482996a7424df9077a47d5899ede2d1d54fe85e6eb12fa", size = 1534816, upload-time = "2026-09-17T18:12:33.566Z" }, + { url = "https://files.pythonhosted.org/packages/da/d7/c78d934e1baedd5c2e2d06ee087e23f9031ddf2dded75193195ab290139d/numcodecs-0.17.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:657b1f9aa4b1025aa0fa7d4bd8d7492900950a11f636dff622bd208c0b99e35e", size = 1196273, upload-time = "2026-09-17T18:12:34.952Z" }, + { url = "https://files.pythonhosted.org/packages/00/c1/bbc350003a32876a6a80ace17f7571ebd0110e39c523b8e07ecf64d431b2/numcodecs-0.17.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4d83befe67a51ba6a988c562209bf13836438c1b6dce23049d84ff42854af32d", size = 1022050, upload-time = "2026-09-17T18:12:36.312Z" }, + { url = "https://files.pythonhosted.org/packages/ca/dc/fa7c6a1ce327093d04ddf0d0acf20fd1fe2a00a9b7eb43b6a7213eec1ae4/numcodecs-0.17.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e4e351566b3ab2f6255a9d91c6c48e1d0f9ec6e2ae409a148e091a8fc0a80b0", size = 1380833, upload-time = "2026-09-17T18:12:37.739Z" }, + { url = "https://files.pythonhosted.org/packages/e6/38/33023f8771e8b7afb9c7f0dd50b0487dbef594617e245707cb965969edd3/numcodecs-0.17.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8697a4631fedded77a75d333e4926b1eb3a11bc7d3e30213e7e565d6910526d0", size = 1424937, upload-time = "2026-09-17T18:12:39.04Z" }, + { url = "https://files.pythonhosted.org/packages/86/43/a166898bd89ecc743762b0192b591a35aaecf442e47f85132a75113622f5/numcodecs-0.17.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4c36f6fd14dc22939172145c24d3b3eab2410c34ed807906a5ece5f4541c7c43", size = 1523408, upload-time = "2026-09-17T18:12:40.72Z" }, ] [[package]] @@ -3065,20 +3047,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] -[[package]] -name = "pooch" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, - { name = "platformdirs" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/83/43/85ef45e8b36c6a48546af7b266592dc32d7f67837a6514d111bced6d7d75/pooch-1.9.0.tar.gz", hash = "sha256:de46729579b9857ffd3e741987a2f6d5e0e03219892c167c6578c0091fb511ed", size = 61788, upload-time = "2026-01-30T19:15:09.649Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl", hash = "sha256:f265597baa9f760d25ceb29d0beb8186c243d6607b0f60b83ecf14078dbc703b", size = 67175, upload-time = "2026-01-30T19:15:08.36Z" }, -] - [[package]] name = "pre-commit" version = "4.6.2" @@ -3582,21 +3550,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4b/72/ac5ca81fe9121fcaa9d828d21017cba00a16a98e4ea5fb60c878f93dda4f/readme_renderer-46.0-py3-none-any.whl", hash = "sha256:d0dae1f74bb273b534770cb4cccb6bb78735540afdb03c2146f4e19dcd412560", size = 14239, upload-time = "2026-08-28T15:18:31.132Z" }, ] -[[package]] -name = "rechunker" -version = "0.5.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "dask", extra = ["array", "diagnostics"] }, - { name = "mypy-extensions" }, - { name = "packaging" }, - { name = "zarr" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8b/39/2b42e3da105e6cec702c7f1e489074d971abafa13bb7ff0b029bd8dc494c/rechunker-0.5.4.tar.gz", hash = "sha256:ad194a24f7d21fdc9914b9b149c04f88419a45fb5d0e3898a44d6dc68215979c", size = 481021, upload-time = "2025-05-01T16:14:58.974Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/7b/8ddf8f5137ae13d6210238f31781090853f25c901a9d2a68ad865679f0b6/rechunker-0.5.4-py3-none-any.whl", hash = "sha256:6999774c53aa80cce3c1d03b013644241732681f79c0cffec28f527d362ceb18", size = 22441, upload-time = "2025-05-01T16:14:57.82Z" }, -] - [[package]] name = "referencing" version = "0.37.0" @@ -4004,14 +3957,11 @@ dependencies = [ { name = "scikit-learn" }, { name = "scipy" }, { name = "xarray" }, - { name = "zarr" }, + { name = "zarr", version = "3.1.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "zarr", version = "3.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] [package.optional-dependencies] -bgen = [ - { name = "cbgen" }, - { name = "rechunker" }, -] plink = [ { name = "bed-reader" }, { name = "partd" }, @@ -4043,7 +3993,7 @@ dev = [ { name = "scanpydoc", version = "0.17.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "scikit-allel" }, { name = "seaborn" }, - { name = "sgkit", extra = ["bgen", "plink"] }, + { name = "sgkit", extra = ["plink"] }, { name = "sphinx" }, { name = "sphinx-autodoc-typehints" }, { name = "sphinx-copybutton" }, @@ -4088,7 +4038,7 @@ test = [ { name = "pytest-mock" }, { name = "requests" }, { name = "scikit-allel" }, - { name = "sgkit", extra = ["bgen", "plink"] }, + { name = "sgkit", extra = ["plink"] }, { name = "statsmodels" }, { name = "yarl" }, ] @@ -4096,7 +4046,6 @@ test = [ [package.metadata] requires-dist = [ { name = "bed-reader", marker = "extra == 'plink'" }, - { name = "cbgen", marker = "extra == 'bgen'", specifier = ">1.0.5" }, { name = "dask", extras = ["array", "dataframe"], specifier = ">=2022.1.0,<=2024.8.0" }, { name = "fsspec", specifier = "!=2021.6.*" }, { name = "numba" }, @@ -4104,19 +4053,18 @@ requires-dist = [ { name = "numpy", marker = "sys_platform == 'darwin'", specifier = ">=2.3.5" }, { name = "pandas", specifier = "<3.0" }, { name = "partd", marker = "extra == 'plink'" }, - { name = "rechunker", marker = "extra == 'bgen'" }, { name = "scikit-learn" }, { name = "scipy", specifier = "<1.16" }, - { name = "xarray", specifier = "<2025.3.1" }, - { name = "zarr", specifier = ">=2.10.0,!=2.11.0,!=2.11.1,!=2.11.2,<3" }, + { name = "xarray", specifier = ">=2026.9.0" }, + { name = "zarr", specifier = ">=3" }, ] -provides-extras = ["plink", "bgen"] +provides-extras = ["plink"] [package.metadata.requires-dev] dev = [ { name = "ablog", specifier = "!=0.10.27" }, { name = "aiohttp" }, - { name = "bio2zarr", extras = ["vcf"], marker = "sys_platform != 'win32'" }, + { name = "bio2zarr", extras = ["vcf"], marker = "sys_platform != 'win32'", specifier = ">=0.2" }, { name = "callee" }, { name = "graphviz" }, { name = "hypothesis" }, @@ -4137,7 +4085,7 @@ dev = [ { name = "scanpydoc" }, { name = "scikit-allel" }, { name = "seaborn" }, - { name = "sgkit", extras = ["plink", "bgen"] }, + { name = "sgkit", extras = ["plink"] }, { name = "sphinx", specifier = "<9.0.0" }, { name = "sphinx-autodoc-typehints" }, { name = "sphinx-copybutton" }, @@ -4163,7 +4111,7 @@ lint = [{ name = "pre-commit" }] packaging = [{ name = "twine" }] test = [ { name = "aiohttp" }, - { name = "bio2zarr", extras = ["vcf"], marker = "sys_platform != 'win32'" }, + { name = "bio2zarr", extras = ["vcf"], marker = "sys_platform != 'win32'", specifier = ">=0.2" }, { name = "callee" }, { name = "graphviz" }, { name = "hypothesis" }, @@ -4177,7 +4125,7 @@ test = [ { name = "pytest-mock" }, { name = "requests" }, { name = "scikit-allel" }, - { name = "sgkit", extras = ["plink", "bgen"] }, + { name = "sgkit", extras = ["plink"] }, { name = "statsmodels" }, { name = "yarl" }, ] @@ -4785,25 +4733,16 @@ wheels = [ [[package]] name = "xarray" -version = "2025.3.0" +version = "2026.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "packaging" }, { name = "pandas" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/8c/e56820a2c682834d8fb4a940956cd41b9f2532fee9692bfaf43b14f33c6e/xarray-2025.3.0.tar.gz", hash = "sha256:531cf6d64e6ab54c19239930a840654f1a79d2140e9f0d11d1e8d69dcc581d0f", size = 3292399, upload-time = "2025-03-20T04:50:58.903Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/f5/781c70b234f54e0401f4f7c4427a7486da38d50dbb138c7d7c55144b3d86/xarray-2026.9.0.tar.gz", hash = "sha256:6abc69694c22fa1f0fb2f357ff4e41d88beb4477ed71091f944b7dbf67ed54fe", size = 3176922, upload-time = "2026-09-29T23:06:25.807Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/19/5dbac77ca82f009f8267d0e7cca4fd3be2a435d83ed98f6f8b9c5cb1747e/xarray-2025.3.0-py3-none-any.whl", hash = "sha256:022b7eac5ebc26d70d34211de732ec1a15127a40455f9492acdde7fbe5bbf091", size = 1270849, upload-time = "2025-03-20T04:50:56.457Z" }, -] - -[[package]] -name = "xyzservices" -version = "2026.9.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c4/bd/b75d5c2312caec607ec995200fa1eb8453225fd869947ec743919c279544/xyzservices-2026.9.1.tar.gz", hash = "sha256:8d1a39bf6b192940cc5db5264eeefc1b20dd184f8b83b1303b078743744f5943", size = 1134047, upload-time = "2026-09-04T10:35:41.399Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/71/9219fe65ef32bddc0b5d14e1d98dfefe9d2c9c1b1d1e93efca1f29fcbd4d/xyzservices-2026.9.1-py3-none-any.whl", hash = "sha256:af4fddac0f1fa5f7951834e2c58e10109e722be548cb1fe4f1a8e8b18cef4c09", size = 98829, upload-time = "2026-09-04T10:35:40.08Z" }, + { url = "https://files.pythonhosted.org/packages/3f/44/3159af4fb3c868970a412e294320ed6bb1a9fbdfa6431050eda8e0b34b6e/xarray-2026.9.0-py3-none-any.whl", hash = "sha256:fe349fa871628b1a0a5217af3fe1283a2862d5485156e6eda354fffb81c3bb7c", size = 1445445, upload-time = "2026-09-29T23:06:23.686Z" }, ] [[package]] @@ -4907,17 +4846,49 @@ wheels = [ [[package]] name = "zarr" -version = "2.18.7" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12' and sys_platform == 'darwin'", + "python_full_version < '3.12' and sys_platform != 'darwin'", +] +dependencies = [ + { name = "donfig" }, + { name = "google-crc32c" }, + { name = "numcodecs", version = "0.15.1", source = { registry = "https://pypi.org/simple" } }, + { name = "numpy" }, + { name = "packaging" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/5a/b8a0cf39a14c770c30bd1f2d120c54000c8cd9e84e8e79f38d9a7ce58071/zarr-3.1.6.tar.gz", hash = "sha256:d95e72cbea4b90e9a70679468b8266400331756232576ae2b43400ac5108d0eb", size = 386531, upload-time = "2026-03-23T17:25:18.748Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/7c/ba8ca8cbe9dbef8e83a95fc208fed8e6686c98b4719aaa0aa7f3d31fe390/zarr-3.1.6-py3-none-any.whl", hash = "sha256:b5a82c5079d1c3d4ee8f06746fa3b9a98a7d804300fa3f4be154362a33e1207e", size = 295655, upload-time = "2026-03-23T17:25:17.189Z" }, +] + +[[package]] +name = "zarr" +version = "3.4.1" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'darwin'", + "python_full_version >= '3.14' and sys_platform != 'darwin'", + "python_full_version == '3.13.*' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and sys_platform != 'darwin'", + "python_full_version == '3.12.*' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and sys_platform != 'darwin'", +] dependencies = [ - { name = "asciitree" }, - { name = "fasteners", marker = "sys_platform != 'emscripten'" }, - { name = "numcodecs" }, + { name = "donfig" }, + { name = "google-crc32c" }, + { name = "msgspec" }, + { name = "numcodecs", version = "0.17.0", source = { registry = "https://pypi.org/simple" } }, { name = "numpy" }, + { name = "packaging" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/1d/01cf9e3ab2d85190278efc3fca9f68563de35ae30ee59e7640e3af98abe3/zarr-2.18.7.tar.gz", hash = "sha256:b2b8f66f14dac4af66b180d2338819981b981f70e196c9a66e6bfaa9e59572f5", size = 3604558, upload-time = "2025-04-09T07:59:28.482Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/62/e8a36a4b65f01499c7aefcf103aabba0c37c018bf135fbf179f6ed2f01a0/zarr-3.4.1.tar.gz", hash = "sha256:b34bda11ceb199c81ee78ecd42cd02f46c7f67a6d8a1e9bc501cafd5a1795356", size = 923257, upload-time = "2026-10-08T17:14:25.972Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/d8/9ffd8c237b3559945bb52103cf0eed64ea098f7b7f573f8d2962ef27b4b2/zarr-2.18.7-py3-none-any.whl", hash = "sha256:ac3dc4033e9ae4e9d7b5e27c97ea3eaf1003cc0a07f010bd83d5134bf8c4b223", size = 211273, upload-time = "2025-04-09T07:59:27.039Z" }, + { url = "https://files.pythonhosted.org/packages/a6/82/0dbc9bc77b49cfb9268dc2b2b1dcd04346c8c7ed52e272b76a628a941656/zarr-3.4.1-py3-none-any.whl", hash = "sha256:38b540578a119352bdce02a720d7bfd99846e77f0728c58b1bab3d1f547526a0", size = 405257, upload-time = "2026-10-08T17:14:23.926Z" }, ] [[package]]