Beispiel #1
0
class TestRefResolver(TestCase):
    def setUp(self):
        self.resolver = RefResolver()
        self.schema = mock.MagicMock()

    def test_it_resolves_local_refs(self):
        ref = "#/properties/foo"
        resolved = self.resolver.resolve(self.schema, ref)
        self.assertEqual(resolved, self.schema["properties"]["foo"])

    def test_it_retrieves_non_local_refs(self):
        schema = '{"type" : "integer"}'
        get_page = mock.Mock(return_value=StringIO(schema))
        resolver = RefResolver(get_page=get_page)

        url = "http://example.com/schema"
        resolved = resolver.resolve(mock.Mock(), url)

        self.assertEqual(resolved, json.loads(schema))
        get_page.assert_called_once_with(url)

    def test_it_uses_urlopen_by_default_for_nonlocal_refs(self):
        self.assertEqual(self.resolver.get_page, urlopen)

    def test_it_accepts_a_ref_store(self):
        store = mock.Mock()
        self.assertEqual(RefResolver(store).store, store)

    def test_it_retrieves_stored_refs(self):
        ref = self.resolver.store["cached_ref"] = mock.Mock()
        resolved = self.resolver.resolve(self.schema, "cached_ref")
        self.assertEqual(resolved, ref)
Beispiel #2
0
class TestRefResolver(TestCase):
    def setUp(self):
        self.resolver = RefResolver()
        self.schema = mock.MagicMock()

    def test_it_resolves_local_refs(self):
        ref = "#/properties/foo"
        resolved = self.resolver.resolve(self.schema, ref)
        self.assertEqual(resolved, self.schema["properties"]["foo"])

    def test_it_retrieves_non_local_refs(self):
        schema = '{"type" : "integer"}'
        get_page = mock.Mock(return_value=StringIO(schema))
        resolver = RefResolver(get_page=get_page)

        url = "http://example.com/schema"
        resolved = resolver.resolve(mock.Mock(), url)

        self.assertEqual(resolved, json.loads(schema))
        get_page.assert_called_once_with(url)

    def test_it_uses_urlopen_by_default_for_nonlocal_refs(self):
        self.assertEqual(self.resolver.get_page, urlopen)

    def test_it_accepts_a_ref_store(self):
        store = mock.Mock()
        self.assertEqual(RefResolver(store).store, store)

    def test_it_retrieves_stored_refs(self):
        ref = self.resolver.store["cached_ref"] = mock.Mock()
        resolved = self.resolver.resolve(self.schema, "cached_ref")
        self.assertEqual(resolved, ref)
Beispiel #3
0
class TestRefResolver(unittest.TestCase):
    def setUp(self):
        self.base_uri = ""
        self.referrer = {}
        self.store = {}
        self.resolver = RefResolver(self.base_uri, self.referrer, self.store)

    def test_it_does_not_retrieve_schema_urls_from_the_network(self):
        ref = Draft3Validator.META_SCHEMA["id"]
        with mock.patch.object(self.resolver, "resolve_remote") as remote:
            resolved = self.resolver.resolve(ref)

        self.assertEqual(resolved, Draft3Validator.META_SCHEMA)
        self.assertFalse(remote.called)

    def test_it_resolves_local_refs(self):
        ref = "#/properties/foo"
        self.referrer["properties"] = {"foo" : object()}
        resolved = self.resolver.resolve(ref)
        self.assertEqual(resolved, self.referrer["properties"]["foo"])

    def test_it_retrieves_stored_refs(self):
        self.resolver.store["cached_ref"] = {"foo" : 12}
        resolved = self.resolver.resolve("cached_ref#/foo")
        self.assertEqual(resolved, 12)

    def test_it_retrieves_unstored_refs_via_urlopen(self):
        ref = "http://bar#baz"
        schema = {"baz" : 12}

        with mock.patch("jsonschema.urlopen") as urlopen:
            urlopen.return_value.read.return_value = json.dumps(schema)
            resolved = self.resolver.resolve(ref)

        self.assertEqual(resolved, 12)
        urlopen.assert_called_once_with("http://bar")

    def test_it_can_construct_a_base_uri_from_a_schema(self):
        schema = {"id" : "foo"}
        resolver = RefResolver.from_schema(schema)
        self.assertEqual(resolver.base_uri, "foo")
        self.assertEqual(resolver.referrer, schema)

    def test_it_can_construct_a_base_uri_from_a_schema_without_id(self):
        schema = {}
        resolver = RefResolver.from_schema(schema)
        self.assertEqual(resolver.base_uri, "")
        self.assertEqual(resolver.referrer, schema)
Beispiel #4
0
 def resolve_reference(self, schema):
     schema = deepcopy(schema)  # avoid changing the original schema
     ref = schema['schema']
     ref_resolver = RefResolver('', None)
     d = ref_resolver.resolve(ref['$ref'])
     schema['schema'] = d
     return schema
Beispiel #5
0
    def _resolve_schema_references(self, schema: dict,
                                   resolver: RefResolver) -> dict:
        if "$ref" in schema:
            reference_path = schema.pop("$ref", None)
            resolved = resolver.resolve(reference_path)[1]
            schema.update(resolved)
            return self._resolve_schema_references(schema, resolver)

        if "properties" in schema:
            for k, val in schema["properties"].items():
                schema["properties"][k] = self._resolve_schema_references(
                    val, resolver)

        if "patternProperties" in schema:
            for k, val in schema["patternProperties"].items():
                schema["patternProperties"][
                    k] = self._resolve_schema_references(val, resolver)

        if "items" in schema:
            schema["items"] = self._resolve_schema_references(
                schema["items"], resolver)

        if "anyOf" in schema:
            for i, element in enumerate(schema["anyOf"]):
                schema["anyOf"][i] = self._resolve_schema_references(
                    element, resolver)

        return schema
 def get_security_definitions(self, schema: Dict[str, Any], resolver: RefResolver) -> Dict[str, Any]:
     """In Open API 3 security definitions are located in ``components`` and may have references inside."""
     components = schema.get("components", {})
     security_schemes = components.get("securitySchemes", {})
     if "$ref" in security_schemes:
         return resolver.resolve(security_schemes["$ref"])[1]
     return security_schemes
Beispiel #7
0
def _deep_resolve_mapping(
    unresolved: JSONMapping, resolver: jsonschema.RefResolver
) -> JSONMapping:
    return {
        k: deep_resolve(resolver.resolve(v["$ref"])[-1] if "$ref" in v else v, resolver)
        for k, v in unresolved.items()
    }
Beispiel #8
0
    def test_it_retrieves_non_local_refs(self):
        schema = '{"type" : "integer"}'
        get_page = mock.Mock(return_value=StringIO(schema))
        resolver = RefResolver(get_page=get_page)

        url = "http://example.com/schema"
        resolved = resolver.resolve(mock.Mock(), url)

        self.assertEqual(resolved, json.loads(schema))
        get_page.assert_called_once_with(url)
Beispiel #9
0
    def test_it_retrieves_non_local_refs(self):
        schema = '{"type" : "integer"}'
        get_page = mock.Mock(return_value=StringIO(schema))
        resolver = RefResolver(get_page=get_page)

        url = "http://example.com/schema"
        resolved = resolver.resolve(mock.Mock(), url)

        self.assertEqual(resolved, json.loads(schema))
        get_page.assert_called_once_with(url)
Beispiel #10
0
def _deep_resolve_sequence(
    unresolved: Sequence, resolver: jsonschema.RefResolver
) -> Sequence:
    return unresolved.__class__(  # type: ignore
        [
            deep_resolve(
                resolver.resolve(item["$ref"])[-1] if "$ref" in item else item, resolver
            )
            for item in unresolved
        ]
    )
Beispiel #11
0
def _resolve_reference(value, root, resolvers):
    base, ref = value.split("#", 1)

    if base:
        resolver, new_root = resolvers[base]
        referrer, resolution = resolver.resolve(value)
        _resolve_schema(resolution, new_root, resolvers)
    else:
        resolver = RefResolver("#", root)
        referrer, resolution = resolver.resolve(value)

    return resolution
Beispiel #12
0
    def resolve_reference(self, value, root):
        """Resolves a reference.

        :param value: The actual reference, e.g. ``_yaml.yaml#/def``
        :param root:
            The containing root of :param:`value`. This needs to be
            passed in order to resolve self-referential $refs,
            e.g. ``#/def``.
        :returns: JSON Schema pointed to by :param:`value`

        """
        base, ref = value.split('#', 1)

        if base:
            resolver, new_root = self.resolvers[base]
            referrer, resolution = resolver.resolve(value)
            self.resolve_schema(resolution, new_root)
        else:
            resolver = RefResolver('#', root)
            referrer, resolution = resolver.resolve(value)

        return resolution
class JsonSchemaResolver(SchemaResolverInterface):
    """
    Object to resolve schemas using `jsonschema` as references fetcher
    The implementation is based on the one provided by Giordon Stark:
    https://gist.github.com/kratsg/96cec81df8c0d78ebdf14bf7b800e938
    """
    def __init__(self, schemas_uri):
        """
        Initializes the inner $ref `jsonschema` resolver.

        :param schemas_uri: str.
        """

        if not schemas_uri.endswith("/"):
            schemas_uri += "/"

        self.schemas_uri = schemas_uri
        self.ref_fetcher = RefResolver(base_uri=self.schemas_uri,
                                       referrer=None)

    def _walk_dict(self, obj, ref):
        """
        Iterates a dictionary within the schema resolving every $ref.

        :param obj: dict.
        :param ref: str.
        :return: dict.
        """

        out_obj = deepcopy(obj)

        for key in obj:

            if key == '$ref':
                path = urljoin(ref, out_obj.pop(key))
                new_ref, new_obj = self.ref_fetcher.resolve(path)
                resolved_obj = self._walk_dict(new_obj, new_ref)
                out_obj.update(resolved_obj)

            elif isinstance(obj[key], dict):
                out_obj[key] = self._walk_dict(obj[key], ref)

            elif isinstance(obj[key], list):
                out_obj[key] = self._walk_list(obj[key], ref)

        return out_obj

    def _walk_list(self, seq, ref):
        """
        Iterates a sequence within the schema resolving every $ref.

        :param seq: list.
        :param ref: str.
        :return: list.
        """

        items = []

        for item in seq:
            if isinstance(item, dict):
                item = self._walk_dict(item, ref)
                items.append(item)
            else:
                items.append(item)

        return items

    def resolve(self, schema_uri):
        """
        Resolves a JSON schema either from a remote or a local URI.

        :param schema_uri: str.
        :return: dict.
        """

        top_ref, top_obj = self.ref_fetcher.resolve(schema_uri)
        resolved_schema = self._walk_dict(top_obj, top_ref)

        return resolved_schema
Beispiel #14
0
Datei: doc.py Projekt: akx/lepo
class APIDefinition:
    version = None
    path_class = Path
    operation_class = None

    def __init__(self, doc):
        """
        Instantiate a new Lepo router.

        :param doc: The OpenAPI definition document.
        :type doc: dict
        """
        self.doc = doc
        self.resolver = RefResolver('', self.doc)

    def resolve_reference(self, ref):
        """
        Resolve a JSON Pointer object reference to the object itself.

        :param ref: Reference string (`#/foo/bar`, for instance)
        :return: The object, if found
        :raises jsonschema.exceptions.RefResolutionError: if there is trouble resolving the reference
        """
        url, resolved = self.resolver.resolve(ref)
        return resolved

    def get_path_mapping(self, path):
        return maybe_resolve(self.doc['paths'][path], self.resolve_reference)

    def get_path_names(self):
        yield from self.doc['paths']

    def get_path(self, path):
        """
        Construct a Path object from a path string.

        The Path string must be declared in the API.

        :type path: str
        :rtype: lepo.path.Path
        """
        mapping = self.get_path_mapping(path)
        return self.path_class(api=self, path=path, mapping=mapping)

    def get_paths(self):
        """
        Iterate over all Path objects declared by the API.

        :rtype: Iterable[lepo.path.Path]
        """
        for path_name in self.get_path_names():
            yield self.get_path(path_name)

    @classmethod
    def from_file(cls, filename):
        """
        Construct an APIDefinition by parsing the given `filename`.

        If PyYAML is installed, YAML files are supported.
        JSON files are always supported.

        :param filename: The filename to read.
        :rtype: APIDefinition
        """
        with open(filename) as infp:
            if filename.endswith('.yaml') or filename.endswith('.yml'):
                import yaml
                data = yaml.safe_load(infp)
            else:
                import json
                data = json.load(infp)
        return cls.from_data(data)

    @classmethod
    def from_data(cls, data):
        version = parse_version(data)
        if version == SWAGGER_2:
            return Swagger2APIDefinition(data)
        if version == OPENAPI_3:
            return OpenAPI3APIDefinition(data)
        raise NotImplementedError('We can never get here.')  # pragma: no cover

    @classmethod
    def from_yaml(cls, yaml_string):
        from yaml import safe_load
        return cls.from_data(safe_load(yaml_string))
Beispiel #15
0
class Router:
    path_class = Path

    def __init__(self, api):
        """
        Instantiate a new Lepo router.

        :param api: The OpenAPI definition object.
        :type api: dict
        """
        self.api = deepcopy(api)
        self.api.pop('host', None)
        self.handlers = {}
        self.resolver = RefResolver('', self.api)

    @classmethod
    def from_file(cls, filename):
        """
        Construct a Router by parsing the given `filename`.

        If PyYAML is installed, YAML files are supported.
        JSON files are always supported.

        :param filename: The filename to read.
        :rtype: Router
        """
        with open(filename) as infp:
            if filename.endswith('.yaml') or filename.endswith('.yml'):
                import yaml
                data = yaml.safe_load(infp)
            else:
                import json
                data = json.load(infp)
        return cls(data)

    def get_path(self, path):
        """
        Construct a Path object from a path string.

        The Path string must be declared in the API.

        :type path: str
        :rtype: lepo.path.Path
        """
        mapping = maybe_resolve(self.api['paths'][path],
                                self.resolve_reference)
        return self.path_class(router=self, path=path, mapping=mapping)

    def get_paths(self):
        """
        Iterate over all Path objects declared by the API.

        :rtype: Iterable[lepo.path.Path]
        """
        for path in self.api['paths']:
            yield self.get_path(path)

    def get_urls(
            self,
            root_view_name=None,
            optional_trailing_slash=False,
            decorate=(),
            name_template='{name}',
    ):
        """
        Get the router's URLs, ready to be installed in `urlpatterns` (directly or via `include`).

        :param root_view_name: The optional url name for an API root view.
                               This may be useful for projects that do not explicitly know where the
                               router is mounted; those projects can then use `reverse('api:root')`,
                               for instance, if they need to construct URLs based on the API's root URL.
        :type root_view_name: str|None

        :param optional_trailing_slash: Whether to fix up the regexen for the router to make any trailing
                                        slashes optional.
        :type optional_trailing_slash: bool

        :param decorate: A function to decorate view functions with, or an iterable of such decorators.
                         Use `(lepo.decorators.csrf_exempt,)` to mark all API views as CSRF exempt.
        :type decorate: function|Iterable[function]

        :param name_template: A `.format()` template for view naming.
        :type name_template: str

        :return: List of URL tuples.
        :rtype: list[tuple]
        """
        if isinstance(decorate, Iterable):
            decorators = decorate

            def decorate(view):
                return reduce(lambda view, decorator: decorator(view),
                              decorators, view)

        urls = []
        for path in self.get_paths():
            regex = path.regex
            if optional_trailing_slash:
                regex = regex.rstrip('$')
                if not regex.endswith('/'):
                    regex += '/'
                regex += '?$'
            view = decorate(path.view_class.as_view())
            urls.append(
                url(regex, view, name=name_template.format(name=path.name)))

        if root_view_name:
            urls.append(
                url(r'^$',
                    root_view,
                    name=name_template.format(name=root_view_name)))
        return urls

    def get_handler(self, operation_id):
        """
        Get the handler function for a given operation.

        To remain Pythonic, both the original and the snake_cased version of the operation ID are
        supported.

        This could be overridden in a subclass.

        :param operation_id: Operation ID.
        :return: Handler function
        :rtype: function
        """
        handler = (self.handlers.get(operation_id)
                   or self.handlers.get(snake_case(operation_id)))
        if handler:
            return handler
        raise MissingHandler(
            'Missing handler for operation %s (tried %s too)' %
            (operation_id, snake_case(operation_id)))

    def add_handlers(self, namespace):
        """
        Add handler functions from the given `namespace`, for instance a module.

        The namespace may be a string, in which case it is expected to be a name of a module.
        It may also be a dictionary mapping names to functions.

        Only non-underscore-prefixed functions and methods are imported.

        :param namespace: Namespace object.
        :type namespace: str|module|dict[str, function]
        """
        if isinstance(namespace, str):
            namespace = import_module(namespace)

        if isinstance(namespace, dict):
            namespace = namespace.items()
        else:
            namespace = vars(namespace).items()

        for name, value in namespace:
            if name.startswith('_'):
                continue
            if isfunction(value) or ismethod(value):
                self.handlers[name] = value

    def resolve_reference(self, ref):
        """
        Resolve a JSON Pointer object reference to the object itself.

        :param ref: Reference string (`#/foo/bar`, for instance)
        :return: The object, if found
        :raises jsonschema.exceptions.RefResolutionError: if there is trouble resolving the reference
        """
        url, resolved = self.resolver.resolve(ref)
        return resolved