Skip to content

Commit ed2b9f7

Browse files
authored
Better errors for pystac/satstac import issues (#125)
1 parent 0bc305f commit ed2b9f7

File tree

2 files changed

+130
-11
lines changed

2 files changed

+130
-11
lines changed

stackstac/stac_types.py

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919
cast,
2020
)
2121

22+
possible_problems: list[str] = []
23+
2224
try:
23-
from satstac import Item as SatstacItem
24-
from satstac import ItemCollection as SatstacItemCollection
25-
except ImportError:
25+
from satstac.item import Item as SatstacItem
26+
from satstac.itemcollection import ItemCollection as SatstacItemCollection
27+
except ImportError as e:
2628

2729
class SatstacItem:
2830
_data: ItemDict
@@ -31,11 +33,20 @@ class SatstacItemCollection:
3133
def __iter__(self) -> Iterator[SatstacItem]:
3234
...
3335

36+
if not isinstance(e, ModuleNotFoundError):
37+
possible_problems.append(
38+
"Your version of `satstac` is too old (or new) for stackstac. "
39+
"`satstac.Item` and `satstac.ItemCollection` aren't supported "
40+
f"because they could not be imported: {e!r}"
41+
)
42+
del e
43+
3444

3545
try:
3646
from pystac import Catalog as PystacCatalog
3747
from pystac import Item as PystacItem
38-
except ImportError:
48+
from pystac import ItemCollection as PystacItemCollection
49+
except ImportError as e:
3950

4051
class PystacItem:
4152
def to_dict(self) -> ItemDict:
@@ -45,18 +56,20 @@ class PystacCatalog:
4556
def get_all_items(self) -> Iterator[PystacItem]:
4657
...
4758

48-
49-
# pystac 1.0
50-
try:
51-
from pystac import ItemCollection as PystacItemCollection
52-
except ImportError:
53-
5459
class PystacItemCollection:
5560
features: List[PystacItem]
5661

5762
def __iter__(self) -> Iterator[PystacItem]:
5863
...
5964

65+
if not isinstance(e, ModuleNotFoundError):
66+
possible_problems.append(
67+
"Your version of `pystac` is too old (or new) for stackstac. "
68+
"`pystac.Item`, `pystac.ItemCollection`, and `pystac.Catalog` aren't supported "
69+
f"because they could not be imported: {e!r}"
70+
)
71+
del e
72+
6073

6174
class EOBand(TypedDict, total=False):
6275
name: str
@@ -160,4 +173,11 @@ def items_to_plain(items: Union[ItemCollectionIsh, ItemIsh]) -> ItemSequence:
160173
if isinstance(items, PystacItemCollection):
161174
return [item.to_dict() for item in items]
162175

163-
raise TypeError(f"Unrecognized STAC collection type {type(items)}: {items!r}")
176+
raise TypeError(
177+
f"Unrecognized STAC collection type {type(items)}: {items!r}"
178+
+ (
179+
"\n".join(["\nPossible problems:"] + possible_problems)
180+
if possible_problems
181+
else ""
182+
)
183+
)

stackstac/tests/test_stac_types.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import importlib
2+
from datetime import datetime
3+
import sys
4+
from types import ModuleType
5+
6+
import pytest
7+
import satstac
8+
import pystac
9+
10+
from stackstac import stac_types
11+
12+
13+
def test_normal_case():
14+
assert stac_types.SatstacItem is satstac.item.Item
15+
assert stac_types.SatstacItemCollection is satstac.itemcollection.ItemCollection
16+
17+
assert stac_types.PystacItem is pystac.Item
18+
assert stac_types.PystacItemCollection is pystac.ItemCollection
19+
assert stac_types.PystacCatalog is pystac.Catalog
20+
21+
22+
def test_missing_satstac(monkeypatch: pytest.MonkeyPatch):
23+
"Test importing works without satstac"
24+
monkeypatch.setitem(sys.modules, "satstac", None) # type: ignore
25+
monkeypatch.setitem(sys.modules, "satstac.item", None) # type: ignore
26+
monkeypatch.setitem(sys.modules, "satstac.itemcollection", None) # type: ignore
27+
# Type "ModuleType | None" cannot be assigned to type "ModuleType"
28+
29+
reloaded_stac_types = importlib.reload(stac_types)
30+
assert "stackstac" in reloaded_stac_types.SatstacItem.__module__
31+
assert "stackstac" in reloaded_stac_types.SatstacItemCollection.__module__
32+
33+
assert not reloaded_stac_types.possible_problems
34+
35+
36+
def test_missing_pystac(monkeypatch: pytest.MonkeyPatch):
37+
"Test importing works without pystac"
38+
monkeypatch.setitem(sys.modules, "pystac", None) # type: ignore
39+
# Type "ModuleType | None" cannot be assigned to type "ModuleType"
40+
41+
reloaded_stac_types = importlib.reload(stac_types)
42+
assert "stackstac" in reloaded_stac_types.PystacItem.__module__
43+
assert "stackstac" in reloaded_stac_types.PystacCatalog.__module__
44+
assert "stackstac" in reloaded_stac_types.PystacItemCollection.__module__
45+
46+
assert not reloaded_stac_types.possible_problems
47+
48+
49+
@pytest.mark.parametrize(
50+
"module, path, inst",
51+
[
52+
(
53+
satstac,
54+
"item.Item",
55+
satstac.item.Item(
56+
{"id": "foo"},
57+
),
58+
),
59+
(
60+
satstac,
61+
"itemcollection.ItemCollection",
62+
satstac.itemcollection.ItemCollection(
63+
[],
64+
),
65+
),
66+
(pystac, "Item", pystac.Item("foo", None, None, datetime(2000, 1, 1), {})),
67+
(pystac, "Catalog", pystac.Catalog("foo", "bar")),
68+
(
69+
pystac,
70+
"ItemCollection",
71+
pystac.ItemCollection(
72+
[],
73+
),
74+
),
75+
],
76+
)
77+
def test_unimportable_path(
78+
module: ModuleType, path: str, inst: object, monkeypatch: pytest.MonkeyPatch
79+
):
80+
"""
81+
Test that importing still works when a type isn't importable from pystac/satstac,
82+
but the overall module is importable. (Simulating a breaking change/incompatible version.)
83+
84+
Verify that a useful error is shown when `items_to_plain` fails.
85+
"""
86+
parts = path.split(".")
87+
modname = module.__name__
88+
89+
# Delete the `path` from `module`. We do this instead of just putting None in `sys.modules`,
90+
# so that we get a proper `ImportError` instead of `ModuleNotFoundError`.
91+
for p in parts:
92+
prev = module
93+
module = getattr(module, p)
94+
monkeypatch.delattr(prev, p)
95+
96+
reloaded_stac_types = importlib.reload(stac_types)
97+
98+
with pytest.raises(TypeError, match=f"Your version of `{modname}` is too old"):
99+
reloaded_stac_types.items_to_plain(inst)

0 commit comments

Comments
 (0)