Skip to content

Commit d640e4c

Browse files
authored
Merge pull request #215 from stac-utils/local_extensions
allow local schemas in stac extensions
2 parents c53a8aa + 1756c6a commit d640e4c

17 files changed

+1399
-60
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ repos:
99
- id: isort
1010
args: ["--profile", "black"]
1111
- repo: https://github.com/psf/black
12-
rev: 20.8b1
12+
rev: 22.3.0
1313
hooks:
1414
- id: black
1515
language_version: python3.8

CHANGELOG.md

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

77
## Unreleased
88

9-
### Changed
9+
### Added
10+
11+
- Added ability to check local schemas in item extensions https://github.com/stac-utils/stac-validator/pull/215
12+
- Added an example on validating a dictionary https://github.com/stac-utils/stac-validator/pull/215
1013

14+
### Changed
15+
1116
- Changed 'ValidationError' error type to 'JSONSchemaValidationError' https://github.com/stac-utils/stac-validator/pull/213
1217

1318
## [v3.1.0] - 2022-04-28

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,15 @@ print(stac.message)
191191
]
192192
```
193193
194+
**Dictionary**
195+
196+
```python
197+
from stac_validator import stac_validator
198+
199+
stac = stac_validator.StacValidate()
200+
stac.validate_dict(dictionary)
201+
print(stac.message)
202+
```
194203
---
195204
196205
# Testing

stac_validator/utilities.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import functools
22
import json
3+
import os
34
from urllib.parse import urlparse
45
from urllib.request import urlopen
56

@@ -14,6 +15,8 @@
1415
"1.0.0",
1516
]
1617

18+
_pathlib = os.path
19+
1720

1821
def is_url(url: str):
1922
try:

stac_validator/validate.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
fetch_and_parse_file,
1414
fetch_and_parse_schema,
1515
get_stac_type,
16+
is_valid_url,
1617
link_request,
1718
set_schema_addr,
1819
)
@@ -97,16 +98,16 @@ def links_validator(self) -> dict:
9798
# get root_url for checking relative links
9899
root_url = ""
99100
for link in self.stac_content["links"]:
100-
if link["rel"] == "self" and link["href"][0:4] == "http":
101+
if link["rel"] == "self" and is_valid_url(link["href"]):
101102
root_url = (
102103
link["href"].split("/")[0] + "//" + link["href"].split("/")[2]
103104
)
104-
elif link["rel"] == "alternate" and link["href"][0:4] == "http":
105+
elif link["rel"] == "alternate" and is_valid_url(link["href"]):
105106
root_url = (
106107
link["href"].split("/")[0] + "//" + link["href"].split("/")[2]
107108
)
108109
for link in self.stac_content["links"]:
109-
if link["href"][0:4] != "http":
110+
if not is_valid_url(link["href"]):
110111
link["href"] = root_url + link["href"][1:]
111112
link_request(link, initial_message)
112113

@@ -125,7 +126,7 @@ def extensions_validator(self, stac_type: str) -> dict:
125126
self.stac_content["stac_extensions"][index] = "projection"
126127
schemas = self.stac_content["stac_extensions"]
127128
for extension in schemas:
128-
if "http" not in extension:
129+
if not (is_valid_url(extension) or extension.endswith(".json")):
129130
# where are the extensions for 1.0.0-beta.2 on cdn.staclint.com?
130131
if self.version == "1.0.0-beta.2":
131132
self.stac_content["stac_version"] = "1.0.0-beta.1"
@@ -153,16 +154,23 @@ def extensions_validator(self, stac_type: str) -> dict:
153154
return message
154155

155156
def custom_validator(self):
156-
# in case the path to custom json schema is local
157-
# it may contain relative references
158-
schema = fetch_and_parse_schema(self.custom)
159-
if os.path.exists(self.custom):
157+
# if schema is hosted online
158+
if is_valid_url(self.custom):
159+
schema = fetch_and_parse_schema(self.custom)
160+
jsonschema.validate(self.stac_content, schema)
161+
# in case the path to a json schema is local
162+
elif os.path.exists(self.custom):
163+
schema = fetch_and_parse_schema(self.custom)
160164
custom_abspath = os.path.abspath(self.custom)
161165
custom_dir = os.path.dirname(custom_abspath).replace("\\", "/")
162166
custom_uri = f"file:///{custom_dir}/"
163167
resolver = RefResolver(custom_uri, self.custom)
164168
jsonschema.validate(self.stac_content, schema, resolver=resolver)
169+
# deal with a relative path in the schema
165170
else:
171+
file_directory = os.path.dirname(os.path.abspath(self.stac_file))
172+
self.custom = os.path.join(file_directory, self.custom)
173+
self.custom = os.path.abspath(os.path.realpath(self.custom))
166174
schema = fetch_and_parse_schema(self.custom)
167175
jsonschema.validate(self.stac_content, schema)
168176

@@ -216,7 +224,7 @@ def recursive_validator(self, stac_type: str) -> bool:
216224
for link in self.stac_content["links"]:
217225
if link["rel"] == "child" or link["rel"] == "item":
218226
address = link["href"]
219-
if "http" not in address:
227+
if not is_valid_url(address):
220228
x = str(base_url).split("/")
221229
x.pop(-1)
222230
st = x[0]

tests/test_assets.py

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,13 @@ def test_assets_v090():
5151

5252

5353
def test_assets_v100():
54-
stac_file = "tests/test_data/v100/core-item.json"
54+
stac_file = "tests/test_data/v100/simple-item.json"
5555
stac = stac_validator.StacValidate(stac_file, assets=True)
5656
stac.run()
5757
assert stac.message == [
5858
{
5959
"version": "1.0.0",
60-
"path": "tests/test_data/v100/core-item.json",
60+
"path": "tests/test_data/v100/simple-item.json",
6161
"schema": [
6262
"https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json"
6363
],
@@ -66,23 +66,14 @@ def test_assets_v100():
6666
"validation_method": "default",
6767
"assets_validated": {
6868
"format_valid": [
69-
"https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic.tif",
70-
"https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.jpg",
71-
"https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.tif",
72-
"https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic_udm.tif",
73-
"http://remotedata.io/catalog/20201211_223832_CS2/extended-metadata.json",
74-
"http://cool-sat.com/catalog/20201211_223832_CS2/20201211_223832_CS2.EPH",
69+
"https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_test.tif",
70+
"https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_test.jpg",
7571
],
7672
"format_invalid": [],
77-
"request_valid": [
78-
"https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic.tif",
79-
"https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.jpg",
80-
"https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.tif",
81-
"https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic_udm.tif",
82-
"http://remotedata.io/catalog/20201211_223832_CS2/extended-metadata.json",
83-
],
73+
"request_valid": [],
8474
"request_invalid": [
85-
"http://cool-sat.com/catalog/20201211_223832_CS2/20201211_223832_CS2.EPH"
75+
"https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_test.tif",
76+
"https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_test.jpg",
8677
],
8778
},
8879
}

tests/test_custom.py

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ def test_custom_item_remote_schema_v1rc2():
100100
]
101101

102102

103-
def test_custom_eo_error_v1rc2():
104-
schema = "https://stac-extensions.github.io/eo/v1.0.0/schema.json"
103+
def test_custom_proj_error_v1rc2():
104+
schema = "https://stac-extensions.github.io/projection/v1.0.0/schema.json"
105105
stac_file = (
106106
"tests/test_data/1rc2/extensions-collection/./proj-example/proj-example.json"
107107
)
@@ -111,11 +111,98 @@ def test_custom_eo_error_v1rc2():
111111
{
112112
"version": "1.0.0-rc.2",
113113
"path": "tests/test_data/1rc2/extensions-collection/./proj-example/proj-example.json",
114-
"schema": ["https://stac-extensions.github.io/eo/v1.0.0/schema.json"],
114+
"schema": [
115+
"https://stac-extensions.github.io/projection/v1.0.0/schema.json"
116+
],
117+
"valid_stac": False,
115118
"asset_type": "ITEM",
116119
"validation_method": "custom",
117-
"valid_stac": False,
118120
"error_type": "JSONSchemaValidationError",
119-
"error_message": "'panchromatic' is not one of ['coastal', 'blue', 'green', 'red', 'rededge', 'yellow', 'pan', 'nir', 'nir08', 'nir09', 'cirrus', 'swir16', 'swir22', 'lwir', 'lwir11', 'lwir12']. Error is in assets -> B8 -> eo:bands -> 0 -> common_name ",
121+
"error_message": "'A' is not of type 'number'. Error is in properties -> proj:centroid -> lat ",
122+
}
123+
]
124+
125+
126+
def test_custom_item_v100_relative_schema():
127+
schema = "../schema/v1.0.0/projection.json"
128+
stac_file = "tests/test_data/v100/extended-item-no-extensions.json"
129+
stac = stac_validator.StacValidate(stac_file, custom=schema)
130+
stac.run()
131+
assert stac.message == [
132+
{
133+
"version": "1.0.0",
134+
"path": "tests/test_data/v100/extended-item-no-extensions.json",
135+
"schema": ["../schema/v1.0.0/projection.json"],
136+
"valid_stac": True,
137+
"asset_type": "ITEM",
138+
"validation_method": "custom",
139+
}
140+
]
141+
142+
143+
def test_custom_item_v100_relative_schema_embedded():
144+
schema = "../../schema/v1.0.0/projection.json"
145+
stac_file = "tests/test_data/v100/embedded/extended-item-no-extensions.json"
146+
stac = stac_validator.StacValidate(stac_file, custom=schema)
147+
stac.run()
148+
assert stac.message == [
149+
{
150+
"version": "1.0.0",
151+
"path": "tests/test_data/v100/embedded/extended-item-no-extensions.json",
152+
"schema": ["../../schema/v1.0.0/projection.json"],
153+
"valid_stac": True,
154+
"asset_type": "ITEM",
155+
"validation_method": "custom",
156+
}
157+
]
158+
159+
160+
def test_custom_item_v100_relative_schema_embedded_same_folder():
161+
schema = "./projection.json"
162+
stac_file = "tests/test_data/v100/embedded/extended-item-no-extensions.json"
163+
stac = stac_validator.StacValidate(stac_file, custom=schema)
164+
stac.run()
165+
assert stac.message == [
166+
{
167+
"version": "1.0.0",
168+
"path": "tests/test_data/v100/embedded/extended-item-no-extensions.json",
169+
"schema": ["./projection.json"],
170+
"valid_stac": True,
171+
"asset_type": "ITEM",
172+
"validation_method": "custom",
173+
}
174+
]
175+
176+
177+
def test_custom_item_v100_relative_schema_embedded_same_folder_2():
178+
schema = "projection.json"
179+
stac_file = "tests/test_data/v100/embedded/extended-item-no-extensions.json"
180+
stac = stac_validator.StacValidate(stac_file, custom=schema)
181+
stac.run()
182+
assert stac.message == [
183+
{
184+
"version": "1.0.0",
185+
"path": "tests/test_data/v100/embedded/extended-item-no-extensions.json",
186+
"schema": ["projection.json"],
187+
"valid_stac": True,
188+
"asset_type": "ITEM",
189+
"validation_method": "custom",
190+
}
191+
]
192+
193+
194+
def test_custom_item_v100_local_schema():
195+
schema = "tests/test_data/schema/v1.0.0/projection.json"
196+
stac_file = "tests/test_data/v100/extended-item-no-extensions.json"
197+
stac = stac_validator.StacValidate(stac_file, custom=schema)
198+
stac.run()
199+
assert stac.message == [
200+
{
201+
"version": "1.0.0",
202+
"path": "tests/test_data/v100/extended-item-no-extensions.json",
203+
"schema": ["tests/test_data/schema/v1.0.0/projection.json"],
204+
"valid_stac": True,
205+
"asset_type": "ITEM",
206+
"validation_method": "custom",
120207
}
121208
]

tests/test_data/1rc2/extensions-collection/proj-example/proj-example.json

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@
163163
3951000
164164
],
165165
"proj:centroid": {
166-
"lat": 34.595302781575604,
166+
"lat": "A",
167167
"lon": -101.34448382627504
168168
},
169169
"proj:shape": [
@@ -246,21 +246,6 @@
246246
"center_wavelength": 0.59,
247247
"full_width_half_max": 0.18
248248
}
249-
],
250-
"proj:shape": [
251-
16781,
252-
16621
253-
],
254-
"proj:transform": [
255-
15,
256-
0,
257-
224992.5,
258-
0,
259-
-15,
260-
6790207.5,
261-
0,
262-
0,
263-
1
264249
]
265250
}
266251
},
@@ -270,9 +255,6 @@
270255
152.52758,
271256
60.63437
272257
],
273-
"stac_extensions": [
274-
"https://stac-extensions.github.io/eo/v1.0.0/schema.json",
275-
"https://stac-extensions.github.io/projection/v1.0.0/schema.json"
276-
],
258+
"stac_extensions": [],
277259
"collection": "landsat-8-l1"
278260
}

0 commit comments

Comments
 (0)