From 98b2ac1140794a1d0fe3e9a83bdc3383588fb6d7 Mon Sep 17 00:00:00 2001 From: Lukas Bindreiter Date: Thu, 29 Jan 2026 14:59:17 +0100 Subject: [PATCH] LocalFileSystem storage client --- CHANGELOG.md | 11 +++ tilebox-storage/tilebox/storage/__init__.py | 12 +++ tilebox-storage/tilebox/storage/aio.py | 93 +++++++++++++++++++-- tilebox-storage/tilebox/storage/granule.py | 25 ++++++ uv.lock | 56 ++++++------- 5 files changed, 162 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9deb1d..1e11f35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +`tilebox-storage`: Added a `LocalFileSystemStorageClient` to access data on a local file system, a mounted network file +system or a syncified directory with a remote file system (e.g. Dropbox, Google Drive, etc.). + +### Changed + +`tilebox-storage`: Renamed the existing `StorageClient` base class in `tilebox.storage.aio` to `CachingStorageClient` +to accomodate the new `StorageClient` base class that does not provide caching, since `LocalFileSystemStorageClient` is +the first client that does not cache data (since it's already on the local file system). + ## [0.47.0] - 2026-01-28 ### Added diff --git a/tilebox-storage/tilebox/storage/__init__.py b/tilebox-storage/tilebox/storage/__init__.py index 28975f7..e7785be 100644 --- a/tilebox-storage/tilebox/storage/__init__.py +++ b/tilebox-storage/tilebox/storage/__init__.py @@ -2,6 +2,7 @@ from tilebox.storage.aio import ASFStorageClient as _ASFStorageClient from tilebox.storage.aio import CopernicusStorageClient as _CopernicusStorageClient +from tilebox.storage.aio import LocalFileSystemStorageClient as _LocalFileSystemStorageClient from tilebox.storage.aio import UmbraStorageClient as _UmbraStorageClient from tilebox.storage.aio import USGSLandsatStorageClient as _USGSLandsatStorageClient @@ -66,3 +67,14 @@ def __init__(self, cache_directory: Path | None = Path.home() / ".cache" / "tile """ super().__init__(cache_directory) self._syncify() + + +class LocalFileSystemStorageClient(_LocalFileSystemStorageClient): + def __init__(self, root: Path) -> None: + """A tilebox storage client for accessing data on a local file system, or a mounted network file system. + + Args: + root: The root directory of the file system to access. + """ + super().__init__(root) + self._syncify() diff --git a/tilebox-storage/tilebox/storage/aio.py b/tilebox-storage/tilebox/storage/aio.py index 1ad4a9d..ce5bda6 100644 --- a/tilebox-storage/tilebox/storage/aio.py +++ b/tilebox-storage/tilebox/storage/aio.py @@ -24,6 +24,7 @@ from tilebox.storage.granule import ( ASFStorageGranule, CopernicusStorageGranule, + LocationStorageGranule, UmbraStorageGranule, USGSLandsatStorageGranule, ) @@ -241,6 +242,10 @@ def _display_quicklook(image_data: bytes | Path, width: int, height: int, image_ class StorageClient(Syncifiable): + """Base class for all storage clients.""" + + +class CachingStorageClient(StorageClient): def __init__(self, cache_directory: Path | None) -> None: self._cache = cache_directory @@ -323,7 +328,7 @@ async def _download_object( return output_path -class ASFStorageClient(StorageClient): +class ASFStorageClient(CachingStorageClient): def __init__(self, user: str, password: str, cache_directory: Path = Path.home() / ".cache" / "tilebox") -> None: """A tilebox storage client that downloads data from the Alaska Satellite Facility. @@ -415,7 +420,7 @@ async def quicklook(self, datapoint: xr.Dataset | ASFStorageGranule, width: int """ granule = ASFStorageGranule.from_data(datapoint) if Image is None: - raise ImportError("IPython is not available, please use download_preview instead.") + raise ImportError("IPython is not available, please use download_quicklook instead.") quicklook = await self._download_quicklook(datapoint) _display_quicklook(quicklook, width, height, f"Image {quicklook.name} © ASF {granule.time.year}") @@ -439,7 +444,7 @@ def _umbra_s3_prefix(datapoint: xr.Dataset | UmbraStorageGranule) -> str: return f"sar-data/tasks/{granule.location}/" -class UmbraStorageClient(StorageClient): +class UmbraStorageClient(CachingStorageClient): _STORAGE_PROVIDER = "Umbra" _BUCKET = "umbra-open-data-catalog" _REGION = "us-west-2" @@ -539,7 +544,7 @@ def _copernicus_s3_prefix(datapoint: xr.Dataset | CopernicusStorageGranule) -> s return granule.location.removeprefix("/eodata/") -class CopernicusStorageClient(StorageClient): +class CopernicusStorageClient(CachingStorageClient): _STORAGE_PROVIDER = "CopernicusDataspace" _BUCKET = "eodata" _ENDPOINT_URL = "https://eodata.dataspace.copernicus.eu" @@ -724,7 +729,7 @@ async def quicklook( ValueError: If no quicklook is available for the given datapoint. """ if Image is None: - raise ImportError("IPython is not available, please use download_preview instead.") + raise ImportError("IPython is not available, please use download_quicklook instead.") granule = CopernicusStorageGranule.from_data(datapoint) quicklook = await self._download_quicklook(granule) _display_quicklook(quicklook, width, height, f"{granule.granule_name} © ESA {granule.time.year}") @@ -750,7 +755,7 @@ def _landsat_s3_prefix(datapoint: xr.Dataset | USGSLandsatStorageGranule) -> str return granule.location.removeprefix("s3://usgs-landsat/") -class USGSLandsatStorageClient(StorageClient): +class USGSLandsatStorageClient(CachingStorageClient): """ A client for downloading USGS Landsat data from the usgs-landsat and usgs-landsat-ard S3 bucket. @@ -883,7 +888,7 @@ async def quicklook( ValueError: If no quicklook is available for the given datapoint. """ if Image is None: - raise ImportError("IPython is not available, please use download_preview instead.") + raise ImportError("IPython is not available, please use download_quicklook instead.") quicklook = await self._download_quicklook(datapoint) _display_quicklook(quicklook, width, height, f"Image {quicklook.name} © USGS") @@ -901,3 +906,77 @@ async def _download_quicklook(self, datapoint: xr.Dataset | USGSLandsatStorageGr await download_objects(self._store, prefix, [granule.thumbnail], output_folder, show_progress=False) return output_folder / granule.thumbnail + + +class LocalFileSystemStorageClient(StorageClient): + def __init__(self, root: Path) -> None: + """A tilebox storage client for accessing data on a local file system, or a mounted network file system. + + Args: + root: The root directory of the file system to access. + """ + super().__init__() + self._root = Path(root) + + async def list_objects(self, datapoint: xr.Dataset | LocationStorageGranule) -> list[str]: + """List all available objects for a given datapoint.""" + granule = LocationStorageGranule.from_data(datapoint) + granule_path = self._root / granule.location + return [p.relative_to(granule_path).as_posix() for p in granule_path.rglob("**/*") if p.is_file()] + + async def download( + self, + datapoint: xr.Dataset | LocationStorageGranule, + ) -> Path: + """No-op download method, as the data is already on the local file system. + + Args: + datapoint: The datapoint to locate the data for in the local file system. + + Returns: + The path to the data on the local file system. + """ + granule = LocationStorageGranule.from_data(datapoint) + granule_path = self._root / granule.location + if not granule_path.exists(): + raise ValueError(f"Data not found on the local file system: {granule_path}") + return granule_path + + async def _download_quicklook(self, datapoint: xr.Dataset | LocationStorageGranule) -> Path: + granule = LocationStorageGranule.from_data(datapoint) + if granule.thumbnail is None: + raise ValueError(f"No quicklook available for {granule.location}") + quicklook_path = self._root / granule.thumbnail + if not quicklook_path.exists(): + raise ValueError(f"Quicklook not found on the local file system: {quicklook_path}") + return quicklook_path + + async def download_quicklook(self, datapoint: xr.Dataset | LocationStorageGranule) -> Path: + """No-op download_quicklook method, as the quicklook image is already on the local file system. + + Args: + datapoint: The datapoint to locate the quicklook image for in the local file system. + + Returns: + The path to the data on the local file system. + + Raises: + ValueError: If no quicklook image is available for the given datapoint, or if the quicklook image is not + found on the local file system. + """ + return await self._download_quicklook(datapoint) + + async def quicklook( + self, datapoint: xr.Dataset | LocationStorageGranule, width: int = 600, height: int = 600 + ) -> None: + """Display the quicklook image for a given datapoint. + + Args: + datapoint: The datapoint to display the quicklook for. + width: Display width of the image in pixels. Defaults to 600. + height: Display height of the image in pixels. Defaults to 600. + """ + quicklook_path = await self._download_quicklook(datapoint) + if Image is None: + raise ImportError("IPython is not available, please use download_quicklook instead.") + _display_quicklook(quicklook_path, width, height, None) diff --git a/tilebox-storage/tilebox/storage/granule.py b/tilebox-storage/tilebox/storage/granule.py index 88c135d..f214bba 100644 --- a/tilebox-storage/tilebox/storage/granule.py +++ b/tilebox-storage/tilebox/storage/granule.py @@ -183,3 +183,28 @@ def from_data(cls, dataset: "xr.Dataset | USGSLandsatStorageGranule") -> "USGSLa dataset.location.item().replace("s3://usgs-landsat-ard/", "s3://usgs-landsat/"), thumbnail, ) + + +@dataclass +class LocationStorageGranule: + location: str + thumbnail: str | None = None + + @classmethod + def from_data(cls, dataset: "xr.Dataset | LocationStorageGranule") -> "LocationStorageGranule": + """Extract the granule information from a datapoint given as xarray dataset.""" + if isinstance(dataset, LocationStorageGranule): + return dataset + + if "location" not in dataset: + raise ValueError("The given dataset has no location information.") + + thumbnail = None + if "thumbnail" in dataset: + thumbnail = dataset.thumbnail.item() + elif "overview" in dataset: + thumbnail = dataset.overview.item() + elif "quicklook" in dataset: + thumbnail = dataset.quicklook.item() + + return cls(dataset.location.item(), thumbnail) diff --git a/uv.lock b/uv.lock index 8315f26..5d7617e 100644 --- a/uv.lock +++ b/uv.lock @@ -69,30 +69,30 @@ wheels = [ [[package]] name = "boto3" -version = "1.42.36" +version = "1.42.37" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/54/06/50ff808cf4f40efada8edc20f9d563ab287864423c874dfb94f755a60c52/boto3-1.42.36.tar.gz", hash = "sha256:a4eb51105c8c5d7b2bc2a9e2316e69baf69a55611275b9f189c0cf59f1aae171", size = 112839, upload-time = "2026-01-27T20:38:26.992Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/ef/0d6ceb88ae2b3638b956190a431e4a8a3697d5769d4bbbede8efcccacaea/boto3-1.42.37.tar.gz", hash = "sha256:d8b6c52c86f3bf04f71a5a53e7fb4d1527592afebffa5170cf3ef7d70966e610", size = 112830, upload-time = "2026-01-28T20:38:43.339Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/d1/35d12f04a7792e2f5e9ddff3c7b60493a32027761380dee7f24ee8ae80cc/boto3-1.42.36-py3-none-any.whl", hash = "sha256:e0ff6f2747bfdec63405b35ea185a7aea35239c3f4fe99e4d29368a6de9c4a84", size = 140604, upload-time = "2026-01-27T20:38:25.349Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a4/cd334f74498acc6ad42a69c48e8c495f6f721d8abe13f8ef0d4b862fb1c0/boto3-1.42.37-py3-none-any.whl", hash = "sha256:e1e38fd178ffc66cfbe9cb6838b8c460000c3eb741e5f40f57eb730780ef0ed4", size = 140604, upload-time = "2026-01-28T20:38:42.135Z" }, ] [[package]] name = "boto3-stubs" -version = "1.42.36" +version = "1.42.37" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore-stubs" }, { name = "types-s3transfer" }, { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f7/bc/1d6fc5e84cc89c670a6c8c224deee2cdc8f05d0db1723912566dec26915c/boto3_stubs-1.42.36.tar.gz", hash = "sha256:0c367fbe305d9c76983989eea81e4ee2f43b992f553fdb8a0de67908877c9b00", size = 100861, upload-time = "2026-01-27T20:59:11.859Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/b7/15995a1261cb3dccaee7e53e1e27d14fb542c56b95883598b53190e7d979/boto3_stubs-1.42.37.tar.gz", hash = "sha256:1620519a55bbb26cebed95b6d8f26ba96b8ea91dadd05eafc3b8f17a587e2108", size = 100870, upload-time = "2026-01-28T20:56:37.32Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/4f/900008ba354fbf3861de324a685a04c352e8292a2af5fb32153378797f67/boto3_stubs-1.42.36-py3-none-any.whl", hash = "sha256:02904609e0d0aefc7f9c330b4312a5a2bff3789b03a1ba30e7cb8acb11c616a5", size = 69783, upload-time = "2026-01-27T20:59:07.746Z" }, + { url = "https://files.pythonhosted.org/packages/12/10/ad4f3ffcdc46df83df4ba06d7692ea0869700537163cd867dd66f966835b/boto3_stubs-1.42.37-py3-none-any.whl", hash = "sha256:07b9ac27196b233b802f8fadff2fa9c01d656927943c618dc862ff00fd592b24", size = 69785, upload-time = "2026-01-28T20:56:29.211Z" }, ] [package.optional-dependencies] @@ -108,28 +108,28 @@ essential = [ [[package]] name = "botocore" -version = "1.42.36" +version = "1.42.37" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/4e/b24089cf7a77d38886ac4fbae300a3c4c6d68c1b9ccb66af03cb07b6c35c/botocore-1.42.36.tar.gz", hash = "sha256:2ebd89cc75927944e2cee51b7adce749f38e0cb269a758a6464a27f8bcca65fb", size = 14909073, upload-time = "2026-01-27T20:38:16.621Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/4d/94292e7686e64d2ede8dae7102bbb11a1474e407c830de4192f2518e6cff/botocore-1.42.37.tar.gz", hash = "sha256:3ec58eb98b0857f67a2ae6aa3ded51597e7335f7640be654e0e86da4f173b5b2", size = 14914621, upload-time = "2026-01-28T20:38:34.586Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/e8/f14d25bd768187424b385bc6a44e2ed5e96964e461ba019add03e48713c7/botocore-1.42.36-py3-none-any.whl", hash = "sha256:2cfae4c482e5e87bd835ab4289b711490c161ba57e852c06b65a03e7c25e08eb", size = 14583066, upload-time = "2026-01-27T20:38:14.02Z" }, + { url = "https://files.pythonhosted.org/packages/72/30/54042dd3ad8161964f8f47aa418785079bd8d2f17053c40d65bafb9f6eed/botocore-1.42.37-py3-none-any.whl", hash = "sha256:f13bb8b560a10714d96fb7b0c7f17828dfa6e6606a1ead8c01c6ebb8765acbd8", size = 14589390, upload-time = "2026-01-28T20:38:31.306Z" }, ] [[package]] name = "botocore-stubs" -version = "1.42.36" +version = "1.42.37" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "types-awscrt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6c/9b/0fe6bdda315e43cb01719eb5b1c19224310fadac696c5b2914b6a4a686e4/botocore_stubs-1.42.36.tar.gz", hash = "sha256:b3e7505f411b26b303ed24835ac29a7aa7ec2aedcead92e4665d1b4566c396bd", size = 42410, upload-time = "2026-01-27T21:25:28.077Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/d4/0348543289c57b766622958ebf0b9cc2d9ebd36e803f25e0e55455bbb165/botocore_stubs-1.42.37.tar.gz", hash = "sha256:7357d1876ae198757dbe0a73f887449ffdda18eb075d7d3cc2e22d3580dcb17c", size = 42399, upload-time = "2026-01-28T21:35:52.863Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/57/a3ad25c101f8839addc754900b8ef890efb5ca350f0cd284e732d526773f/botocore_stubs-1.42.36-py3-none-any.whl", hash = "sha256:15bed118ffc4d413a75822c7182af9a9e9989ccdf5d9528bc7b98bb03edf2971", size = 66761, upload-time = "2026-01-27T21:25:26.92Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c8/41d4e54f92865aac2afcae22c6e892659f5172b264d7dec28cf1bb36de7a/botocore_stubs-1.42.37-py3-none-any.whl", hash = "sha256:5a9b2a4062f7cc19e0648508f67d3f1a1fd8d3e0d6f5a0d3244cc9656e54cc67", size = 66761, upload-time = "2026-01-28T21:35:51.749Z" }, ] [[package]] @@ -911,15 +911,15 @@ wheels = [ [[package]] name = "hypothesis" -version = "6.151.3" +version = "6.151.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "sortedcontainers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e9/aa/0e1aff2de954a1b11acf643519599c3da97d461cf62e312cab266aa9f855/hypothesis-6.151.3.tar.gz", hash = "sha256:31bcd884b0be8f478aa2e52f0f73be03e8f6a9a2c66706fb93410ebaa5c27bcc", size = 475822, upload-time = "2026-01-28T05:37:32.494Z" } +sdist = { url = "https://files.pythonhosted.org/packages/47/03/9fd03d5db09029250e69745c1600edab16fe90947636f77a12ba92d79939/hypothesis-6.151.4.tar.gz", hash = "sha256:658a62da1c3ccb36746ac2f7dc4bb1a6e76bd314e0dc54c4e1aaba2503d5545c", size = 475706, upload-time = "2026-01-29T01:30:14.985Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/9a/e39da18745c889c7959e0ef4b53b71522062cc94714f99ac9d5b86cb222e/hypothesis-6.151.3-py3-none-any.whl", hash = "sha256:879a4b3dec37678fd1bd3a060aaaf3eedc2aab5543d9a85e5f2a04a6445683c6", size = 543275, upload-time = "2026-01-28T05:37:30.312Z" }, + { url = "https://files.pythonhosted.org/packages/9b/6d/01ad1b6c3b8cb2bb47eeaa9765dabc27cbe68e3b59f6cff83d5668f57780/hypothesis-6.151.4-py3-none-any.whl", hash = "sha256:a1cf7e0fdaa296d697a68ff3c0b3912c0050f07aa37e7d2ff33a966749d1d9b4", size = 543146, upload-time = "2026-01-29T01:30:12.805Z" }, ] [[package]] @@ -1302,26 +1302,26 @@ wheels = [ [[package]] name = "mypy-boto3-ec2" -version = "1.42.36" +version = "1.42.37" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/ac/ca563f74c25937a624f797a13f90eaac51591d2c3aaba768fdea36547dee/mypy_boto3_ec2-1.42.36.tar.gz", hash = "sha256:0468a5fe39eb7d45c6e0af4aeacf25663f251acfe0b6c227effdbc2e630beec6", size = 433827, upload-time = "2026-01-27T20:54:18.76Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/c6/864d5fd7f0e763f5ac537905de40ffedc693ec85cc5429d02bda57ae404d/mypy_boto3_ec2-1.42.37.tar.gz", hash = "sha256:b0e169def4c7a983c8e1813c1b27b9afef331b8972df1b63d141e7732b6aa1bc", size = 434122, upload-time = "2026-01-28T20:51:36.719Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/3f/fd1dae13beb721f1889140b8090321d5cc5befe13c223df04099cc296110/mypy_boto3_ec2-1.42.36-py3-none-any.whl", hash = "sha256:7ea0cd827ed77d60c0c6355b1752ac683898de6f19c2db942c7a87b5b1c483bb", size = 423968, upload-time = "2026-01-27T20:54:14.151Z" }, + { url = "https://files.pythonhosted.org/packages/cb/11/1c93fcd8f824c76c360ead47e83e1eb4d23b3d5c38d131482e2cdf616235/mypy_boto3_ec2-1.42.37-py3-none-any.whl", hash = "sha256:5c3c3e6fc207cc83ba31b925f4c13b4f8444791bf5c9e9a822e95d2e9c9050a2", size = 424244, upload-time = "2026-01-28T20:51:29.15Z" }, ] [[package]] name = "mypy-boto3-lambda" -version = "1.42.8" +version = "1.42.37" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/85/b1/d80eed0b9ec23f6995610e215928e62db130bde9a3b4bba336714b1eae83/mypy_boto3_lambda-1.42.8.tar.gz", hash = "sha256:55deadbfaf0e5f118237831a84d35f48dc7164ce2bf7efdcb54f54aef4025602", size = 51742, upload-time = "2025-12-11T22:12:46.909Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/a8/caec8f9a196143f991c603b9364b0a1300b5c54d0c833c6c1dc792b3050c/mypy_boto3_lambda-1.42.37.tar.gz", hash = "sha256:94f7f0708f9b5ffa5b8b3eb6d564be1ef402ebb8b8cd96045332b7a3bc1ea0e0", size = 51821, upload-time = "2026-01-28T20:51:44.74Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/16/0cdb7af03c752146e603ea5cb2bac531f5099f3850bfee89f0806671d968/mypy_boto3_lambda-1.42.8-py3-none-any.whl", hash = "sha256:fbb6646138520c675a4c4adff334e830b010d5c077dee8d5187346809ebb6f72", size = 60081, upload-time = "2025-12-11T22:12:38.099Z" }, + { url = "https://files.pythonhosted.org/packages/b8/4c/d172fac61aa689c96fe286e7adb9774e5094f5c0a52fa1a6888699b47d7f/mypy_boto3_lambda-1.42.37-py3-none-any.whl", hash = "sha256:9614518cbe3c300d3d1e2d9c3d857c3829c44a8544c4cd4ca393d35181b22619", size = 60175, upload-time = "2026-01-28T20:51:39.865Z" }, ] [[package]] @@ -1338,14 +1338,14 @@ wheels = [ [[package]] name = "mypy-boto3-s3" -version = "1.42.21" +version = "1.42.37" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a0/32/aa7208348dc8db8bd4ea357e5e6e1e8bcba44419033d03456c3b767a6c98/mypy_boto3_s3-1.42.21.tar.gz", hash = "sha256:cab71c918aac7d98c4d742544c722e37d8e7178acb8bc88a0aead7b1035026d2", size = 76024, upload-time = "2026-01-03T02:46:35.161Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/41/44066f4cd3421bacb6aad4ec7b1da8d0f8858560e526166db64d95fa7ad7/mypy_boto3_s3-1.42.37.tar.gz", hash = "sha256:628a4652f727870a07e1c3854d6f30dc545a7dd5a4b719a2c59c32a95d92e4c1", size = 76317, upload-time = "2026-01-28T20:51:52.971Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/c0/01babfa8cef5f992a2a0f3d52fc1123fbbc336ab6decfdfc8f702e88a8af/mypy_boto3_s3-1.42.21-py3-none-any.whl", hash = "sha256:f5b7d1ed718ba5b00f67e95a9a38c6a021159d3071ea235e6cf496e584115ded", size = 83169, upload-time = "2026-01-03T02:46:33.356Z" }, + { url = "https://files.pythonhosted.org/packages/94/06/cb6050ecd72f5fa449bac80ad1a4711719367c4f545201317f36e3999784/mypy_boto3_s3-1.42.37-py3-none-any.whl", hash = "sha256:7c118665f3f583dbfde1013ce47908749f9d2a760f28f59ec65732306ee9cec9", size = 83439, upload-time = "2026-01-28T20:51:49.99Z" }, ] [[package]] @@ -2418,7 +2418,7 @@ dependencies = [ { name = "tilebox-grpc" }, { name = "tqdm" }, { name = "xarray", version = "2025.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "xarray", version = "2025.12.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "xarray", version = "2026.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] [package.dev-dependencies] @@ -2833,7 +2833,7 @@ wheels = [ [[package]] name = "xarray" -version = "2025.12.0" +version = "2026.1.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", @@ -2851,9 +2851,9 @@ dependencies = [ { name = "packaging", marker = "python_full_version >= '3.11'" }, { name = "pandas", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/af/7b945f331ba8911fdfff2fdfa092763156119f124be1ba4144615c540222/xarray-2025.12.0.tar.gz", hash = "sha256:73f6a6fadccc69c4d45bdd70821a47c72de078a8a0313ff8b1e97cd54ac59fed", size = 3082244, upload-time = "2025-12-05T21:51:22.432Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/85/113ff1e2cde9e8a5b13c2f0ef4e9f5cd6ca3a036b6452f4dd523419289b5/xarray-2026.1.0.tar.gz", hash = "sha256:0c9814761f9d9a9545df37292d3fda89f83201f3e02ae0f09f03313d9cfdd5e2", size = 3107024, upload-time = "2026-01-28T17:49:03.822Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/e4/62a677feefde05b12a70a4fc9bdc8558010182a801fbcab68cb56c2b0986/xarray-2025.12.0-py3-none-any.whl", hash = "sha256:9e77e820474dbbe4c6c2954d0da6342aa484e33adaa96ab916b15a786181e970", size = 1381742, upload-time = "2025-12-05T21:51:20.841Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8e/952a351c10df395d9bab850f611f4368834ae9104d6449049f5a49e00925/xarray-2026.1.0-py3-none-any.whl", hash = "sha256:5fcc03d3ed8dfb662aa254efe6cd65efc70014182bbc2126e4b90d291d970d41", size = 1403009, upload-time = "2026-01-28T17:49:01.538Z" }, ] [[package]]