Skip to content

Commit 8be1813

Browse files
committed
Move file-level error reporting for duplicate keys to read_json
1 parent d8e4bde commit 8be1813

File tree

2 files changed

+71
-30
lines changed

2 files changed

+71
-30
lines changed

pystac/stac_io.py

Lines changed: 42 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import json
44
from typing import (
55
Any,
6-
Callable,
76
Dict,
87
List,
98
Optional,
@@ -18,7 +17,7 @@
1817
from urllib.error import HTTPError
1918

2019
import pystac
21-
from pystac.utils import safe_urlparse, get_opt
20+
from pystac.utils import safe_urlparse
2221
from pystac.serialization import (
2322
merge_common_properties,
2423
identify_stac_object_type,
@@ -105,8 +104,7 @@ def json_dumps(self, json_dict: Dict[str, Any], *args: Any, **kwargs: Any) -> st
105104
This method may be overwritten in :class:`StacIO` sub-classes to provide custom
106105
serialization logic. The method accepts arbitrary keyword arguments. These are
107106
not used by the default implementation, but may be used by sub-class
108-
implementations (see :meth:`DuplicateKeyReportingMixin.json_dumps` as an
109-
example).
107+
implementations.
110108
111109
Args:
112110
@@ -300,36 +298,51 @@ class DuplicateKeyReportingMixin(StacIO):
300298
See https://github.com/stac-utils/pystac/issues/313
301299
"""
302300

303-
def json_loads(self, txt: str, *args: Any, **kwargs: Any) -> Dict[str, Any]:
304-
source: Union[str, "Link_Type"] = get_opt(kwargs.get("source"))
301+
def json_loads(self, txt: str, *_: Any, **__: Any) -> Dict[str, Any]:
302+
"""Overwrites :meth:`StacIO.json_loads` as the internal method used by
303+
:class:`DuplicateKeyReportingMixin` for deserializing a JSON string to a
304+
dictionary while checking for duplicate object keys.
305+
306+
Raises:
307+
308+
pystac.DuplicateObjectKeyError : If a duplicate object key is found.
309+
"""
305310
result: Dict[str, Any] = json.loads(
306-
txt, object_pairs_hook=self.duplicate_object_names_report_builder(source)
311+
txt, object_pairs_hook=self._report_duplicate_object_names
307312
)
308313
return result
309314

310-
@staticmethod
311-
def duplicate_object_names_report_builder(
312-
source: Union[str, "Link_Type"]
313-
) -> Callable[[List[Tuple[str, Any]]], Dict[str, Any]]:
314-
def report_duplicate_object_names(
315-
object_pairs: List[Tuple[str, Any]]
316-
) -> Dict[str, Any]:
317-
result: Dict[str, Any] = {}
318-
for key, value in object_pairs:
319-
if key in result:
320-
url = (
321-
source
322-
if isinstance(source, str)
323-
else source.get_absolute_href()
324-
)
325-
raise DuplicateObjectKeyError(
326-
f"Found duplicate object name “{key}” in “{url}”"
327-
)
328-
else:
329-
result[key] = value
330-
return result
315+
def read_json(
316+
self, source: Union[str, "Link_Type"], *args: Any, **kwargs: Any
317+
) -> Dict[str, Any]:
318+
"""Overwrites :meth:`StacIO.read_json` for deserializing a JSON file to a
319+
dictionary while checking for duplicate object keys.
320+
321+
Raises:
322+
323+
pystac.DuplicateObjectKeyError : If a duplicate object key is found.
324+
"""
325+
txt = self.read_text(source, *args, **kwargs)
326+
try:
327+
return self.json_loads(txt, source=source)
328+
except pystac.DuplicateObjectKeyError as e:
329+
url = source if isinstance(source, str) else source.get_absolute_href()
330+
msg = str(e) + f" in {url}"
331+
raise pystac.DuplicateObjectKeyError(msg)
331332

332-
return report_duplicate_object_names
333+
@staticmethod
334+
def _report_duplicate_object_names(
335+
object_pairs: List[Tuple[str, Any]]
336+
) -> Dict[str, Any]:
337+
result: Dict[str, Any] = {}
338+
for key, value in object_pairs:
339+
if key in result:
340+
raise pystac.DuplicateObjectKeyError(
341+
f'Found duplicate object name "{key}"'
342+
)
343+
else:
344+
result[key] = value
345+
return result
333346

334347

335348
class STAC_IO:

tests/test_stac_io.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import tempfile
55

66
import pystac
7-
from pystac.stac_io import STAC_IO, StacIO
7+
from pystac.stac_io import STAC_IO, StacIO, DefaultStacIO, DuplicateKeyReportingMixin
88
from tests.utils import TestCases
99

1010

@@ -117,3 +117,31 @@ def test_read_from_stac_object(self) -> None:
117117
TestCases.get_path("data-files/catalogs/test-case-1/catalog.json")
118118
)
119119
self.assertIsInstance(catalog, pystac.Catalog)
120+
121+
def test_report_duplicate_keys(self) -> None:
122+
# Directly from dict
123+
class ReportingStacIO(DefaultStacIO, DuplicateKeyReportingMixin):
124+
pass
125+
126+
stac_io = ReportingStacIO()
127+
test_json = """{
128+
"key": "value_1",
129+
"key": "value_2"
130+
}"""
131+
132+
with self.assertRaises(pystac.DuplicateObjectKeyError) as excinfo:
133+
stac_io.json_loads(test_json)
134+
self.assertEqual(str(excinfo.exception), 'Found duplicate object name "key"')
135+
136+
# From file
137+
with tempfile.TemporaryDirectory() as tmp_dir:
138+
src_href = os.path.join(tmp_dir, "test.json")
139+
with open(src_href, "w") as dst:
140+
dst.write(test_json)
141+
142+
with self.assertRaises(pystac.DuplicateObjectKeyError) as excinfo:
143+
stac_io.read_json(src_href)
144+
self.assertEqual(
145+
str(excinfo.exception),
146+
f'Found duplicate object name "key" in {src_href}',
147+
)

0 commit comments

Comments
 (0)