Skip to content

Commit 0c1084a

Browse files
authored
stac copy --copy-assets should copy collection assets (#437)
* Target collection assets as well as item assets * Update min pystac * Update changelog
1 parent e68fd72 commit 0c1084a

File tree

14 files changed

+1056
-99
lines changed

14 files changed

+1056
-99
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
### Changed
1717

1818
- Update pystac dependency to 0.7 and shapely to 2.0 ([#441](https://github.com/stac-utils/stactools/pull/441))
19+
- Make `stac copy --copy-assets` copy assets to collections ([#437](https://github.com/stac-utils/stactools/pull/437))
1920

2021
### Fixed
2122

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ dependencies = [
3131
"lxml>=4.9.2",
3232
"numpy>=1.23.0",
3333
"pyproj>=3.3",
34-
"pystac[validation]>=1.7.0",
34+
"pystac[validation]>=1.8.2",
3535
"rasterio>=1.3.2",
3636
"shapely>=2.0.1",
3737
"stac-check>=1.3.2",

src/stactools/cli/commands/add_asset.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
import click
44
import pystac
55
import pystac.utils
6-
from stactools.core import add_asset_to_item
6+
from stactools.core import add_asset
77

88

99
def _add_asset(
10-
item_path: str,
10+
owner_path: str,
1111
asset_key: str,
1212
asset_path: str,
1313
title: Optional[str] = None,
@@ -17,23 +17,23 @@ def _add_asset(
1717
move_assets: bool = False,
1818
ignore_conflicts: bool = False,
1919
) -> None:
20-
item = pystac.read_file(item_path)
21-
if not isinstance(item, pystac.Item):
22-
raise click.BadArgumentUsage(f"{item_path} is not a STAC Item")
20+
owner = pystac.read_file(owner_path)
21+
if not isinstance(owner, (pystac.Item, pystac.Collection)):
22+
raise click.BadArgumentUsage(f"{owner_path} is not a STAC Item or Collection")
2323
asset = pystac.Asset(asset_path, title, description, media_type, roles)
24-
item = add_asset_to_item(
25-
item,
24+
owner = add_asset(
25+
owner,
2626
asset_key,
2727
asset,
2828
move_assets=move_assets,
2929
ignore_conflicts=ignore_conflicts,
3030
)
31-
item.save_object()
31+
owner.save_object()
3232

3333

3434
def create_add_asset_command(cli: click.Group) -> click.Command:
35-
@cli.command("add-asset", short_help="Add an asset to an item.")
36-
@click.argument("item_path")
35+
@cli.command("add-asset", short_help="Add an asset to an item or collection.")
36+
@click.argument("owner_path")
3737
@click.argument("asset_key")
3838
@click.argument("asset_path")
3939
@click.option("--title", help="Optional title of the asset")
@@ -55,7 +55,7 @@ def create_add_asset_command(cli: click.Group) -> click.Command:
5555
@click.option(
5656
"--move-assets",
5757
is_flag=True,
58-
help="Move asset to the target Item's location.",
58+
help="Move asset to the target Item or Collection's location.",
5959
)
6060
@click.option(
6161
"--ignore-conflicts",
@@ -67,7 +67,7 @@ def create_add_asset_command(cli: click.Group) -> click.Command:
6767
),
6868
)
6969
def add_asset_command(
70-
item_path: str,
70+
owner_path: str,
7171
asset_key: str,
7272
asset_path: str,
7373
title: Optional[str] = None,
@@ -78,7 +78,7 @@ def add_asset_command(
7878
ignore_conflicts: bool = False,
7979
) -> None:
8080
_add_asset(
81-
pystac.utils.make_absolute_href(item_path),
81+
pystac.utils.make_absolute_href(owner_path),
8282
asset_key,
8383
pystac.utils.make_absolute_href(asset_path),
8484
title,

src/stactools/cli/commands/copy.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88

99
def create_move_assets_command(cli: click.Group) -> click.Command:
1010
@cli.command(
11-
"move-assets", short_help="Move or copy assets in a STAC to the Item locations."
11+
"move-assets",
12+
help=(
13+
"Move or copy assets in a STAC catalog to the locations of the "
14+
"items or collections that own them."
15+
),
1216
)
1317
@click.argument("catalog_path")
1418
@click.option("-c", "--copy", help="Copy assets instead of moving.", is_flag=True)
@@ -68,7 +72,7 @@ def create_copy_command(cli: click.Group) -> click.Command:
6872
"--copy-assets",
6973
"-a",
7074
is_flag=True,
71-
help="Copy all item assets to be alongside the new item location.",
75+
help="Copy all asset files to be alongside the new location.",
7276
)
7377
@click.option(
7478
"-l",

src/stactools/core/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from stactools.core.add import add_item
2-
from stactools.core.add_asset import add_asset_to_item
2+
from stactools.core.add_asset import add_asset, add_asset_to_item
33
from stactools.core.add_raster import add_raster_to_item
44
from stactools.core.copy import (
55
copy_catalog,
66
move_all_assets,
7+
move_asset_file,
78
move_asset_file_to_item,
89
move_assets,
910
)
@@ -13,12 +14,14 @@
1314

1415
__all__ = [
1516
"add_item",
17+
"add_asset",
1618
"add_asset_to_item",
1719
"add_raster_to_item",
1820
"copy_catalog",
1921
"layout_catalog",
2022
"merge_all_items",
2123
"merge_items",
24+
"move_asset_file",
2225
"move_asset_file_to_item",
2326
"move_assets",
2427
"move_all_assets",

src/stactools/core/add_asset.py

Lines changed: 59 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,43 @@
11
import logging
2+
import warnings
3+
from typing import Union, cast
24

3-
from pystac import Asset, Item
5+
from pystac import Asset, Collection, Item
46
from pystac.utils import is_absolute_href, make_relative_href
5-
from stactools.core.copy import move_asset_file_to_item
7+
from stactools.core.copy import move_asset_file
68

79
logger = logging.getLogger(__name__)
810

911

10-
def add_asset_to_item(
11-
item: Item,
12+
def add_asset(
13+
owner: Union[Collection, Item],
1214
key: str,
1315
asset: Asset,
1416
move_assets: bool = False,
1517
ignore_conflicts: bool = False,
16-
) -> Item:
17-
"""Adds an asset to an item.
18+
) -> Union[Collection, Item]:
19+
"""Adds an asset to an item or collection.
1820
1921
Args:
20-
item (Item): The PySTAC Item to which the asset will be added.
22+
owner (Item or Collection): The PySTAC Item or Collecitonto which the asset
23+
will be added.
2124
key (str): The unique key of the asset.
2225
asset (Asset): The PySTAC Asset to add.
23-
move_assets (bool): If True, move the asset file alongside the target item.
26+
move_assets (bool): If True, move the asset file alongside the target owner.
2427
ignore_conflicts (bool): If True, asset with the same key will not be added,
2528
and asset file that would overwrite an existing file will not be moved.
2629
If False, either of these situations will throw an error.
2730
2831
Returns:
29-
Item: Returns an updated Item with the added Asset.
30-
This operation mutates the Item.
32+
owner: Returns an updated Item or Collection with the added Asset.
33+
This operation mutates the owner.
3134
"""
32-
item_href = item.get_self_href()
35+
owner_href = owner.get_self_href()
3336
asset_href = asset.get_absolute_href()
34-
if key in item.assets:
37+
if key in owner.assets:
3538
if not ignore_conflicts:
3639
raise Exception(
37-
f"Target item {item} already has an asset with key {key}, "
40+
f"Target {owner} already has an asset with key {key}, "
3841
"cannot add asset in from {asset_href}"
3942
)
4043
else:
@@ -43,23 +46,54 @@ def add_asset_to_item(
4346
f"Asset {asset} must have an href to be added. The href "
4447
"value should be an absolute path or URL."
4548
)
46-
if not item_href and move_assets:
49+
if not owner_href and move_assets:
50+
raise ValueError(f"Target {owner} must have an href to move an asset to it")
51+
if not owner_href and not is_absolute_href(asset.href):
4752
raise ValueError(
48-
f"Target Item {item} must have an href to move an asset to the item"
49-
)
50-
if not item_href and not is_absolute_href(asset.href):
51-
raise ValueError(
52-
f"Target Item {item} must have an href to add "
53+
f"Target {owner} must have an href to add "
5354
"an asset with a relative href"
5455
)
5556
if move_assets:
56-
new_asset_href = move_asset_file_to_item(
57-
item, asset_href, ignore_conflicts=ignore_conflicts
57+
new_asset_href = move_asset_file(
58+
owner, asset_href, ignore_conflicts=ignore_conflicts
5859
)
5960
else:
60-
if not is_absolute_href(asset.href) and item_href is not None:
61-
asset_href = make_relative_href(asset_href, item_href)
61+
if not is_absolute_href(asset.href) and owner_href is not None:
62+
asset_href = make_relative_href(asset_href, owner_href)
6263
new_asset_href = asset_href
6364
asset.href = new_asset_href
64-
item.add_asset(key, asset)
65-
return item
65+
owner.add_asset(key, asset)
66+
return owner
67+
68+
69+
def add_asset_to_item(
70+
item: Item,
71+
key: str,
72+
asset: Asset,
73+
move_assets: bool = False,
74+
ignore_conflicts: bool = False,
75+
) -> Item:
76+
"""Adds an asset to an item.
77+
78+
Args:
79+
item (Item): The PySTAC Item to which the asset will be added.
80+
key (str): The unique key of the asset.
81+
asset (Asset): The PySTAC Asset to add.
82+
move_assets (bool): If True, move the asset file alongside the target item.
83+
ignore_conflicts (bool): If True, asset with the same key will not be added,
84+
and asset file that would overwrite an existing file will not be moved.
85+
If False, either of these situations will throw an error.
86+
87+
Returns:
88+
Item: Returns an updated Item with the added Asset.
89+
This operation mutates the Item.
90+
"""
91+
warnings.warn(
92+
"'add_asset_to_item' is deprecated. Use 'add_asset' instead", DeprecationWarning
93+
)
94+
return cast(
95+
Item,
96+
add_asset(
97+
item, key, asset, move_assets=move_assets, ignore_conflicts=ignore_conflicts
98+
),
99+
)

0 commit comments

Comments
 (0)