Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more endpoints to APILayoutStrategy #1335

Merged
merged 11 commits into from
May 3, 2024
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Changelog

## [Unreleased]
- Allow object ID as input for getting APILayoutStrategy hrefs and add `items`, `collections`, `search`, `conformance`, `service_desc` and `service_doc` href methods. ([#1335](https://github.com/stac-utils/pystac/pull/1335))
gadomski marked this conversation as resolved.
Show resolved Hide resolved


## [v1.10.0] - 2024-03-28

Expand Down
3 changes: 3 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ a template.
* :class:`pystac.layout.BestPracticesLayoutStrategy`: Layout strategy that represents
the catalog layout described in the :stac-spec:`STAC Best Practices documentation
<best-practices.md>`.
* :class:`pystac.layout.APILayoutStrategy`: Layout strategy that represents
the catalog layout described in
the :stac-api-spec:`STAC API documentation <overview.md#endpoints>`.
* :class:`pystac.layout.TemplateLayoutStrategy`: Layout strategy that can take strings
to be supplied to a :class:`~pystac.layout.LayoutTemplate` to derive paths.
* :class:`pystac.layout.CustomLayoutStrategy`: Layout strategy that allows users to
Expand Down
5 changes: 5 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@
"v{}/%s".format(STACVersion.DEFAULT_STAC_VERSION),
"%s path",
),
"stac-api-spec": (
"https://github.com/radiantearth/stac-api-spec/tree/"
"v{}/%s".format(STACVersion.DEFAULT_STAC_VERSION),
thomas-maschler marked this conversation as resolved.
Show resolved Hide resolved
"%s path",
),
"stac-ext": ("https://github.com/stac-extensions/%s", "%s extension"),
}

Expand Down
12 changes: 6 additions & 6 deletions docs/tutorials/creating-a-landsat-stac.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,9 @@
"source": [
"def get_metadata(xml_url: str) -> Dict[str, Any]:\n",
" result = XmlDictConfig(ElementTree.XML(stac_io.read_text(pc.sign(xml_url))))\n",
" result[\"ORIGINAL_URL\"] = (\n",
" xml_url # Include the original URL in the metadata for use later\n",
" )\n",
" result[\n",
" \"ORIGINAL_URL\"\n",
" ] = xml_url # Include the original URL in the metadata for use later\n",
" return result"
]
},
Expand Down Expand Up @@ -1654,9 +1654,9 @@
" elif isinstance(ext_data, RasterBand):\n",
" RasterExtension.ext(asset, add_if_missing=True).bands = [ext_data]\n",
" elif isinstance(ext_data, list):\n",
" ClassificationExtension.ext(asset, add_if_missing=True).bitfields = (\n",
" ext_data\n",
" )\n",
" ClassificationExtension.ext(\n",
" asset, add_if_missing=True\n",
" ).bitfields = ext_data\n",
" item.add_asset(band[\"name\"], asset)"
]
},
Expand Down
137 changes: 129 additions & 8 deletions pystac/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,25 +550,146 @@ class APILayoutStrategy(HrefLayoutStrategy):
``./collections/${collection}/items/${id}``.
"""

def get_catalog_href(self, cat: Catalog, parent_dir: str, is_root: bool) -> str:
def get_catalog_href(
self, cat: Catalog | str, parent_dir: str, is_root: bool
) -> str:
"""
Generate a catalog href based on the provided parameters.

Parameters:
cat (Catalog | str): The catalog object or its ID.
parent_dir (str): The parent directory for the catalog.
is_root (bool): A flag indicating whether the catalog is a root catalog.

Returns:
str: The generated catalog href.
"""

if not isinstance(cat, str):
cat = cat.id

if is_root:
cat_href = parent_dir
else:
cat_href = posixpath.join(parent_dir, f"{cat.id}")
cat_href = posixpath.join(parent_dir, cat)

return cat_href

def get_collections_href(self, parent_dir: str) -> str:
"""
Generate a collections href based on the provided parent directory.

Parameters:
parent_dir (str): The parent directory for the collections.

Returns:
str: The generated collections href.
"""
return posixpath.join(parent_dir, "collections")

def get_collection_href(
self, col: Collection, parent_dir: str, is_root: bool
self, col: Collection | str, parent_dir: str, is_root: bool
) -> str:
"""
Generate a collection href based on the provided parameters.

Parameters:
col (Collection | str): The collection object or its ID.
parent_dir (str): The parent directory for the collection.
is_root (bool): A flag indicating whether the collection is a root collection.

Returns:
str: The generated collection href.

Raises:
ValueError: If the collection is set as root.
"""

if not isinstance(col, str):
col = col.id

if is_root:
raise ValueError("Collections cannot be root")

col_root = posixpath.join(parent_dir, "collections")
col_root = self.get_collections_href(parent_dir)

return posixpath.join(col_root, f"{col.id}")
return posixpath.join(col_root, col)

def get_item_href(self, item: Item, parent_dir: str) -> str:
item_root = posixpath.join(parent_dir, "items")
def get_items_href(self, parent_dir: str) -> str:
"""
Generate an items href based on the provided parent directory.

Parameters:
parent_dir (str): The parent directory for the items.

Returns:
str: The generated items href.
"""
return posixpath.join(parent_dir, "items")

return posixpath.join(item_root, f"{item.id}")
def get_item_href(self, item: Item | str, parent_dir: str) -> str:
"""
Generate an item href based on the provided parameters.

Parameters:
item (Item | str): The item object or its ID.
parent_dir (str): The parent directory for the item.

Returns:
str: The generated item href.
"""
if not isinstance(item, str):
item = item.id

item_root = self.get_items_href(parent_dir)

return posixpath.join(item_root, item)

def get_search_href(self, parent_dir: str) -> str:
"""
Generate a search href based on the provided parent directory.

Parameters:
parent_dir (str): The parent directory for the search.

Returns:
str: The generated search href.
"""
return posixpath.join(parent_dir, "search")

def get_conformance_href(self, parent_dir: str) -> str:
"""
Generate a conformance href based on the provided parent directory.

Parameters:
parent_dir (str): The parent directory for the conformance.

Returns:
str: The generated conformance href.
"""
return posixpath.join(parent_dir, "conformance")

def get_service_desc_href(self, parent_dir: str) -> str:
"""
Generate an API service description href based on the provided parent directory.

Parameters:
parent_dir (str): The parent directory for the API.

Returns:
str: The generated API href.
"""
return posixpath.join(parent_dir, "api")

def get_service_doc_href(self, parent_dir: str) -> str:
"""
Generate an API service documentation href based on
the provided parent directory.

Parameters:
parent_dir (str): The parent directory for the API.

Returns:
str: The generated API href.
"""
return posixpath.join(parent_dir, "api.html")
28 changes: 15 additions & 13 deletions pystac/serialization/migrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,19 +91,21 @@ def _get_object_migrations() -> (
}


def _get_removed_extension_migrations() -> dict[
str,
tuple[
list[STACObjectType] | None,
None
| (
Callable[
[dict[str, Any], STACVersionID, STACJSONDescription],
set[str] | None,
]
),
],
]:
def _get_removed_extension_migrations() -> (
dict[
str,
tuple[
list[STACObjectType] | None,
None
| (
Callable[
[dict[str, Any], STACVersionID, STACJSONDescription],
set[str] | None,
]
),
],
]
):
"""Handles removed extensions.

This does not handle renamed extension or extensions that were absorbed
Expand Down
45 changes: 45 additions & 0 deletions tests/test_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,11 +458,25 @@ def test_produces_layout_for_root_catalog(self) -> None:
)
self.assertEqual(href, "http://example.com")

def test_produces_layout_for_root_catalog_str(self) -> None:
cat = pystac.Catalog(id="test", description="test desc")
href = self.strategy.get_catalog_href(
cat.id, parent_dir="http://example.com", is_root=True
)
self.assertEqual(href, "http://example.com")

def test_produces_layout_for_child_catalog(self) -> None:
cat = pystac.Catalog(id="test", description="test desc")
href = self.strategy.get_href(cat, parent_dir="http://example.com")
self.assertEqual(href, "http://example.com/test")

def test_produces_layout_for_child_catalog_str(self) -> None:
cat = pystac.Catalog(id="test", description="test desc")
href = self.strategy.get_catalog_href(
cat.id, parent_dir="http://example.com", is_root=False
)
self.assertEqual(href, "http://example.com/test")

def test_cannot_produce_layout_for_root_collection(self) -> None:
collection = TestCases.case_8()
with pytest.raises(ValueError):
Expand All @@ -475,6 +489,13 @@ def test_produces_layout_for_child_collection(self) -> None:
href = self.strategy.get_href(collection, parent_dir="http://example.com")
self.assertEqual(href, f"http://example.com/collections/{collection.id}")

def test_produces_layout_for_child_collection_str(self) -> None:
collection = TestCases.case_8()
href = self.strategy.get_collection_href(
collection.id, parent_dir="http://example.com", is_root=False
)
self.assertEqual(href, f"http://example.com/collections/{collection.id}")

def test_produces_layout_for_item(self) -> None:
collection = TestCases.case_8()
col_href = self.strategy.get_href(collection, parent_dir="http://example.com")
Expand All @@ -483,6 +504,14 @@ def test_produces_layout_for_item(self) -> None:
expected = f"http://example.com/collections/{collection.id}/items/{item.id}"
self.assertEqual(href, expected)

def test_produces_layout_for_item_str(self) -> None:
collection = TestCases.case_8()
col_href = self.strategy.get_href(collection, parent_dir="http://example.com")
item = next(collection.get_items(recursive=True))
href = self.strategy.get_item_href(item.id, parent_dir=col_href)
expected = f"http://example.com/collections/{collection.id}/items/{item.id}"
self.assertEqual(href, expected)

def test_produces_normalized_layout(self) -> None:
cat = pystac.Catalog(id="test_catalog", description="Test Catalog")
col = pystac.Collection(
Expand Down Expand Up @@ -530,3 +559,19 @@ def test_produces_normalized_layout(self) -> None:
item.self_href
== "http://example.com/collections/test_collection/items/test_item"
)

def test_produces_layout_for_search(self) -> None:
href = self.strategy.get_search_href(parent_dir="http://example.com")
self.assertEqual(href, "http://example.com/search")

def test_produces_layout_for_conformance(self) -> None:
href = self.strategy.get_conformance_href(parent_dir="http://example.com")
self.assertEqual(href, "http://example.com/conformance")

def test_produces_layout_for_service_description(self) -> None:
href = self.strategy.get_service_desc_href(parent_dir="http://example.com")
self.assertEqual(href, "http://example.com/api")

def test_produces_layout_for_service_doc(self) -> None:
href = self.strategy.get_service_doc_href(parent_dir="http://example.com")
self.assertEqual(href, "http://example.com/api.html")
Loading