class VocabularyKeyword(Keyword): key = "$vocabulary" def __init__(self, parentschema: JSONSchema, value: Mapping[str, bool]): super().__init__(parentschema, value) if not isinstance(parentschema, Metaschema): return if (core_vocab_uri := str(parentschema.core_vocabulary.uri)) not in value or \ value[core_vocab_uri] is not True: raise JSONSchemaError( f'The "$vocabulary" keyword must list the core vocabulary with a value of true' ) for vocab_uri, vocab_required in value.items(): try: (vocab_uri := URI(vocab_uri)).validate(require_scheme=True, require_normalized=True) except URIError as e: raise JSONSchemaError from e try: vocabulary = Catalogue.get_vocabulary(vocab_uri) parentschema.kwclasses.update(vocabulary.kwclasses) except CatalogueError: if vocab_required: raise JSONSchemaError( f"The metaschema requires an unrecognized vocabulary '{vocab_uri}'" )
def __init__(self, parentschema: JSONSchema, value: str): super().__init__(parentschema, value) if value != '#': raise JSONSchemaError( f'"$recursiveRef" may only take the value "#"') self.refschema = None
def resolve(self) -> None: uri = URI(self.json.value) if not uri.has_absolute_base(): if (base_uri := self.parentschema.base_uri) is not None: uri = uri.resolve(base_uri) else: raise JSONSchemaError( f'No base URI against which to resolve the "$dynamicRef" value "{uri}"' )
def __init__(self, parentschema: JSONSchema, value: str): super().__init__(parentschema, value) # this is not required by the spec, but it doesn't make sense # for a $dynamicRef *not* to end in a plain-name fragment if (fragment := URI(value).fragment) is None or '/' in fragment: raise JSONSchemaError( 'The value for "$dynamicRef" must end in a plain-name fragment' )
def __init__(self, parentschema: JSONSchema, value: Mapping[str, bool]): super().__init__(parentschema, value) if not isinstance(parentschema, Metaschema): return if (core_vocab_uri := str(parentschema.core_vocabulary.uri)) not in value or \ value[core_vocab_uri] is not True: raise JSONSchemaError( f'The "$vocabulary" keyword must list the core vocabulary with a value of true' )
def __init__(self, parentschema: JSONSchema, value: str): super().__init__(parentschema, value) (uri := URI(value)).validate(require_normalized=True, allow_fragment=False) if not uri.is_absolute(): if (base_uri := parentschema.base_uri) is not None: uri = uri.resolve(base_uri) else: raise JSONSchemaError( f'No base URI against which to resolve the "$id" value "{value}"' )
key = "$recursiveRef" def __init__(self, parentschema: JSONSchema, value: str): super().__init__(parentschema, value) if value != '#': raise JSONSchemaError( f'"$recursiveRef" may only take the value "#"') self.refschema = None def resolve(self) -> None: if (base_uri := self.parentschema.base_uri) is not None: self.refschema = Catalogue.get_schema( base_uri, metaschema_uri=self.parentschema.metaschema_uri) else: raise JSONSchemaError( f'No base URI against which to resolve "$recursiveRef"') def evaluate(self, instance: JSON, scope: Scope) -> None: refschema = self.refschema if (recursive_anchor := refschema.get("$recursiveAnchor")) and \ recursive_anchor.value is True: base_scope = scope.root for key in scope.path: if (base_schema := base_scope.schema) is refschema: break if (base_anchor := base_schema.get("$recursiveAnchor")) and \ base_anchor.value is True: refschema = base_schema break base_scope = base_scope.children[key]
def metaschema(self) -> Metaschema: from jschon.catalogue import Catalogue if (uri := self.metaschema_uri) is None: raise JSONSchemaError( "The schema's metaschema URI has not been set")
def validate(self) -> JSONSchema: if not self.metaschema.evaluate(JSON(self.value)).valid: raise JSONSchemaError( f"The schema is invalid against its metaschema") return self
parent = self.parent while parent is not None: if isinstance(parent, JSONSchema): return parent parent = parent.parent @property def metaschema(self) -> Metaschema: from jschon.catalogue import Catalogue if (uri := self.metaschema_uri) is None: raise JSONSchemaError( "The schema's metaschema URI has not been set") if not isinstance(metaschema := Catalogue.get_schema(uri), Metaschema): raise JSONSchemaError( f"The schema referenced by {uri} is not a metachema") return metaschema @property def metaschema_uri(self) -> Optional[URI]: if self._metaschema_uri is not None: return self._metaschema_uri if self.parentschema is not None: return self.parentschema.metaschema_uri @metaschema_uri.setter def metaschema_uri(self, value: Optional[URI]) -> None: self._metaschema_uri = value @property
uri, metaschema_uri=self.parentschema.metaschema_uri) def evaluate(self, instance: JSON, scope: Scope) -> None: self.refschema.evaluate(instance, scope) class AnchorKeyword(Keyword): key = "$anchor" def __init__(self, parentschema: JSONSchema, value: str): super().__init__(parentschema, value) if (base_uri := parentschema.base_uri) is not None: uri = URI(f'{base_uri}#{value}') else: raise JSONSchemaError(f'No base URI for "$anchor" value "{value}"') # just add a schema reference to the catalogue, rather than updating # the schema URI itself; this way we keep canonical URIs consistent # for subschemas regardless of anchor usage # parentschema.uri = uri Catalogue.add_schema(uri, parentschema) def can_evaluate(self, instance: JSON) -> bool: return False class DynamicRefKeyword(Keyword): key = "$dynamicRef" def __init__(self, parentschema: JSONSchema, value: str):