예제 #1
0
    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)
예제 #2
0
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}'"
                    )
예제 #3
0
    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]
예제 #4
0
    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
예제 #5
0
    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):
예제 #6
0
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)
예제 #7
0
 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)
예제 #8
0
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"),
    )
예제 #9
0
    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
예제 #10
0
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(