Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jsonschema 4.18+ is now required #142

Merged
merged 1 commit into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Removed

- Dropped support for Python 3.6 & 3.7, as these are now end of life.
- Drop jsonschema 3 support
- Jsonschema 4.18+ is now required. Support for 3 and older versions of 4 is removed.

### Changed

- Restore jsonschema's type validator, as its performance has improved in recent Python versions https://github.com/OpenDataServices/lib-cove/pull/127
- Allow `SchemaJsonMixin` classes to define a `validator` method, that accepts lib-cove's JSON Schema draft 4 validator class and its format checker, and returns a validator instance. https://github.com/OpenDataServices/lib-cove/pull/128
- If the above `validator` option is not being used, allow `SchemaJsonMixin` classes to define a `registry` value. Should be a referencing.Registry instance. https://github.com/OpenDataServices/lib-cove/pull/123

### Fixed

Expand Down
61 changes: 28 additions & 33 deletions libcove/lib/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@
import jsonref
import jsonschema.validators
import requests
from referencing import Registry, Resource

try:
from functools import cached_property
except ImportError:
from cached_property import cached_property

from flattentool import unflatten
from jsonschema import FormatChecker, RefResolver
from jsonschema import FormatChecker
from jsonschema._utils import extras_msg, find_additional_properties, uniq
from jsonschema.exceptions import UndefinedTypeCheck, ValidationError

Expand Down Expand Up @@ -822,25 +823,30 @@ def get_schema_validation_errors(
if hasattr(schema_obj, "validator"):
our_validator = schema_obj.validator(validator, format_checker)
else:
if getattr(schema_obj, "extended", None):
resolver = CustomRefResolver(
"",
pkg_schema_obj,
config=getattr(schema_obj, "config", None),
schema_url=schema_obj.schema_host,
schema_file=schema_obj.extended_schema_file,
file_schema_name=schema_obj.schema_name,
)
if hasattr(schema_obj, "registry"):
registry = schema_obj.registry
else:
resolver = CustomRefResolver(
"",
pkg_schema_obj,
config=getattr(schema_obj, "config", None),
schema_url=schema_obj.schema_host,
)
if getattr(schema_obj, "extended", None):
resolver = CustomRefResolver(
"",
pkg_schema_obj,
config=getattr(schema_obj, "config", None),
schema_url=schema_obj.schema_host,
schema_file=schema_obj.extended_schema_file,
file_schema_name=schema_obj.schema_name,
)
else:
resolver = CustomRefResolver(
"",
pkg_schema_obj,
config=getattr(schema_obj, "config", None),
schema_url=schema_obj.schema_host,
)

registry = Registry(retrieve=resolver.retrieve)

our_validator = validator(
pkg_schema_obj, format_checker=format_checker, resolver=resolver
pkg_schema_obj, format_checker=format_checker, registry=registry
)

for e in our_validator.iter_errors(json_data):
Expand Down Expand Up @@ -1165,7 +1171,7 @@ def get_fields_present(*args, **kwargs):
}


class CustomRefResolver(RefResolver):
class CustomRefResolver:
"""This RefResolver is only for use with the jsonschema library"""

def __init__(self, *args, **kw):
Expand All @@ -1178,26 +1184,19 @@ def __init__(self, *args, **kw):
# this is ignored when you supply a file
self.schema_url = kw.pop("schema_url", "")
self.config = kw.pop("config", "")
super().__init__(*args, **kw)

def resolve_remote(self, uri):
def retrieve(self, uri):
schema_name = uri.split("/")[-1]
if self.schema_file and self.file_schema_name == schema_name:
uri = self.schema_file
else:
uri = urljoin(self.schema_url, schema_name)

document = self.store.get(uri)

if document:
return document
if uri.startswith("http"):
# This branch of the if-statement in-lines `RefResolver.resolve_remote()`, but using `get_request()`.
# https://github.com/python-jsonschema/jsonschema/blob/dbc398245a583cb2366795dc529ae042d10c1577/jsonschema/validators.py#L1008-L1023
scheme = urlsplit(uri).scheme

if scheme in self.handlers:
result = self.handlers[scheme](uri)
elif scheme in ["http", "https"]:
if scheme in ("http", "https"):
# Requests has support for detecting the correct encoding of
# json over http
result = get_request(uri, config=self.config).json()
Expand All @@ -1206,16 +1205,12 @@ def resolve_remote(self, uri):
with urlopen(uri) as url:
result = json.loads(url.read().decode("utf-8"))

if self.cache_remote:
self.store[uri] = result
return result
else:
with open(uri) as schema_file:
result = json.load(schema_file)

add_is_codelist(result)
self.store[uri] = result
return result
return Resource.from_contents(result)


def _get_schema_deprecated_paths(
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
long_description="A data review library",
install_requires=[
"jsonref",
"jsonschema>=4",
"jsonschema>=4.18",
"referencing",
"requests",
"cached-property;python_version<'3.8'",
"flattentool>=0.11.0",
Expand Down
11 changes: 4 additions & 7 deletions tests/lib/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import jsonschema
import pytest
from freezegun import freeze_time
from referencing.exceptions import CannotDetermineSpecification

from libcove.lib.common import (
SchemaJsonMixin,
Expand Down Expand Up @@ -766,7 +767,7 @@ def get_pkg_schema_obj(self):
assert "[Decimal('3.1')] is too short" in validation_error_json


def test_property_that_is_not_json_schema_doesnt_raise_exception(caplog, tmpdir):
def test_property_that_is_not_json_schema_does_raise_exception(tmpdir):
tmpdir.join("test.json").write(
json.dumps({"properties": {"bad_property": "not_a_json_schema"}})
)
Expand All @@ -778,12 +779,8 @@ class DummySchemaObj:
def get_pkg_schema_obj(self):
return {"$ref": "test.json"}

validation_errors = get_schema_validation_errors({}, DummySchemaObj(), "", {}, {})
assert validation_errors == {}
assert (
"A 'properties' object contains a 'bad_property' value that is not a JSON Schema: 'not_a_json_schema'"
in caplog.text
)
with pytest.raises(CannotDetermineSpecification):
get_schema_validation_errors({}, DummySchemaObj(), "", {}, {})


@pytest.mark.parametrize(
Expand Down
Loading