Skip to content

Commit d5cadb2

Browse files
committed
get_root|child|item_links should be json-like
* Allow media_type on get_links and get_single_link to an iterable * Use `application/json", "application/geo+json" or None as the media_type for `get_root_link`, `get_child_links` and `get_item_links`
1 parent 312a081 commit d5cadb2

File tree

3 files changed

+81
-22
lines changed

3 files changed

+81
-22
lines changed

pystac/catalog.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,10 @@ def get_child_links(self) -> list[Link]:
464464
Return:
465465
List[Link]: List of links of this catalog with ``rel == 'child'``
466466
"""
467-
return self.get_links(pystac.RelType.CHILD)
467+
return self.get_links(
468+
rel=pystac.RelType.CHILD,
469+
media_type=[None, pystac.MediaType.GEOJSON, pystac.MediaType.JSON],
470+
)
468471

469472
def clear_children(self) -> None:
470473
"""Removes all children from this catalog.
@@ -624,7 +627,10 @@ def get_item_links(self) -> list[Link]:
624627
Return:
625628
List[Link]: List of links of this catalog with ``rel == 'item'``
626629
"""
627-
return self.get_links(pystac.RelType.ITEM)
630+
return self.get_links(
631+
rel=pystac.RelType.ITEM,
632+
media_type=[None, pystac.MediaType.GEOJSON, pystac.MediaType.JSON],
633+
)
628634

629635
def to_dict(
630636
self, include_self_link: bool = True, transform_hrefs: bool = True

pystac/stac_object.py

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,7 @@
33
from abc import ABC, abstractmethod
44
from collections.abc import Callable, Iterable
55
from html import escape
6-
from typing import (
7-
TYPE_CHECKING,
8-
Any,
9-
TypeVar,
10-
cast,
11-
)
6+
from typing import TYPE_CHECKING, Any, TypeAlias, TypeVar, cast
127

138
import pystac
149
from pystac import STACError
@@ -27,6 +22,8 @@
2722

2823
S = TypeVar("S", bound="STACObject")
2924

25+
STACObject_MediaType: TypeAlias = str | pystac.MediaType | None
26+
3027

3128
class STACObjectType(StringEnum):
3229
CATALOG = "Catalog"
@@ -177,7 +174,7 @@ def traverse(obj: str | STACObject, visited: set[str | STACObject]) -> bool:
177174
def get_single_link(
178175
self,
179176
rel: str | pystac.RelType | None = None,
180-
media_type: str | pystac.MediaType | None = None,
177+
media_type: STACObject_MediaType | Iterable[STACObject_MediaType] = None,
181178
) -> Link | None:
182179
"""Get a single :class:`~pystac.Link` instance associated with this
183180
object.
@@ -186,37 +183,41 @@ def get_single_link(
186183
rel : If set, filter links such that only those
187184
matching this relationship are returned.
188185
media_type: If set, filter the links such that only
189-
those matching media_type are returned
186+
those matching media_type are returned. media_type can
187+
be a single value or a list of values.
190188
191189
Returns:
192-
Optional[:class:`~pystac.Link`]: First link that matches ``rel``
190+
:class:`~pystac.Link` | None: First link that matches ``rel``
193191
and/or ``media_type``, or else the first link associated with
194192
this object.
195193
"""
196194
if rel is None and media_type is None:
197195
return next(iter(self.links), None)
196+
if media_type and isinstance(media_type, (str, pystac.MediaType)):
197+
media_type = [media_type]
198198
return next(
199199
(
200200
link
201201
for link in self.links
202202
if (rel is None or link.rel == rel)
203-
and (media_type is None or link.media_type == media_type)
203+
and (media_type is None or link.media_type in media_type)
204204
),
205205
None,
206206
)
207207

208208
def get_links(
209209
self,
210210
rel: str | pystac.RelType | None = None,
211-
media_type: str | pystac.MediaType | None = None,
211+
media_type: STACObject_MediaType | Iterable[STACObject_MediaType] = None,
212212
) -> list[Link]:
213213
"""Gets the :class:`~pystac.Link` instances associated with this object.
214214
215215
Args:
216216
rel : If set, filter links such that only those
217217
matching this relationship are returned.
218218
media_type: If set, filter the links such that only
219-
those matching media_type are returned
219+
those matching media_type are returned. media_type can
220+
be a single value or a list of values.
220221
221222
Returns:
222223
List[:class:`~pystac.Link`]: A list of links that match ``rel`` and/
@@ -225,13 +226,14 @@ def get_links(
225226
"""
226227
if rel is None and media_type is None:
227228
return self.links
228-
else:
229-
return [
230-
link
231-
for link in self.links
232-
if (rel is None or link.rel == rel)
233-
and (media_type is None or link.media_type == media_type)
234-
]
229+
if media_type and isinstance(media_type, (str, pystac.MediaType)):
230+
media_type = [media_type]
231+
return [
232+
link
233+
for link in self.links
234+
if (rel is None or link.rel == rel)
235+
and (media_type is None or link.media_type in media_type)
236+
]
235237

236238
def clear_links(self, rel: str | pystac.RelType | None = None) -> None:
237239
"""Clears all :class:`~pystac.Link` instances associated with this object.
@@ -252,7 +254,10 @@ def get_root_link(self) -> Link | None:
252254
:class:`~pystac.Link` or None: The root link for this object,
253255
or ``None`` if no root link is set.
254256
"""
255-
return self.get_single_link(pystac.RelType.ROOT)
257+
return self.get_single_link(
258+
rel=pystac.RelType.ROOT,
259+
media_type=[None, pystac.MediaType.GEOJSON, pystac.MediaType.JSON],
260+
)
256261

257262
@property
258263
def self_href(self) -> str:

tests/test_catalog.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,6 +1407,10 @@ def test_get_links(self) -> None:
14071407
len(catalog.get_links(rel="search", media_type="application/geo+json")) == 1
14081408
)
14091409
assert len(catalog.get_links(media_type="text/html")) == 1
1410+
assert (
1411+
len(catalog.get_links(media_type=["text/html", "application/geo+json"]))
1412+
== 2
1413+
)
14101414
assert len(catalog.get_links(rel="search")) == 2
14111415
assert len(catalog.get_links(rel="via")) == 0
14121416
assert len(catalog.get_links()) == 6
@@ -1968,3 +1972,47 @@ def test_add_item_layout_strategy(
19681972
template = template.format(id=item.id).replace("$", "")
19691973

19701974
assert item.self_href == f"{base_url}/{template}/{item_id}.json"
1975+
1976+
1977+
def test_get_child_links_cares_about_media_type(catalog: pystac.Catalog) -> None:
1978+
catalog.links.extend(
1979+
[
1980+
pystac.Link(
1981+
rel="child", target="./child-1.json", media_type="application/json"
1982+
),
1983+
pystac.Link(
1984+
rel="child", target="./child-2.json", media_type="application/geo+json"
1985+
),
1986+
pystac.Link(rel="child", target="./child-3.json"),
1987+
# this one won't get counted since it's the wrong media_type
1988+
pystac.Link(rel="child", target="./child.html", media_type="text/html"),
1989+
]
1990+
)
1991+
1992+
assert len(catalog.get_child_links()) == 3
1993+
1994+
1995+
def test_get_item_links_cares_about_media_type(catalog: pystac.Catalog) -> None:
1996+
catalog.links.extend(
1997+
[
1998+
pystac.Link(
1999+
rel="item", target="./item-1.json", media_type="application/json"
2000+
),
2001+
pystac.Link(
2002+
rel="item", target="./item-2.json", media_type="application/geo+json"
2003+
),
2004+
pystac.Link(rel="item", target="./item-3.json"),
2005+
# this one won't get counted since it's the wrong media_type
2006+
pystac.Link(rel="item", target="./item.html", media_type="text/html"),
2007+
]
2008+
)
2009+
2010+
assert len(catalog.get_item_links()) == 3
2011+
2012+
2013+
def test_get_root_link_cares_about_media_type(catalog: pystac.Catalog) -> None:
2014+
catalog.links.insert(
2015+
0, pystac.Link(rel="root", target="./self.json", media_type="text/html")
2016+
)
2017+
root_link = catalog.get_root_link()
2018+
assert root_link and root_link.target != "./self.json"

0 commit comments

Comments
 (0)