Skip to content

Commit 27d6132

Browse files
author
Jon Duckworth
authored
Merge pull request #512 from duckontheweb/fix/link-inheritance
Return calling class from Link constructor methods
2 parents 0432554 + be548c6 commit 27d6132

File tree

10 files changed

+128
-32
lines changed

10 files changed

+128
-32
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
- Bug in `pystac.serialization.identify_stac_object_type` where invalid objects with
2222
`stac_version == 1.0.0` were incorrectly identified as Catalogs
2323
([#487](https://github.com/stac-utils/pystac/pull/487))
24+
- `Link` constructor classes (e.g. `Link.from_dict`, `Link.canonical`, etc.) now return
25+
the calling class instead of always returning the `Link` class
26+
([#512](https://github.com/stac-utils/pystac/pull/512))
2427

2528
### Removed
2629

pystac/asset.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@ def clone(self) -> "Asset":
127127
Returns:
128128
Asset: The clone of this asset.
129129
"""
130-
return Asset(
130+
cls = self.__class__
131+
return cls(
131132
href=self.href,
132133
title=self.title,
133134
description=self.description,
@@ -139,8 +140,8 @@ def clone(self) -> "Asset":
139140
def __repr__(self) -> str:
140141
return "<Asset href={}>".format(self.href)
141142

142-
@staticmethod
143-
def from_dict(d: Dict[str, Any]) -> "Asset":
143+
@classmethod
144+
def from_dict(cls, d: Dict[str, Any]) -> "Asset":
144145
"""Constructs an Asset from a dict.
145146
146147
Returns:
@@ -156,7 +157,7 @@ def from_dict(d: Dict[str, Any]) -> "Asset":
156157
if any(d):
157158
properties = d
158159

159-
return Asset(
160+
return cls(
160161
href=href,
161162
media_type=media_type,
162163
title=title,

pystac/catalog.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,8 @@ def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]:
471471
return d
472472

473473
def clone(self) -> "Catalog":
474-
clone = Catalog(
474+
cls = self.__class__
475+
clone = cls(
475476
id=self.id,
476477
description=self.description,
477478
title=self.title,

pystac/collection.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,8 @@ def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]:
638638
return d
639639

640640
def clone(self) -> "Collection":
641-
clone = Collection(
641+
cls = self.__class__
642+
clone = cls(
642643
id=self.id,
643644
description=self.description,
644645
extent=self.extent.clone(),

pystac/item.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -885,7 +885,8 @@ def to_dict(self, include_self_link: bool = True) -> Dict[str, Any]:
885885
return d
886886

887887
def clone(self) -> "Item":
888-
clone = Item(
888+
cls = self.__class__
889+
clone = cls(
889890
id=self.id,
890891
geometry=deepcopy(self.geometry),
891892
bbox=copy(self.bbox),

pystac/link.py

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -269,16 +269,16 @@ def clone(self) -> "Link":
269269
Returns:
270270
Link: The cloned link.
271271
"""
272-
273-
return Link(
272+
cls = self.__class__
273+
return cls(
274274
rel=self.rel,
275275
target=self.target,
276276
media_type=self.media_type,
277277
title=self.title,
278278
)
279279

280-
@staticmethod
281-
def from_dict(d: Dict[str, Any]) -> "Link":
280+
@classmethod
281+
def from_dict(cls, d: Dict[str, Any]) -> "Link":
282282
"""Deserializes a Link from a dict.
283283
284284
Args:
@@ -297,55 +297,56 @@ def from_dict(d: Dict[str, Any]) -> "Link":
297297
if any(d):
298298
properties = d
299299

300-
return Link(
300+
return cls(
301301
rel=rel,
302302
target=href,
303303
media_type=media_type,
304304
title=title,
305305
properties=properties,
306306
)
307307

308-
@staticmethod
309-
def root(c: "Catalog_Type") -> "Link":
308+
@classmethod
309+
def root(cls, c: "Catalog_Type") -> "Link":
310310
"""Creates a link to a root Catalog or Collection."""
311-
return Link(pystac.RelType.ROOT, c, media_type=pystac.MediaType.JSON)
311+
return cls(pystac.RelType.ROOT, c, media_type=pystac.MediaType.JSON)
312312

313-
@staticmethod
314-
def parent(c: "Catalog_Type") -> "Link":
313+
@classmethod
314+
def parent(cls, c: "Catalog_Type") -> "Link":
315315
"""Creates a link to a parent Catalog or Collection."""
316-
return Link(pystac.RelType.PARENT, c, media_type=pystac.MediaType.JSON)
316+
return cls(pystac.RelType.PARENT, c, media_type=pystac.MediaType.JSON)
317317

318-
@staticmethod
319-
def collection(c: "Collection_Type") -> "Link":
318+
@classmethod
319+
def collection(cls, c: "Collection_Type") -> "Link":
320320
"""Creates a link to an item's Collection."""
321-
return Link(pystac.RelType.COLLECTION, c, media_type=pystac.MediaType.JSON)
321+
return cls(pystac.RelType.COLLECTION, c, media_type=pystac.MediaType.JSON)
322322

323-
@staticmethod
324-
def self_href(href: str) -> "Link":
323+
@classmethod
324+
def self_href(cls, href: str) -> "Link":
325325
"""Creates a self link to a file's location."""
326-
return Link(pystac.RelType.SELF, href, media_type=pystac.MediaType.JSON)
326+
return cls(pystac.RelType.SELF, href, media_type=pystac.MediaType.JSON)
327327

328-
@staticmethod
329-
def child(c: "Catalog_Type", title: Optional[str] = None) -> "Link":
328+
@classmethod
329+
def child(cls, c: "Catalog_Type", title: Optional[str] = None) -> "Link":
330330
"""Creates a link to a child Catalog or Collection."""
331-
return Link(
331+
return cls(
332332
pystac.RelType.CHILD, c, title=title, media_type=pystac.MediaType.JSON
333333
)
334334

335-
@staticmethod
336-
def item(item: "Item_Type", title: Optional[str] = None) -> "Link":
335+
@classmethod
336+
def item(cls, item: "Item_Type", title: Optional[str] = None) -> "Link":
337337
"""Creates a link to an Item."""
338-
return Link(
338+
return cls(
339339
pystac.RelType.ITEM, item, title=title, media_type=pystac.MediaType.JSON
340340
)
341341

342-
@staticmethod
342+
@classmethod
343343
def canonical(
344+
cls,
344345
item_or_collection: Union["Item_Type", "Collection_Type"],
345346
title: Optional[str] = None,
346347
) -> "Link":
347348
"""Creates a canonical link to an Item or Collection."""
348-
return Link(
349+
return cls(
349350
pystac.RelType.CANONICAL,
350351
item_or_collection,
351352
title=title,

tests/test_catalog.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,3 +1153,9 @@ def test_from_file_returns_subclass(self) -> None:
11531153
custom_catalog = self.BasicCustomCatalog.from_file(self.TEST_CASE_1)
11541154

11551155
self.assertIsInstance(custom_catalog, self.BasicCustomCatalog)
1156+
1157+
def test_clone(self) -> None:
1158+
custom_catalog = self.BasicCustomCatalog.from_file(self.TEST_CASE_1)
1159+
cloned_catalog = custom_catalog.clone()
1160+
1161+
self.assertIsInstance(cloned_catalog, self.BasicCustomCatalog)

tests/test_collection.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,3 +388,9 @@ def test_from_file_returns_subclass(self) -> None:
388388
custom_collection = self.BasicCustomCollection.from_file(self.MULTI_EXTENT)
389389

390390
self.assertIsInstance(custom_collection, self.BasicCustomCollection)
391+
392+
def test_clone(self) -> None:
393+
custom_collection = self.BasicCustomCollection.from_file(self.MULTI_EXTENT)
394+
cloned_collection = custom_collection.clone()
395+
396+
self.assertIsInstance(cloned_collection, self.BasicCustomCollection)

tests/test_item.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,3 +739,32 @@ def test_from_file_returns_subclass(self) -> None:
739739
custom_item = self.BasicCustomItem.from_file(self.SAMPLE_ITEM)
740740

741741
self.assertIsInstance(custom_item, self.BasicCustomItem)
742+
743+
def test_clone(self) -> None:
744+
custom_item = self.BasicCustomItem.from_file(self.SAMPLE_ITEM)
745+
cloned_item = custom_item.clone()
746+
747+
self.assertIsInstance(cloned_item, self.BasicCustomItem)
748+
749+
750+
class AssetSubClassTest(unittest.TestCase):
751+
class CustomAsset(Asset):
752+
pass
753+
754+
def setUp(self) -> None:
755+
self.maxDiff = None
756+
with open(TestCases.get_path("data-files/item/sample-item.json")) as src:
757+
item_dict = json.load(src)
758+
759+
self.asset_dict = item_dict["assets"]["analytic"]
760+
761+
def test_from_dict(self) -> None:
762+
asset = self.CustomAsset.from_dict(self.asset_dict)
763+
764+
self.assertIsInstance(asset, self.CustomAsset)
765+
766+
def test_clone(self) -> None:
767+
asset = self.CustomAsset.from_dict(self.asset_dict)
768+
cloned_asset = asset.clone()
769+
770+
self.assertIsInstance(cloned_asset, self.CustomAsset)

tests/test_link.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,50 @@ def test_canonical_collection(self) -> None:
135135
link = pystac.Link.canonical(self.collection)
136136
expected = {"rel": "canonical", "href": None, "type": "application/json"}
137137
self.assertEqual(expected, link.to_dict())
138+
139+
140+
class LinkInheritanceTest(unittest.TestCase):
141+
def setUp(self) -> None:
142+
self.maxDiff = None
143+
self.collection = pystac.Collection(
144+
"collection id", "desc", extent=ARBITRARY_EXTENT
145+
)
146+
self.item = pystac.Item(
147+
id="test-item",
148+
geometry=None,
149+
bbox=None,
150+
datetime=TEST_DATETIME,
151+
properties={},
152+
)
153+
154+
class CustomLink(pystac.Link):
155+
pass
156+
157+
def test_from_dict(self) -> None:
158+
link = self.CustomLink.from_dict(
159+
{"rel": "r", "href": "t", "type": "a/b", "title": "t", "c": "d", "1": 2}
160+
)
161+
self.assertIsInstance(link, self.CustomLink)
162+
163+
def test_collection(self) -> None:
164+
link = self.CustomLink.collection(self.collection)
165+
self.assertIsInstance(link, self.CustomLink)
166+
167+
def test_child(self) -> None:
168+
link = self.CustomLink.child(self.collection)
169+
self.assertIsInstance(link, self.CustomLink)
170+
171+
def test_canonical_item(self) -> None:
172+
link = self.CustomLink.canonical(self.item)
173+
self.assertIsInstance(link, self.CustomLink)
174+
175+
def test_canonical_collection(self) -> None:
176+
link = self.CustomLink.canonical(self.collection)
177+
self.assertIsInstance(link, self.CustomLink)
178+
179+
def test_clone(self) -> None:
180+
link = self.CustomLink.from_dict(
181+
{"rel": "r", "href": "t", "type": "a/b", "title": "t", "c": "d", "1": 2}
182+
)
183+
cloned_link = link.clone()
184+
self.assertIsInstance(cloned_link, self.CustomLink)

0 commit comments

Comments
 (0)