Ejemplo n.º 1
0
 def test_cache_remote_off(self):
     ref = "foo://bar"
     foo_handler = mock.Mock()
     resolver = RefResolver("", {}, cache_remote=False, handlers={"foo": foo_handler})
     with resolver.resolving(ref):
         pass
     self.assertEqual(foo_handler.call_count, 1)
Ejemplo n.º 2
0
 def test_custom_uri_scheme_handlers(self):
     schema = {"foo": "bar"}
     ref = "foo://bar"
     foo_handler = mock.Mock(return_value=schema)
     resolver = RefResolver("", {}, handlers={"foo": foo_handler})
     with resolver.resolving(ref) as resolved:
         self.assertEqual(resolved, schema)
     foo_handler.assert_called_once_with(ref)
Ejemplo n.º 3
0
 def test_if_you_give_it_junk_you_get_a_resolution_error(self):
     ref = "foo://bar"
     foo_handler = mock.Mock(side_effect=ValueError("Oh no! What's this?"))
     resolver = RefResolver("", {}, handlers={"foo" : foo_handler})
     with self.assertRaises(RefResolutionError) as err:
         with resolver.resolving(ref):
             pass
     self.assertEqual(str(err.exception), "Oh no! What's this?")
Ejemplo n.º 4
0
 def test_cache_remote_on(self):
     ref = "foo://bar"
     foo_handler = mock.Mock()
     resolver = RefResolver("", {}, cache_remote=True, handlers={"foo": foo_handler})
     with resolver.resolving(ref):
         pass
     with resolver.resolving(ref):
         pass
     foo_handler.assert_called_once_with(ref)
Ejemplo n.º 5
0
 def test_it_resolves_local_refs_with_id(self):
     schema = {"id": "foo://bar/schema#", "a": {"foo": "bar"}}
     resolver = RefResolver.from_schema(schema)
     with resolver.resolving("#/a") as resolved:
         self.assertEqual(resolved, schema["a"])
     with resolver.resolving("foo://bar/schema#/a") as resolved:
         self.assertEqual(resolved, schema["a"])
Ejemplo n.º 6
0
    def __init__(self, spec_dict, origin_url=None, http_client=None,
                 config=None):
        self.spec_dict = spec_dict
        self.origin_url = origin_url
        self.http_client = http_client
        self.api_url = None
        self.config = dict(CONFIG_DEFAULTS, **(config or {}))

        # (key, value) = (simple format def name, Model type)
        # (key, value) = (#/ format def ref, Model type)
        self.definitions = {}

        # (key, value) = (simple resource name, Resource)
        # (key, value) = (#/ format resource ref, Resource)
        self.resources = None

        # (key, value) = (simple ref name, param_spec in dict form)
        # (key, value) = (#/ format ref name, param_spec in dict form)
        self.params = None

        # Built on-demand - see get_op_for_request(..)
        self._request_to_op_map = None

        # (key, value) = (format name, SwaggerFormat)
        self.user_defined_formats = {}
        self.format_checker = FormatChecker()

        self.resolver = RefResolver(
            base_uri=origin_url or '',
            referrer=self.spec_dict,
            handlers=build_http_handlers(http_client))
Ejemplo n.º 7
0
 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, "")
     with resolver.resolving("") as resolved:
         self.assertEqual(resolved, schema)
     with resolver.resolving("#") as resolved:
         self.assertEqual(resolved, schema)
Ejemplo n.º 8
0
 def __init__(self, name, schema):
     self.name = name
     self.base_uri = schema.get('id', '')
     # replace all relative references with absolute refs to avoid
     # clashes in self.definitions
     self.schema = Reference.replace_relative_references(self.base_uri, schema)
     # maps full jsonschema fragment path to internal structure,
     # for example a StructDef or a TypeDef.
     self.definitions = {}
     self.refresolver = RefResolver.from_schema(schema)
Ejemplo n.º 9
0
class WalkInstance(Walk):

    def __init__(self, merger, base, head):
        Walk.__init__(self, merger)
        self.base_resolver = RefResolver("", base.val)
        self.head_resolver = RefResolver("", head.val)

    def add_meta(self, head, meta):
        if meta is None:
            rv = dict()
        else:
            rv = dict(meta)

        rv['value'] = head
        return rv

    def default_strategy(self, schema, base, head, meta, **kwargs):
        if self.is_type(head, "object"):
            return "objectMerge"
        else:
            return "overwrite"

    def work(self, strategy, schema, base, head, meta, **kwargs):
        assert isinstance(schema, JSONValue)
        assert isinstance(base, JSONValue)
        assert isinstance(head, JSONValue)

        log.debug("work   : %sbase %s, head %s" % (self._indent(), base.ref, head.ref))

        if not base.is_undef():
            with self.base_resolver.resolving(base.ref) as resolved:
                assert base.val == resolved

        if not head.is_undef():
            with self.head_resolver.resolving(head.ref) as resolved:
                assert head.val == resolved

        rv = strategy.merge(self, base, head, schema, meta, **kwargs)

        assert isinstance(rv, JSONValue)
        return rv
Ejemplo n.º 10
0
 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.resolution_scope, "foo")
     with resolver.resolving("") as resolved:
         self.assertEqual(resolved, schema)
     with resolver.resolving("#") as resolved:
         self.assertEqual(resolved, schema)
     with resolver.resolving("foo") as resolved:
         self.assertEqual(resolved, schema)
     with resolver.resolving("foo#") as resolved:
         self.assertEqual(resolved, schema)
Ejemplo n.º 11
0
    def __init__(self, _json_schema_folders=[], _uri_handlers=None):
        """
        Initiate the SchemaTools class

        :param _json_schema_folders: A list of folders where schema files are stored
        :param : _uri_handlers: A dict of uri_handlers, resolves a URI prefix to a actual schema.

        """

        if not _json_schema_folders:
            _json_schema_folders = []

        if not _uri_handlers:
            self.uri_handlers = {}
        else:
            self.uri_handlers = _uri_handlers


        # All methods that have no handlers should use the cache handler.
        for _curr_key, _curr_value  in _uri_handlers.items():
            if _curr_value is None:
                _uri_handlers[_curr_key] = self.cache_handler

        self.resolver = RefResolver(base_uri="",
                                handlers=self.uri_handlers, referrer=None, cache_remote=True)

        self.mongodb_validator = MongodbValidator(resolver= self.resolver)

        self.json_schema_objects = {}

        # Load application specific schemas
        for _curr_folder in _json_schema_folders:
            _loaded_uris = self.load_schemas_from_directory(os.path.abspath(_curr_folder))

            # Resolve all the schemas
            for _curr_uri in _loaded_uris:
                self.json_schema_objects[_curr_uri] = self.resolveSchema(self.json_schema_objects[_curr_uri])

        write_to_log("Schemas loaded and resolved: " +
                     str.join(", ",  ["\"" +_curr_schema["title"] + "\""  for _curr_schema in self.json_schema_objects.values()])
                     , _category=EC_NOTIFICATION, _severity=SEV_DEBUG)
Ejemplo n.º 12
0
    def __init__(self, spec_dict, origin_url=None, http_client=None,
                 config=None):
        self.spec_dict = spec_dict
        self.origin_url = origin_url
        self.http_client = http_client
        self.api_url = None
        self.config = dict(CONFIG_DEFAULTS, **(config or {}))

        # (key, value) = (simple format def name, Model type)
        # (key, value) = (#/ format def ref, Model type)
        self.definitions = {}

        # (key, value) = (simple resource name, Resource)
        # (key, value) = (#/ format resource ref, Resource)
        self.resources = None

        # (key, value) = (simple ref name, param_spec in dict form)
        # (key, value) = (#/ format ref name, param_spec in dict form)
        self.params = None

        # Built on-demand - see get_op_for_request(..)
        self._request_to_op_map = None

        # (key, value) = (format name, SwaggerFormat)
        self.user_defined_formats = {}
        self.format_checker = FormatChecker()

        self.resolver = RefResolver(
            base_uri=origin_url or '',
            referrer=self.spec_dict,
            handlers=build_http_handlers(http_client),
        )

        self._validate_config()

        if self.config['internally_dereference_refs']:
            # If internally_dereference_refs is enabled we do NOT need to resolve references anymore
            # it's useless to evaluate is_ref every time
            self.deref = lambda ref_dict: ref_dict
        else:
            self.deref = self._force_deref
Ejemplo n.º 13
0
def check_against_qal_schema(_ref, _data):
    """ Check JSON against given schema
    :param _ref: The name of the schema; "resources"
    :param _data: The data to be validated
    """
    # First create a resolver to resolve the current schema.
    _resolver = RefResolver(base_uri="",
                        handlers={"qal": qal_uri_handler}, referrer=None, cache_remote=True)

    _schema = _resolver.resolve(_ref)[1]
    _base_ref = urlparse(_ref).scheme + "://" + urlparse(_ref).netloc
    if _ref != _base_ref:
        _resolver.referrer = _resolver.resolve(_base_ref)[1]
    else:
        _resolver.referrer = _schema

    _resolver.store[""] = _resolver.referrer

    Draft4Validator(schema=_schema, resolver=_resolver).validate(_data)
Ejemplo n.º 14
0
 def setUp(self):
     self.referrer = {}
     self.store = {self.stored_uri : self.stored_schema}
     self.resolver = RefResolver(self.base_uri, self.referrer, self.store)
Ejemplo n.º 15
0
class TestRefResolver(unittest.TestCase):

    base_uri = ""
    stored_uri = "foo://stored"
    stored_schema = {"stored" : "schema"}

    def setUp(self):
        self.referrer = {}
        self.store = {self.stored_uri : self.stored_schema}
        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:
            with self.resolver.resolving(ref) as resolved:
                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()}
        with self.resolver.resolving(ref) as resolved:
            self.assertEqual(resolved, self.referrer["properties"]["foo"])

    def test_it_resolves_local_refs_with_id(self):
        schema = {"id": "foo://bar/schema#", "a": {"foo": "bar"}}
        resolver = RefResolver.from_schema(schema)
        with resolver.resolving("#/a") as resolved:
            self.assertEqual(resolved, schema["a"])
        with resolver.resolving("foo://bar/schema#/a") as resolved:
            self.assertEqual(resolved, schema["a"])

    def test_it_retrieves_stored_refs(self):
        with self.resolver.resolving(self.stored_uri) as resolved:
            self.assertIs(resolved, self.stored_schema)

        self.resolver.store["cached_ref"] = {"foo" : 12}
        with self.resolver.resolving("cached_ref#/foo") as resolved:
            self.assertEqual(resolved, 12)

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

        with mock.patch("jsonschema.validators.requests") as requests:
            requests.get.return_value.json.return_value = schema
            with self.resolver.resolving(ref) as resolved:
                self.assertEqual(resolved, 12)
        requests.get.assert_called_once_with("http://bar")

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

        with mock.patch("jsonschema.validators.requests", None):
            with mock.patch("jsonschema.validators.urlopen") as urlopen:
                urlopen.return_value.read.return_value = (
                    json.dumps(schema).encode("utf8"))
                with self.resolver.resolving(ref) as resolved:
                    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")
        with resolver.resolving("") as resolved:
            self.assertEqual(resolved, schema)
        with resolver.resolving("#") as resolved:
            self.assertEqual(resolved, schema)
        with resolver.resolving("foo") as resolved:
            self.assertEqual(resolved, schema)
        with resolver.resolving("foo#") as resolved:
            self.assertEqual(resolved, 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, "")
        with resolver.resolving("") as resolved:
            self.assertEqual(resolved, schema)
        with resolver.resolving("#") as resolved:
            self.assertEqual(resolved, schema)

    def test_custom_uri_scheme_handlers(self):
        schema = {"foo": "bar"}
        ref = "foo://bar"
        foo_handler = mock.Mock(return_value=schema)
        resolver = RefResolver("", {}, handlers={"foo": foo_handler})
        with resolver.resolving(ref) as resolved:
            self.assertEqual(resolved, schema)
        foo_handler.assert_called_once_with(ref)

    def test_cache_remote_on(self):
        ref = "foo://bar"
        foo_handler = mock.Mock()
        resolver = RefResolver(
            "", {}, cache_remote=True, handlers={"foo" : foo_handler},
        )
        with resolver.resolving(ref):
            pass
        with resolver.resolving(ref):
            pass
        foo_handler.assert_called_once_with(ref)

    def test_cache_remote_off(self):
        ref = "foo://bar"
        foo_handler = mock.Mock()
        resolver = RefResolver(
            "", {}, cache_remote=False, handlers={"foo" : foo_handler},
        )
        with resolver.resolving(ref):
            pass
        with resolver.resolving(ref):
            pass
        self.assertEqual(foo_handler.call_count, 2)

    def test_if_you_give_it_junk_you_get_a_resolution_error(self):
        ref = "foo://bar"
        foo_handler = mock.Mock(side_effect=ValueError("Oh no! What's this?"))
        resolver = RefResolver("", {}, handlers={"foo" : foo_handler})
        with self.assertRaises(RefResolutionError) as err:
            with resolver.resolving(ref):
                pass
        self.assertEqual(str(err.exception), "Oh no! What's this?")
Ejemplo n.º 16
0
 def test_helpful_error_message_on_failed_pop_scope(self):
     resolver = RefResolver("", {})
     resolver.pop_scope()
     with self.assertRaises(RefResolutionError) as exc:
         resolver.pop_scope()
     self.assertIn("Failed to pop the scope", str(exc.exception))
Ejemplo n.º 17
0
 def setUp(self):
     self.referrer = {}
     self.store = {self.stored_uri: self.stored_schema}
     self.resolver = RefResolver(self.base_uri, self.referrer, self.store)
Ejemplo n.º 18
0
class Spec(object):
    """Represents a Swagger Specification for a service.

    :param spec_dict: Swagger API specification in json-like dict form
    :param origin_url: URL from which the spec was retrieved.
    :param http_client: Used to retrieve the spec via http/https.
    :type http_client: :class:`bravado.http_client.HTTPClient`
    :param config: Configuration dict. See CONFIG_DEFAULTS.
    """
    def __init__(self,
                 spec_dict,
                 origin_url=None,
                 http_client=None,
                 config=None):
        self.spec_dict = spec_dict
        self.origin_url = origin_url
        self.http_client = http_client
        self.api_url = None
        self.config = dict(CONFIG_DEFAULTS, **(config or {}))

        # (key, value) = (simple format def name, Model type)
        # (key, value) = (#/ format def ref, Model type)
        self.definitions = {}

        # (key, value) = (simple resource name, Resource)
        # (key, value) = (#/ format resource ref, Resource)
        self.resources = None

        # (key, value) = (simple ref name, param_spec in dict form)
        # (key, value) = (#/ format ref name, param_spec in dict form)
        self.params = None

        # Built on-demand - see get_op_for_request(..)
        self._request_to_op_map = None

        # (key, value) = (format name, SwaggerFormat)
        self.user_defined_formats = {}
        self.format_checker = FormatChecker()

        self.resolver = RefResolver(
            base_uri=origin_url or '',
            referrer=self.spec_dict,
            handlers=build_http_handlers(http_client),
        )

        # spec dict used to build resources, in case internally_dereference_refs config is enabled
        # it will be overridden by the dereferenced specs (by build method). More context in PR#263
        self._internal_spec_dict = spec_dict

    @cached_property
    def client_spec_dict(self):
        """Return a copy of spec_dict with x-scope metadata removed so that it
        is suitable for consumption by Swagger clients.

        You may be asking, "Why is there a difference between the Swagger spec
        a client sees and the one used internally?". Well, as part of the
        ingestion process, x-scope metadata is added to spec_dict so that
        $refs can be de-reffed successfully during request/response validation
        and marshalling. This metadata is specific to the context of the
        server and contains files and paths that are not relevant to the
        client. This is required so the client does not re-use (and in turn,
        re-creates) the invalid x-scope metadata created by the server.

        For example, a section of spec_dict that contains a ref would change
        as follows.

        Before:

          'MON': {
            '$ref': '#/definitions/DayHours',
            'x-scope': [
                'file:///happyhour/api_docs/swagger.json',
                'file:///happyhour/api_docs/swagger.json#/definitions/WeekHours'
            ]
          }

        After:

          'MON': {
            '$ref': '#/definitions/DayHours'
          }

        """
        return strip_xscope(self.spec_dict)

    @classmethod
    def from_dict(cls,
                  spec_dict,
                  origin_url=None,
                  http_client=None,
                  config=None):
        """Build a :class:`Spec` from Swagger API Specification

        :param spec_dict: swagger spec in json-like dict form.
        :param origin_url: the url used to retrieve the spec, if any
        :type  origin_url: str
        :param http_client: http client used to download remote $refs
        :param config: Configuration dict. See CONFIG_DEFAULTS.
        """
        spec = cls(spec_dict, origin_url, http_client, config)
        spec.build()
        return spec

    def _validate_spec(self):
        if self.config['validate_swagger_spec']:
            self.resolver = validator20.validate_spec(
                spec_dict=self.spec_dict,
                spec_url=self.origin_url or '',
                http_handlers=build_http_handlers(self.http_client),
            )

    def build(self):
        self._validate_spec()

        model_discovery(self)

        if self.config['internally_dereference_refs']:
            # Avoid to evaluate is_ref every time, no references are possible at this time
            self.deref = lambda ref_dict: ref_dict
            self._internal_spec_dict = self.deref_flattened_spec

        for user_defined_format in self.config['formats']:
            self.register_format(user_defined_format)

        self.resources = build_resources(self)

        self.api_url = build_api_serving_url(self.spec_dict, self.origin_url)

    def _force_deref(self, ref_dict):
        """Dereference ref_dict (if it is indeed a ref) and return what the
        ref points to.

        :param ref_dict:  {'$ref': '#/blah/blah'}
        :return: dereferenced value of ref_dict
        :rtype: scalar, list, dict
        """
        if ref_dict is None or not is_ref(ref_dict):
            return ref_dict

        # Restore attached resolution scope before resolving since the
        # resolver doesn't have a traversal history (accumulated scope_stack)
        # when asked to resolve.
        with in_scope(self.resolver, ref_dict):
            _, target = self.resolver.resolve(ref_dict['$ref'])
            return target

    # NOTE: deref gets overridden, if internally_dereference_refs is enabled, after calling build
    deref = _force_deref

    def get_op_for_request(self, http_method, path_pattern):
        """Return the Swagger operation for the passed in request http method
        and path pattern. Makes it really easy for server-side implementations
        to map incoming requests to the Swagger spec.

        :param http_method: http method of the request
        :param path_pattern: request path pattern. e.g. /foo/{bar}/baz/{id}

        :returns: the matching operation or None if a match couldn't be found
        :rtype: :class:`bravado_core.operation.Operation`
        """
        if self._request_to_op_map is None:
            # lazy initialization
            self._request_to_op_map = {}
            base_path = self.spec_dict.get('basePath', '').rstrip('/')
            for resource in self.resources.values():
                for op in resource.operations.values():
                    full_path = base_path + op.path_name
                    key = (op.http_method, full_path)
                    self._request_to_op_map[key] = op

        key = (http_method.lower(), path_pattern)
        return self._request_to_op_map.get(key)

    def register_format(self, user_defined_format):
        """Registers a user-defined format to be used with this spec.

        :type user_defined_format:
            :class:`bravado_core.formatter.SwaggerFormat`
        """
        name = user_defined_format.format
        self.user_defined_formats[name] = user_defined_format
        validate = return_true_wrapper(user_defined_format.validate)
        self.format_checker.checks(name,
                                   raises=(SwaggerValidationError, ))(validate)

    def get_format(self, name):
        """
        :param name: Name of the format to retrieve
        :rtype: :class:`bravado_core.formatter.SwaggerFormat`
        """
        user_defined_format = self.user_defined_formats.get(name)
        if user_defined_format is None:
            user_defined_format = formatter.DEFAULT_FORMATS.get(name)

        if user_defined_format is None:
            warnings.warn(
                message='{0} format is not registered with bravado-core!'.
                format(name),
                category=Warning,
            )
        return user_defined_format

    @cached_property
    def security_definitions(self):
        security_defs = {}
        for security_name, security_def in iteritems(
                self.spec_dict.get('securityDefinitions', {})):
            security_defs[security_name] = SecurityDefinition(
                self, security_def)
        return security_defs

    @cached_property
    def flattened_spec(self):
        """
        Representation of the current swagger specs that could be written to a single file.
        :rtype: dict
        """

        if not self.config['validate_swagger_spec']:
            log.warning(
                'Flattening unvalidated specs could produce invalid specs. '
                'Use it at your risk or enable `validate_swagger_specs`', )

        return strip_xscope(spec_dict=flattened_spec(swagger_spec=self), )

    @cached_property
    def deref_flattened_spec(self):
        deref_spec_dict = JsonRef.replace_refs(self.flattened_spec)

        @memoize_by_id
        def descend(obj):
            # Inline modification of obj
            # This method is needed because JsonRef could produce performance penalties in accessing
            # the proxied attributes
            if isinstance(obj, JsonRef):
                # Extract the proxied value
                # http://jsonref.readthedocs.io/en/latest/#jsonref.JsonRef.__subject__
                return obj.__subject__
            if is_dict_like(obj):
                for key in list(iterkeys(obj)):
                    obj[key] = descend(obj[key])
            elif is_list_like(obj):
                # obj is list like object provided from flattened_spec specs.
                # This guarantees that it cannot be a tuple instance and
                # inline object modification are allowed
                for index in range(len(obj)):
                    obj[index] = descend(obj[index])
            return obj

        try:
            return descend(deref_spec_dict)
        finally:
            # Make sure that all memory allocated, for caching, could be released
            descend.cache.clear()
Ejemplo n.º 19
0
def validate_schema_value(schema, value, swagger_resolver=None):
    # pass resolver to avoid to refetch schema files
    if swagger_resolver is None:
        swagger_resolver = RefResolver.from_schema(schema)
    create_dereffing_validator(swagger_resolver)(schema, resolver=swagger_resolver).validate(value)
Ejemplo n.º 20
0
 def __init__(self, merger, base, head):
     Walk.__init__(self, merger)
     self.base_resolver = RefResolver("", base.val)
     self.head_resolver = RefResolver("", head.val)
Ejemplo n.º 21
0
class Spec(object):
    """Represents a Swagger Specification for a service.

    :param spec_dict: Swagger API specification in json-like dict form
    :param origin_url: URL from which the spec was retrieved.
    :param http_client: Used to retrive the spec via http/https.
    :type http_client: :class:`bravado.http_client.HTTPClient`
    :param config: Configuration dict. See CONFIG_DEFAULTS.
    """
    def __init__(self, spec_dict, origin_url=None, http_client=None,
                 config=None):
        self.spec_dict = spec_dict
        self.origin_url = origin_url
        self.http_client = http_client
        self.api_url = None
        self.config = dict(CONFIG_DEFAULTS, **(config or {}))

        # (key, value) = (simple format def name, Model type)
        # (key, value) = (#/ format def ref, Model type)
        self.definitions = {}

        # (key, value) = (simple resource name, Resource)
        # (key, value) = (#/ format resource ref, Resource)
        self.resources = None

        # (key, value) = (simple ref name, param_spec in dict form)
        # (key, value) = (#/ format ref name, param_spec in dict form)
        self.params = None

        # Built on-demand - see get_op_for_request(..)
        self._request_to_op_map = None

        # (key, value) = (format name, SwaggerFormat)
        self.user_defined_formats = {}
        self.format_checker = FormatChecker()

        self.resolver = RefResolver(
            base_uri=origin_url or '',
            referrer=self.spec_dict,
            handlers=build_http_handlers(http_client))

    @classmethod
    def from_dict(cls, spec_dict, origin_url=None, http_client=None,
                  config=None):
        """Build a :class:`Spec` from Swagger API Specificiation

        :param spec_dict: swagger spec in json-like dict form.
        :param origin_url: the url used to retrieve the spec, if any
        :type  origin_url: str
        :param config: Configuration dict. See CONFIG_DEFAULTS.
        """
        spec = cls(spec_dict, origin_url, http_client, config)
        spec.build()
        return spec

    def build(self):
        if self.config['validate_swagger_spec']:
            self.resolver = validator20.validate_spec(
                self.spec_dict, spec_url=self.origin_url or '',
                http_handlers=build_http_handlers(self.http_client))

        post_process_spec(
            self,
            on_container_callbacks=[
                functools.partial(
                    tag_models, visited_models={}, swagger_spec=self),
                functools.partial(
                    collect_models, models=self.definitions,
                    swagger_spec=self)
            ])

        for format in self.config['formats']:
            self.register_format(format)

        self.api_url = build_api_serving_url(self.spec_dict, self.origin_url)
        self.resources = build_resources(self)

    def deref(self, ref_dict):
        """Dereference ref_dict (if it is indeed a ref) and return what the
        ref points to.

        :param ref_dict:  {'$ref': '#/blah/blah'}
        :return: dereferenced value of ref_dict
        :rtype: scalar, list, dict
        """
        if ref_dict is None or not is_ref(ref_dict):
            return ref_dict

        # Restore attached resolution scope before resolving since the
        # resolver doesn't have a traversal history (accumulated scope_stack)
        # when asked to resolve.
        with in_scope(self.resolver, ref_dict):
            log.debug('Resolving {0} with scope {1}: {2}'.format(
                ref_dict['$ref'],
                len(self.resolver._scopes_stack),
                self.resolver._scopes_stack))

            _, target = self.resolver.resolve(ref_dict['$ref'])
            return target

    def get_op_for_request(self, http_method, path_pattern):
        """Return the Swagger operation for the passed in request http method
        and path pattern. Makes it really easy for server-side implementations
        to map incoming requests to the Swagger spec.

        :param http_method: http method of the request
        :param path_pattern: request path pattern. e.g. /foo/{bar}/baz/{id}

        :returns: the matching operation or None if a match couldn't be found
        :rtype: :class:`bravado_core.operation.Operation`
        """
        if self._request_to_op_map is None:
            # lazy initialization
            self._request_to_op_map = {}
            base_path = self.spec_dict.get('basePath', '').rstrip('/')
            for resource in self.resources.values():
                for op in resource.operations.values():
                    full_path = base_path + op.path_name
                    key = (op.http_method, full_path)
                    self._request_to_op_map[key] = op

        key = (http_method.lower(), path_pattern)
        return self._request_to_op_map.get(key)

    def register_format(self, user_defined_format):
        """Registers a user-defined format to be used with this spec.

        :type user_defined_format:
            :class:`bravado_core.formatter.SwaggerFormat`
        """
        name = user_defined_format.format
        self.user_defined_formats[name] = user_defined_format
        validate = return_true_wrapper(user_defined_format.validate)
        self.format_checker.checks(
            name, raises=(SwaggerValidationError,))(validate)

    def get_format(self, name):
        """
        :param name: Name of the format to retrieve
        :rtype: :class:`bravado_core.formatters.SwaggerFormat`
        """
        if name in formatter.DEFAULT_FORMATS:
            return formatter.DEFAULT_FORMATS[name]
        format = self.user_defined_formats.get(name)
        if format is None:
            warnings.warn('{0} format is not registered with bravado-core!'
                          .format(name), Warning)
        return format
Ejemplo n.º 22
0
class Spec(object):
    """Represents a Swagger Specification for a service.

    :param spec_dict: Swagger API specification in json-like dict form
    :param origin_url: URL from which the spec was retrieved.
    :param http_client: Used to retrive the spec via http/https.
    :type http_client: :class:`bravado.http_client.HTTPClient`
    :param config: Configuration dict. See CONFIG_DEFAULTS.
    """
    def __init__(self,
                 spec_dict,
                 origin_url=None,
                 http_client=None,
                 config=None):
        self.spec_dict = spec_dict
        self.origin_url = origin_url
        self.http_client = http_client
        self.api_url = None
        self.config = dict(CONFIG_DEFAULTS, **(config or {}))

        # Cached copy of spec_dict with x-scope metadata removed.
        # See @property client_spec_dict().
        self._client_spec_dict = None

        # (key, value) = (simple format def name, Model type)
        # (key, value) = (#/ format def ref, Model type)
        self.definitions = {}

        # (key, value) = (simple resource name, Resource)
        # (key, value) = (#/ format resource ref, Resource)
        self.resources = None

        # (key, value) = (simple ref name, param_spec in dict form)
        # (key, value) = (#/ format ref name, param_spec in dict form)
        self.params = None

        # Built on-demand - see get_op_for_request(..)
        self._request_to_op_map = None

        # (key, value) = (format name, SwaggerFormat)
        self.user_defined_formats = {}
        self.format_checker = FormatChecker()

        self.resolver = RefResolver(base_uri=origin_url or '',
                                    referrer=self.spec_dict,
                                    handlers=build_http_handlers(http_client))

    @property
    def client_spec_dict(self):
        """Return a copy of spec_dict with x-scope metadata removed so that it
        is suitable for consumption by Swagger clients.

        You may be asking, "Why is there a difference between the Swagger spec
        a client sees and the one used internally?". Well, as part of the
        ingestion process, x-scope metadata is added to spec_dict so that
        $refs can be de-reffed successfully during requset/response validation
        and marshalling. This medatadata is specific to the context of the
        server and contains files and paths that are not relevant to the
        client. This is required so the client does not re-use (and in turn,
        re-creates) the invalid x-scope metadata created by the server.

        For example, a section of spec_dict that contains a ref would change
        as folows.

        Before:

          'MON': {
            '$ref': '#/definitions/DayHours',
            'x-scope': [
                'file:///happyhour/api_docs/swagger.json',
                'file:///happyhour/api_docs/swagger.json#/definitions/WeekHours'
            ]
          }

        After:

          'MON': {
            '$ref': '#/definitions/DayHours'
          }

        """
        if self._client_spec_dict is None:
            self._client_spec_dict = strip_xscope(self.spec_dict)
        return self._client_spec_dict

    @classmethod
    def from_dict(cls,
                  spec_dict,
                  origin_url=None,
                  http_client=None,
                  config=None):
        """Build a :class:`Spec` from Swagger API Specificiation

        :param spec_dict: swagger spec in json-like dict form.
        :param origin_url: the url used to retrieve the spec, if any
        :type  origin_url: str
        :param: http_client: http client used to download remote $refs
        :param config: Configuration dict. See CONFIG_DEFAULTS.
        """
        spec = cls(spec_dict, origin_url, http_client, config)
        spec.build()
        return spec

    def build(self):
        if self.config['validate_swagger_spec']:
            self.resolver = validator20.validate_spec(
                self.spec_dict,
                spec_url=self.origin_url or '',
                http_handlers=build_http_handlers(self.http_client))

        post_process_spec(self,
                          on_container_callbacks=[
                              functools.partial(tag_models,
                                                visited_models={},
                                                swagger_spec=self),
                              functools.partial(collect_models,
                                                models=self.definitions,
                                                swagger_spec=self)
                          ])

        for format in self.config['formats']:
            self.register_format(format)

        self.api_url = build_api_serving_url(self.spec_dict, self.origin_url)
        self.resources = build_resources(self)

    def deref(self, ref_dict):
        """Dereference ref_dict (if it is indeed a ref) and return what the
        ref points to.

        :param ref_dict:  {'$ref': '#/blah/blah'}
        :return: dereferenced value of ref_dict
        :rtype: scalar, list, dict
        """
        if ref_dict is None or not is_ref(ref_dict):
            return ref_dict

        # Restore attached resolution scope before resolving since the
        # resolver doesn't have a traversal history (accumulated scope_stack)
        # when asked to resolve.
        with in_scope(self.resolver, ref_dict):
            log.debug('Resolving {0} with scope {1}: {2}'.format(
                ref_dict['$ref'], len(self.resolver._scopes_stack),
                self.resolver._scopes_stack))

            _, target = self.resolver.resolve(ref_dict['$ref'])
            return target

    def get_op_for_request(self, http_method, path_pattern):
        """Return the Swagger operation for the passed in request http method
        and path pattern. Makes it really easy for server-side implementations
        to map incoming requests to the Swagger spec.

        :param http_method: http method of the request
        :param path_pattern: request path pattern. e.g. /foo/{bar}/baz/{id}

        :returns: the matching operation or None if a match couldn't be found
        :rtype: :class:`bravado_core.operation.Operation`
        """
        if self._request_to_op_map is None:
            # lazy initialization
            self._request_to_op_map = {}
            base_path = self.spec_dict.get('basePath', '').rstrip('/')
            for resource in self.resources.values():
                for op in resource.operations.values():
                    full_path = base_path + op.path_name
                    key = (op.http_method, full_path)
                    self._request_to_op_map[key] = op

        key = (http_method.lower(), path_pattern)
        return self._request_to_op_map.get(key)

    def register_format(self, user_defined_format):
        """Registers a user-defined format to be used with this spec.

        :type user_defined_format:
            :class:`bravado_core.formatter.SwaggerFormat`
        """
        name = user_defined_format.format
        self.user_defined_formats[name] = user_defined_format
        validate = return_true_wrapper(user_defined_format.validate)
        self.format_checker.checks(name,
                                   raises=(SwaggerValidationError, ))(validate)

    def get_format(self, name):
        """
        :param name: Name of the format to retrieve
        :rtype: :class:`bravado_core.formatters.SwaggerFormat`
        """
        if name in formatter.DEFAULT_FORMATS:
            return formatter.DEFAULT_FORMATS[name]
        format = self.user_defined_formats.get(name)
        if format is None:
            warnings.warn(
                '{0} format is not registered with bravado-core!'.format(name),
                Warning)
        return format
Ejemplo n.º 23
0
class TestRefResolver(unittest.TestCase):

    base_uri = ""
    stored_uri = "foo://stored"
    stored_schema = {"stored": "schema"}

    def setUp(self):
        self.referrer = {}
        self.store = {self.stored_uri: self.stored_schema}
        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:
            with self.resolver.resolving(ref) as resolved:
                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()}
        with self.resolver.resolving(ref) as resolved:
            self.assertEqual(resolved, self.referrer["properties"]["foo"])

    def test_it_resolves_local_refs_with_id(self):
        schema = {"id": "foo://bar/schema#", "a": {"foo": "bar"}}
        resolver = RefResolver.from_schema(schema)
        with resolver.resolving("#/a") as resolved:
            self.assertEqual(resolved, schema["a"])
        with resolver.resolving("foo://bar/schema#/a") as resolved:
            self.assertEqual(resolved, schema["a"])

    def test_it_retrieves_stored_refs(self):
        with self.resolver.resolving(self.stored_uri) as resolved:
            self.assertIs(resolved, self.stored_schema)

        self.resolver.store["cached_ref"] = {"foo": 12}
        with self.resolver.resolving("cached_ref#/foo") as resolved:
            self.assertEqual(resolved, 12)

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

        with mock.patch("jsonschema.validators.requests") as requests:
            requests.get.return_value.json.return_value = schema
            with self.resolver.resolving(ref) as resolved:
                self.assertEqual(resolved, 12)
        requests.get.assert_called_once_with("http://bar")

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

        with mock.patch("jsonschema.validators.requests", None):
            with mock.patch("jsonschema.validators.urlopen") as urlopen:
                urlopen.return_value.read.return_value = (
                    json.dumps(schema).encode("utf8"))
                with self.resolver.resolving(ref) as resolved:
                    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")
        with resolver.resolving("") as resolved:
            self.assertEqual(resolved, schema)
        with resolver.resolving("#") as resolved:
            self.assertEqual(resolved, schema)
        with resolver.resolving("foo") as resolved:
            self.assertEqual(resolved, schema)
        with resolver.resolving("foo#") as resolved:
            self.assertEqual(resolved, 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, "")
        with resolver.resolving("") as resolved:
            self.assertEqual(resolved, schema)
        with resolver.resolving("#") as resolved:
            self.assertEqual(resolved, schema)

    def test_custom_uri_scheme_handlers(self):
        schema = {"foo": "bar"}
        ref = "foo://bar"
        foo_handler = mock.Mock(return_value=schema)
        resolver = RefResolver("", {}, handlers={"foo": foo_handler})
        with resolver.resolving(ref) as resolved:
            self.assertEqual(resolved, schema)
        foo_handler.assert_called_once_with(ref)

    def test_cache_remote_on(self):
        ref = "foo://bar"
        foo_handler = mock.Mock()
        resolver = RefResolver(
            "",
            {},
            cache_remote=True,
            handlers={"foo": foo_handler},
        )
        with resolver.resolving(ref):
            pass
        with resolver.resolving(ref):
            pass
        foo_handler.assert_called_once_with(ref)

    def test_cache_remote_off(self):
        ref = "foo://bar"
        foo_handler = mock.Mock()
        resolver = RefResolver(
            "",
            {},
            cache_remote=False,
            handlers={"foo": foo_handler},
        )
        with resolver.resolving(ref):
            pass
        with resolver.resolving(ref):
            pass
        self.assertEqual(foo_handler.call_count, 2)

    def test_if_you_give_it_junk_you_get_a_resolution_error(self):
        ref = "foo://bar"
        foo_handler = mock.Mock(side_effect=ValueError("Oh no! What's this?"))
        resolver = RefResolver("", {}, handlers={"foo": foo_handler})
        with self.assertRaises(RefResolutionError) as err:
            with resolver.resolving(ref):
                pass
        self.assertEqual(str(err.exception), "Oh no! What's this?")
Ejemplo n.º 24
0
 def dereferencer(self, spec_dict):
     spec_resolver = RefResolver('', spec_dict, handlers=default_handlers)
     return Dereferencer(spec_resolver)
Ejemplo n.º 25
0
def run(arguments, stdout=sys.stdout, stderr=sys.stderr, stdin=sys.stdin):
    outputter = _Outputter.from_arguments(
        arguments=arguments,
        stdout=stdout,
        stderr=stderr,
    )

    try:
        schema = outputter.load(arguments["schema"])
    except _CannotLoadFile:
        return 1

    if arguments["validator"] is None:
        arguments["validator"] = validator_for(schema)

    try:
        arguments["validator"].check_schema(schema)
    except SchemaError as error:
        outputter.validation_error(
            instance_path=arguments["schema"],
            error=error,
        )
        return 1

    if arguments["instances"]:
        load, instances = outputter.load, arguments["instances"]
    else:

        def load(_):
            try:
                return json.load(stdin)
            except JSONDecodeError:
                outputter.parsing_error(
                    path="<stdin>",
                    exc_info=sys.exc_info(),
                )
                raise _CannotLoadFile()

        instances = ["<stdin>"]

    resolver = RefResolver(
        base_uri=arguments["base_uri"],
        referrer=schema,
    ) if arguments["base_uri"] is not None else None

    validator = arguments["validator"](schema, resolver=resolver)
    exit_code = 0
    for each in instances:
        try:
            instance = load(each)
        except _CannotLoadFile:
            exit_code = 1
        else:
            exit_code |= _validate_instance(
                instance_path=each,
                instance=instance,
                validator=validator,
                outputter=outputter,
            )

    return exit_code
Ejemplo n.º 26
0
class SchemaTools():
    """
        The schema tools class does all the handling, validation and transformation of schemas in the optimal framework.
    """
    # The json_schema_objects keeps the parsed and instantiated schema objects.
    json_schema_objects = None
    # json_schema_folder is the folder of where the json schemas are kept
    json_schema_folders = None

    # The resolve is responsible for resolving URI prefixes referencing other schemas,
    resolver = None
    # The URI-handlers are a dict of callbacks, used when resolving URI:s.
    uri_handlers = None

    # An instance of the mongodb JSON validator.
    mongodb_validator = None


    # Default handler, will only look at the cache
    def cache_handler(self, _uri):
        print ("fetching " + _uri)
        if _uri in self.json_schema_objects:
            return self.json_schema_objects[_uri]
        else:
            return None

    # noinspection PyDefaultArgument
    def __init__(self, _json_schema_folders=[], _uri_handlers=None):
        """
        Initiate the SchemaTools class

        :param _json_schema_folders: A list of folders where schema files are stored
        :param : _uri_handlers: A dict of uri_handlers, resolves a URI prefix to a actual schema.

        """

        if not _json_schema_folders:
            _json_schema_folders = []

        if not _uri_handlers:
            self.uri_handlers = {}
        else:
            self.uri_handlers = _uri_handlers


        # All methods that have no handlers should use the cache handler.
        for _curr_key, _curr_value  in _uri_handlers.items():
            if _curr_value is None:
                _uri_handlers[_curr_key] = self.cache_handler

        self.resolver = RefResolver(base_uri="",
                                handlers=self.uri_handlers, referrer=None, cache_remote=True)

        self.mongodb_validator = MongodbValidator(resolver= self.resolver)

        self.json_schema_objects = {}

        # Load application specific schemas
        for _curr_folder in _json_schema_folders:
            _loaded_uris = self.load_schemas_from_directory(os.path.abspath(_curr_folder))

            # Resolve all the schemas
            for _curr_uri in _loaded_uris:
                self.json_schema_objects[_curr_uri] = self.resolveSchema(self.json_schema_objects[_curr_uri])

        write_to_log("Schemas loaded and resolved: " +
                     str.join(", ",  ["\"" +_curr_schema["title"] + "\""  for _curr_schema in self.json_schema_objects.values()])
                     , _category=EC_NOTIFICATION, _severity=SEV_DEBUG)


    @staticmethod
    def check_schema_fields(_curr_schema_obj, _curr_file):
        """ Check so all mandatory fields are in the schema
        :param _curr_schema_obj: Schema to check
        :param _curr_file: File name use in error message

        """

        def raise_field_error(_collection):
            raise Exception("Schematools.check_schema_fields: The \"" + _collection + "\"" +
                            " field is not in the schema-\"" + _curr_file + "\"")

        if "version" not in _curr_schema_obj:
            raise_field_error("version")

    def load_schema_from_file(self, _file_name):
        """
        Loads a specified schema from a file, checks it and stores it in the schema cache.

        :param _file_name: The name of the schema file

        """
        try:
            _curr_file = open(_file_name, "r")
        except Exception as e:
            raise Exception("load_schema_from_file: Error loading \"" + _file_name +
                            "\": " + str(e))
        try:
            _json_schema_obj = json.load(_curr_file)

        except Exception as e:
            raise Exception("load_schema_from_file: Error parsing \"" + _file_name +
                            "\"" + str(e))

        _curr_file.close()

        try:
            self.check_schema_fields(_json_schema_obj, _file_name)
        except SchemaError as scherr:
            raise Exception("load_schema_from_file: SchemaError in " + _file_name + " at path:" + str(
                scherr.path) + "\nMessage:\n" + str(scherr.message))
        except Exception as e:
            raise Exception("load_schema_from_file: schema validation in " + _file_name + ", error :" + str(e))

        return _json_schema_obj

    def load_schemas_from_directory(self, _schema_folder, _destination = None):
        """
        Load and validate all schemas in a folder structure, add to json_schema_objects

        :param _schema_folder: Where to look

        """
        _loaded_uris = []
        if _destination == None:
            _destination = self.json_schema_objects

        def _recurse(_folder):
            for _root, _dirs, _files in os.walk(_folder):
                for _file in _files:
                    if _file[-5:].lower() == ".json":
                        _ref = "ref://" + ".".join(os.path.relpath(_root, _schema_folder).split(os.path.sep) + [_file[0:-5]])
                        _destination[_ref] = self.load_schema_from_file (os.path.join(_root, _file))
                        if _ref not in _loaded_uris:
                            _loaded_uris.append(_ref)



                for _dir in _dirs:
                    _recurse(os.path.join(_folder, _dir))

        _recurse(_schema_folder)

        return  _loaded_uris


    def apply(self, _data, _schema_ref=None):
        """
        Validate the JSON in _data against a JSON schema.

        :param _data: The JSON data to validate
        :param _schema_ref: If set, validate against the specified schema, and not the one in the data.
        :return: the schema object that was validated against.

        """
        if _schema_ref is not None:
            _json_schema_obj = self.json_schema_objects[_schema_ref]
        else:
            if "schemaRef" in _data:
                try:
                    _json_schema_obj = self.json_schema_objects[_data["schemaRef"]]
                except KeyError as e:
                    raise Exception("SchemaTools.apply, invalid schemaRef: " + _data["schemaRef"])
            else:
                raise Exception("SchemaTools.apply, data must have a schemaRef attribute")

        self.mongodb_validator.apply(_data, _json_schema_obj)
        return _data, _json_schema_obj

    def validate(self, _data, _schema_ref=None):
        """
        Validate the JSON in _data against a JSON schema.

        :param _data: The JSON data to validate
        :param _schema_ref: If set, validate against the specified schema, and not the one in the data.
        :return: the schema object that was validated against.

        """
        if _schema_ref is not None:
            _json_schema_obj = self.json_schema_objects[_schema_ref]
        else:
            _json_schema_obj = self.json_schema_objects[_data["schemaRef"]]

        self.mongodb_validator.validate(_data, _json_schema_obj)
        return _data, _json_schema_obj


    def non_base_type(self, _type):
        if _type not in ["array", "string", "integer", "object"]:
            return [_type]
        else:
            return []

    def resolveSchema(self, _schema):
        """
        Recursively resolve all I{$ref} JSON references in a JSON Schema.
        :param _schema: A L{dict} with a JSON Schema.
        :return: The resolved JSON Schema, a L{dict}.
        """
        _result = deepcopy(_schema)



        def local_resolve(_obj, _ref_history):
            """
            Recurse the JSON-tree and see where there are unresolved remote references.
            :param _obj: The node to resolve
            :param _ref_history: The previously resolved remote references, for cyclical check
            """

            if isinstance(_obj, list):
                # Loop any list
                for item in _obj:
                    local_resolve(item, _ref_history)
                return

            if isinstance(_obj, dict):

                if "$ref" in _obj:
                    _curr_ref = _obj["$ref"]

                    # Do not resolve local references
                    if _curr_ref[0] == "#":
                        return

                    # Check for cyclical references
                    if _curr_ref in _ref_history:
                        raise Exception("Error, cyclical remote reference: " + str(_curr_ref) + ":  Formers " + str(_ref_history))

                    with self.resolver.resolving(_curr_ref) as resolved:
                        # Resolve the resolved schema
                        local_resolve(resolved, _ref_history + [_curr_ref])
                        # Remove the reference
                        del _obj["$ref"]
                        # Add the resolved fragment to the schema
                        _obj.update(resolved)


                else:
                    # Loop all properties
                    for _key, _value in _obj.items():
                        if isinstance(_value, dict) and "type" in _value:
                            local_resolve(_value, _ref_history)

                        else:
                            local_resolve(_value, _ref_history)
        try:
            local_resolve(_result, [])
        except Exception as e:
            raise Exception("schemaTools.resolveSchema: Error resolving schema:" + str(e) + "Schema " + json.dumps(_schema, indent=4))

        # Make top allOf into properties
        if "allOf" in _result:
            _new_properties = {}
            for _curr_properties in _result["allOf"]:
                _new_properties.update(_curr_properties["properties"])

            _result["properties"] = _new_properties
            del _result["allOf"]

        _result["$schema"] = "http://json-schema.org/draft-04/schema#"

        try:
            self.mongodb_validator.check_schema(_result)
        except Exception as e:
            raise Exception("schemaTools.resolveSchema: error validating resolved schema:" + str(e) + "Schema " + json.dumps(_result, indent=4))

        return _result
Ejemplo n.º 27
0
class Spec(object):
    """Represents a Swagger Specification for a service.

    :param spec_dict: Swagger API specification in json-like dict form
    :param origin_url: URL from which the spec was retrieved.
    :param http_client: Used to retrive the spec via http/https.
    :type http_client: :class:`bravado.http_client.HTTPClient`
    :param config: Configuration dict. See CONFIG_DEFAULTS.
    """

    def __init__(self, spec_dict, origin_url=None, http_client=None,
                 config=None):
        self.spec_dict = spec_dict
        self.origin_url = origin_url
        self.http_client = http_client
        self.api_url = None
        self.config = dict(CONFIG_DEFAULTS, **(config or {}))

        # (key, value) = (simple format def name, Model type)
        # (key, value) = (#/ format def ref, Model type)
        self.definitions = {}

        # (key, value) = (simple resource name, Resource)
        # (key, value) = (#/ format resource ref, Resource)
        self.resources = None

        # (key, value) = (simple ref name, param_spec in dict form)
        # (key, value) = (#/ format ref name, param_spec in dict form)
        self.params = None

        # Built on-demand - see get_op_for_request(..)
        self._request_to_op_map = None

        # (key, value) = (format name, SwaggerFormat)
        self.user_defined_formats = {}
        self.format_checker = FormatChecker()

        self.resolver = RefResolver(
            base_uri=origin_url or '',
            referrer=self.spec_dict,
            handlers=build_http_handlers(http_client),
        )

        self._validate_config()

        if self.config['internally_dereference_refs']:
            # If internally_dereference_refs is enabled we do NOT need to resolve references anymore
            # it's useless to evaluate is_ref every time
            self.deref = lambda ref_dict: ref_dict
        else:
            self.deref = self._force_deref

    def _validate_config(self):
        """
        Validates the correctness of the configurations injected and makes sure that:
        - no extra config keys are available on the config dictionary
        - dependent configs are checked

        :return: True if the initial configs are valid, False otherwise
        :rtype: bool
        """
        are_config_changed = False

        extraneous_keys = set(iterkeys(self.config)) - set(iterkeys(CONFIG_DEFAULTS))
        if extraneous_keys:
            are_config_changed = True
            for key in extraneous_keys:
                warnings.warn(
                    message='config {} is not a recognized config key'.format(key),
                    category=Warning,
                )

        if self.config['internally_dereference_refs'] and not self.config['validate_swagger_spec']:
            are_config_changed = True
            self.config['internally_dereference_refs'] = False
            warnings.warn(
                message='internally_dereference_refs config disabled because validate_swagger_spec has to be enabled',
                category=Warning,
            )

        return not are_config_changed

    @cached_property
    def client_spec_dict(self):
        """Return a copy of spec_dict with x-scope metadata removed so that it
        is suitable for consumption by Swagger clients.

        You may be asking, "Why is there a difference between the Swagger spec
        a client sees and the one used internally?". Well, as part of the
        ingestion process, x-scope metadata is added to spec_dict so that
        $refs can be de-reffed successfully during requset/response validation
        and marshalling. This medatadata is specific to the context of the
        server and contains files and paths that are not relevant to the
        client. This is required so the client does not re-use (and in turn,
        re-creates) the invalid x-scope metadata created by the server.

        For example, a section of spec_dict that contains a ref would change
        as folows.

        Before:

          'MON': {
            '$ref': '#/definitions/DayHours',
            'x-scope': [
                'file:///happyhour/api_docs/swagger.json',
                'file:///happyhour/api_docs/swagger.json#/definitions/WeekHours'
            ]
          }

        After:

          'MON': {
            '$ref': '#/definitions/DayHours'
          }

        """
        return strip_xscope(self.spec_dict)

    @classmethod
    def from_dict(cls, spec_dict, origin_url=None, http_client=None, config=None):
        """Build a :class:`Spec` from Swagger API Specificiation

        :param spec_dict: swagger spec in json-like dict form.
        :param origin_url: the url used to retrieve the spec, if any
        :type  origin_url: str
        :param: http_client: http client used to download remote $refs
        :param config: Configuration dict. See CONFIG_DEFAULTS.
        """
        spec = cls(spec_dict, origin_url, http_client, config)
        spec.build()
        return spec

    def _validate_spec(self):
        if self.config['validate_swagger_spec']:
            self.resolver = validator20.validate_spec(
                spec_dict=self.spec_dict,
                spec_url=self.origin_url or '',
                http_handlers=build_http_handlers(self.http_client),
            )

    def build(self):
        self._validate_spec()
        post_process_spec(
            self,
            on_container_callbacks=[
                functools.partial(
                    tag_models,
                    visited_models={},
                    swagger_spec=self,
                ),
                functools.partial(
                    collect_models,
                    models=self.definitions,
                    swagger_spec=self,
                ),
            ],
        )

        for format in self.config['formats']:
            self.register_format(format)

        self.api_url = build_api_serving_url(self.spec_dict, self.origin_url)
        self.resources = build_resources(self)

    @cached_property
    def _internal_spec_dict(self):
        if self.config['internally_dereference_refs']:
            return self.deref_flattened_spec
        else:
            return self.spec_dict

    def _force_deref(self, ref_dict):
        """Dereference ref_dict (if it is indeed a ref) and return what the
        ref points to.

        :param ref_dict:  {'$ref': '#/blah/blah'}
        :return: dereferenced value of ref_dict
        :rtype: scalar, list, dict
        """
        if ref_dict is None or not is_ref(ref_dict):
            return ref_dict

        # Restore attached resolution scope before resolving since the
        # resolver doesn't have a traversal history (accumulated scope_stack)
        # when asked to resolve.
        with in_scope(self.resolver, ref_dict):
            _, target = self.resolver.resolve(ref_dict['$ref'])
            return target

    def deref(self, ref_dict):
        # This method is actually set in __init__
        pass

    def get_op_for_request(self, http_method, path_pattern):
        """Return the Swagger operation for the passed in request http method
        and path pattern. Makes it really easy for server-side implementations
        to map incoming requests to the Swagger spec.

        :param http_method: http method of the request
        :param path_pattern: request path pattern. e.g. /foo/{bar}/baz/{id}

        :returns: the matching operation or None if a match couldn't be found
        :rtype: :class:`bravado_core.operation.Operation`
        """
        if self._request_to_op_map is None:
            # lazy initialization
            self._request_to_op_map = {}
            base_path = self.spec_dict.get('basePath', '').rstrip('/')
            for resource in self.resources.values():
                for op in resource.operations.values():
                    full_path = base_path + op.path_name
                    key = (op.http_method, full_path)
                    self._request_to_op_map[key] = op

        key = (http_method.lower(), path_pattern)
        return self._request_to_op_map.get(key)

    def register_format(self, user_defined_format):
        """Registers a user-defined format to be used with this spec.

        :type user_defined_format:
            :class:`bravado_core.formatter.SwaggerFormat`
        """
        name = user_defined_format.format
        self.user_defined_formats[name] = user_defined_format
        validate = return_true_wrapper(user_defined_format.validate)
        self.format_checker.checks(
            name, raises=(SwaggerValidationError,))(validate)

    def get_format(self, name):
        """
        :param name: Name of the format to retrieve
        :rtype: :class:`bravado_core.formatters.SwaggerFormat`
        """
        if name in formatter.DEFAULT_FORMATS:
            return formatter.DEFAULT_FORMATS[name]
        format = self.user_defined_formats.get(name)
        if format is None:
            warnings.warn(
                message='{0} format is not registered with bravado-core!'.format(name),
                category=Warning,
            )
        return format

    @cached_property
    def security_definitions(self):
        security_defs = {}
        for security_name, security_def in iteritems(self.spec_dict.get('securityDefinitions', {})):
            security_defs[security_name] = SecurityDefinition(self, security_def)
        return security_defs

    @cached_property
    def flattened_spec(self):
        """
        Representation of the current swagger specs that could be written to a single file.
        NOTE: The representation strips out all the definitions that are not referenced
        :return:
        """

        if not self.config['validate_swagger_spec']:
            raise RuntimeError('Swagger Specs have to be validated before flattening.')

        # If resources are defined it means that Spec has been built and so swagger specs have been validated
        if self.resources is None:
            self._validate_spec()

        return strip_xscope(
            spec_dict=flattened_spec(
                spec_dict=self.spec_dict,
                spec_resolver=self.resolver,
                spec_url=self.origin_url,
                http_handlers=build_http_handlers(self.http_client),
                spec_definitions=self.definitions,
            ),
        )

    @cached_property
    def deref_flattened_spec(self):
        deref_spec_dict = JsonRef.replace_refs(self.flattened_spec)

        @memoize_by_id
        def descend(obj):
            # Inline modification of obj
            # This method is needed because JsonRef could produce performance penalties in accessing
            # the proxied attributes
            if isinstance(obj, JsonRef):
                # Extract the proxied value
                # http://jsonref.readthedocs.io/en/latest/#jsonref.JsonRef.__subject__
                return obj.__subject__
            if is_dict_like(obj):
                for key in list(iterkeys(obj)):
                    obj[key] = descend(obj[key])
            elif is_list_like(obj):
                # obj is list like object provided from flattened_spec specs.
                # This guarantees that it cannot be a tuple instance and
                # inline object modification are allowed
                for index in range(len(obj)):
                    obj[index] = descend(obj[index])
            return obj

        try:
            return descend(deref_spec_dict)
        finally:
            # Make sure that all memory allocated, for caching, could be released
            descend.cache.clear()