Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

change STACTypeError to create short informative message #1126

Merged
merged 1 commit into from
May 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
- Updated raster extension to work with the item_assets extension's AssetDefinition objects ([#1110](https://github.com/stac-utils/pystac/pull/1110))
- Return all validation errors from validation methods of `JsonSchemaSTACValidator` ([#1120](https://github.com/stac-utils/pystac/pull/1120))
- EO extension updated to v1.1.0 ([#1131](https://github.com/stac-utils/pystac/pull/1131))
- Use `id` in STACTypeError instead of entire dict ([#1126](https://github.com/stac-utils/pystac/pull/1126))

### Deprecated

Expand Down
4 changes: 1 addition & 3 deletions pystac/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -1136,7 +1136,7 @@ def from_dict(
d = migrate_to_latest(d, info)

if not cls.matches_object_type(d):
raise STACTypeError(f"{d} does not represent a {cls.__name__} instance")
raise STACTypeError(d, cls)

catalog_type = CatalogType.determine_type(d)

Expand Down Expand Up @@ -1187,8 +1187,6 @@ def from_file(
stac_io = pystac.StacIO.default()

result = super().from_file(href, stac_io)
if not isinstance(result, Catalog):
raise pystac.STACTypeError(f"{result} is not a {Catalog}.")
result._stac_io = stac_io

return result
Expand Down
11 changes: 1 addition & 10 deletions pystac/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ def from_dict(
d = migrate_to_latest(d, info)

if not cls.matches_object_type(d):
raise STACTypeError(f"{d} does not represent a {cls.__name__} instance")
raise STACTypeError(d, cls)

catalog_type = CatalogType.determine_type(d)

Expand Down Expand Up @@ -754,15 +754,6 @@ def full_copy(
) -> Collection:
return cast(Collection, super().full_copy(root, parent))

@classmethod
def from_file(
cls: Type[C], href: str, stac_io: Optional[pystac.StacIO] = None
) -> C:
result = super().from_file(href, stac_io)
if not isinstance(result, Collection):
raise pystac.STACTypeError(f"{result} is not a {Collection}.")
return result

@classmethod
def matches_object_type(cls, d: Dict[str, Any]) -> bool:
return identify_stac_object_type(d) == STACObjectType.COLLECTION
25 changes: 23 additions & 2 deletions pystac/errors.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Optional, Union
from typing import Any, Dict, Optional, Union


class TemplateError(Exception):
Expand All @@ -24,7 +24,28 @@ class STACTypeError(Exception):
a Catalog JSON was read in as an Item.
"""

pass
def __init__(
self,
bad_dict: Dict[str, Any],
expected: type,
extra_message: Optional[str] = "",
):
"""
Construct an exception with an appropriate error message from bad_dict and the
expected that it didn't align with.

Args:
bad_dict: Dictionary that did not match the expected type
expected: The expected type.
extra_message: message that will be appended to the exception message.
"""
message = (
f"JSON (id = {bad_dict.get('id', 'unknown')}) does not represent"
f" a {expected.__name__} instance."
)
if extra_message:
message += " " + extra_message
super().__init__(message)


class DuplicateObjectKeyError(Exception):
Expand Down
18 changes: 2 additions & 16 deletions pystac/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,9 +432,7 @@ def from_dict(
d = migrate_to_latest(d, info)

if not cls.matches_object_type(d):
raise pystac.STACTypeError(
f"{d} does not represent a {cls.__name__} instance"
)
raise pystac.STACTypeError(d, cls)

# some fields are passed through to __init__
pass_through_fields = [
Expand Down Expand Up @@ -504,23 +502,11 @@ def full_copy(
) -> Item:
return cast(Item, super().full_copy(root, parent))

@classmethod
def from_file(
cls: Type[T], href: str, stac_io: Optional[pystac.StacIO] = None
) -> T:
result = super().from_file(href, stac_io)
if not isinstance(result, Item):
raise pystac.STACTypeError(f"{result} is not a {Item}.")
return result

@classmethod
def matches_object_type(cls, d: Dict[str, Any]) -> bool:
for field in ("type", "stac_version"):
if field not in d:
raise pystac.STACTypeError(
f"{d} does not represent a {cls.__name__} instance"
f"'{field}' is missing."
)
raise pystac.STACTypeError(d, cls, f"'{field}' is missing.")
return identify_stac_object_type(d) == STACObjectType.ITEM

@property
Expand Down
2 changes: 1 addition & 1 deletion pystac/item_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def from_dict(
hit of a deepcopy.
"""
if not cls.is_item_collection(d):
raise STACTypeError("Dict is not a valid ItemCollection")
raise STACTypeError(d, cls)

items = [
pystac.Item.from_dict(item, preserve_dict=preserve_dict, root=root)
Expand Down
3 changes: 2 additions & 1 deletion pystac/serialization/identify.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,8 @@ def identify_stac_object(json_dict: Dict[str, Any]) -> STACJSONDescription:
object_type = identify_stac_object_type(json_dict)

if object_type is None:
raise pystac.STACTypeError("JSON does not represent a STAC object.")
extra_message = f"'type' attribute is {json_dict.get('type', 'not set')}"
raise pystac.STACTypeError(json_dict, pystac.STACObject, extra_message)

version_range = STACVersionRange()

Expand Down
7 changes: 7 additions & 0 deletions tests/data-files/catalogs/invalid-catalog/catalog.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "Catalog",
"id": "broken_cat",
"description": "test catalog w/o stac_version",
"links": [],
"stac_extensions": []
}
8 changes: 6 additions & 2 deletions tests/serialization/test_identify.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ def test_identify_invalid_stac_object_with_version(self) -> None:
with pytest.raises(pystac.STACTypeError) as ctx:
identify_stac_object(invalid_dict)

assert "JSON does not represent a STAC object" in str(ctx.value.args[0])
assert "JSON (id = concepts) does not represent a STACObject instance." in str(
ctx.value.args[0]
)

def test_identify_non_stac_raises_error(self) -> None:
plain_feature_dict = {
Expand All @@ -81,7 +83,9 @@ def test_identify_non_stac_raises_error(self) -> None:
with pytest.raises(pystac.STACTypeError) as ctx:
identify_stac_object(plain_feature_dict)

assert "JSON does not represent a STAC object" in str(ctx.value.args[0])
assert "JSON (id = unknown) does not represent a STACObject instance." in str(
ctx.value.args[0]
)

def test_identify_invalid_with_stac_version(self) -> None:
not_stac = {"stac_version": "0.9.0", "type": "Custom"}
Expand Down
8 changes: 7 additions & 1 deletion tests/test_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ def test_from_dict_preserves_dict(self) -> None:
_ = Catalog.from_dict(param_dict, preserve_dict=False)
assert param_dict != catalog_dict

def test_from_file_bad_catalog(self) -> None:
with pytest.raises(pystac.errors.STACTypeError) as ctx:
_ = Catalog.from_file(TestCases.get_path(TestCases.bad_catalog_case))
assert "(id = broken_cat) does not represent a STACObject" in ctx.value.args[0]
assert "is Catalog" in ctx.value.args[0]

def test_from_dict_set_root(self) -> None:
path = TestCases.get_path("data-files/catalogs/test-case-1/catalog.json")
with open(path) as f:
Expand Down Expand Up @@ -1281,7 +1287,7 @@ def test_catalog_with_href_caches_by_href(self) -> None:
# cached only by HREF
assert len(cache.id_keys_to_objects) == 0

def testfrom_invalid_dict_raises_exception(self) -> None:
def test_from_invalid_dict_raises_exception(self) -> None:
stac_io = pystac.StacIO.default()
collection_dict = stac_io.read_json(
TestCases.get_path("data-files/collections/multi-extent.json")
Expand Down
2 changes: 2 additions & 0 deletions tests/utils/test_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ def __init__(


class TestCases:
bad_catalog_case = "data-files/catalogs/invalid-catalog/catalog.json"

@staticmethod
def get_path(rel_path: str) -> str:
return os.path.abspath(os.path.join(os.path.dirname(__file__), "..", rel_path))
Expand Down