Skip to content

Commit 0ca2972

Browse files
authored
Better control on subfolders in generate_subcatalogs (#595)
* LayoutTemplate.get_template_values -> .substitute * test that template elements can be merged in a subdir * fix test to use "/" as separator in template * fix docs on the use of "/" as template path separator * add changes to CHANGELOG.md
1 parent 98edeaa commit 0ca2972

File tree

5 files changed

+40
-14
lines changed

5 files changed

+40
-14
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
### Fixed
1919

20+
- `generate_subcatalogs` can include multiple template values in a single subfolder layer
21+
([#595](https://github.com/stac-utils/pystac/pull/595))
2022
- Avoid implicit re-exports ([#591](https://github.com/stac-utils/pystac/pull/591))
2123

2224
### Deprecated

docs/concepts.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ To use them you can pass in a strategy to the normalize_hrefs call.
6969
Using templates
7070
'''''''''''''''
7171

72-
You can utilze template strings to determine the file paths of HREFs set on Catalogs,
72+
You can utilize template strings to determine the file paths of HREFs set on Catalogs,
7373
Collection or Items. These templates use python format strings, which can name
7474
the property or attribute of the item you want to use for replacing the template
7575
variable. For example:
@@ -84,7 +84,9 @@ variable. For example:
8484
8585
The above code will save items in subfolders based on the collection ID, year and month
8686
of it's datetime (or start_datetime if a date range is defined and no datetime is
87-
defined).
87+
defined). Note that the forward slash (``/``) should be used as path separator in the
88+
template string regardless of the system path separator (thus both in POSIX-compliant
89+
and Windows environments).
8890

8991
You can use dot notation to specify attributes of objects or keys in dictionaries for
9092
template variables. PySTAC will look at the object, it's ``properties`` and its

pystac/catalog.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -666,26 +666,22 @@ def generate_subcatalogs(
666666
for link in item_links:
667667
link.resolve_stac_object(root=self.get_root())
668668
item = cast(pystac.Item, link.target)
669-
item_parts = layout_template.get_template_values(item)
669+
subcat_ids = layout_template.substitute(item).split("/")
670670
id_iter = reversed(parent_ids)
671671
if all(
672-
[
673-
"{}".format(id) == next(id_iter, None)
674-
for id in reversed(list(item_parts.values()))
675-
]
672+
["{}".format(id) == next(id_iter, None) for id in reversed(subcat_ids)]
676673
):
677674
# Skip items for which the sub-catalog structure already
678675
# matches the template. The list of parent IDs can include more
679676
# elements on the root side, so compare the reversed sequences.
680677
keep_item_links.append(link)
681678
continue
682679
curr_parent = self
683-
for k, v in item_parts.items():
684-
subcat_id = "{}".format(v)
680+
for subcat_id in subcat_ids:
685681
subcat = curr_parent.get_child(subcat_id)
686682
if subcat is None:
687-
subcat_desc = "Catalog of items from {} with {} of {}".format(
688-
curr_parent.id, k, v
683+
subcat_desc = "Catalog of items from {} with id {}".format(
684+
curr_parent.id, subcat_id
689685
)
690686
subcat = pystac.Catalog(id=subcat_id, description=subcat_desc)
691687
curr_parent.add_child(subcat)

pystac/layout.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ class LayoutTemplate:
5454
| ``collection`` | The collection ID of an Item's collection. |
5555
+--------------------+--------------------------------------------------------+
5656
57+
The forward slash (``/``) should be used as path separator in the template
58+
string regardless of the system path separator (thus both in POSIX-compliant
59+
and Windows environments).
60+
5761
Examples::
5862
5963
# Uses the year, month and day of the item

tests/test_catalog.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,30 @@ def test_generate_subcatalogs_does_not_change_item_count(self) -> None:
413413
actual, expected, msg=" for child '{}'".format(child.id)
414414
)
415415

416+
def test_generate_subcatalogs_merge_template_elements(self) -> None:
417+
catalog = Catalog(id="test", description="Test")
418+
item_properties = [
419+
dict(property1=p1, property2=p2) for p1 in ("A", "B") for p2 in (1, 2)
420+
]
421+
for ni, properties in enumerate(item_properties):
422+
catalog.add_item(
423+
Item(
424+
id="item{}".format(ni),
425+
geometry=ARBITRARY_GEOM,
426+
bbox=ARBITRARY_BBOX,
427+
datetime=datetime.utcnow(),
428+
properties=properties,
429+
)
430+
)
431+
result = catalog.generate_subcatalogs("${property1}_${property2}")
432+
433+
actual_subcats = set([cat.id for cat in result])
434+
expected_subcats = set(
435+
["{}_{}".format(d["property1"], d["property2"]) for d in item_properties]
436+
)
437+
self.assertEqual(len(result), len(expected_subcats))
438+
self.assertSetEqual(actual_subcats, expected_subcats)
439+
416440
def test_generate_subcatalogs_can_be_applied_multiple_times(self) -> None:
417441
catalog = TestCases.test_case_8()
418442

@@ -511,9 +535,7 @@ def test_generate_subcatalogs_works_for_subcatalogs_with_same_ids(self) -> None:
511535
)
512536
)
513537

514-
result = catalog.generate_subcatalogs(
515-
join_path_or_url(JoinType.PATH, "${property1}", "${property2}")
516-
)
538+
result = catalog.generate_subcatalogs("${property1}/${property2}")
517539
self.assertEqual(len(result), 6)
518540

519541
catalog.normalize_hrefs("/")

0 commit comments

Comments
 (0)