Skip to content

Commit 3f6b58e

Browse files
committed
Simplify from_file logic
1 parent 6e0785b commit 3f6b58e

9 files changed

+101
-38
lines changed

pystac/__init__.py

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,11 @@
7676
def read_file(href: str) -> 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.
@@ -90,22 +91,12 @@ def read_file(href: str) -> STACObject:
9091
9192
Raises:
9293
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
94+
:class:`~pystac.STACObject`. Note that an :class:`~pystac.ItemCollection`
95+
is not a :class:`~pystac.STACObject` and must be read using
9596
:meth:`ItemCollection.from_file <pystac.ItemCollection.from_file>`
9697
"""
9798
stac_io = StacIO.default()
98-
d = stac_io.read_json(href)
99-
typ = pystac.serialization.identify.identify_stac_object_type(d)
100-
101-
if typ == STACObjectType.CATALOG:
102-
return Catalog.from_file(href)
103-
elif typ == STACObjectType.COLLECTION:
104-
return Collection.from_file(href)
105-
elif typ == STACObjectType.ITEM:
106-
return Item.from_file(href)
107-
else:
108-
raise STACTypeError(f"Cannot read file of type {typ}")
99+
return stac_io.read_stac_object(href)
109100

110101

111102
def write_file(
@@ -148,7 +139,7 @@ def read_dict(
148139
or :class`~Item` based on the contents of the dict.
149140
150141
This is a convenience method for either
151-
:meth:`stac_io.stac_object_from_dict <stac_io.stac_object_from_dict>`.
142+
:meth:`StacIO.stac_object_from_dict <pystac.StacIO.stac_object_from_dict>`.
152143
153144
Args:
154145
d : The dict to parse.
@@ -162,8 +153,8 @@ def read_dict(
162153
163154
Raises:
164155
STACTypeError : If the ``d`` dictionary does not represent a valid
165-
:class:`~pystac.STACObject`. Note that an :class:`~pystac.ItemCollection` is not
166-
a :class:`~pystac.STACObject` and must be read using
156+
:class:`~pystac.STACObject`. Note that an :class:`~pystac.ItemCollection`
157+
is not a :class:`~pystac.STACObject` and must be read using
167158
:meth:`ItemCollection.from_dict <pystac.ItemCollection.from_dict>`
168159
"""
169160
if stac_io is None:

pystac/catalog.py

Lines changed: 16 additions & 5 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.STACTypeError(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.identify_dict(d):
915+
raise STACTypeError(f"{d} does not represent a {cls.__name__} instance")
909916

910917
catalog_type = CatalogType.determine_type(d)
911918

@@ -955,3 +962,7 @@ def from_file(cls, href: str, stac_io: Optional[pystac.StacIO] = None) -> "Catal
955962
result._stac_io = stac_io
956963

957964
return result
965+
966+
@classmethod
967+
def identify_dict(cls, d: Dict[str, Any]) -> bool:
968+
return identify_stac_object_type(d) == STACObjectType.CATALOG

pystac/collection.py

Lines changed: 15 additions & 4 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.identify_dict(d):
596+
raise STACTypeError(f"{d} does not represent a {cls.__name__} instance")
590597

591598
catalog_type = CatalogType.determine_type(d)
592599

@@ -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 identify_dict(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.identify_dict(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 identify_dict(cls, d: Dict[str, Any]) -> bool:
994+
return identify_stac_object_type(d) == STACObjectType.ITEM

pystac/stac_object.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
from pystac import STACError
77
from pystac.link import Link
88
from pystac.utils import is_absolute_href, make_absolute_href
9-
from pystac import serialization
10-
from pystac.serialization.identify import identify_stac_object
119

1210
if TYPE_CHECKING:
1311
from pystac.catalog import Catalog as Catalog_Type
@@ -465,16 +463,17 @@ def from_file(
465463
The specific STACObject implementation class that is represented
466464
by the JSON read from the file located at HREF.
467465
"""
466+
if cls == STACObject:
467+
return pystac.read_file(href)
468+
468469
if stac_io is None:
469470
stac_io = pystac.StacIO.default()
470471

471472
if not is_absolute_href(href):
472473
href = make_absolute_href(href)
473474

474475
d = stac_io.read_json(href)
475-
info = identify_stac_object(d)
476-
d = serialization.migrate.migrate_to_latest(d, info)
477-
o = cls.from_dict(d, href=href)
476+
o = cls.from_dict(d, href=href, migrate=True)
478477

479478
# Set the self HREF, if it's not already set to something else.
480479
if o.get_self_href() is None:
@@ -513,3 +512,16 @@ def from_dict(
513512
STACObject: The STACObject parsed from this dict.
514513
"""
515514
pass
515+
516+
@classmethod
517+
@abstractmethod
518+
def identify_dict(cls, d: Dict[str, Any]) -> bool:
519+
"""Returns a boolean indicating whether the given dictionary represents a valid
520+
instance of this :class:`~STACObject` sub-class.
521+
522+
Args:
523+
d : A dictionary to identify
524+
"""
525+
raise NotImplementedError(
526+
"identify_dict must be implemented by the STACObject subclass."
527+
)

tests/test_catalog.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,14 @@ def test_catalog_with_href_caches_by_href(self) -> None:
974974
# cached only by HREF
975975
self.assertEqual(len(cache.id_keys_to_objects), 0)
976976

977+
def testfrom_invalid_dict_raises_exception(self) -> None:
978+
stac_io = pystac.StacIO.default()
979+
collection_dict = stac_io.read_json(
980+
TestCases.get_path("data-files/collections/multi-extent.json")
981+
)
982+
with self.assertRaises(pystac.STACTypeError):
983+
_ = pystac.Catalog.from_dict(collection_dict)
984+
977985

978986
class FullCopyTest(unittest.TestCase):
979987
def check_link(self, link: pystac.Link, tag: str) -> None:

tests/test_collection.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,14 @@ def test_schema_summary(self) -> None:
196196

197197
self.assertIsInstance(instruments_schema, dict)
198198

199+
def test_from_invalid_dict_raises_exception(self) -> None:
200+
stac_io = pystac.StacIO.default()
201+
catalog_dict = stac_io.read_json(
202+
TestCases.get_path("data-files/catalogs/test-case-1/catalog.json")
203+
)
204+
with self.assertRaises(pystac.STACTypeError):
205+
_ = pystac.Collection.from_dict(catalog_dict)
206+
199207

200208
class ExtentTest(unittest.TestCase):
201209
def test_spatial_allows_single_bbox(self) -> None:

tests/test_item.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,14 @@ def test_make_asset_href_relative_is_noop_on_relative_hrefs(self) -> None:
216216
item.make_asset_hrefs_relative()
217217
self.assertEqual(asset.get_absolute_href(), original_href)
218218

219+
def test_from_invalid_dict_raises_exception(self) -> None:
220+
stac_io = pystac.StacIO.default()
221+
catalog_dict = stac_io.read_json(
222+
TestCases.get_path("data-files/catalogs/test-case-1/catalog.json")
223+
)
224+
with self.assertRaises(pystac.STACTypeError):
225+
_ = pystac.Item.from_dict(catalog_dict)
226+
219227

220228
class CommonMetadataTest(unittest.TestCase):
221229
def setUp(self) -> None:

tests/test_writing.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ def validate_catalog_link_type(
7272
href: str, link_type: str, should_include_self: bool
7373
) -> None:
7474
cat_dict = pystac.StacIO.default().read_json(href)
75-
cat = pystac.Catalog.from_file(href)
75+
cat = pystac.read_file(href)
76+
if not isinstance(cat, pystac.Catalog):
77+
raise pystac.STACTypeError(f"File at {href} is not a Catalog.")
7678

7779
rels = set([link["rel"] for link in cat_dict["links"]])
7880
self.assertEqual("self" in rels, should_include_self)

0 commit comments

Comments
 (0)