Skip to content

Commit b48681e

Browse files
authored
Merge pull request #138 from stac-utils/validation-summary
- Results summary for options that produce numerous results, ie. --collections, --item-collection, --recursive - Support for --verbose flag to show verbose results summary - Added --output/-o option to save validation results to a file - Tests for CLI options
2 parents 20e7d6a + 6d480de commit b48681e

File tree

8 files changed

+502
-62
lines changed

8 files changed

+502
-62
lines changed

CHANGELOG.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ The format is (loosely) based on [Keep a Changelog](http://keepachangelog.com/)
66

77
## Unreleased
88

9+
## [v1.11.0] - 2025-06-22
10+
11+
### Added
12+
13+
- Results summary for options that produce numerous results, ie. --collections, --item-collection, --recursive ([#138](https://github.com/stac-utils/stac-check/pull/138))
14+
- Support for --verbose flag to show verbose results summary ([#138](https://github.com/stac-utils/stac-check/pull/138))
15+
- Added `--output`/`-o` option to save validation results to a file ([#138](https://github.com/stac-utils/stac-check/pull/138))
16+
- Tests for CLI options ([#138](https://github.com/stac-utils/stac-check/pull/138))
17+
918
## [v1.10.1] - 2025-06-21
1019

1120
### Fixed
@@ -285,7 +294,8 @@ The format is (loosely) based on [Keep a Changelog](http://keepachangelog.com/)
285294
- Validation from stac-validator 2.3.0
286295
- Links and assets validation checks
287296

288-
[Unreleased]: https://github.com/stac-utils/stac-check/compare/v1.10.1...main
297+
[Unreleased]: https://github.com/stac-utils/stac-check/compare/v1.11.0...main
298+
[v1.11.0]: https://github.com/stac-utils/stac-check/compare/v1.10.1...v1.11.0
289299
[v1.10.1]: https://github.com/stac-utils/stac-check/compare/v1.10.0...v1.10.1
290300
[v1.10.0]: https://github.com/stac-utils/stac-check/compare/v1.9.1...v1.10.0
291301
[v1.9.1]: https://github.com/stac-utils/stac-check/compare/v1.9.0...v1.9.1

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ Options:
111111
multiple times.
112112
--pydantic Use stac-pydantic for enhanced validation with Pydantic models.
113113
--verbose Show verbose error messages.
114+
-o, --output FILE Save output to the specified file.
114115
--item-collection Validate item collection response. Can be combined with
115116
--pages. Defaults to one page.
116117
--collections Validate collections endpoint response. Can be combined with

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from setuptools import find_packages, setup
55

6-
__version__ = "1.10.1"
6+
__version__ = "1.11.0"
77

88
with open("README.md", "r") as fh:
99
long_description = fh.read()

stac_check/api_lint.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,13 @@ def __init__(
3737
object_list_key: str,
3838
pages: Optional[int] = 1,
3939
headers: Optional[Dict] = None,
40+
verbose: bool = False,
4041
):
4142
self.source = source
4243
self.object_list_key = object_list_key
4344
self.pages = pages if pages is not None else 1
4445
self.headers = headers or {}
46+
self.verbose = verbose
4547
self.version = None
4648
self.validator_version = self._get_validator_version()
4749

@@ -148,7 +150,7 @@ def lint_all(self) -> List[Dict]:
148150
results_by_url = {}
149151
for obj, obj_url in self.iterate_objects():
150152
try:
151-
linter = Linter(obj)
153+
linter = Linter(obj, verbose=self.verbose)
152154
msg = dict(linter.message)
153155
msg["path"] = obj_url
154156
msg["best_practices"] = linter.best_practices_msg

stac_check/cli.py

Lines changed: 106 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import importlib.metadata
22
import sys
3+
from typing import Optional
34

45
import click
56

@@ -12,6 +13,7 @@
1213
recursive_message,
1314
)
1415
from stac_check.lint import Linter
16+
from stac_check.utilities import handle_output
1517

1618

1719
@click.option(
@@ -48,6 +50,12 @@
4850
@click.option(
4951
"-l", "--links", is_flag=True, help="Validate links for format and response."
5052
)
53+
@click.option(
54+
"--output",
55+
"-o",
56+
type=click.Path(dir_okay=False, writable=True),
57+
help="Save output to the specified file. Only works with --collections, --item-collection, or --recursive.",
58+
)
5159
@click.option(
5260
"--no-assets-urls",
5361
is_flag=True,
@@ -74,19 +82,44 @@
7482
@click.argument("file")
7583
@click.version_option(version=importlib.metadata.distribution("stac-check").version)
7684
def main(
77-
file,
78-
collections,
79-
item_collection,
80-
pages,
81-
recursive,
82-
max_depth,
83-
assets,
84-
links,
85-
no_assets_urls,
86-
header,
87-
pydantic,
88-
verbose,
89-
):
85+
file: str,
86+
collections: bool,
87+
item_collection: bool,
88+
pages: Optional[int],
89+
recursive: bool,
90+
max_depth: Optional[int],
91+
assets: bool,
92+
links: bool,
93+
no_assets_urls: bool,
94+
header: tuple[tuple[str, str], ...],
95+
pydantic: bool,
96+
verbose: bool,
97+
output: Optional[str],
98+
) -> None:
99+
"""Main entry point for the stac-check CLI.
100+
101+
Args:
102+
file: The STAC file or URL to validate
103+
collections: Validate a collections endpoint
104+
item_collection: Validate an item collection
105+
pages: Number of pages to validate (for API endpoints)
106+
recursive: Recursively validate linked STAC objects
107+
max_depth: Maximum depth for recursive validation
108+
assets: Validate assets
109+
links: Validate links
110+
no_assets_urls: Disable URL validation for assets
111+
header: Additional HTTP headers
112+
pydantic: Use stac-pydantic for validation
113+
verbose: Show verbose output
114+
output: Save output to file (only with --collections, --item-collection, or --recursive)
115+
"""
116+
# Check if output is used without --collections, --item-collection, or --recursive
117+
if output and not any([collections, item_collection, recursive]):
118+
click.echo(
119+
"Error: --output can only be used with --collections, --item-collection, or --recursive",
120+
err=True,
121+
)
122+
sys.exit(1)
90123
# Check if pydantic validation is requested but not installed
91124
if pydantic:
92125
try:
@@ -99,8 +132,52 @@ def main(
99132
)
100133
pydantic = False
101134

102-
if not collections and not item_collection:
103-
# Create a standard Linter for single file or recursive validation
135+
if collections or item_collection:
136+
# Handle API-based validation (collections or item collections)
137+
api_linter = ApiLinter(
138+
source=file,
139+
object_list_key="collections" if collections else "features",
140+
pages=pages if pages else 1,
141+
headers=dict(header),
142+
verbose=verbose,
143+
)
144+
results = api_linter.lint_all()
145+
146+
# Create a dummy Linter instance for display purposes
147+
display_linter = Linter(
148+
file,
149+
assets=assets,
150+
links=links,
151+
headers=dict(header),
152+
pydantic=pydantic,
153+
verbose=verbose,
154+
)
155+
156+
# Show intro message in the terminal
157+
intro_message(display_linter)
158+
159+
# Define output generation function (without intro message since we already showed it)
160+
def generate_output():
161+
if collections:
162+
collections_message(
163+
api_linter,
164+
results=results,
165+
cli_message_func=cli_message,
166+
verbose=verbose,
167+
)
168+
elif item_collection:
169+
item_collection_message(
170+
api_linter,
171+
results=results,
172+
cli_message_func=cli_message,
173+
verbose=verbose,
174+
)
175+
176+
# Handle output (without duplicating the intro message)
177+
handle_output(output, generate_output)
178+
sys.exit(0 if all(msg.get("valid_stac") is True for msg in results) else 1)
179+
else:
180+
# Handle file-based validation (single file or recursive)
104181
linter = Linter(
105182
file,
106183
assets=assets,
@@ -112,34 +189,20 @@ def main(
112189
pydantic=pydantic,
113190
verbose=verbose,
114191
)
115-
intro_message(linter)
116-
# If recursive validation is enabled, use recursive_message
117-
if recursive:
118-
# Pass the cli_message function to avoid circular imports
119-
recursive_message(linter, cli_message_func=cli_message)
120-
else:
121-
# Otherwise, just display the standard CLI message
122-
cli_message(linter)
123192

124-
sys.exit(0 if linter.valid_stac else 1)
125-
else:
126-
if item_collection:
127-
object_list_key = "features"
128-
elif collections:
129-
object_list_key = "collections"
193+
intro_message(linter)
130194

131-
linter = ApiLinter(
132-
source=file,
133-
object_list_key=object_list_key,
134-
pages=pages,
135-
headers=dict(header),
136-
)
137-
results = linter.lint_all()
195+
# Show intro message in the terminal
138196
intro_message(linter)
139-
if collections:
140-
collections_message(linter, results=results, cli_message_func=cli_message)
141-
elif item_collection:
142-
item_collection_message(
143-
linter, results=results, cli_message_func=cli_message
144-
)
145-
sys.exit(0 if all(msg.get("valid_stac") is True for msg in results) else 1)
197+
198+
# Define output generation function (without intro message since we already showed it)
199+
def generate_output():
200+
if recursive:
201+
recursive_message(linter, cli_message_func=cli_message, verbose=verbose)
202+
else:
203+
cli_message(linter)
204+
205+
# Handle output (without duplicating the intro message)
206+
handle_output(output if recursive else None, generate_output)
207+
208+
sys.exit(0 if linter.valid_stac else 1)

0 commit comments

Comments
 (0)