def test_templates_item_start_datetime(self) -> None: year = 2020 month = 11 day = 3 date = "2020-11-03" dt = datetime(year, month, day, 18, 30) template = LayoutTemplate("${year}/${month}/${day}/${date}/item.json") item = pystac.Item( "test", geometry=ARBITRARY_GEOM, bbox=ARBITRARY_BBOX, datetime=None, properties={ "start_datetime": dt.isoformat(), "end_datetime": (dt + timedelta(days=1)).isoformat(), }, ) parts = template.get_template_values(item) self.assertEqual(set(parts), set(["year", "month", "day", "date"])) self.assertEqual(parts["year"], year) self.assertEqual(parts["month"], month) self.assertEqual(parts["day"], day) self.assertEqual(parts["date"], date) path = template.substitute(item) self.assertEqual(path, "2020/11/3/2020-11-03/item.json")
def test_nested_properties(self) -> None: dt = datetime(2020, 11, 3, 18, 30) template = LayoutTemplate( "${test.prop}/${ext:extra.test.prop}/item.json") item = pystac.Item( "test", geometry=ARBITRARY_GEOM, bbox=ARBITRARY_BBOX, datetime=dt, properties={"test": { "prop": 4326 }}, extra_fields={"ext:extra": { "test": { "prop": 3857 } }}, ) parts = template.get_template_values(item) self.assertEqual(set(parts), set(["test.prop", "ext:extra.test.prop"])) self.assertEqual(parts["test.prop"], 4326) self.assertEqual(parts["ext:extra.test.prop"], 3857) path = template.substitute(item) self.assertEqual(path, "4326/3857/item.json")
def test_defaults(self) -> None: template = LayoutTemplate("${doesnotexist}/collection.json", defaults={"doesnotexist": "yes"}) catalog = TestCases.test_case_4().get_child("acc") assert catalog is not None catalog.extra_fields = {"one": "two"} path = template.substitute(catalog) self.assertEqual(path, "yes/collection.json")
def test_throws_for_no_collection(self) -> None: template = LayoutTemplate("${collection}/item.json") collection = TestCases.test_case_4().get_child("acc") assert collection is not None item = next(iter(collection.get_all_items())) item.set_collection(None) assert item.collection_id is None with self.assertRaises(TemplateError): template.get_template_values(item)
def test_templates_item_collection(self) -> None: template = LayoutTemplate("${collection}/item.json") collection = TestCases.test_case_4().get_child("acc") assert collection is not None item = next(iter(collection.get_all_items())) assert item.collection_id is not None parts = template.get_template_values(item) self.assertEqual(len(parts), 1) self.assertIn("collection", parts) self.assertEqual(parts["collection"], item.collection_id) path = template.substitute(item) self.assertEqual(path, "{}/item.json".format(item.collection_id))
def test_substitute_with_colon_properties(self) -> None: dt = datetime(2020, 11, 3, 18, 30) template = LayoutTemplate("${ext:prop}/item.json") item = pystac.Item( "test", geometry=ARBITRARY_GEOM, bbox=ARBITRARY_BBOX, datetime=dt, properties={"ext:prop": 1}, ) path = template.substitute(item) self.assertEqual(path, "1/item.json")
def generate_subcatalogs(self, template, defaults=None, **kwargs): """Walks through the catalog and generates subcatalogs for items based on the template string. See :class:`~pystac.layout.LayoutTemplate` for details on the construction of template strings. This template string will be applied to the items, and subcatalogs will be created that separate and organize the items based on template values. Args: template (str): A template string that can be consumed by a :class:`~pystac.layout.LayoutTemplate` defaults (dict): Default values for the template variables that will be used if the property cannot be found on the item. Returns: [catalog]: List of new catalogs created """ result = [] for child in self.get_children(): result.extend( child.generate_subcatalogs(template, defaults=defaults)) layout_template = LayoutTemplate(template, defaults=defaults) subcat_id_to_cat = {} items = list(self.get_items()) for item in items: item_parts = layout_template.get_template_values(item) curr_parent = self for k, v in item_parts.items(): subcat_id = '{}'.format(v) subcat = subcat_id_to_cat.get(subcat_id) if subcat is None: subcat_desc = 'Catalog of items from {} with {} of {}'.format( curr_parent.id, k, v) subcat = pystac.Catalog(id=subcat_id, description=subcat_desc) curr_parent.add_child(subcat) subcat_id_to_cat[subcat_id] = subcat result.append(subcat) curr_parent = subcat self.remove_item(item.id) curr_parent.add_item(item) return result
def test_docstring_examples(self) -> None: item = pystac.Item.from_file( TestCases.get_path("data-files/examples/1.0.0-beta.2/item-spec/" "examples/landsat8-sample.json")) item.common_metadata.license = "CC-BY-3.0" # Uses the year, month and day of the item template1 = LayoutTemplate("${year}/${month}/${day}") path1 = template1.substitute(item) self.assertEqual(path1, "2014/6/2") # Uses a custom extension properties found on in the item properties. template2 = LayoutTemplate("${landsat:path}/${landsat:row}") path2 = template2.substitute(item) self.assertEqual(path2, "107/18") # Uses the collection ID and a common metadata property for an item. template3 = LayoutTemplate("${collection}/${common_metadata.license}") path3 = template3.substitute(item) self.assertEqual(path3, "landsat-8-l1/CC-BY-3.0")
def generate_subcatalogs( self, template: str, defaults: Optional[Dict[str, Any]] = None, parent_ids: Optional[List[str]] = None, ) -> List["Catalog"]: """Walks through the catalog and generates subcatalogs for items based on the template string. See :class:`~pystac.layout.LayoutTemplate` for details on the construction of template strings. This template string will be applied to the items, and subcatalogs will be created that separate and organize the items based on template values. Args: template : A template string that can be consumed by a :class:`~pystac.layout.LayoutTemplate` defaults : Default values for the template variables that will be used if the property cannot be found on the item. parent_ids : Optional list of the parent catalogs' identifiers. If the bottom-most subcatalogs already match the template, no subcatalog is added. Returns: [catalog]: List of new catalogs created """ result: List[Catalog] = [] parent_ids = parent_ids or list() parent_ids.append(self.id) for child in self.get_children(): result.extend( child.generate_subcatalogs( template, defaults=defaults, parent_ids=parent_ids.copy() ) ) layout_template = LayoutTemplate(template, defaults=defaults) keep_item_links: List[Link] = [] item_links = [lk for lk in self.links if lk.rel == pystac.RelType.ITEM] for link in item_links: link.resolve_stac_object(root=self.get_root()) item = cast(pystac.Item, link.target) subcat_ids = layout_template.substitute(item).split("/") id_iter = reversed(parent_ids) if all( ["{}".format(id) == next(id_iter, None) for id in reversed(subcat_ids)] ): # Skip items for which the sub-catalog structure already # matches the template. The list of parent IDs can include more # elements on the root side, so compare the reversed sequences. keep_item_links.append(link) continue curr_parent = self for subcat_id in subcat_ids: subcat = curr_parent.get_child(subcat_id) if subcat is None: subcat_desc = "Catalog of items from {} with id {}".format( curr_parent.id, subcat_id ) subcat = pystac.Catalog(id=subcat_id, description=subcat_desc) curr_parent.add_child(subcat) result.append(subcat) curr_parent = subcat # resolve collection link so when added back points to correct location col_link = item.get_single_link(pystac.RelType.COLLECTION) if col_link is not None: col_link.resolve_stac_object() curr_parent.add_item(item) # keep only non-item links and item links that have not been moved elsewhere self.links = [ lk for lk in self.links if lk.rel != pystac.RelType.ITEM ] + keep_item_links return result