Skip to content

Commit 0ba1a1d

Browse files
author
Jon Duckworth
authored
Merge pull request #555 from gadomski/issues/499-resolving-self-href
Allow resolved self links
2 parents 17c52ed + 21feb88 commit 0ba1a1d

File tree

4 files changed

+95
-22
lines changed

4 files changed

+95
-22
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
### Fixed
3232

3333
- Added `Collections` as a type that can be extended for extensions whose fields can appear in collection summaries ([#547](https://github.com/stac-utils/pystac/pull/547))
34+
- Allow resolved self links when getting an object's self href ([#555](https://github.com/stac-utils/pystac/pull/555))
3435

3536
### Deprecated
3637

pystac/link.py

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from copy import copy
2-
from typing import Any, Dict, Optional, TYPE_CHECKING, Union, cast
2+
from typing import Any, Dict, Optional, TYPE_CHECKING, Union
33

44
import pystac
55
from pystac.utils import make_absolute_href, make_relative_href, is_absolute_href
@@ -52,11 +52,6 @@ class Link:
5252
"""The relation of the link (e.g. 'child', 'item'). Registered rel Types are
5353
preferred. See :class:`~pystac.RelType` for common media types."""
5454

55-
target: Union[str, "STACObject_Type"]
56-
"""The target of the link. If the link is unresolved, or the link is to something
57-
that is not a STACObject, the target is an HREF. If resolved, the target is a
58-
STACObject."""
59-
6055
media_type: Optional[str]
6156
"""Optional description of the media type. Registered Media Types are preferred.
6257
See :class:`~pystac.MediaType` for common media types."""
@@ -82,7 +77,12 @@ def __init__(
8277
extra_fields: Optional[Dict[str, Any]] = None,
8378
) -> None:
8479
self.rel = rel
85-
self.target = target
80+
if isinstance(target, str):
81+
self._target_href: Optional[str] = target
82+
self._target_object = None
83+
else:
84+
self._target_href = None
85+
self._target_object = target
8686
self.media_type = media_type
8787
self.title = title
8888
self.extra_fields = extra_fields or {}
@@ -119,10 +119,10 @@ def get_href(self) -> Optional[str]:
119119
In all other cases, this method will return an absolute HREF.
120120
"""
121121
# get the self href
122-
if self.is_resolved():
123-
href = cast(pystac.STACObject, self.target).get_self_href()
122+
if self._target_object:
123+
href = self._target_object.get_self_href()
124124
else:
125-
href = cast(Optional[str], self.target)
125+
href = self._target_href
126126

127127
if href and is_absolute_href(href) and self.owner and self.owner.get_root():
128128
root = self.owner.get_root()
@@ -158,16 +158,55 @@ def get_absolute_href(self) -> Optional[str]:
158158
from this link; however, if the link is relative, has no owner,
159159
and has an unresolved target, this will return a relative HREF.
160160
"""
161-
if self.is_resolved():
162-
href = cast(pystac.STACObject, self.target).get_self_href()
161+
if self._target_object:
162+
href = self._target_object.get_self_href()
163163
else:
164-
href = cast(Optional[str], self.target)
164+
href = self._target_href
165165

166166
if href is not None and self.owner is not None:
167167
href = make_absolute_href(href, self.owner.get_self_href())
168168

169169
return href
170170

171+
@property
172+
def target(self) -> Union[str, "STACObject_Type"]:
173+
"""The target of the link. If the link is unresolved, or the link is to something
174+
that is not a STACObject, the target is an HREF. If resolved, the target is a
175+
STACObject."""
176+
if self._target_object:
177+
return self._target_object
178+
elif self._target_href:
179+
return self._target_href
180+
else:
181+
raise ValueError("No target defined for link.")
182+
183+
@target.setter
184+
def target(self, target: Union[str, "STACObject_Type"]) -> None:
185+
"""Sets this link's target to a string or a STAC object."""
186+
if isinstance(target, str):
187+
self._target_href = target
188+
self._target_object = None
189+
else:
190+
self._target_href = None
191+
self._target_object = target
192+
193+
def get_target_str(self) -> Optional[str]:
194+
"""Returns this link's target as a string.
195+
196+
If a string href was provided, returns that. If not, tries to resolve
197+
the self link of the target object.
198+
"""
199+
if self._target_href:
200+
return self._target_href
201+
elif self._target_object:
202+
return self._target_object.get_self_href()
203+
else:
204+
return None
205+
206+
def has_target_href(self) -> bool:
207+
"""Returns true if this link has a string href in its target information."""
208+
return self._target_href is not None
209+
171210
def __repr__(self) -> str:
172211
return "<Link rel={} target={}>".format(self.rel, self.target)
173212

@@ -180,8 +219,10 @@ def resolve_stac_object(self, root: Optional["Catalog_Type"] = None) -> "Link":
180219
If provided, the root's resolved object cache is used to search for
181220
previously resolved instances of the STAC object.
182221
"""
183-
if isinstance(self.target, str):
184-
target_href = self.target
222+
if self._target_object:
223+
pass
224+
elif self._target_href:
225+
target_href = self._target_href
185226

186227
# If it's a relative link, base it off the parent.
187228
if not is_absolute_href(target_href):
@@ -221,17 +262,17 @@ def resolve_stac_object(self, root: Optional["Catalog_Type"] = None) -> "Link":
221262
if root is not None:
222263
obj = root._resolved_objects.get_or_cache(obj)
223264
obj.set_root(root)
265+
self._target_object = obj
224266
else:
225-
obj = self.target
226-
227-
self.target = obj
267+
raise ValueError("Cannot resolve STAC object without a target")
228268

229269
if (
230270
self.owner
231271
and self.rel in [pystac.RelType.CHILD, pystac.RelType.ITEM]
232272
and isinstance(self.owner, pystac.Catalog)
233273
):
234-
self.target.set_parent(self.owner)
274+
assert self._target_object
275+
self._target_object.set_parent(self.owner)
235276

236277
return self
237278

@@ -241,7 +282,7 @@ def is_resolved(self) -> bool:
241282
Returns:
242283
bool: True if this link is resolved.
243284
"""
244-
return not isinstance(self.target, str)
285+
return self._target_object is not None
245286

246287
def to_dict(self) -> Dict[str, Any]:
247288
"""Generate a dictionary representing the JSON of this serialized Link.

pystac/stac_object.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,8 @@ def get_self_href(self) -> Optional[str]:
158158
links have absolute (as opposed to relative) HREFs.
159159
"""
160160
self_link = self.get_single_link(pystac.RelType.SELF)
161-
if self_link:
162-
return cast(str, self_link.target)
161+
if self_link and self_link.has_target_href():
162+
return self_link.get_target_str()
163163
else:
164164
return None
165165

tests/test_link.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import datetime
2+
import os.path
23
import unittest
4+
from tempfile import TemporaryDirectory
35
from typing import Any, Dict, List
46

57
import pystac
@@ -82,6 +84,35 @@ def test_resolve_stac_object_no_root_and_target_is_item(self) -> None:
8284
link = pystac.Link("my rel", target=self.item)
8385
link.resolve_stac_object()
8486

87+
def test_resolved_self_href(self) -> None:
88+
catalog = pystac.Catalog(id="test", description="test desc")
89+
with TemporaryDirectory() as temporary_directory:
90+
catalog.normalize_and_save(temporary_directory)
91+
path = os.path.join(temporary_directory, "catalog.json")
92+
catalog = pystac.Catalog.from_file(path)
93+
link = catalog.get_single_link(pystac.RelType.SELF)
94+
assert link
95+
link.resolve_stac_object()
96+
self.assertEqual(link.get_absolute_href(), path)
97+
98+
def test_target_getter_setter(self) -> None:
99+
link = pystac.Link("my rel", target="./foo/bar.json")
100+
self.assertEqual(link.target, "./foo/bar.json")
101+
self.assertEqual(link.get_target_str(), "./foo/bar.json")
102+
103+
link.target = self.item
104+
self.assertEqual(link.target, self.item)
105+
self.assertEqual(link.get_target_str(), self.item.get_self_href())
106+
107+
link.target = "./bar/foo.json"
108+
self.assertEqual(link.target, "./bar/foo.json")
109+
110+
def test_get_target_str_no_href(self) -> None:
111+
self.item.remove_links("self")
112+
link = pystac.Link("self", target=self.item)
113+
self.item.add_link(link)
114+
self.assertIsNone(link.get_target_str())
115+
85116

86117
class StaticLinkTest(unittest.TestCase):
87118
def setUp(self) -> None:

0 commit comments

Comments
 (0)