Skip to content

Add support for remote YAML/TOML/JSON5 schemas #295

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

Merged
merged 1 commit into from
Aug 8, 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: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ Unreleased
non-JSON format supported by `check-jsonschema`. The file type is inferred
only from the file extension in these cases and defaults to JSON if there is
no recognizable extension.
- Remote schemafiles (http/s) now support YAML, TOML, and JSON5 formats, if the
URL ends with the appropriate extension and the matching parser is available.
Extensionless URLs are treated as JSON.

0.23.3
------
Expand Down
2 changes: 1 addition & 1 deletion src/check_jsonschema/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def _fail(self, msg: str, err: Exception | None = None) -> t.NoReturn:

def get_validator(
self, path: pathlib.Path, doc: dict[str, t.Any]
) -> jsonschema.Validator:
) -> jsonschema.protocols.Validator:
try:
return self._schema_loader.get_validator(
path, doc, self._format_opts, self._fill_defaults
Expand Down
10 changes: 5 additions & 5 deletions src/check_jsonschema/schema_loader/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@


def _extend_with_default(
validator_class: type[jsonschema.Validator],
validator_class: type[jsonschema.protocols.Validator],
) -> type[jsonschema.Validator]:
validate_properties = validator_class.VALIDATORS["properties"]

Expand Down Expand Up @@ -51,7 +51,7 @@ def get_validator(
instance_doc: dict[str, t.Any],
format_opts: FormatOptions,
fill_defaults: bool,
) -> jsonschema.Validator:
) -> jsonschema.protocols.Validator:
raise NotImplementedError


Expand Down Expand Up @@ -112,7 +112,7 @@ def get_validator(
instance_doc: dict[str, t.Any],
format_opts: FormatOptions,
fill_defaults: bool,
) -> jsonschema.Validator:
) -> jsonschema.protocols.Validator:
retrieval_uri = self.get_schema_retrieval_uri()
schema = self.get_schema()

Expand Down Expand Up @@ -141,7 +141,7 @@ def get_validator(
registry=reference_registry,
format_checker=format_checker,
)
return t.cast(jsonschema.Validator, validator)
return t.cast(jsonschema.protocols.Validator, validator)


class BuiltinSchemaLoader(SchemaLoader):
Expand All @@ -163,7 +163,7 @@ def get_validator(
instance_doc: dict[str, t.Any],
format_opts: FormatOptions,
fill_defaults: bool,
) -> jsonschema.Validator:
) -> jsonschema.protocols.Validator:
schema_validator = jsonschema.validators.validator_for(instance_doc)
meta_validator_class = jsonschema.validators.validator_for(
schema_validator.META_SCHEMA, default=schema_validator
Expand Down
15 changes: 12 additions & 3 deletions src/check_jsonschema/schema_loader/readers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

import json
import io
import typing as t

import ruamel.yaml
Expand Down Expand Up @@ -48,19 +48,28 @@ def __init__(
disable_cache: bool,
) -> None:
self.url = url
self.parsers = ParserSet()
self.downloader = CacheDownloader(
url,
cache_filename,
disable_cache=disable_cache,
validation_callback=json.loads,
validation_callback=self._parse,
)
self._parsed_schema: t.Any | None = None

def _parse(self, schema_bytes: bytes) -> t.Any:
if self._parsed_schema is None:
self._parsed_schema = self.parsers.parse_data_with_path(
io.BytesIO(schema_bytes), self.url, default_filetype="json"
)
return self._parsed_schema

def get_retrieval_uri(self) -> str:
return self.url

def _read_impl(self) -> t.Any:
with self.downloader.open() as fp:
return json.load(fp)
return self._parse(fp.read())

def read_schema(self) -> dict:
return _run_load_callback(self.url, self._read_impl)
19 changes: 13 additions & 6 deletions tests/unit/test_schema_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import pathlib

import pytest
import responses

from check_jsonschema.schema_loader import SchemaLoader, SchemaParseError
from check_jsonschema.schema_loader.readers import HttpSchemaReader, LocalSchemaReader
Expand Down Expand Up @@ -40,12 +41,12 @@ def test_schemaloader_path_handling_relative_local_path(in_tmp_dir, filename):
[
"schema.yaml",
"schema.yml",
"https://foo.example.com/schema.yaml",
"https://foo.example.com/schema.yml",
],
)
def test_schemaloader_local_yaml_data(tmp_path, filename):
f = tmp_path / filename
f.write_text(
"""
def test_schemaloader_yaml_data(tmp_path, filename):
schema_text = """
---
"$schema": https://json-schema.org/draft/2020-12/schema
type: object
Expand All @@ -60,8 +61,14 @@ def test_schemaloader_local_yaml_data(tmp_path, filename):
c:
type: string
"""
)
sl = SchemaLoader(str(f))
if filename.startswith("http"):
responses.add("GET", filename, body=schema_text)
path = filename
else:
f = tmp_path / filename
f.write_text(schema_text)
path = str(f)
sl = SchemaLoader(path)
schema = sl.get_schema()
assert schema == {
"$schema": "https://json-schema.org/draft/2020-12/schema",
Expand Down