Skip to content

Commit 2739661

Browse files
authored
Merge pull request #451 from duckontheweb/fix/gh-410-stac-object-inheritance
Fix STACObject inheritance
2 parents 012833a + a6aa201 commit 2739661

File tree

12 files changed

+281
-82
lines changed

12 files changed

+281
-82
lines changed

pystac/__init__.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -73,34 +73,40 @@
7373
)
7474

7575

76-
def read_file(href: str) -> STACObject:
76+
def read_file(href: str, stac_io: Optional[StacIO] = None) -> STACObject:
7777
"""Reads a STAC object from a file.
7878
79-
This method will return either a Catalog, a Collection, or an Item based on what the
80-
file contains.
79+
This method will return either a Catalog, a Collection, or an Item based on what
80+
the file contains.
8181
82-
This is a convenience method for :meth:`STACObject.from_file <pystac.STACObject.from_file>`
82+
This is a convenience method for :meth:`StacIO.read_stac_object
83+
<pystac.StacIO.read_stac_object>`
8384
8485
Args:
8586
href : The HREF to read the object from.
87+
stac_io: Optional :class:`~StacIO` instance to use for I/O operations. If not
88+
provided, will use :meth:`StacIO.default` to create an instance.
8689
8790
Returns:
8891
The specific STACObject implementation class that is represented
8992
by the JSON read from the file located at HREF.
9093
9194
Raises:
9295
STACTypeError : If the file at ``href`` does not represent a valid
93-
:class:`~pystac.STACObject`. Note that an :class:`~pystac.ItemCollection` is not
94-
a :class:`~pystac.STACObject` and must be read using
96+
:class:`~pystac.STACObject`. Note that an :class:`~pystac.ItemCollection`
97+
is not a :class:`~pystac.STACObject` and must be read using
9598
:meth:`ItemCollection.from_file <pystac.ItemCollection.from_file>`
9699
"""
97-
return STACObject.from_file(href)
100+
if stac_io is None:
101+
stac_io = StacIO.default()
102+
return stac_io.read_stac_object(href)
98103

99104

100105
def write_file(
101106
obj: STACObject,
102107
include_self_link: bool = True,
103108
dest_href: Optional[str] = None,
109+
stac_io: Optional[StacIO] = None,
104110
) -> None:
105111
"""Writes a STACObject to a file.
106112
@@ -120,8 +126,14 @@ def write_file(
120126
Otherwise, leave out the self link.
121127
dest_href : Optional HREF to save the file to. If ``None``, the object will be
122128
saved to the object's ``"self"`` href.
129+
stac_io: Optional :class:`~StacIO` instance to use for I/O operations. If not
130+
provided, will use :meth:`StacIO.default` to create an instance.
123131
"""
124-
obj.save_object(include_self_link=include_self_link, dest_href=dest_href)
132+
if stac_io is None:
133+
stac_io = StacIO.default()
134+
obj.save_object(
135+
include_self_link=include_self_link, dest_href=dest_href, stac_io=stac_io
136+
)
125137

126138

127139
def read_dict(
@@ -137,7 +149,7 @@ def read_dict(
137149
or :class`~Item` based on the contents of the dict.
138150
139151
This is a convenience method for either
140-
:meth:`stac_io.stac_object_from_dict <stac_io.stac_object_from_dict>`.
152+
:meth:`StacIO.stac_object_from_dict <pystac.StacIO.stac_object_from_dict>`.
141153
142154
Args:
143155
d : The dict to parse.
@@ -151,8 +163,8 @@ def read_dict(
151163
152164
Raises:
153165
STACTypeError : If the ``d`` dictionary does not represent a valid
154-
:class:`~pystac.STACObject`. Note that an :class:`~pystac.ItemCollection` is not
155-
a :class:`~pystac.STACObject` and must be read using
166+
:class:`~pystac.STACObject`. Note that an :class:`~pystac.ItemCollection`
167+
is not a :class:`~pystac.STACObject` and must be read using
156168
:meth:`ItemCollection.from_dict <pystac.ItemCollection.from_dict>`
157169
"""
158170
if stac_io is None:

pystac/catalog.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
from copy import deepcopy
33
from enum import Enum
4+
from pystac.errors import STACTypeError
45
from typing import (
56
Any,
67
Callable,
@@ -15,14 +16,19 @@
1516
)
1617

1718
import pystac
18-
from pystac.stac_object import STACObject
19+
from pystac.stac_object import STACObject, STACObjectType
1920
from pystac.layout import (
2021
BestPracticesLayoutStrategy,
2122
HrefLayoutStrategy,
2223
LayoutTemplate,
2324
)
2425
from pystac.link import Link
2526
from pystac.cache import ResolvedObjectCache
27+
from pystac.serialization import (
28+
identify_stac_object_type,
29+
identify_stac_object,
30+
migrate_to_latest,
31+
)
2632
from pystac.utils import is_absolute_href, make_absolute_href
2733

2834
if TYPE_CHECKING:
@@ -902,10 +908,11 @@ def from_dict(
902908
migrate: bool = False,
903909
) -> "Catalog":
904910
if migrate:
905-
result = pystac.read_dict(d, href=href, root=root)
906-
if not isinstance(result, Catalog):
907-
raise pystac.STACError(f"{result} is not a Catalog")
908-
return result
911+
info = identify_stac_object(d)
912+
d = migrate_to_latest(d, info)
913+
914+
if not cls.matches_object_type(d):
915+
raise STACTypeError(f"{d} does not represent a {cls.__name__} instance")
909916

910917
catalog_type = CatalogType.determine_type(d)
911918

@@ -919,7 +926,7 @@ def from_dict(
919926

920927
d.pop("stac_version")
921928

922-
cat = Catalog(
929+
cat = cls(
923930
id=id,
924931
description=description,
925932
title=title,
@@ -946,7 +953,16 @@ def full_copy(
946953

947954
@classmethod
948955
def from_file(cls, href: str, stac_io: Optional[pystac.StacIO] = None) -> "Catalog":
956+
if stac_io is None:
957+
stac_io = pystac.StacIO.default()
958+
949959
result = super().from_file(href, stac_io)
950960
if not isinstance(result, Catalog):
951961
raise pystac.STACTypeError(f"{result} is not a {Catalog}.")
962+
result._stac_io = stac_io
963+
952964
return result
965+
966+
@classmethod
967+
def matches_object_type(cls, d: Dict[str, Any]) -> bool:
968+
return identify_stac_object_type(d) == STACObjectType.CATALOG

pystac/collection.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from copy import copy, deepcopy
22
from datetime import datetime as Datetime
3+
from pystac.errors import STACTypeError
34
from typing import (
45
Any,
56
Dict,
@@ -23,6 +24,11 @@
2324
from pystac.layout import HrefLayoutStrategy
2425
from pystac.link import Link
2526
from pystac.utils import datetime_to_str
27+
from pystac.serialization import (
28+
identify_stac_object_type,
29+
identify_stac_object,
30+
migrate_to_latest,
31+
)
2632
from pystac.summaries import Summaries
2733

2834
if TYPE_CHECKING:
@@ -583,10 +589,11 @@ def from_dict(
583589
migrate: bool = False,
584590
) -> "Collection":
585591
if migrate:
586-
result = pystac.read_dict(d, href=href, root=root)
587-
if not isinstance(result, Collection):
588-
raise pystac.STACError(f"{result} is not a Catalog")
589-
return result
592+
info = identify_stac_object(d)
593+
d = migrate_to_latest(d, info)
594+
595+
if not cls.matches_object_type(d):
596+
raise STACTypeError(f"{d} does not represent a {cls.__name__} instance")
590597

591598
catalog_type = CatalogType.determine_type(d)
592599

@@ -610,7 +617,7 @@ def from_dict(
610617

611618
d.pop("stac_version")
612619

613-
collection = Collection(
620+
collection = cls(
614621
id=id,
615622
description=description,
616623
extent=extent,
@@ -676,3 +683,7 @@ def from_file(
676683
if not isinstance(result, Collection):
677684
raise pystac.STACTypeError(f"{result} is not a {Collection}.")
678685
return result
686+
687+
@classmethod
688+
def matches_object_type(cls, d: Dict[str, Any]) -> bool:
689+
return identify_stac_object_type(d) == STACObjectType.COLLECTION

pystac/item.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
from pystac import STACError, STACObjectType
1010
from pystac.asset import Asset
1111
from pystac.link import Link
12+
from pystac.serialization import (
13+
identify_stac_object_type,
14+
identify_stac_object,
15+
migrate_to_latest,
16+
)
1217
from pystac.stac_object import STACObject
1318
from pystac.utils import (
1419
is_absolute_href,
@@ -912,10 +917,13 @@ def from_dict(
912917
migrate: bool = False,
913918
) -> "Item":
914919
if migrate:
915-
result = pystac.read_dict(d, href=href, root=root)
916-
if not isinstance(result, Item):
917-
raise pystac.STACError(f"{result} is not a Catalog")
918-
return result
920+
info = identify_stac_object(d)
921+
d = migrate_to_latest(d, info)
922+
923+
if not cls.matches_object_type(d):
924+
raise pystac.STACTypeError(
925+
f"{d} does not represent a {cls.__name__} instance"
926+
)
919927

920928
d = deepcopy(d)
921929
id = d.pop("id")
@@ -980,3 +988,7 @@ def from_file(cls, href: str, stac_io: Optional[pystac.StacIO] = None) -> "Item"
980988
if not isinstance(result, Item):
981989
raise pystac.STACTypeError(f"{result} is not a {Item}.")
982990
return result
991+
992+
@classmethod
993+
def matches_object_type(cls, d: Dict[str, Any]) -> bool:
994+
return identify_stac_object_type(d) == STACObjectType.ITEM

pystac/serialization/__init__.py

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,8 @@
11
# flake8: noqa
2-
from typing import Any, Dict, Optional, TYPE_CHECKING
3-
4-
import pystac
52
from pystac.serialization.identify import (
63
STACVersionRange,
74
identify_stac_object,
85
identify_stac_object_type,
96
)
107
from pystac.serialization.common_properties import merge_common_properties
118
from pystac.serialization.migrate import migrate_to_latest
12-
13-
if TYPE_CHECKING:
14-
from pystac.stac_object import STACObject
15-
from pystac.catalog import Catalog
16-
17-
18-
def stac_object_from_dict(
19-
d: Dict[str, Any], href: Optional[str] = None, root: Optional["Catalog"] = None
20-
) -> "STACObject":
21-
"""Determines how to deserialize a dictionary into a STAC object.
22-
23-
Args:
24-
d : The dict to parse.
25-
href : Optional href that is the file location of the object being
26-
parsed.
27-
root : Optional root of the catalog for this object.
28-
If provided, the root's resolved object cache can be used to search for
29-
previously resolved instances of the STAC object.
30-
31-
Note: This is used internally in StacIO instances to deserialize STAC Objects.
32-
"""
33-
if identify_stac_object_type(d) == pystac.STACObjectType.ITEM:
34-
collection_cache = None
35-
if root is not None:
36-
collection_cache = root._resolved_objects.as_collection_cache()
37-
38-
# Merge common properties in case this is an older STAC object.
39-
merge_common_properties(d, json_href=href, collection_cache=collection_cache)
40-
41-
info = identify_stac_object(d)
42-
43-
d = migrate_to_latest(d, info)
44-
45-
if info.object_type == pystac.STACObjectType.CATALOG:
46-
return pystac.Catalog.from_dict(d, href=href, root=root, migrate=False)
47-
48-
if info.object_type == pystac.STACObjectType.COLLECTION:
49-
return pystac.Collection.from_dict(d, href=href, root=root, migrate=False)
50-
51-
if info.object_type == pystac.STACObjectType.ITEM:
52-
return pystac.Item.from_dict(d, href=href, root=root, migrate=False)
53-
54-
raise pystac.STACTypeError(f"Unknown STAC object type {info.object_type}")

pystac/stac_io.py

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@
1919

2020
import pystac
2121
from pystac.utils import safe_urlparse
22-
import pystac.serialization
22+
from pystac.serialization import (
23+
merge_common_properties,
24+
identify_stac_object_type,
25+
identify_stac_object,
26+
migrate_to_latest,
27+
)
2328

2429
# Use orjson if available
2530
try:
@@ -95,12 +100,31 @@ def stac_object_from_dict(
95100
href: Optional[str] = None,
96101
root: Optional["Catalog_Type"] = None,
97102
) -> "STACObject_Type":
98-
result = pystac.serialization.stac_object_from_dict(d, href, root)
99-
if isinstance(result, pystac.Catalog):
100-
# Set the stac_io instance for usage by io operations
101-
# where this catalog is the root.
103+
if identify_stac_object_type(d) == pystac.STACObjectType.ITEM:
104+
collection_cache = None
105+
if root is not None:
106+
collection_cache = root._resolved_objects.as_collection_cache()
107+
108+
# Merge common properties in case this is an older STAC object.
109+
merge_common_properties(
110+
d, json_href=href, collection_cache=collection_cache
111+
)
112+
113+
info = identify_stac_object(d)
114+
d = migrate_to_latest(d, info)
115+
116+
if info.object_type == pystac.STACObjectType.CATALOG:
117+
result = pystac.Catalog.from_dict(d, href=href, root=root, migrate=False)
102118
result._stac_io = self
103-
return result
119+
return result
120+
121+
if info.object_type == pystac.STACObjectType.COLLECTION:
122+
return pystac.Collection.from_dict(d, href=href, root=root, migrate=False)
123+
124+
if info.object_type == pystac.STACObjectType.ITEM:
125+
return pystac.Item.from_dict(d, href=href, root=root, migrate=False)
126+
127+
raise ValueError(f"Unknown STAC object type {info.object_type}")
104128

105129
def read_json(
106130
self, source: Union[str, "Link_Type"], *args: Any, **kwargs: Any
@@ -302,7 +326,30 @@ def stac_object_from_dict(
302326
root: Optional["Catalog_Type"] = None,
303327
) -> "STACObject_Type":
304328
STAC_IO.issue_deprecation_warning()
305-
return pystac.serialization.stac_object_from_dict(d, href, root)
329+
if identify_stac_object_type(d) == pystac.STACObjectType.ITEM:
330+
collection_cache = None
331+
if root is not None:
332+
collection_cache = root._resolved_objects.as_collection_cache()
333+
334+
# Merge common properties in case this is an older STAC object.
335+
merge_common_properties(
336+
d, json_href=href, collection_cache=collection_cache
337+
)
338+
339+
info = identify_stac_object(d)
340+
341+
d = migrate_to_latest(d, info)
342+
343+
if info.object_type == pystac.STACObjectType.CATALOG:
344+
return pystac.Catalog.from_dict(d, href=href, root=root, migrate=False)
345+
346+
if info.object_type == pystac.STACObjectType.COLLECTION:
347+
return pystac.Collection.from_dict(d, href=href, root=root, migrate=False)
348+
349+
if info.object_type == pystac.STACObjectType.ITEM:
350+
return pystac.Item.from_dict(d, href=href, root=root, migrate=False)
351+
352+
raise ValueError(f"Unknown STAC object type {info.object_type}")
306353

307354
# This is set in __init__.py
308355
_STAC_OBJECT_CLASSES = None

0 commit comments

Comments
 (0)