Пример #1
0
def get_ref(*, ref: str, schemas: types.Schemas) -> NameSchema:
    """
    Get the schema referenced by ref.

    Raises SchemaNotFoundError if a $ref resolution fails.

    Args:
        ref: The reference to the schema.
        schemas: The schemas to use to resolve the ref.

    Returns:
        The schema referenced by ref.

    """
    # Check for remote $ref
    if not ref.startswith("#"):
        return get_remote_ref(ref=ref)

    # Checking value of $ref
    match = _REF_PATTER.match(ref)
    if not match:
        raise exceptions.SchemaNotFoundError(
            f"{ref} format incorrect, expected #/components/schemas/<SchemaName>"
        )

    # Retrieving new schema
    ref_name = match.group(1)
    ref_schema = schemas.get(ref_name)
    if ref_schema is None:
        raise exceptions.SchemaNotFoundError(
            f"{ref_name} was not found in schemas.")

    return ref_name, ref_schema
Пример #2
0
def _spec_to_schema_name(
        *,
        spec: types.AnyUnique,
        schema_names: typing.Optional[typing.List[str]] = None) -> str:
    """
    Convert a specification to the name of the matched schema.

    Use the schema names defined in common-schemas.json to find the first matching
    schema.

    Args:
        spec: The specification to convert.
        schema_names: The names of the schemas to check.

    Returns:
        The name of the specification.

    """
    if schema_names is None:
        schema_names = _COMMON_SCHEMAS.keys()

    for name in schema_names:
        try:
            jsonschema.validate(instance=spec,
                                schema=_COMMON_SCHEMAS[name],
                                resolver=_resolver)
            return name
        except jsonschema.ValidationError:
            continue
    raise exceptions.SchemaNotFoundError(
        "Specification did not match any schemas.")
Пример #3
0
def _add_backref_to_schemas(*, name: str, schemas: types.Schemas,
                            backref: types.Schema, property_name: str) -> None:
    """
    Add backref schema for a model that does not exist to its schema.

    Raise SchemaNotFoundError if the schema does not exist.

    Look for model in schemas. If it already has allOf, append backref, otherwise wrap
    the schema in an allOf with x-backrefs key.

    Args:
        name: The name of the model of the backref.
        schemas: All the model schemas.
        backref: The schema for the backref.
        property_name: The name under which to add the schema.

    """
    # Retrieve schema for model
    schema = schemas.get(name)
    if schema is None:
        raise exceptions.SchemaNotFoundError(
            f"The schema {name} was not found in schemas.")

    # Calculate the schema addition
    backref_schema = {"type": "object", "x-backrefs": {property_name: backref}}

    # Add backref to allOf
    all_of = schema.get("allOf")
    if all_of is None:
        schemas[name] = {"allOf": [schemas[name], backref_schema]}
        return
    all_of.append(backref_schema)
Пример #4
0
def _retrieve_schema(*, schemas: types.Schemas, path: str) -> NameSchema:
    """
    Retrieve schema at a path from schemas.

    Raise SchemaNotFoundError if the schema is not found at the path.

    Args:
        schemas: All the schemas.
        path: The location to retrieve the schema from.

    Returns:
        The schema at the path from the schemas.

    """
    # Strip leading /
    if path.startswith("/"):
        path = path[1:]

    # Get the first directory/file as the head and the remaining path as the tail
    path_components = path.split("/", 1)

    try:
        # Base case, no tail
        if len(path_components) == 1:
            return path_components[0], schemas[path_components[0]]
        # Recursive case, call again with path tail
        return _retrieve_schema(schemas=schemas[path_components[0]],
                                path=path_components[1])
    except KeyError as exc:
        raise exceptions.SchemaNotFoundError(
            f"The schema was not found in the remote schemas. Path subsection: {path}"
        ) from exc
Пример #5
0
    def get_schemas(self, *, context: str) -> types.Schema:
        """
        Retrieve the schemas for a context.

        Raise MissingArgumentError if the context for the original OpenAPI specification
            has not been set.
        Raise SchemaNotFoundError if the context doesn't exist or is not a json nor yaml
            file.

        Args:
            context: The path, relative to the original OpenAPI specification, for the
                file containing the schemas.

        Returns:
            The schemas.

        """
        # Check whether the context is already loaded
        if context in self._schemas:
            return self._schemas[context]

        if self.spec_context is None:
            raise exceptions.MissingArgumentError(
                "Cannot find the file containing the remote reference, either "
                "initialize OpenAlchemy with init_json or init_yaml or pass the path "
                "to the OpenAPI specification to OpenAlchemy.")

        # Check for json, yaml or yml file extension
        _, extension = os.path.splitext(context)
        extension = extension.lower()
        if extension not in {".json", ".yaml", ".yml"}:
            raise exceptions.SchemaNotFoundError(
                "The remote context is not a JSON nor YAML file. The path is: "
                f"{context}")

        # Get context manager with file
        try:
            if _URL_REF_PATTERN.search(context) is not None:
                file_cm = request.urlopen(context)
            else:
                spec_dir = os.path.dirname(self.spec_context)
                remote_spec_filename = os.path.join(spec_dir, context)
                file_cm = open(remote_spec_filename)
        except (FileNotFoundError, error.HTTPError) as exc:
            raise exceptions.SchemaNotFoundError(
                "The file with the remote reference was not found. The path is: "
                f"{context}") from exc

        # Calculate location of schemas
        with file_cm as in_file:
            if extension == ".json":
                try:
                    schemas = json.load(in_file)
                except json.JSONDecodeError as exc:
                    raise exceptions.SchemaNotFoundError(
                        "The remote reference file is not valid JSON. The path "
                        f"is: {context}") from exc
            else:
                # Import as needed to make yaml optional
                import yaml  # pylint: disable=import-outside-toplevel

                try:
                    schemas = yaml.safe_load(in_file)
                except yaml.scanner.ScannerError as exc:
                    raise exceptions.SchemaNotFoundError(
                        "The remote reference file is not valid YAML. The path "
                        f"is: {context}") from exc

        # Store for faster future retrieval
        self._schemas[context] = schemas
        return schemas
Пример #6
0
def _add_remote_context(*, context: str, ref: str) -> str:
    """
    Add remote context to any $ref within a schema retrieved from a remote reference.

    There are 5 cases:
    1. The $ref value starts with # in which case the context is prepended.
    2. The $ref starts with a filename in which case only the directory portion of the
        context is prepended.
    3. The $ref starts with a relative path and ends with a file in which case the
        directory portion of the context is prepended and merged so that the shortest
        possible relative path is used.
    4. The $ref starts with a HTTP protocol, in which case no changes are made.
    5. The $ref starts with // in which case the HTTP protocol of the context is
        prepended.

    Raise SchemaNotFoundError if the $ref starts with // when the context does not start
        with a HTTP protocol.

    After the paths are merged the following operations are done:
    1. a normalized relative path is calculated (eg. turning ./dir1/../dir2 to ./dir2)
        and
    2. the case is normalized.

    Args:
        context: The context of the document from which the schema was retrieved which
            is the relative path to the file on the system from the base OpenAPI
            specification.
        ref: The value of a $ref within the schema.

    Returns:
        The $ref value with the context of the document included.

    """
    # Check for URL reference
    url_match = _URL_REF_PATTERN.search(ref)
    if url_match is not None:
        return ref
    if ref.startswith("//"):
        context_protocol = _URL_REF_PATTERN.search(context)
        if context_protocol is None:
            raise exceptions.SchemaNotFoundError(
                "A reference starting with // is only valid from within a document "
                f"loaded from a URL. The reference is {ref}, the location of the "
                f"document with the reference is {context}.")
        return f"{context_protocol.group(1)}{ref}"

    # Handle reference within document
    ref_context, ref_schema = _separate_context_path(ref=ref)
    if not ref_context:
        return f"{context}{ref}"

    # Break context into components
    # Default where context is not a URL
    context_hostname = ""
    context_path = context
    # Gather components if the context is a URL
    hostname_match = _HOSTNAME_REF_PATTERM.search(context)
    if hostname_match is not None:
        context_hostname = hostname_match.group(1)
        context_path = hostname_match.group(2)
    context_path_head, _ = os.path.split(context_path)

    # Handle reference outside document
    new_ref_context_path = os.path.join(context_path_head, ref_context)
    norm_new_ref_context_path = _norm_context(context=new_ref_context_path)
    return f"{context_hostname}{norm_new_ref_context_path}#{ref_schema}"