diff --git a/CHANGELOG.md b/CHANGELOG.md index 8472643..d173c81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Progress reporting ([#55](https://github.com/stac-utils/stac-asset/pull/55), [#69](https://github.com/stac-utils/stac-asset/pull/69)) - `ErrorStrategy` ([#69](https://github.com/stac-utils/stac-asset/pull/69)) - `fail_fast` ([#69](https://github.com/stac-utils/stac-asset/pull/69)) -- `asset_exists` and `Client.href_exists` ([#81](https://github.com/stac-utils/stac-asset/pull/81)) +- `assert_asset_exists`, `asset_exists`, `Client.assert_href_exists`, `Client.href_exists` ([#81](https://github.com/stac-utils/stac-asset/pull/81), [#85](https://github.com/stac-utils/stac-asset/pull/85)) ### Changed diff --git a/src/stac_asset/__init__.py b/src/stac_asset/__init__.py index 8a1269c..9a99a4a 100644 --- a/src/stac_asset/__init__.py +++ b/src/stac_asset/__init__.py @@ -15,6 +15,7 @@ from ._functions import ( + assert_asset_exists, asset_exists, download_asset, download_collection, @@ -53,6 +54,7 @@ "HttpClient", "PlanetaryComputerClient", "S3Client", + "assert_asset_exists", "asset_exists", "download_asset", "download_collection", diff --git a/src/stac_asset/_functions.py b/src/stac_asset/_functions.py index 5e66ed4..235f0fe 100644 --- a/src/stac_asset/_functions.py +++ b/src/stac_asset/_functions.py @@ -392,20 +392,22 @@ async def download_asset( return asset -async def asset_exists( +async def assert_asset_exists( asset: Asset, config: Optional[Config] = None, clients: Optional[List[Client]] = None, -) -> bool: - """Returns true if the asset exists, false if not. +) -> None: + """Asserts that an asset exists. + + Raises the source error if it does not. Args: asset: The asset the check for existence config: The download configuration to use for the existence check clients: Any pre-configured clients to use for the existence check - Returns: - bool: Whether the assets href exists or not + Raises: + Exception: An exception from the underlying client. """ if config is None: config = Config() @@ -413,9 +415,32 @@ async def asset_exists( href = get_absolute_asset_href(asset, config.alternate_assets) if href: client = await clients_.get_client(href) - return await client.href_exists(href) + await client.assert_href_exists(href) else: + raise ValueError("asset does not have an absolute href") + + +async def asset_exists( + asset: Asset, + config: Optional[Config] = None, + clients: Optional[List[Client]] = None, +) -> bool: + """Returns true if an asset exists. + + Args: + asset: The asset the check for existence + config: The download configuration to use for the existence check + clients: Any pre-configured clients to use for the existence check + + Returns: + bool: Whether the asset exists or not + """ + try: + await assert_asset_exists(asset, config, clients) + except Exception: return False + else: + return True def make_asset_hrefs_relative( diff --git a/src/stac_asset/client.py b/src/stac_asset/client.py index 849ad8b..6a2729c 100644 --- a/src/stac_asset/client.py +++ b/src/stac_asset/client.py @@ -133,11 +133,26 @@ async def href_exists(self, href: str) -> bool: bool: Whether the href exists """ try: - async for _ in self.open_href(href): - break + await self.assert_href_exists(href) except Exception: return False - return True + else: + return True + + async def assert_href_exists(self, href: str) -> None: + """Asserts that a href exists. + + The default implementation naïvely opens the href and reads one chunk. + Clients may implement specialized behavior. + + Args: + href: The href to open + + Raises: + Exception: The underlying error when trying to open the file. + """ + async for _ in self.open_href(href): + break async def close(self) -> None: """Close this client.""" diff --git a/src/stac_asset/filesystem_client.py b/src/stac_asset/filesystem_client.py index c2c0b2f..181cb53 100644 --- a/src/stac_asset/filesystem_client.py +++ b/src/stac_asset/filesystem_client.py @@ -50,6 +50,11 @@ async def open_url( async for chunk in f: yield chunk + async def assert_href_exists(self, href: str) -> None: + """Asserts that an href exists.""" + if not os.path.exists(href): + raise ValueError(f"href does not exist on the filesystem: {href}") + async def __aenter__(self) -> FilesystemClient: return self diff --git a/src/stac_asset/http_client.py b/src/stac_asset/http_client.py index 3b086a5..d03d4b8 100644 --- a/src/stac_asset/http_client.py +++ b/src/stac_asset/http_client.py @@ -74,17 +74,13 @@ async def open_url( async for chunk, _ in response.content.iter_chunks(): yield chunk - async def href_exists(self, href: str) -> bool: - """Returns true if the href exists. + async def assert_href_exists(self, href: str) -> None: + """Asserts that the href exists. Uses a HEAD request. """ - try: - async with self.session.head(href) as response: - # For some reason, mypy can't see that this is a bool - return response.status >= 200 and response.status < 300 # type: ignore - except Exception: - return False + async with self.session.head(href) as response: + response.raise_for_status() async def close(self) -> None: """Close this http client. diff --git a/src/stac_asset/planetary_computer_client.py b/src/stac_asset/planetary_computer_client.py index 7d9d756..ac59024 100644 --- a/src/stac_asset/planetary_computer_client.py +++ b/src/stac_asset/planetary_computer_client.py @@ -99,15 +99,14 @@ async def open_url( ): yield chunk - async def href_exists(self, href: str) -> bool: - """Returns true if the href exists. + async def assert_href_exists(self, href: str) -> None: + """Asserts that the href exists. Uses a HEAD request on a signed url. """ href = await self._maybe_sign_href(href) async with self.session.head(href) as response: - # For some reason, mypy can't see that this is a bool - return response.status >= 200 and response.status < 300 # type: ignore + response.raise_for_status() async def _sign(self, url: URL) -> URL: assert url.host diff --git a/src/stac_asset/s3_client.py b/src/stac_asset/s3_client.py index f6ab3b6..cd57366 100644 --- a/src/stac_asset/s3_client.py +++ b/src/stac_asset/s3_client.py @@ -114,18 +114,13 @@ async def has_credentials(self) -> bool: """Returns true if the sessions has credentials.""" return await self.session.get_credentials() is not None - async def href_exists(self, href: str) -> bool: - """Return true if the href exists. + async def assert_href_exists(self, href: str) -> None: + """Asserts that the href exists. Uses ``head_object`` """ async with self._create_client() as client: - try: - await client.head_object(**self._params(URL(href))) - except Exception: - return False - else: - return True + await client.head_object(**self._params(URL(href))) def _create_client(self) -> ClientCreatorContext: retries = { diff --git a/tests/test_functions.py b/tests/test_functions.py index 2289d6b..101c42c 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -189,3 +189,9 @@ async def test_queue(tmp_path: Path, item: Item) -> None: async def test_asset_exists(item: Item) -> None: assert await stac_asset.asset_exists(item.assets["data"]) assert not await stac_asset.asset_exists(Asset(href="not-a-file")) + + +async def test_asert_asset_exists(item: Item) -> None: + await stac_asset.assert_asset_exists(item.assets["data"]) + with pytest.raises(ValueError): + await stac_asset.assert_asset_exists(Asset(href="not-a-file"))