def uri(self, value: Optional[URI]) -> None: from jschon.catalogue import Catalogue if self._uri != value: if self._uri is not None: Catalogue.del_schema(self._uri) self._uri = value if self._uri is not None: Catalogue.add_schema(self._uri, self)
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 evaluate(self, instance: JSON, scope: Scope) -> None: refschema = self.refschema if self.dynamic: base_scope = scope.root checked_uris = set() for key in scope.path: if (base_schema := base_scope.schema) is refschema: break if (base_uri := base_schema.base_uri ) is not None and base_uri not in checked_uris: checked_uris |= {base_uri} target_uri = URI(f"#{self.fragment}").resolve(base_uri) try: found_schema = Catalogue.get_schema(target_uri) if (dynamic_anchor := found_schema.get("$dynamicAnchor")) and \ dynamic_anchor.value == self.fragment: refschema = found_schema break except CatalogueError: pass base_scope = base_scope.children[key]
def __init__(self, parentschema: JSONSchema, value: str): super().__init__(parentschema, value) from jschon.catalogue import Catalogue try: self.validator: FormatValidator = Catalogue.get_format_validator(value) except CatalogueError: self.validator = None
def __init__( self, value: Union[bool, Mapping[str, AnyJSONCompatible]], *, uri: URI = None, metaschema_uri: URI = None, parent: JSON = None, key: str = None, ): from jschon.catalogue import Catalogue if uri is not None: Catalogue.add_schema(uri, self) self._uri: Optional[URI] = uri self._metaschema_uri: Optional[URI] = metaschema_uri self.keywords: Dict[str, Keyword] = {} # don't call super().__init__ self.value: Union[bool, Mapping[str, AnyJSONCompatible]] self.type: str self.parent: Optional[JSON] = parent self.key: Optional[str] = key if isinstance(value, bool): self.type = "boolean" self.value = value elif isinstance(value, Mapping) and all( isinstance(k, str) for k in value): self.type = "object" self.value = {} if self.parent is None and self.uri is None: self.uri = URI(f'mem:{uuid4()}') self._bootstrap(value) kwclasses = { key: kwclass for key in value if ((kwclass := self.metaschema.kwclasses.get(key)) and # skip bootstrapped keywords key not in self.keywords) } for kwclass in self._resolve_dependencies(kwclasses):
class RefKeyword(Keyword): key = "$ref" def __init__(self, parentschema: JSONSchema, value: str): super().__init__(parentschema, 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 "$ref" value "{uri}"' ) self.refschema = Catalogue.get_schema( uri, metaschema_uri=self.parentschema.metaschema_uri)
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)
def initialize(): Catalogue.add_directory( base_uri=URI('https://json-schema.org/draft/2019-09/'), base_dir=catalogue_dir / 'json-schema-spec-2019-09', ) Catalogue.create_vocabulary( URI("https://json-schema.org/draft/2019-09/vocab/core"), SchemaKeyword, VocabularyKeyword, IdKeyword, RefKeyword, AnchorKeyword, RecursiveRefKeyword_2019_09, RecursiveAnchorKeyword_2019_09, DefsKeyword, CommentKeyword, ) Catalogue.create_vocabulary( URI("https://json-schema.org/draft/2019-09/vocab/applicator"), AllOfKeyword, AnyOfKeyword, OneOfKeyword, NotKeyword, IfKeyword, ThenKeyword, ElseKeyword, DependentSchemasKeyword, ItemsKeyword_2019_09, AdditionalItemsKeyword_2019_09, UnevaluatedItemsKeyword_2019_09, ContainsKeyword, PropertiesKeyword, PatternPropertiesKeyword, AdditionalPropertiesKeyword, UnevaluatedPropertiesKeyword, PropertyNamesKeyword, ) Catalogue.create_vocabulary( URI("https://json-schema.org/draft/2019-09/vocab/validation"), TypeKeyword, EnumKeyword, ConstKeyword, MultipleOfKeyword, MaximumKeyword, ExclusiveMaximumKeyword, MinimumKeyword, ExclusiveMinimumKeyword, MaxLengthKeyword, MinLengthKeyword, PatternKeyword, MaxItemsKeyword, MinItemsKeyword, UniqueItemsKeyword, MaxContainsKeyword, MinContainsKeyword, MaxPropertiesKeyword, MinPropertiesKeyword, RequiredKeyword, DependentRequiredKeyword, ) Catalogue.create_vocabulary( URI("https://json-schema.org/draft/2019-09/vocab/format"), FormatKeyword, ) Catalogue.create_vocabulary( URI("https://json-schema.org/draft/2019-09/vocab/meta-data"), TitleKeyword, DescriptionKeyword, DefaultKeyword, DeprecatedKeyword, ReadOnlyKeyword, WriteOnlyKeyword, ExamplesKeyword, ) Catalogue.create_vocabulary( URI("https://json-schema.org/draft/2019-09/vocab/content"), ContentMediaTypeKeyword, ContentEncodingKeyword, ContentSchemaKeyword, ) Catalogue.create_metaschema( URI("https://json-schema.org/draft/2019-09/schema"), URI("https://json-schema.org/draft/2019-09/vocab/core"), URI("https://json-schema.org/draft/2019-09/vocab/applicator"), URI("https://json-schema.org/draft/2019-09/vocab/validation"), URI("https://json-schema.org/draft/2019-09/vocab/format"), URI("https://json-schema.org/draft/2019-09/vocab/meta-data"), URI("https://json-schema.org/draft/2019-09/vocab/content"), )
def parentschema(self) -> Optional[JSONSchema]: 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
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): 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(