Skip to content

Commit 41afba7

Browse files
authored
Merge pull request #232 from stac-utils/collections_validation
Validate /collections endpoint
2 parents 5ff4944 + f3510be commit 41afba7

File tree

7 files changed

+126
-11
lines changed

7 files changed

+126
-11
lines changed

.pre-commit-config.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
repos:
22
- repo: https://github.com/PyCQA/flake8
3-
rev: 6.1.0
3+
rev: 7.0.0
44
hooks:
55
- id: flake8
66
- repo: https://github.com/timothycrosley/isort
7-
rev: 5.12.0
7+
rev: 5.13.2
88
hooks:
99
- id: isort
1010
args: ["--profile", "black"]
1111
- repo: https://github.com/psf/black
12-
rev: 23.11.0
12+
rev: 24.1.1
1313
hooks:
1414
- id: black
1515
language_version: python3.8
1616
- repo: https://github.com/pre-commit/mirrors-mypy
17-
rev: v1.7.0
17+
rev: v1.8.0
1818
hooks:
1919
- id: mypy
2020
exclude: /tests/

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ The format is (loosely) based on [Keep a Changelog](http://keepachangelog.com/)
88

99
### Added
1010

11+
- Added ability to validate response from a /collections endpoint [#220](https://github.com/stac-utils/stac-validator/issues/220)
1112
- Added mypy to pre-commit config ([#229](https://github.com/stac-utils/stac-validator/pull/224))
1213

1314
## [v3.3.2] - 2023-11-17

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ Options:
106106
-m, --max-depth INTEGER Maximum depth to traverse when recursing. Omit this
107107
argument to get full recursion. Ignored if
108108
`recursive == False`.
109+
--collections Validate /collections response.
109110
--item-collection Validate item collection response. Can be combined
110111
with --pages. Defaults to one page.
111112
-p, --pages INTEGER Maximum number of pages to validate via --item-

stac_validator/stac_validator.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,25 @@ def item_collection_summary(message: List[Dict[str, Any]]) -> None:
4646
click.secho(f"valid_items: {valid_count}")
4747

4848

49+
def collections_summary(message: List[Dict[str, Any]]) -> None:
50+
"""Prints a summary of the validation results for an item collection response.
51+
52+
Args:
53+
message (List[Dict[str, Any]]): The validation results for the item collection.
54+
55+
Returns:
56+
None
57+
"""
58+
valid_count = 0
59+
for collection in message:
60+
if "valid_stac" in collection and collection["valid_stac"] is True:
61+
valid_count = valid_count + 1
62+
click.secho()
63+
click.secho("--collections summary", bold=True)
64+
click.secho(f"collections_validated: {len(message)}")
65+
click.secho(f"valid_collections: {valid_count}")
66+
67+
4968
@click.command()
5069
@click.argument("stac_file")
5170
@click.option(
@@ -80,6 +99,11 @@ def item_collection_summary(message: List[Dict[str, Any]]) -> None:
8099
type=int,
81100
help="Maximum depth to traverse when recursing. Omit this argument to get full recursion. Ignored if `recursive == False`.",
82101
)
102+
@click.option(
103+
"--collections",
104+
is_flag=True,
105+
help="Validate /collections response.",
106+
)
83107
@click.option(
84108
"--item-collection",
85109
is_flag=True,
@@ -102,6 +126,7 @@ def item_collection_summary(message: List[Dict[str, Any]]) -> None:
102126
)
103127
def main(
104128
stac_file: str,
129+
collections: bool,
105130
item_collection: bool,
106131
pages: int,
107132
recursive: bool,
@@ -120,6 +145,7 @@ def main(
120145
121146
Args:
122147
stac_file (str): Path to the STAC file to be validated.
148+
collections (bool): Validate response from /collections endpoint.
123149
item_collection (bool): Whether to validate item collection responses.
124150
pages (int): Maximum number of pages to validate via `item_collection`.
125151
recursive (bool): Whether to recursively validate all related STAC objects.
@@ -143,6 +169,7 @@ def main(
143169
valid = True
144170
stac = StacValidate(
145171
stac_file=stac_file,
172+
collections=collections,
146173
item_collection=item_collection,
147174
pages=pages,
148175
recursive=recursive,
@@ -155,8 +182,10 @@ def main(
155182
verbose=verbose,
156183
log=log_file,
157184
)
158-
if not item_collection:
185+
if not item_collection and not collections:
159186
valid = stac.run()
187+
elif collections:
188+
stac.validate_collections()
160189
else:
161190
stac.validate_item_collection()
162191

@@ -169,6 +198,8 @@ def main(
169198

170199
if item_collection:
171200
item_collection_summary(message)
201+
elif collections:
202+
collections_summary(message)
172203

173204
sys.exit(0 if valid else 1)
174205

stac_validator/validate.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class StacValidate:
2525
2626
Attributes:
2727
stac_file (str): The path or URL to the STAC object to be validated.
28+
collections (bool): Validate response from a /collections endpoint.
2829
item_collection (bool): Whether the STAC object to be validated is an item collection.
2930
pages (int): The maximum number of pages to validate if `item_collection` is True.
3031
recursive (bool): Whether to recursively validate related STAC objects.
@@ -45,6 +46,7 @@ class StacValidate:
4546
def __init__(
4647
self,
4748
stac_file: Optional[str] = None,
49+
collections: bool = False,
4850
item_collection: bool = False,
4951
pages: Optional[int] = None,
5052
recursive: bool = False,
@@ -58,6 +60,7 @@ def __init__(
5860
log: str = "",
5961
):
6062
self.stac_file = stac_file
63+
self.collections = collections
6164
self.item_collection = item_collection
6265
self.pages = pages
6366
self.message: List = []
@@ -392,6 +395,27 @@ def validate_item_collection_dict(self, item_collection: Dict) -> None:
392395
self.schema = ""
393396
self.validate_dict(item)
394397

398+
def validate_collections(self) -> None:
399+
""" "Validate STAC collections from a /collections endpoint.
400+
401+
Raises:
402+
URLError: If there is an issue with the URL used to fetch the item collection.
403+
JSONDecodeError: If the item collection content cannot be parsed as JSON.
404+
ValueError: If the item collection does not conform to the STAC specification.
405+
TypeError: If the item collection content is not a dictionary or JSON object.
406+
FileNotFoundError: If the item collection file cannot be found.
407+
ConnectionError: If there is an issue with the internet connection used to fetch the item collection.
408+
exceptions.SSLError: If there is an issue with the SSL connection used to fetch the item collection.
409+
OSError: If there is an issue with the file system (e.g., read/write permissions) while trying to write to the log file.
410+
411+
Returns:
412+
None
413+
"""
414+
collections = fetch_and_parse_file(str(self.stac_file))
415+
for collection in collections["collections"]:
416+
self.schema = ""
417+
self.validate_dict(collection)
418+
395419
def validate_item_collection(self) -> None:
396420
"""Validate a STAC item collection.
397421
@@ -429,9 +453,9 @@ def validate_item_collection(self) -> None:
429453
break
430454
except Exception as e:
431455
message = {}
432-
message[
433-
"pagination_error"
434-
] = f"Validating the item collection failed on page {page}: {str(e)}"
456+
message["pagination_error"] = (
457+
f"Validating the item collection failed on page {page}: {str(e)}"
458+
)
435459
self.message.append(message)
436460

437461
def run(self) -> bool:
@@ -457,7 +481,11 @@ def run(self) -> bool:
457481
"""
458482
message = {}
459483
try:
460-
if self.stac_file is not None and not self.item_collection:
484+
if (
485+
self.stac_file is not None
486+
and not self.item_collection
487+
and not self.collections
488+
):
461489
self.stac_content = fetch_and_parse_file(self.stac_file)
462490

463491
stac_type = get_stac_type(self.stac_content).upper()

tests/test_validate_collections.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""
2+
Description: Test stac-validator on --collections (/collections validation).
3+
4+
"""
5+
6+
from stac_validator import stac_validator
7+
8+
9+
def test_validate_collections_remote():
10+
stac_file = "https://earth-search.aws.element84.com/v0/collections"
11+
stac = stac_validator.StacValidate(stac_file, collections=True)
12+
stac.validate_collections()
13+
14+
assert stac.message == [
15+
{
16+
"version": "1.0.0-beta.2",
17+
"path": "https://earth-search.aws.element84.com/v0/collections",
18+
"schema": [
19+
"https://schemas.stacspec.org/v1.0.0-beta.2/collection-spec/json-schema/collection.json"
20+
],
21+
"valid_stac": True,
22+
"asset_type": "COLLECTION",
23+
"validation_method": "default",
24+
},
25+
{
26+
"version": "1.0.0-beta.2",
27+
"path": "https://earth-search.aws.element84.com/v0/collections",
28+
"schema": [
29+
"https://schemas.stacspec.org/v1.0.0-beta.2/collection-spec/json-schema/collection.json"
30+
],
31+
"valid_stac": True,
32+
"asset_type": "COLLECTION",
33+
"validation_method": "default",
34+
},
35+
{
36+
"version": "1.0.0-beta.2",
37+
"path": "https://earth-search.aws.element84.com/v0/collections",
38+
"schema": [
39+
"https://schemas.stacspec.org/v1.0.0-beta.2/collection-spec/json-schema/collection.json"
40+
],
41+
"valid_stac": True,
42+
"asset_type": "COLLECTION",
43+
"validation_method": "default",
44+
},
45+
{
46+
"version": "1.0.0-beta.2",
47+
"path": "https://earth-search.aws.element84.com/v0/collections",
48+
"schema": [
49+
"https://schemas.stacspec.org/v1.0.0-beta.2/collection-spec/json-schema/collection.json"
50+
],
51+
"valid_stac": True,
52+
"asset_type": "COLLECTION",
53+
"validation_method": "default",
54+
},
55+
]

tests/test_validate_item_collection.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
"""
2-
Description: Test the validator
2+
Description: Test stac-validator on item-collection validation.
33
44
"""
55

6-
76
from stac_validator import stac_validator
87

98

0 commit comments

Comments
 (0)