diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 48a7df79..d3d20fb2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -106,7 +106,7 @@ jobs: cache-dependency-glob: "pyproject.toml" - name: Install xarray and dependencies run: | - uv add --dev .[complete] pint>=0.22 + uv add --dev ".[complete]" "pint>=0.22" - name: Install upstream flox run: | uv add git+https://github.com/dcherian/flox.git@${{ github.ref }} diff --git a/flox/aggregations.py b/flox/aggregations.py index 6246942b..d0ce8343 100644 --- a/flox/aggregations.py +++ b/flox/aggregations.py @@ -15,6 +15,8 @@ from . import aggregate_flox, aggregate_npg, xrutils from . import xrdtypes as dtypes from .lib import dask_array_type, sparse_array_type +from .multiarray import MultiArray +from .xrutils import notnull if TYPE_CHECKING: FuncTuple = tuple[Callable | str, ...] @@ -161,8 +163,8 @@ def __init__( self, name: str, *, - numpy: str | None = None, - chunk: str | FuncTuple | None, + numpy: partial | str | None = None, + chunk: partial | str | FuncTuple | None, combine: str | FuncTuple | None, preprocess: Callable | None = None, finalize: Callable | None = None, @@ -343,57 +345,183 @@ def _mean_finalize(sum_, count): ) -# TODO: fix this for complex numbers -def _var_finalize(sumsq, sum_, count, ddof=0): +def var_chunk( + group_idx, array, *, skipna: bool, engine: str, axis=-1, size=None, fill_value=None, dtype=None +): + # Calculate length and sum - important for the adjustment terms to sum squared deviations + array_lens = generic_aggregate( + group_idx, + array, + func="nanlen", + engine=engine, + axis=axis, + size=size, + fill_value=0, # Unpack fill value bc it's currently defined for multiarray + dtype=dtype, + ) + + array_sums = generic_aggregate( + group_idx, + array, + func="nansum" if skipna else "sum", + engine=engine, + axis=axis, + size=size, + fill_value=0, # Unpack fill value bc it's currently defined for multiarray + dtype=dtype, + ) + + # Calculate sum squared deviations - the main part of variance sum with np.errstate(invalid="ignore", divide="ignore"): - result = (sumsq - (sum_**2 / count)) / (count - ddof) - result[count <= ddof] = np.nan - return result + array_means = array_sums / array_lens + + sum_squared_deviations = generic_aggregate( + group_idx, + (array - array_means[..., group_idx]) ** 2, + func="nansum" if skipna else "sum", + engine=engine, + axis=axis, + size=size, + fill_value=0, # Unpack fill value bc it's currently defined for multiarray + dtype=dtype, + ) + + return MultiArray((sum_squared_deviations, array_sums, array_lens)) + + +def _var_combine(array, axis, keepdims=True): + def clip_last(array, ax, n=1): + """Return array except the last element along axis + Purely included to tidy up the adj_terms line + """ + assert n > 0, "Clipping nothing off the end isn't implemented" + not_last = [slice(None, None) for i in range(array.ndim)] + not_last[ax] = slice(None, -n) + return array[*not_last] + + def clip_first(array, ax, n=1): + """Return array except the first element along axis + Purely included to tidy up the adj_terms line + """ + not_first = [slice(None, None) for i in range(array.ndim)] + not_first[ax] = slice(n, None) + return array[*not_first] + + for ax in axis: + if array.shape[ax] == 1: + continue + + sum_deviations, sum_X, sum_len = array.arrays + + # Calculate parts needed for cascading combination + cumsum_X = np.cumsum(sum_X, axis=ax) + cumsum_len = np.cumsum(sum_len, axis=ax) + + # There will be instances in which one or both chunks being merged are empty + # In which case, the adjustment term should be zero, but will throw a divide-by-zero error + # We're going to add a constant to the bottom of the adjustment term equation on those instances + # and count on the zeros on the top making our adjustment term still zero + zero_denominator = (clip_last(cumsum_len, ax) == 0) | (clip_first(sum_len, ax) == 0) + + # Adjustment terms to tweak the sum of squared deviations because not every chunk has the same mean + with np.errstate(invalid="ignore", divide="ignore"): + adj_terms = ( + clip_last(cumsum_len, ax) * clip_first(sum_X, ax) + - clip_first(sum_len, ax) * clip_last(cumsum_X, ax) + ) ** 2 / ( + clip_last(cumsum_len, ax) + * clip_first(sum_len, ax) + * (clip_last(cumsum_len, ax) + clip_first(sum_len, ax)) + + zero_denominator.astype(int) + ) + + check = adj_terms * zero_denominator + assert np.all(check[notnull(check)] == 0), ( + "Instances where we add something to the denominator must come out to zero" + ) + + array = MultiArray( + ( + np.sum(sum_deviations, axis=ax, keepdims=keepdims) + + np.sum(adj_terms, axis=ax, keepdims=keepdims), # sum of squared deviations + np.sum(sum_X, axis=ax, keepdims=keepdims), # sum of array items + np.sum(sum_len, axis=ax, keepdims=keepdims), # sum of array lengths + ) + ) + return array + + +def is_var_chunk_reduction(agg: Callable) -> bool: + if isinstance(agg, partial): + agg = agg.func + return agg is blockwise_or_numpy_var or agg is var_chunk + + +def _var_finalize(multiarray, ddof=0): + den = multiarray.arrays[2] + den -= ddof + # preserve nans for groups with 0 obs; so these values are -ddof + with np.errstate(invalid="ignore", divide="ignore"): + ret = multiarray.arrays[0] + ret /= den + ret[den < 0] = np.nan + return ret -def _std_finalize(sumsq, sum_, count, ddof=0): - return np.sqrt(_var_finalize(sumsq, sum_, count, ddof)) +def _std_finalize(multiarray, ddof=0): + return np.sqrt(_var_finalize(multiarray, ddof)) + + +def blockwise_or_numpy_var(*args, skipna: bool, ddof=0, std=False, **kwargs): + res = _var_finalize(var_chunk(*args, skipna=skipna, **kwargs), ddof) + return np.sqrt(res) if std else res # var, std always promote to float, so we set nan var = Aggregation( "var", - chunk=("sum_of_squares", "sum", "nanlen"), - combine=("sum", "sum", "sum"), + chunk=partial(var_chunk, skipna=False), + numpy=partial(blockwise_or_numpy_var, skipna=False), + combine=(_var_combine,), finalize=_var_finalize, - fill_value=0, + fill_value=((0, 0, 0),), final_fill_value=np.nan, - dtypes=(None, None, np.intp), + dtypes=(None,), final_dtype=np.floating, ) + nanvar = Aggregation( "nanvar", - chunk=("nansum_of_squares", "nansum", "nanlen"), - combine=("sum", "sum", "sum"), + chunk=partial(var_chunk, skipna=True), + numpy=partial(blockwise_or_numpy_var, skipna=True), + combine=(_var_combine,), finalize=_var_finalize, - fill_value=0, + fill_value=((0, 0, 0),), final_fill_value=np.nan, - dtypes=(None, None, np.intp), + dtypes=(None,), final_dtype=np.floating, ) + std = Aggregation( "std", - chunk=("sum_of_squares", "sum", "nanlen"), - combine=("sum", "sum", "sum"), + chunk=partial(var_chunk, skipna=False), + numpy=partial(blockwise_or_numpy_var, skipna=False, std=True), + combine=(_var_combine,), finalize=_std_finalize, - fill_value=0, + fill_value=((0, 0, 0),), final_fill_value=np.nan, - dtypes=(None, None, np.intp), + dtypes=(None,), final_dtype=np.floating, ) nanstd = Aggregation( "nanstd", - chunk=("nansum_of_squares", "nansum", "nanlen"), - combine=("sum", "sum", "sum"), + chunk=partial(var_chunk, skipna=True), + numpy=partial(blockwise_or_numpy_var, skipna=True, std=True), + combine=(_var_combine,), finalize=_std_finalize, - fill_value=0, + fill_value=((0, 0, 0),), final_fill_value=np.nan, - dtypes=(None, None, np.intp), + dtypes=(None,), final_dtype=np.floating, ) diff --git a/flox/core.py b/flox/core.py index 3109e85d..8681efed 100644 --- a/flox/core.py +++ b/flox/core.py @@ -44,6 +44,7 @@ _atleast_1d, _initialize_aggregation, generic_aggregate, + is_var_chunk_reduction, quantile_new_dims_func, ) from .cache import memoize @@ -1289,7 +1290,8 @@ def chunk_reduce( # optimize that out. previous_reduction: T_Func = "" for reduction, fv, kw, dt in zip(funcs, fill_values, kwargss, dtypes): - if empty: + # UGLY! but this is because the `var` breaks our design assumptions + if empty and not is_var_chunk_reduction(reduction): result = np.full(shape=final_array_shape, fill_value=fv, like=array) elif is_nanlen(reduction) and is_nanlen(previous_reduction): result = results["intermediates"][-1] @@ -1298,6 +1300,10 @@ def chunk_reduce( kw_func = dict(size=size, dtype=dt, fill_value=fv) kw_func.update(kw) + # UGLY! but this is because the `var` breaks our design assumptions + if is_var_chunk_reduction(reduction): + kw_func.update(engine=engine) + if callable(reduction): # passing a custom reduction for npg to apply per-group is really slow! # So this `reduction` has to do the groupby-aggregation @@ -2785,6 +2791,7 @@ def groupby_reduce( array = array.view(np.int64) elif is_cftime: offset = array.min() + assert offset is not None array = datetime_to_numeric(array, offset, datetime_unit="us") if nax == 1 and by_.ndim > 1 and expected_ is None: diff --git a/flox/multiarray.py b/flox/multiarray.py new file mode 100644 index 00000000..20116099 --- /dev/null +++ b/flox/multiarray.py @@ -0,0 +1,97 @@ +from collections.abc import Callable +from typing import Self + +import numpy as np + +MULTIARRAY_HANDLED_FUNCTIONS: dict[Callable, Callable] = {} + + +class MultiArray: + arrays: tuple[np.ndarray, ...] + + def __init__(self, arrays): + self.arrays = arrays + assert all(arrays[0].shape == a.shape for a in arrays), "Expect all arrays to have the same shape" + + def astype(self, dt, **kwargs) -> Self: + return type(self)(tuple(array.astype(dt, **kwargs) for array in self.arrays)) + + def reshape(self, shape, **kwargs) -> Self: + return type(self)(tuple(array.reshape(shape, **kwargs) for array in self.arrays)) + + def squeeze(self, axis=None) -> Self: + return type(self)(tuple(array.squeeze(axis) for array in self.arrays)) + + def __setitem__(self, key, value) -> None: + assert len(value) == len(self.arrays) + for array, val in zip(self.arrays, value): + array[key] = val + + def __array_function__(self, func, types, args, kwargs): + if func not in MULTIARRAY_HANDLED_FUNCTIONS: + return NotImplemented + # Note: this allows subclasses that don't override + # __array_function__ to handle MyArray objects + # if not all(issubclass(t, MyArray) for t in types): # I can't see this being relevant at all for this code, but maybe it's safer to leave it in? + # return NotImplemented + return MULTIARRAY_HANDLED_FUNCTIONS[func](*args, **kwargs) + + # Shape is needed, seems likely that the other two might be + # Making some strong assumptions here that all the arrays are the same shape, and I don't really like this + @property + def dtype(self) -> np.dtype: + return self.arrays[0].dtype + + @property + def shape(self) -> tuple[int, ...]: + return self.arrays[0].shape + + @property + def ndim(self) -> int: + return self.arrays[0].ndim + + def __getitem__(self, key) -> Self: + return type(self)([array[key] for array in self.arrays]) + + +def implements(numpy_function): + """Register an __array_function__ implementation for MyArray objects.""" + + def decorator(func): + MULTIARRAY_HANDLED_FUNCTIONS[numpy_function] = func + return func + + return decorator + + +@implements(np.expand_dims) +def expand_dims(multiarray, axis) -> MultiArray: + return MultiArray(tuple(np.expand_dims(a, axis) for a in multiarray.arrays)) + + +@implements(np.concatenate) +def concatenate(multiarrays, axis) -> MultiArray: + n_arrays = len(multiarrays[0].arrays) + for ma in multiarrays[1:]: + assert len(ma.arrays) == n_arrays + return MultiArray( + tuple(np.concatenate(tuple(ma.arrays[i] for ma in multiarrays), axis) for i in range(n_arrays)) + ) + + +@implements(np.transpose) +def transpose(multiarray, axes) -> MultiArray: + return MultiArray(tuple(np.transpose(a, axes) for a in multiarray.arrays)) + + +@implements(np.squeeze) +def squeeze(multiarray, axis) -> MultiArray: + return MultiArray(tuple(np.squeeze(a, axis) for a in multiarray.arrays)) + + +@implements(np.full) +def full(shape, fill_values, *args, **kwargs) -> MultiArray: + """All arguments except fill_value are shared by each array in the MultiArray. + Iterate over fill_values to create arrays + """ + return MultiArray(tuple(np.full(shape, fv, *args, **kwargs) for fv in fill_values)) diff --git a/flox/xrutils.py b/flox/xrutils.py index 2addf35f..4adc56b2 100644 --- a/flox/xrutils.py +++ b/flox/xrutils.py @@ -147,6 +147,9 @@ def is_scalar(value: Any, include_0d: bool = True) -> bool: def notnull(data): + if isinstance(data, tuple) and len(data) == 3 and data == (0, 0, 0): + # boo: another special case for Var + return True if not is_duck_array(data): data = np.asarray(data) @@ -164,6 +167,9 @@ def notnull(data): def isnull(data: Any): + if isinstance(data, tuple) and len(data) == 3 and data == (0, 0, 0): + # boo: another special case for Var + return False if data is None: return False if not is_duck_array(data): diff --git a/pyproject.toml b/pyproject.toml index 887aeb7a..494dcdff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -169,17 +169,6 @@ testpaths = ["tests"] ignore-words-list = "nd,nax,coo" skip = "*.html" -[tool.uv] -dev-dependencies = [ - "hypothesis", - "pytest>=7", - "pytest-cov", - "pytest-pretty", - "pytest-xdist", - "syrupy", - "pooch", - "codecov", -] diff --git a/tests/strategies.py b/tests/strategies.py index 76102047..ea9c2ed7 100644 --- a/tests/strategies.py +++ b/tests/strategies.py @@ -108,9 +108,8 @@ def insert_nans(draw: st.DrawFn, array: np.ndarray) -> np.ndarray: "any", "all", ] + list(SCIPY_STATS_FUNCS) -SKIPPED_FUNCS = ["var", "std", "nanvar", "nanstd"] -func_st = st.sampled_from([f for f in ALL_FUNCS if f not in NON_NUMPY_FUNCS and f not in SKIPPED_FUNCS]) +func_st = st.sampled_from([f for f in ALL_FUNCS if f not in NON_NUMPY_FUNCS]) @st.composite diff --git a/tests/test_core.py b/tests/test_core.py index 31c6ab5a..36d988ed 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -553,7 +553,7 @@ def test_groupby_agg_dask(func, shape, array_chunks, group_chunks, add_nan, dtyp def test_groupby_agg_cubed(func, shape, array_chunks, group_chunks, add_nan, engine, reindex): """Tests groupby_reduce with cubed arrays against groupby_reduce with numpy arrays""" - if func in ["first", "last"] or func in BLOCKWISE_FUNCS: + if func in ["first", "last", "var", "nanvar", "std", "nanstd"] or func in BLOCKWISE_FUNCS: pytest.skip() if "arg" in func and (engine in ["flox", "numbagg"] or reindex): @@ -2240,3 +2240,38 @@ def test_sparse_nan_fill_value_reductions(chunks, fill_value, shape, func): expected = np.expand_dims(npfunc(numpy_array, axis=-1), axis=-1) actual, *_ = groupby_reduce(array, by, func=func, axis=-1) assert_equal(actual, expected) + + +@pytest.mark.parametrize("func", ("nanvar", "var")) +@pytest.mark.parametrize( + # Should fail at 10e8 for old algorithm, and survive 10e12 for current + "exponent", + (2, 4, 6, 8, 10, 12), +) +def test_std_var_precision(func, exponent, engine): + # Generate a dataset with small variance and big mean + # Check that func with engine gives you the same answer as numpy + + size = 1000 + offset = 10**exponent + array = np.linspace(-1, 1, size) # has zero mean + labels = np.arange(size) % 2 # Ideally we'd parametrize this too. + + # These two need to be the same function, but with the offset added and not added + no_offset, _ = groupby_reduce(array, labels, engine=engine, func=func) + with_offset, _ = groupby_reduce(array + offset, labels, engine=engine, func=func) + + expected = np.concatenate([np.nanvar(array[::2], keepdims=True), np.nanvar(array[1::2], keepdims=True)]) + expected_offset = np.concatenate( + [np.nanvar(array[::2] + offset, keepdims=True), np.nanvar(array[1::2] + offset, keepdims=True)] + ) + + tol = {"rtol": 3e-8, "atol": 1e-9} # Not sure how stringent to be here + + assert_equal(expected, no_offset, tol) + assert_equal(expected_offset, with_offset, tol) + if exponent < 10: + # TODO: figure this exponent limit + # TODO: Failure threshold in my external tests is dependent on dask chunksize, + # maybe needs exploring better? + assert_equal(no_offset, with_offset, tol) diff --git a/tests/test_properties.py b/tests/test_properties.py index a1b10511..86142afc 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -66,18 +66,25 @@ def bfill(array, axis, dtype=None): def not_overflowing_array(array: np.ndarray[Any, Any]) -> bool: if array.dtype.kind in "Mm": array = array.view(np.int64) + array = array.ravel() + array = array[notnull(array)] + if array.size == 0: + return True + if array.dtype.kind == "f": info = np.finfo(array.dtype) + limit = 2 ** (info.nmant + 1) elif array.dtype.kind in ["i", "u"]: info = np.iinfo(array.dtype) # type: ignore[assignment] else: return True - array = array.ravel() - array = array[notnull(array)] with warnings.catch_warnings(): warnings.simplefilter("ignore", RuntimeWarning) result = bool(np.all((array < info.max / array.size) & (array > info.min / array.size))) + if array.dtype.kind == "f": + result = result and bool(np.all(np.abs(array) < limit / array.size)) + # note(f"returning {result}, {array.min()} vs {info.min}, {array.max()} vs {info.max}") return result @@ -99,7 +106,7 @@ def test_groupby_reduce(data, array, func: str) -> None: # TODO: funny bugs with overflows here is_cftime = _contains_cftime_datetimes(array) - assume(not (is_cftime and func in ["prod", "nanprod"])) + assume(not (is_cftime and func in ["prod", "nanprod", "var", "nanvar", "std", "nanstd"])) axis = -1 by = data.draw( @@ -203,7 +210,7 @@ def test_groupby_reduce_numpy_vs_other(data, array, func: str) -> None: result_other, *_ = groupby_reduce(array, by, **kwargs) result_numpy, *_ = groupby_reduce(numpy_array, by, **kwargs) assert isinstance(result_other, type(array)) - assert_equal(result_numpy, result_other) + assert_equal(result_other, result_numpy) @given( diff --git a/uv.lock b/uv.lock index 86c4f048..2688d630 100644 --- a/uv.lock +++ b/uv.lock @@ -251,6 +251,27 @@ css = [ { name = "tinycss2" }, ] +[[package]] +name = "bokeh" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "jinja2" }, + { name = "narwhals" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "pyyaml" }, + { name = "tornado", marker = "sys_platform != 'emscripten'" }, + { name = "xyzservices" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/bd/8455ecfaa8100dbfbb2af40061c689a7a9c808f4f8c9582f0efd0c8c9a19/bokeh-3.8.0.tar.gz", hash = "sha256:bfdf5e9df910653b097f70cd38f4c2399d91af6e54a618126e2387cc33c9ec03", size = 6529746, upload-time = "2025-08-29T12:16:55.005Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/9a/8e641b5415e12036d8a206147b8229d917a767b7d939521458d90feddcf5/bokeh-3.8.0-py3-none-any.whl", hash = "sha256:117c5e559231ad39fef87891a1a1b62b3bfefbaa47d536023537338f46015841", size = 7205343, upload-time = "2025-08-29T12:16:52.77Z" }, +] + [[package]] name = "build" version = "1.3.0" @@ -828,6 +849,17 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/56/55dda22a8dbe291032735f7025dd69d9a70e4c440279ae98cc7c3888aa70/dask-2025.9.0-py3-none-any.whl", hash = "sha256:cb8d74476dda10c558234c02d1639386cc5c9cef0252245cf77043fb1f2495d1", size = 1477763, upload-time = "2025-09-10T10:16:06.474Z" }, ] +[package.optional-dependencies] +complete = [ + { name = "bokeh" }, + { name = "distributed" }, + { name = "jinja2" }, + { name = "lz4" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "pyarrow" }, +] + [[package]] name = "debugpy" version = "1.8.16" @@ -876,6 +908,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, ] +[[package]] +name = "distributed" +version = "2025.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "cloudpickle" }, + { name = "dask" }, + { name = "jinja2" }, + { name = "locket" }, + { name = "msgpack" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyyaml" }, + { name = "sortedcontainers" }, + { name = "tblib" }, + { name = "toolz" }, + { name = "tornado" }, + { name = "urllib3" }, + { name = "zict" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/3f/115c2828366c08d631935a19575ebc23491788cb2a93baa1ad3e94b5358f/distributed-2025.9.0.tar.gz", hash = "sha256:f10e09d6f314e8959b97b633a44ce1807e89197445119fa313333c2df527d25a", size = 1101035, upload-time = "2025-09-10T10:15:19.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/b2/810bb4f4dfe5560d5eb36efda2221e51d1e38a04c3c10fa3cec311ce21e4/distributed-2025.9.0-py3-none-any.whl", hash = "sha256:87c35e4aefb5525d44032b40d7d780b4e9e398b6d567ca5bb6cc7b2150c48aa9", size = 1008961, upload-time = "2025-09-10T10:15:16.425Z" }, +] + [[package]] name = "docutils" version = "0.21.2" @@ -1038,7 +1096,7 @@ dev = [ { name = "cftime" }, { name = "codecov" }, { name = "cubed" }, - { name = "dask" }, + { name = "dask", extra = ["complete"] }, { name = "hypothesis" }, { name = "ipykernel" }, { name = "line-profiler" }, @@ -1277,7 +1335,7 @@ provides-extras = ["all", "test", "docs"] [package.metadata.requires-dev] all = [ { name = "cachey" }, - { name = "dask", extras = ["core"] }, + { name = "dask" }, { name = "numba" }, { name = "numbagg", specifier = ">=0.3" }, { name = "xarray" }, @@ -1299,7 +1357,7 @@ complete = [ { name = "cftime" }, { name = "codecov" }, { name = "cubed", specifier = ">=0.20.0" }, - { name = "dask", extras = ["core"] }, + { name = "dask" }, { name = "hypothesis" }, { name = "lxml" }, { name = "matplotlib" }, @@ -1323,8 +1381,8 @@ dev = [ { name = "cftime" }, { name = "codecov" }, { name = "cubed", specifier = ">=0.20.0" }, - { name = "dask", extras = ["all"] }, - { name = "dask", extras = ["core"] }, + { name = "dask" }, + { name = "dask", extras = ["complete"] }, { name = "hypothesis" }, { name = "ipykernel" }, { name = "line-profiler" }, @@ -1437,7 +1495,7 @@ numpy1 = [ { name = "cftime" }, { name = "codecov" }, { name = "cubed", specifier = ">=0.20.0" }, - { name = "dask", extras = ["core"] }, + { name = "dask" }, { name = "hypothesis" }, { name = "lxml" }, { name = "matplotlib" }, @@ -2401,6 +2459,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b6/db/8f620f1ac62cf32554821b00b768dd5957ac8e3fd051593532be5b40b438/lxml-6.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:51bd5d1a9796ca253db6045ab45ca882c09c071deafffc22e06975b7ace36300", size = 3518127, upload-time = "2025-08-22T10:37:51.66Z" }, ] +[[package]] +name = "lz4" +version = "4.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/5a/945f5086326d569f14c84ac6f7fcc3229f0b9b1e8cc536b951fd53dfb9e1/lz4-4.4.4.tar.gz", hash = "sha256:070fd0627ec4393011251a094e08ed9fdcc78cb4e7ab28f507638eee4e39abda", size = 171884, upload-time = "2025-04-01T22:55:58.62Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/e8/63843dc5ecb1529eb38e1761ceed04a0ad52a9ad8929ab8b7930ea2e4976/lz4-4.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ddfc7194cd206496c445e9e5b0c47f970ce982c725c87bd22de028884125b68f", size = 220898, upload-time = "2025-04-01T22:55:23.085Z" }, + { url = "https://files.pythonhosted.org/packages/e4/94/c53de5f07c7dc11cf459aab2a1d754f5df5f693bfacbbe1e4914bfd02f1e/lz4-4.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:714f9298c86f8e7278f1c6af23e509044782fa8220eb0260f8f8f1632f820550", size = 189685, upload-time = "2025-04-01T22:55:24.413Z" }, + { url = "https://files.pythonhosted.org/packages/fe/59/c22d516dd0352f2a3415d1f665ccef2f3e74ecec3ca6a8f061a38f97d50d/lz4-4.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8474c91de47733856c6686df3c4aca33753741da7e757979369c2c0d32918ba", size = 1239225, upload-time = "2025-04-01T22:55:25.737Z" }, + { url = "https://files.pythonhosted.org/packages/81/af/665685072e71f3f0e626221b7922867ec249cd8376aca761078c8f11f5da/lz4-4.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80dd27d7d680ea02c261c226acf1d41de2fd77af4fb2da62b278a9376e380de0", size = 1265881, upload-time = "2025-04-01T22:55:26.817Z" }, + { url = "https://files.pythonhosted.org/packages/90/04/b4557ae381d3aa451388a29755cc410066f5e2f78c847f66f154f4520a68/lz4-4.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b7d6dddfd01b49aedb940fdcaf32f41dc58c926ba35f4e31866aeec2f32f4f4", size = 1185593, upload-time = "2025-04-01T22:55:27.896Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e4/03636979f4e8bf92c557f998ca98ee4e6ef92e92eaf0ed6d3c7f2524e790/lz4-4.4.4-cp311-cp311-win32.whl", hash = "sha256:4134b9fd70ac41954c080b772816bb1afe0c8354ee993015a83430031d686a4c", size = 88259, upload-time = "2025-04-01T22:55:29.03Z" }, + { url = "https://files.pythonhosted.org/packages/07/f0/9efe53b4945441a5d2790d455134843ad86739855b7e6199977bf6dc8898/lz4-4.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:f5024d3ca2383470f7c4ef4d0ed8eabad0b22b23eeefde1c192cf1a38d5e9f78", size = 99916, upload-time = "2025-04-01T22:55:29.933Z" }, + { url = "https://files.pythonhosted.org/packages/87/c8/1675527549ee174b9e1db089f7ddfbb962a97314657269b1e0344a5eaf56/lz4-4.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:6ea715bb3357ea1665f77874cf8f55385ff112553db06f3742d3cdcec08633f7", size = 89741, upload-time = "2025-04-01T22:55:31.184Z" }, + { url = "https://files.pythonhosted.org/packages/f7/2d/5523b4fabe11cd98f040f715728d1932eb7e696bfe94391872a823332b94/lz4-4.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:23ae267494fdd80f0d2a131beff890cf857f1b812ee72dbb96c3204aab725553", size = 220669, upload-time = "2025-04-01T22:55:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/91/06/1a5bbcacbfb48d8ee5b6eb3fca6aa84143a81d92946bdb5cd6b005f1863e/lz4-4.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fff9f3a1ed63d45cb6514bfb8293005dc4141341ce3500abdfeb76124c0b9b2e", size = 189661, upload-time = "2025-04-01T22:55:33.413Z" }, + { url = "https://files.pythonhosted.org/packages/fa/08/39eb7ac907f73e11a69a11576a75a9e36406b3241c0ba41453a7eb842abb/lz4-4.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ea7f07329f85a8eda4d8cf937b87f27f0ac392c6400f18bea2c667c8b7f8ecc", size = 1238775, upload-time = "2025-04-01T22:55:34.835Z" }, + { url = "https://files.pythonhosted.org/packages/e9/26/05840fbd4233e8d23e88411a066ab19f1e9de332edddb8df2b6a95c7fddc/lz4-4.4.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ccab8f7f7b82f9fa9fc3b0ba584d353bd5aa818d5821d77d5b9447faad2aaad", size = 1265143, upload-time = "2025-04-01T22:55:35.933Z" }, + { url = "https://files.pythonhosted.org/packages/b7/5d/5f2db18c298a419932f3ab2023deb689863cf8fd7ed875b1c43492479af2/lz4-4.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e43e9d48b2daf80e486213128b0763deed35bbb7a59b66d1681e205e1702d735", size = 1185032, upload-time = "2025-04-01T22:55:37.454Z" }, + { url = "https://files.pythonhosted.org/packages/c4/e6/736ab5f128694b0f6aac58343bcf37163437ac95997276cd0be3ea4c3342/lz4-4.4.4-cp312-cp312-win32.whl", hash = "sha256:33e01e18e4561b0381b2c33d58e77ceee850a5067f0ece945064cbaac2176962", size = 88284, upload-time = "2025-04-01T22:55:38.536Z" }, + { url = "https://files.pythonhosted.org/packages/40/b8/243430cb62319175070e06e3a94c4c7bd186a812e474e22148ae1290d47d/lz4-4.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d21d1a2892a2dcc193163dd13eaadabb2c1b803807a5117d8f8588b22eaf9f12", size = 99918, upload-time = "2025-04-01T22:55:39.628Z" }, + { url = "https://files.pythonhosted.org/packages/6c/e1/0686c91738f3e6c2e1a243e0fdd4371667c4d2e5009b0a3605806c2aa020/lz4-4.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:2f4f2965c98ab254feddf6b5072854a6935adab7bc81412ec4fe238f07b85f62", size = 89736, upload-time = "2025-04-01T22:55:40.5Z" }, + { url = "https://files.pythonhosted.org/packages/3b/3c/d1d1b926d3688263893461e7c47ed7382a969a0976fc121fc678ec325fc6/lz4-4.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ed6eb9f8deaf25ee4f6fad9625d0955183fdc90c52b6f79a76b7f209af1b6e54", size = 220678, upload-time = "2025-04-01T22:55:41.78Z" }, + { url = "https://files.pythonhosted.org/packages/26/89/8783d98deb058800dabe07e6cdc90f5a2a8502a9bad8c5343c641120ace2/lz4-4.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:18ae4fe3bafb344dbd09f976d45cbf49c05c34416f2462828f9572c1fa6d5af7", size = 189670, upload-time = "2025-04-01T22:55:42.775Z" }, + { url = "https://files.pythonhosted.org/packages/22/ab/a491ace69a83a8914a49f7391e92ca0698f11b28d5ce7b2ececa2be28e9a/lz4-4.4.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57fd20c5fc1a49d1bbd170836fccf9a338847e73664f8e313dce6ac91b8c1e02", size = 1238746, upload-time = "2025-04-01T22:55:43.797Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/a1f2f4fdc6b7159c0d12249456f9fe454665b6126e98dbee9f2bd3cf735c/lz4-4.4.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9cb387c33f014dae4db8cb4ba789c8d2a0a6d045ddff6be13f6c8d9def1d2a6", size = 1265119, upload-time = "2025-04-01T22:55:44.943Z" }, + { url = "https://files.pythonhosted.org/packages/50/6e/e22e50f5207649db6ea83cd31b79049118305be67e96bec60becf317afc6/lz4-4.4.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0be9f68240231e1e44118a4ebfecd8a5d4184f0bdf5c591c98dd6ade9720afd", size = 1184954, upload-time = "2025-04-01T22:55:46.161Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c4/2a458039645fcc6324ece731d4d1361c5daf960b553d1fcb4261ba07d51c/lz4-4.4.4-cp313-cp313-win32.whl", hash = "sha256:e9ec5d45ea43684f87c316542af061ef5febc6a6b322928f059ce1fb289c298a", size = 88289, upload-time = "2025-04-01T22:55:47.601Z" }, + { url = "https://files.pythonhosted.org/packages/00/96/b8e24ea7537ab418074c226279acfcaa470e1ea8271003e24909b6db942b/lz4-4.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:a760a175b46325b2bb33b1f2bbfb8aa21b48e1b9653e29c10b6834f9bb44ead4", size = 99925, upload-time = "2025-04-01T22:55:48.463Z" }, + { url = "https://files.pythonhosted.org/packages/a5/a5/f9838fe6aa132cfd22733ed2729d0592259fff074cefb80f19aa0607367b/lz4-4.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:f4c21648d81e0dda38b4720dccc9006ae33b0e9e7ffe88af6bf7d4ec124e2fba", size = 89743, upload-time = "2025-04-01T22:55:49.716Z" }, +] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -2630,6 +2720,44 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl", hash = "sha256:93691da911e5d9d2e23bc54472892aff676df27a75274962ff9edc210364266d", size = 53481, upload-time = "2025-08-29T07:20:42.218Z" }, ] +[[package]] +name = "msgpack" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/b1/ea4f68038a18c77c9467400d166d74c4ffa536f34761f7983a104357e614/msgpack-1.1.1.tar.gz", hash = "sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd", size = 173555, upload-time = "2025-06-13T06:52:51.324Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/83/97f24bf9848af23fe2ba04380388216defc49a8af6da0c28cc636d722502/msgpack-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:71ef05c1726884e44f8b1d1773604ab5d4d17729d8491403a705e649116c9558", size = 82728, upload-time = "2025-06-13T06:51:50.68Z" }, + { url = "https://files.pythonhosted.org/packages/aa/7f/2eaa388267a78401f6e182662b08a588ef4f3de6f0eab1ec09736a7aaa2b/msgpack-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:36043272c6aede309d29d56851f8841ba907a1a3d04435e43e8a19928e243c1d", size = 79279, upload-time = "2025-06-13T06:51:51.72Z" }, + { url = "https://files.pythonhosted.org/packages/f8/46/31eb60f4452c96161e4dfd26dbca562b4ec68c72e4ad07d9566d7ea35e8a/msgpack-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a32747b1b39c3ac27d0670122b57e6e57f28eefb725e0b625618d1b59bf9d1e0", size = 423859, upload-time = "2025-06-13T06:51:52.749Z" }, + { url = "https://files.pythonhosted.org/packages/45/16/a20fa8c32825cc7ae8457fab45670c7a8996d7746ce80ce41cc51e3b2bd7/msgpack-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a8b10fdb84a43e50d38057b06901ec9da52baac6983d3f709d8507f3889d43f", size = 429975, upload-time = "2025-06-13T06:51:53.97Z" }, + { url = "https://files.pythonhosted.org/packages/86/ea/6c958e07692367feeb1a1594d35e22b62f7f476f3c568b002a5ea09d443d/msgpack-1.1.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba0c325c3f485dc54ec298d8b024e134acf07c10d494ffa24373bea729acf704", size = 413528, upload-time = "2025-06-13T06:51:55.507Z" }, + { url = "https://files.pythonhosted.org/packages/75/05/ac84063c5dae79722bda9f68b878dc31fc3059adb8633c79f1e82c2cd946/msgpack-1.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:88daaf7d146e48ec71212ce21109b66e06a98e5e44dca47d853cbfe171d6c8d2", size = 413338, upload-time = "2025-06-13T06:51:57.023Z" }, + { url = "https://files.pythonhosted.org/packages/69/e8/fe86b082c781d3e1c09ca0f4dacd457ede60a13119b6ce939efe2ea77b76/msgpack-1.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8b55ea20dc59b181d3f47103f113e6f28a5e1c89fd5b67b9140edb442ab67f2", size = 422658, upload-time = "2025-06-13T06:51:58.419Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2b/bafc9924df52d8f3bb7c00d24e57be477f4d0f967c0a31ef5e2225e035c7/msgpack-1.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a28e8072ae9779f20427af07f53bbb8b4aa81151054e882aee333b158da8752", size = 427124, upload-time = "2025-06-13T06:51:59.969Z" }, + { url = "https://files.pythonhosted.org/packages/a2/3b/1f717e17e53e0ed0b68fa59e9188f3f610c79d7151f0e52ff3cd8eb6b2dc/msgpack-1.1.1-cp311-cp311-win32.whl", hash = "sha256:7da8831f9a0fdb526621ba09a281fadc58ea12701bc709e7b8cbc362feabc295", size = 65016, upload-time = "2025-06-13T06:52:01.294Z" }, + { url = "https://files.pythonhosted.org/packages/48/45/9d1780768d3b249accecc5a38c725eb1e203d44a191f7b7ff1941f7df60c/msgpack-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fd1b58e1431008a57247d6e7cc4faa41c3607e8e7d4aaf81f7c29ea013cb458", size = 72267, upload-time = "2025-06-13T06:52:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/e3/26/389b9c593eda2b8551b2e7126ad3a06af6f9b44274eb3a4f054d48ff7e47/msgpack-1.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae497b11f4c21558d95de9f64fff7053544f4d1a17731c866143ed6bb4591238", size = 82359, upload-time = "2025-06-13T06:52:03.909Z" }, + { url = "https://files.pythonhosted.org/packages/ab/65/7d1de38c8a22cf8b1551469159d4b6cf49be2126adc2482de50976084d78/msgpack-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:33be9ab121df9b6b461ff91baac6f2731f83d9b27ed948c5b9d1978ae28bf157", size = 79172, upload-time = "2025-06-13T06:52:05.246Z" }, + { url = "https://files.pythonhosted.org/packages/0f/bd/cacf208b64d9577a62c74b677e1ada005caa9b69a05a599889d6fc2ab20a/msgpack-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f64ae8fe7ffba251fecb8408540c34ee9df1c26674c50c4544d72dbf792e5ce", size = 425013, upload-time = "2025-06-13T06:52:06.341Z" }, + { url = "https://files.pythonhosted.org/packages/4d/ec/fd869e2567cc9c01278a736cfd1697941ba0d4b81a43e0aa2e8d71dab208/msgpack-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a494554874691720ba5891c9b0b39474ba43ffb1aaf32a5dac874effb1619e1a", size = 426905, upload-time = "2025-06-13T06:52:07.501Z" }, + { url = "https://files.pythonhosted.org/packages/55/2a/35860f33229075bce803a5593d046d8b489d7ba2fc85701e714fc1aaf898/msgpack-1.1.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb643284ab0ed26f6957d969fe0dd8bb17beb567beb8998140b5e38a90974f6c", size = 407336, upload-time = "2025-06-13T06:52:09.047Z" }, + { url = "https://files.pythonhosted.org/packages/8c/16/69ed8f3ada150bf92745fb4921bd621fd2cdf5a42e25eb50bcc57a5328f0/msgpack-1.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d275a9e3c81b1093c060c3837e580c37f47c51eca031f7b5fb76f7b8470f5f9b", size = 409485, upload-time = "2025-06-13T06:52:10.382Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b6/0c398039e4c6d0b2e37c61d7e0e9d13439f91f780686deb8ee64ecf1ae71/msgpack-1.1.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fd6b577e4541676e0cc9ddc1709d25014d3ad9a66caa19962c4f5de30fc09ef", size = 412182, upload-time = "2025-06-13T06:52:11.644Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d0/0cf4a6ecb9bc960d624c93effaeaae75cbf00b3bc4a54f35c8507273cda1/msgpack-1.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb29aaa613c0a1c40d1af111abf025f1732cab333f96f285d6a93b934738a68a", size = 419883, upload-time = "2025-06-13T06:52:12.806Z" }, + { url = "https://files.pythonhosted.org/packages/62/83/9697c211720fa71a2dfb632cad6196a8af3abea56eece220fde4674dc44b/msgpack-1.1.1-cp312-cp312-win32.whl", hash = "sha256:870b9a626280c86cff9c576ec0d9cbcc54a1e5ebda9cd26dab12baf41fee218c", size = 65406, upload-time = "2025-06-13T06:52:14.271Z" }, + { url = "https://files.pythonhosted.org/packages/c0/23/0abb886e80eab08f5e8c485d6f13924028602829f63b8f5fa25a06636628/msgpack-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:5692095123007180dca3e788bb4c399cc26626da51629a31d40207cb262e67f4", size = 72558, upload-time = "2025-06-13T06:52:15.252Z" }, + { url = "https://files.pythonhosted.org/packages/a1/38/561f01cf3577430b59b340b51329803d3a5bf6a45864a55f4ef308ac11e3/msgpack-1.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0", size = 81677, upload-time = "2025-06-13T06:52:16.64Z" }, + { url = "https://files.pythonhosted.org/packages/09/48/54a89579ea36b6ae0ee001cba8c61f776451fad3c9306cd80f5b5c55be87/msgpack-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9", size = 78603, upload-time = "2025-06-13T06:52:17.843Z" }, + { url = "https://files.pythonhosted.org/packages/a0/60/daba2699b308e95ae792cdc2ef092a38eb5ee422f9d2fbd4101526d8a210/msgpack-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8", size = 420504, upload-time = "2025-06-13T06:52:18.982Z" }, + { url = "https://files.pythonhosted.org/packages/20/22/2ebae7ae43cd8f2debc35c631172ddf14e2a87ffcc04cf43ff9df9fff0d3/msgpack-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a", size = 423749, upload-time = "2025-06-13T06:52:20.211Z" }, + { url = "https://files.pythonhosted.org/packages/40/1b/54c08dd5452427e1179a40b4b607e37e2664bca1c790c60c442c8e972e47/msgpack-1.1.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac", size = 404458, upload-time = "2025-06-13T06:52:21.429Z" }, + { url = "https://files.pythonhosted.org/packages/2e/60/6bb17e9ffb080616a51f09928fdd5cac1353c9becc6c4a8abd4e57269a16/msgpack-1.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b", size = 405976, upload-time = "2025-06-13T06:52:22.995Z" }, + { url = "https://files.pythonhosted.org/packages/ee/97/88983e266572e8707c1f4b99c8fd04f9eb97b43f2db40e3172d87d8642db/msgpack-1.1.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7", size = 408607, upload-time = "2025-06-13T06:52:24.152Z" }, + { url = "https://files.pythonhosted.org/packages/bc/66/36c78af2efaffcc15a5a61ae0df53a1d025f2680122e2a9eb8442fed3ae4/msgpack-1.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5", size = 424172, upload-time = "2025-06-13T06:52:25.704Z" }, + { url = "https://files.pythonhosted.org/packages/8c/87/a75eb622b555708fe0427fab96056d39d4c9892b0c784b3a721088c7ee37/msgpack-1.1.1-cp313-cp313-win32.whl", hash = "sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323", size = 65347, upload-time = "2025-06-13T06:52:26.846Z" }, + { url = "https://files.pythonhosted.org/packages/ca/91/7dc28d5e2a11a5ad804cf2b7f7a5fcb1eb5a4966d66a5d2b41aee6376543/msgpack-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69", size = 72341, upload-time = "2025-06-13T06:52:27.835Z" }, +] + [[package]] name = "mypy" version = "1.18.1" @@ -2715,6 +2843,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/df/76d0321c3797b54b60fef9ec3bd6f4cfd124b9e422182156a1dd418722cf/myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d", size = 84579, upload-time = "2025-02-12T10:53:02.078Z" }, ] +[[package]] +name = "narwhals" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/b8/3cb005704866f1cc19e8d6b15d0467255821ba12d82f20ea15912672e54c/narwhals-2.5.0.tar.gz", hash = "sha256:8ae0b6f39597f14c0dc52afc98949d6f8be89b5af402d2d98101d2f7d3561418", size = 558573, upload-time = "2025-09-12T10:04:24.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl", hash = "sha256:7e213f9ca7db3f8bf6f7eff35eaee6a1cf80902997e1b78d49b7755775d8f423", size = 407296, upload-time = "2025-09-12T10:04:22.524Z" }, +] + [[package]] name = "nbclient" version = "0.10.2" @@ -4272,6 +4409,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, ] +[[package]] +name = "tblib" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/95/4b3044ec4bf248186769629bbfb495a458deb6e4c1f9eff7f298ae1e336e/tblib-3.1.0.tar.gz", hash = "sha256:06404c2c9f07f66fee2d7d6ad43accc46f9c3361714d9b8426e7f47e595cd652", size = 30766, upload-time = "2025-03-31T12:58:27.473Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/44/aa5c8b10b2cce7a053018e0d132bd58e27527a0243c4985383d5b6fd93e9/tblib-3.1.0-py3-none-any.whl", hash = "sha256:670bb4582578134b3d81a84afa1b016128b429f3d48e6cbbaecc9d15675e984e", size = 12552, upload-time = "2025-03-31T12:58:26.142Z" }, +] + [[package]] name = "tenacity" version = "9.1.2" @@ -4622,6 +4768,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8d/f0/73c24457c941b8b08f7d090853e40f4b2cdde88b5da721f3f28e98df77c9/xarray-2025.9.0-py3-none-any.whl", hash = "sha256:79f0e25fb39571f612526ee998ee5404d8725a1db3951aabffdb287388885df0", size = 1349595, upload-time = "2025-09-04T04:20:24.36Z" }, ] +[[package]] +name = "xyzservices" +version = "2025.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/af/c0f7f97bb320d14c089476f487b81f733238cc5603e0914f2e409f49d589/xyzservices-2025.4.0.tar.gz", hash = "sha256:6fe764713648fac53450fbc61a3c366cb6ae5335a1b2ae0c3796b495de3709d8", size = 1134722, upload-time = "2025-04-25T10:38:09.669Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/7d/b77455d7c7c51255b2992b429107fab811b2e36ceaf76da1e55a045dc568/xyzservices-2025.4.0-py3-none-any.whl", hash = "sha256:8d4db9a59213ccb4ce1cf70210584f30b10795bff47627cdfb862b39ff6e10c9", size = 90391, upload-time = "2025-04-25T10:38:08.468Z" }, +] + [[package]] name = "zarr" version = "3.1.2" @@ -4638,6 +4793,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e0/a3/d3d4fd394a10b1256f9dccb2fe0ddd125fc575d7c437b1c70df050f14176/zarr-3.1.2-py3-none-any.whl", hash = "sha256:c3e180f53ee0ef91b86f7feff6f9dd381ddd1b512d1a46580530966a493387b6", size = 261041, upload-time = "2025-08-25T15:32:29.522Z" }, ] +[[package]] +name = "zict" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/ac/3c494dd7ec5122cff8252c1a209b282c0867af029f805ae9befd73ae37eb/zict-3.0.0.tar.gz", hash = "sha256:e321e263b6a97aafc0790c3cfb3c04656b7066e6738c37fffcca95d803c9fba5", size = 33238, upload-time = "2023-04-17T21:41:16.041Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/ab/11a76c1e2126084fde2639514f24e6111b789b0bfa4fc6264a8975c7e1f1/zict-3.0.0-py2.py3-none-any.whl", hash = "sha256:5796e36bd0e0cc8cf0fbc1ace6a68912611c1dbd74750a3f3026b9b9d6a327ae", size = 43332, upload-time = "2023-04-17T21:41:13.444Z" }, +] + [[package]] name = "zipp" version = "3.23.0"