diff --git a/CHANGELOG.md b/CHANGELOG.md index 798096b..431870b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - More `HttpClient` attributes to `Config` ([#177](https://github.com/stac-utils/stac-asset/pull/177)) - `derived_from` link ([#178](https://github.com/stac-utils/stac-asset/pull/178)) +- Option for templated paths when downloading item collections ([#181](https://github.com/stac-utils/stac-asset/pull/181)) ### Removed diff --git a/README.md b/README.md index 15d3cf2..9611efb 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,15 @@ stac-client search https://planetarycomputer.microsoft.com/api/stac/v1 \ stac-asset download -i rendered_preview -q ``` +By default, all assets are stored in a folder named after the item ID. To change this, you can use the `-p` flag and specify a path template using PySTAC layout template [variables](https://pystac.readthedocs.io/en/latest/api/layout.html#pystac.layout.LayoutTemplate): + +``` +stac-client search https://planetarycomputer.microsoft.com/api/stac/v1 \ + -c landsat-c2-l2 \ + --max-items 1 | \ + stac-asset download -i rendered_preview -p '${collection}' +``` + See [the documentation](https://stac-asset.readthedocs.io/en/latest/index.html) for more examples and complete API and CLI documentation. ### Clients diff --git a/src/stac_asset/_cli.py b/src/stac_asset/_cli.py index 3c8e971..5e4df16 100644 --- a/src/stac_asset/_cli.py +++ b/src/stac_asset/_cli.py @@ -55,6 +55,11 @@ def cli() -> None: @cli.command() @click.argument("href", required=False) @click.argument("directory", required=False) +@click.option( + "-p", + "--path-template", + help="String to be interpolated to specify where to store downloaded files", +) @click.option( "-a", "--alternate-assets", @@ -125,6 +130,7 @@ def cli() -> None: def download( href: Optional[str], directory: Optional[str], + path_template: Optional[str], alternate_assets: List[str], include: List[str], exclude: List[str], @@ -160,6 +166,7 @@ def download( download_async( href, directory, + path_template, alternate_assets, include, exclude, @@ -178,6 +185,7 @@ def download( async def download_async( href: Optional[str], directory: Optional[str], + path_template: Optional[str], alternate_assets: List[str], include: List[str], exclude: List[str], @@ -241,6 +249,7 @@ async def download() -> Union[Item, ItemCollection]: return await _functions.download_item_collection( item_collection, directory_str, + path_template, file_name=file_name, config=config, messages=messages, diff --git a/src/stac_asset/_functions.py b/src/stac_asset/_functions.py index 475c541..1901d67 100644 --- a/src/stac_asset/_functions.py +++ b/src/stac_asset/_functions.py @@ -19,6 +19,7 @@ import pystac.utils from pystac import Asset, Collection, Item, ItemCollection, Link, STACError +from pystac.layout import LayoutTemplate from yarl import URL from .client import Client, Clients @@ -307,6 +308,7 @@ async def download_collection( async def download_item_collection( item_collection: ItemCollection, directory: PathLikeObject, + path_template: Optional[str] = None, file_name: Optional[str] = "item-collection.json", config: Optional[Config] = None, messages: Optional[MessageQueue] = None, @@ -318,6 +320,8 @@ async def download_item_collection( Args: item_collection: The item collection to download directory: The destination directory + path_template: String to be interpolated to specify where to store + downloaded files. file_name: The name of the item collection file to save. If not provided, will not be saved. config: The download configuration @@ -335,7 +339,9 @@ async def download_item_collection( async with Downloads(config=config or Config(), clients=clients) as downloads: for item in item_collection.items: item.set_self_href(None) - root = Path(directory) / item.id + root = Path(directory) / LayoutTemplate( + path_template if path_template is not None else "${id}" + ).substitute(item) await downloads.add(item, root, None, keep_non_downloaded) await downloads.download(messages) if file_name: diff --git a/src/stac_asset/blocking.py b/src/stac_asset/blocking.py index 603705d..80cb11c 100644 --- a/src/stac_asset/blocking.py +++ b/src/stac_asset/blocking.py @@ -108,6 +108,7 @@ def download_collection( def download_item_collection( item_collection: ItemCollection, directory: PathLikeObject, + path_template: Optional[str] = None, file_name: Optional[str] = "item-collection.json", config: Optional[Config] = None, messages: Optional[MessageQueue] = None, @@ -119,6 +120,8 @@ def download_item_collection( Args: item_collection: The item collection to download directory: The destination directory + path_template: String to be interpolated to specify where to store + downloaded files. file_name: The name of the item collection file to save. If not provided, will not be saved. config: The download configuration @@ -142,6 +145,7 @@ def download_item_collection( messages=messages, clients=clients, keep_non_downloaded=keep_non_downloaded, + path_template=path_template, ) ) diff --git a/tests/data/item.json b/tests/data/item.json index c7feb8d..a69ceab 100644 --- a/tests/data/item.json +++ b/tests/data/item.json @@ -10,11 +10,16 @@ { "href": "./catalog.json", "rel": "parent" + }, + { + "href": "./collection.json", + "rel": "collection" } ], "assets": { "data": { "href": "./20201211_223832_CS2.jpg" } - } -} \ No newline at end of file + }, + "collection": "test-collection" +} diff --git a/tests/test_functions.py b/tests/test_functions.py index b52c0c5..d3a82ac 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -134,6 +134,22 @@ async def test_download_item_collection_with_file_name_and_cd( ) +async def test_download_item_collection_with_path_template( + tmp_path: Path, item_collection: ItemCollection +) -> None: + await stac_asset.download_item_collection( + item_collection, + tmp_path, + file_name="item-collection.json", + path_template="${collection}", + ) + item_collection = ItemCollection.from_file(str(tmp_path / "item-collection.json")) + assert isinstance(item_collection.items[0].collection_id, str) + assert item_collection.items[0].assets["data"].href == str( + tmp_path / "test-collection/20201211_223832_CS2.jpg" + ) + + async def test_download_collection(tmp_path: Path, collection: Collection) -> None: collection = await stac_asset.download_collection( collection, tmp_path, file_name="collection.json"