Skip to content

Commit dfc7c21

Browse files
authored
Support stac-check config_file (#687)
1 parent 222931b commit dfc7c21

File tree

5 files changed

+91
-3
lines changed

5 files changed

+91
-3
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
- Support stac-check config_file ([#687](https://github.com/stac-utils/stac-api-validator/pull/687))
11+
1012
## [0.6.6] - 2025-07-29
1113

1214
Dependency updates.

src/stac_api_validator/__main__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@
137137
multiple=True,
138138
help="Headers to attach to the main request and dependent pystac requests, curl syntax",
139139
)
140+
@click.option(
141+
"--stac-check-config",
142+
help="Path to a YAML stac-check configuration file",
143+
)
140144
def main(
141145
log_level: str,
142146
root_url: str,
@@ -162,6 +166,7 @@ def main(
162166
query_in_values: Optional[str] = None,
163167
transaction_collection: Optional[str] = None,
164168
headers: Optional[List[str]] = None,
169+
stac_check_config: Optional[str] = None,
165170
) -> int:
166171
"""STAC API Validator."""
167172
logging.basicConfig(stream=sys.stdout, level=log_level)
@@ -202,6 +207,7 @@ def main(
202207
),
203208
transaction_collection=transaction_collection,
204209
headers=processed_headers,
210+
stac_check_config=stac_check_config,
205211
)
206212
except Exception as e:
207213
click.secho(

src/stac_api_validator/validations.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -375,10 +375,16 @@ def stac_check(
375375
method: Method = Method.GET,
376376
open_assets_urls: bool = True,
377377
headers: Optional[dict] = None,
378+
config_file: Optional[str] = None,
378379
) -> None:
379380
try:
380381
logger.debug(f"stac-check validation: {url}")
381-
linter = Linter(url, assets_open_urls=open_assets_urls, headers=headers or {})
382+
linter = Linter(
383+
url,
384+
config_file=config_file,
385+
assets_open_urls=open_assets_urls,
386+
headers=headers or {},
387+
)
382388
if not linter.valid_stac:
383389
errors += f"[{context}] : {method} {url} is not a valid STAC object: {linter.error_msg}"
384390
if msgs := linter.best_practices_msg[1:]: # first msg is a header, so skip
@@ -560,6 +566,7 @@ def validate_api(
560566
transaction_collection: Optional[str],
561567
headers: Optional[Dict[str, str]],
562568
open_assets_urls: bool = True,
569+
stac_check_config: Optional[str] = None,
563570
) -> Tuple[Warnings, Errors]:
564571
warnings = Warnings()
565572
errors = Errors()
@@ -611,15 +618,27 @@ def validate_api(
611618
if "collections" in ccs_to_validate:
612619
logger.info("Validating STAC API - Collections conformance class.")
613620
validate_collections(
614-
landing_page_body, collection, errors, warnings, r_session, open_assets_urls
621+
landing_page_body,
622+
collection,
623+
errors,
624+
warnings,
625+
r_session,
626+
open_assets_urls,
627+
stac_check_config,
615628
)
616629

617630
conforms_to = landing_page_body.get("conformsTo", [])
618631

619632
if "features" in ccs_to_validate:
620633
logger.info("Validating STAC API - Features conformance class.")
621634
validate_collections(
622-
landing_page_body, collection, errors, warnings, r_session, open_assets_urls
635+
landing_page_body,
636+
collection,
637+
errors,
638+
warnings,
639+
r_session,
640+
open_assets_urls,
641+
stac_check_config,
623642
)
624643
validate_features(
625644
landing_page_body,
@@ -631,6 +650,7 @@ def validate_api(
631650
r_session,
632651
validate_pagination,
633652
open_assets_urls,
653+
stac_check_config,
634654
)
635655

636656
if "transaction" in ccs_to_validate:
@@ -982,6 +1002,7 @@ def validate_collections(
9821002
warnings: Warnings,
9831003
r_session: Session,
9841004
open_assets_urls: bool = True,
1005+
stac_check_config: Optional[str] = None,
9851006
) -> None:
9861007
if not (data_link := link_by_rel(root_body["links"], "data")):
9871008
errors += f"[{Context.COLLECTIONS}] /: Link[rel=data] must href /collections"
@@ -1091,6 +1112,7 @@ def validate_collections(
10911112
Method.GET,
10921113
open_assets_urls,
10931114
r_session.headers,
1115+
stac_check_config,
10941116
)
10951117

10961118
# todo: collection pagination
@@ -1106,6 +1128,7 @@ def validate_features(
11061128
r_session: Session,
11071129
validate_pagination: bool,
11081130
open_assets_urls: bool = True,
1131+
stac_check_config: Optional[str] = None,
11091132
) -> None:
11101133
if not geometry:
11111134
errors += f"[{Context.FEATURES}] Geometry parameter required for running Features validations."
@@ -1216,6 +1239,7 @@ def validate_features(
12161239
Method.GET,
12171240
open_assets_urls,
12181241
r_session.headers,
1242+
stac_check_config,
12191243
)
12201244

12211245
# Validate Features non-existent item
@@ -1326,6 +1350,7 @@ def validate_features(
13261350
Method.GET,
13271351
open_assets_urls,
13281352
r_session.headers,
1353+
stac_check_config,
13291354
)
13301355

13311356
if validate_pagination:
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
linting:
2+
# Identifiers should consist of only lowercase characters, numbers, '_', and '-'
3+
searchable_identifiers: true
4+
# Item name '{self.object_id}' should not contain ':' or '/'
5+
percent_encoded: true
6+
# Item file names should match their ids
7+
item_id_file_name: true
8+
# Collections and catalogs should be named collection.json and catalog.json
9+
catalog_id_file_name: true
10+
# A STAC collection should contain a summaries field
11+
check_summaries: true
12+
# Datetime fields should not be set to null
13+
null_datetime: true
14+
# best practices - check unlocated items to make sure bbox field is not set
15+
check_unlocated: true
16+
# best practices - recommend items have a geometry
17+
check_geometry: true
18+
# check to see if there are too many links
19+
bloated_links: true
20+
# best practices - check for bloated metadata in properties
21+
bloated_metadata: true
22+
# best practices - ensure thumbnail is a small file size ["png", "jpeg", "jpg", "webp"]
23+
check_thumbnail: true
24+
# best practices - ensure that links in catalogs and collections include a title field
25+
links_title: true
26+
# best practices - ensure that links in catalogs and collections include self link
27+
links_self: true
28+
29+
# Geometry validation settings [BETA]
30+
geometry_validation:
31+
# Master switch to enable/disable all geometry validation checks
32+
enabled: true
33+
# check if geometry coordinates are potentially ordered incorrectly (longitude, latitude)
34+
geometry_coordinates_order: true
35+
# check if geometry coordinates contain definite errors (latitude > ±90°, longitude > ±180°)
36+
geometry_coordinates_definite_errors: true
37+
# check if bbox matches the bounds of the geometry
38+
bbox_geometry_match: true
39+
# check if a bbox that crosses the antimeridian is correctly formatted
40+
bbox_antimeridian: true
41+
42+
settings:
43+
# number of links before the bloated links warning is shown
44+
max_links: 20
45+
# number of properties before the bloated metadata warning is shown
46+
max_properties: 20

tests/test_validations.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ def sample_item() -> Generator[pystac.Item, None, None]:
4444
yield pystac.Item.from_dict(data)
4545

4646

47+
@pytest.fixture
48+
def stac_check_config() -> Generator[str, None, None]:
49+
current_path = pathlib.Path(os.path.dirname(os.path.abspath(__file__)))
50+
51+
yield current_path / "resources" / "stac-check-config.yaml"
52+
53+
4754
@pytest.fixture
4855
def expected_headers(requests_version: str) -> Generator[Dict[str, str], None, None]:
4956
yield {
@@ -91,6 +98,7 @@ def test_validate_api(
9198
request: pytest.FixtureRequest,
9299
r_session: requests.Session,
93100
expected_headers: Dict[str, str],
101+
stac_check_config: str,
94102
) -> None:
95103
if request.config.getoption("typeguard_packages"):
96104
pytest.skip(
@@ -114,6 +122,7 @@ def test_validate_api(
114122
query_config=None,
115123
transaction_collection=None,
116124
headers=headers,
125+
stac_check_config=stac_check_config,
117126
)
118127
assert retrieve_mock.call_count == 1
119128
r_session = retrieve_mock.call_args.args[-1]

0 commit comments

Comments
 (0)