Skip to content

Commit e4330bc

Browse files
authored
add custom validator as input (#1320)
* add custom validator as input * update changelog * update validator arg in docstrings * add unittest for new custom validator argument * fix linting issues
1 parent 56c614c commit e4330bc

File tree

4 files changed

+90
-9
lines changed

4 files changed

+90
-9
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
### Changed
66

7+
- Add `validator` input to `STACObject.validate` for inline reference of the validator to use
8+
([#1320](https://github.com/stac-utils/pystac/pull/1320))
79
- Made item pickles smaller by changing how nested links are stored([#1285](https://github.com/stac-utils/pystac/pull/1285))
810
- Add APILayoutStrategy ([#1294](https://github.com/stac-utils/pystac/pull/1294))
911
- Allow setting a default layout strategy for Catalog ([#1295](https://github.com/stac-utils/pystac/pull/1295))

pystac/stac_object.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,27 @@ def __init__(self, stac_extensions: list[str]) -> None:
6161
self.links = []
6262
self.stac_extensions = stac_extensions
6363

64-
def validate(self) -> list[Any]:
64+
def validate(
65+
self,
66+
validator: pystac.validation.stac_validator.STACValidator | None = None,
67+
) -> list[Any]:
6568
"""Validate this STACObject.
6669
6770
Returns a list of validation results, which depends on the validation
68-
implementation. For JSON Schema validation, this will be a list
69-
of schema URIs that were used during validation.
71+
implementation. For JSON Schema validation (default validator), this
72+
will be a list of schema URIs that were used during validation.
7073
74+
Args:
75+
validator : A custom validator to use for validation of the object.
76+
If omitted, the default validator from
77+
:class:`~pystac.validation.RegisteredValidator`
78+
will be used instead.
7179
Raises:
7280
STACValidationError
7381
"""
7482
import pystac.validation
7583

76-
return pystac.validation.validate(self)
84+
return pystac.validation.validate(self, validator=validator)
7785

7886
def add_link(self, link: Link) -> None:
7987
"""Add a link to this object's set of links.

pystac/validation/__init__.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,18 @@
1919
from pystac.validation.stac_validator import JsonSchemaSTACValidator, STACValidator
2020

2121

22-
def validate(stac_object: STACObject) -> list[Any]:
22+
def validate(
23+
stac_object: STACObject,
24+
validator: STACValidator | None = None,
25+
) -> list[Any]:
2326
"""Validates a :class:`~pystac.STACObject`.
2427
2528
Args:
2629
stac_object : The stac object to validate.
30+
validator : A custom validator to use for validation of the STAC object.
31+
If omitted, the default validator from
32+
:class:`~pystac.validation.RegisteredValidator`
33+
will be used instead.
2734
2835
Returns:
2936
List[Object]: List of return values from the validation calls for the
@@ -39,6 +46,7 @@ def validate(stac_object: STACObject) -> list[Any]:
3946
stac_version=pystac.get_stac_version(),
4047
extensions=stac_object.stac_extensions,
4148
href=stac_object.get_self_href(),
49+
validator=validator,
4250
)
4351

4452

@@ -48,6 +56,7 @@ def validate_dict(
4856
stac_version: str | None = None,
4957
extensions: list[str] | None = None,
5058
href: str | None = None,
59+
validator: STACValidator | None = None,
5160
) -> list[Any]:
5261
"""Validate a stac object serialized as JSON into a dict.
5362
@@ -67,6 +76,10 @@ def validate_dict(
6776
extensions : Extension IDs for this stac object. If not supplied,
6877
PySTAC's identification logic to identify the extensions.
6978
href : Optional HREF of the STAC object being validated.
79+
validator : A custom validator to use for validation of the STAC dictionary.
80+
If omitted, the default validator from
81+
:class:`~pystac.validation.RegisteredValidator`
82+
will be used instead.
7083
7184
Returns:
7285
List[Object]: List of return values from the validation calls for the
@@ -104,7 +117,8 @@ def _get_uri(ext: str) -> str | None:
104117

105118
extensions = [uri for uri in map(_get_uri, extensions) if uri is not None]
106119

107-
return RegisteredValidator.get_validator().validate(
120+
validator = validator or RegisteredValidator.get_validator()
121+
return validator.validate(
108122
stac_dict, stac_object_type, stac_version, extensions, href
109123
)
110124

@@ -248,4 +262,8 @@ def set_validator(validator: STACValidator) -> None:
248262
RegisteredValidator.set_validator(validator)
249263

250264

251-
__all__ = ["GetSchemaError"]
265+
__all__ = [
266+
"GetSchemaError",
267+
"JsonSchemaSTACValidator",
268+
"RegisteredValidator",
269+
]

tests/validation/test_validate.py

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import shutil
44
import tempfile
55
from datetime import datetime, timezone
6-
from typing import Any
6+
from typing import Any, cast
77

88
import jsonschema
99
import pytest
@@ -14,7 +14,7 @@
1414
from pystac.cache import CollectionCache
1515
from pystac.serialization.common_properties import merge_common_properties
1616
from pystac.utils import get_opt
17-
from pystac.validation import GetSchemaError
17+
from pystac.validation import GetSchemaError, JsonSchemaSTACValidator
1818
from tests.utils import TestCases
1919
from tests.utils.test_cases import ExampleInfo
2020

@@ -183,6 +183,59 @@ def test_validates_geojson_with_tuple_coordinates(self) -> None:
183183
# Should not raise.
184184
item.validate()
185185

186+
@pytest.mark.vcr()
187+
def test_validate_custom_validator(self) -> None:
188+
"""This test verifies the use of a custom validator class passed as
189+
input to :meth:`~pystac.stac_object.STACObject.validate` and every
190+
underlying function. This validator is effective only for the call
191+
for which it was provided, contrary to
192+
:class:`~pystac.validation.RegisteredValidator`
193+
that persists it globally until reset.
194+
"""
195+
custom_extension_uri = (
196+
"https://stac-extensions.github.io/custom-extension/v1.0.0/schema.json"
197+
)
198+
custom_extension_schema = {
199+
"$schema": "http://json-schema.org/draft-07/schema#",
200+
"$id": f"{custom_extension_uri}#",
201+
"type": "object",
202+
"properties": {
203+
"properties": {
204+
"type": "object",
205+
"required": ["custom-extension:test"],
206+
"properties": {"custom-extension:test": {"type": "integer"}},
207+
}
208+
},
209+
}
210+
item = cast(
211+
pystac.Item,
212+
pystac.read_file(TestCases.get_path("data-files/item/sample-item.json")),
213+
)
214+
item.stac_extensions.append(custom_extension_uri)
215+
item.properties["custom-extension:test"] = 123
216+
217+
with pytest.raises(pystac.validation.GetSchemaError):
218+
item.validate() # default validator does not know the extension
219+
220+
class CustomValidator(JsonSchemaSTACValidator):
221+
def _get_schema(self, schema_uri: str) -> dict[str, Any]:
222+
if schema_uri == custom_extension_uri:
223+
return custom_extension_schema
224+
return super()._get_schema(schema_uri)
225+
226+
# validate that the custom schema is found with the custom validator
227+
custom_validator = CustomValidator()
228+
item.validate(validator=custom_validator)
229+
230+
# make sure validation is effective
231+
item.properties["custom-extension:test"] = "bad-value"
232+
with pytest.raises(pystac.errors.STACValidationError):
233+
item.validate(validator=custom_validator)
234+
235+
# verify that the custom validator is not persisted
236+
with pytest.raises(pystac.validation.GetSchemaError):
237+
item.validate()
238+
186239

187240
@pytest.mark.block_network
188241
def test_catalog_latest_version_uses_local(catalog: pystac.Catalog) -> None:

0 commit comments

Comments
 (0)