Source code for ClearMap.config.validators.json_schema_utils
from __future__ import annotations
from itertools import chain
from pathlib import Path
from typing import Dict, Any, List, Callable, Optional
from urllib.parse import urljoin
import yaml
from jsonschema.validators import validator_for
from referencing import Registry, Resource
YAML_EXTENSIONS = ('.yml', '.yaml')
[docs]
def load_yaml_schema(path: Path) -> Dict[str, Any]:
data = yaml.safe_load(path.read_text(encoding='utf-8'))
if not isinstance(data, dict):
raise ValueError(f'Schema at "{path}" did not parse to a mapping.')
data.setdefault('$id', path.resolve().as_uri())
return data
[docs]
def build_schema_registry_from_dir(schemas_dir: Path, mutate: Optional[Callable[[Path, Dict[str, Any]], None]] = None) -> Registry:
reg = Registry()
schemas_dir = Path(schemas_dir)
for p in chain(*(schemas_dir.glob(f'*.schema{ext}') for ext in YAML_EXTENSIONS)):
data = load_yaml_schema(p)
if mutate is not None:
mutate(p, data)
res = Resource.from_contents(data)
declared_id = data['$id']
# by declared id
reg = reg.with_resource(declared_id, res)
# by file:// path
reg = reg.with_resource(p.resolve().as_uri(), res)
# sibling alias for ./typedefs.schema.yaml style refs
# base = declared_id.rsplit('/', 1)[0] + '/'
base = urljoin(declared_id, '.') # parent folder w/ trailing slash
reg = reg.with_resource(urljoin(base, p.name), res)
for ext in YAML_EXTENSIONS:
if not declared_id.endswith(f'.schema{ext}'):
reg = reg.with_resource(f'{declared_id}.schema{ext}', res)
return reg
# FIXME: set develpopment flag to machine.verbosity < INFO
[docs]
def compile_validator(schema: dict, *, registry=None, format_checker=None, development=True):
Validator = validator_for(schema)
if development:
Validator.check_schema(schema)
return Validator(schema, registry=registry, format_checker=format_checker)
[docs]
def inject_image_content_types(typedefs: Dict[str, Any], content_types: List[str]) -> None:
"""
Inject dynamic enum values into typedefs, e.g. for contentType.
So that they are read from single source of truth in code and
not duplicated verbatim in the schema file.
Parameters
----------
typedefs: Dict[str, Any]
The typedefs schema dictionary to modify in place.
"""
defs = typedefs.get('$defs') or {}
if content_types is not None and 'imageContentType' in defs:
content_type_def = defs['imageContentType']
content_type_def.clear()
content_type_def['anyOf'] = [
{
'type': 'string',
'enum': sorted(map(str, content_types))
},
{
'type': 'null'
}
]