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_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_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_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_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?")
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)
class Converter(object): def __init__(self, schema): self._entire_schema = schema self._resolver = RefResolver('', self._entire_schema) def convert(self): return self._convert(self._entire_schema) def _convert(self, schema): if not schema: return Schema(object) elif isinstance(schema, list): schemas = [self._convert(x) for x in schema] return Schema(Any(*schemas)) elif isinstance(schema, dict) and '$ref' in schema: with self._resolver.resolving(schema['$ref']) as resolved: print('Followed ref: %s -> %s' % (schema['$ref'], resolved)) return self._convert(resolved) elif isinstance(schema, dict) and 'type' in schema: result = self._convert(schema['type']) if schema['type'] == 'object': required_props = schema.get('required', []) properties = schema.get('properties', {}) additional_props = schema.get('additionalProperties') # handle all keys in properties, careful to mark those fields # that are required. for key, val in properties.items(): if key in required_props: result = result.extend({ voluptuous.Required(key): self._convert(val), }) else: result = result.extend({key: self._convert(val)}) for key in required_props: if key not in properties: # required fields not mentioned in properties must # respect the additionalProperties schema (if it is not # a bool). else, any value is accepted. if isinstance(additional_props, dict): result = result.extend({ voluptuous.Required(key): self._convert(additional_props) }) else: result = result.extend({ voluptuous.Required(key): object, }) if 'additionalProperties' in schema: if additional_props is False: result.extra = voluptuous.PREVENT_EXTRA elif additional_props is True: result.extra = voluptuous.ALLOW_EXTRA else: result = result.extend({ voluptuous.Extra: self._convert(additional_props), }) if 'minProperties' in schema or 'maxProperties' in schema: length = voluptuous.Length( min=schema.get('minProperties'), max=schema.get('maxProperties'), ) result = Schema(All(result, length)) elif schema['type'] == 'array': if 'items' in schema: items = schema['items'] length = voluptuous.Length( min=schema.get('minItems'), max=schema.get('maxItems'), ) if isinstance(items, dict): return Schema(All([self._convert(items)], length)) elif isinstance(items, list): array_validator = EnumArray( [self._convert(x) for x in items], additional_items=schema.get( 'additionalItems', True), ) return Schema(All(array_validator, length)) else: raise Exception( "Invalid schema for `items`: {}".format(schema)) elif schema['type'] in ('integer', 'number'): _schemas = [result] if 'multipleOf' in schema: _schemas.append(MultipleOf(schema['multipleOf'])) r = voluptuous.Range( min=schema.get('minimum'), max=schema.get('maximum'), min_included=not schema.get('exclusiveMinimum', False), max_included=not schema.get('exclusiveMaximum', False), ) if r.min is not None or r.max is not None: _schemas.append(r) result = Schema(All(*_schemas)) return result elif isinstance(schema, dict) and 'anyOf' in schema: schemas = [self._convert(x) for x in schema['anyOf']] return Schema(Any(*schemas)) elif isinstance(schema, dict) and 'allOf' in schema: schemas = [self._convert(x) for x in schema['allOf']] return Schema(All(*schemas)) elif isinstance(schema, dict) and 'oneOf' in schema: schemas = [self._convert(x) for x in schema['oneOf']] return voluptuous.SomeOf(schemas, min_valid=1, max_valid=1) elif isinstance(schema, dict) and ('minLength' in schema or 'maxLength' in schema): return voluptuous.Length( min=schema.get('minLength'), max=schema.get('maxLength'), ) elif schema == 'string': return Schema(str) elif schema == 'integer': return Schema(int) elif schema == 'number': return Schema(Any(int, float)) elif schema == 'boolean': return Schema(bool) elif schema == 'null': return Schema(None) elif schema == 'object': return Schema({}, extra=voluptuous.ALLOW_EXTRA) elif schema == 'array': return Schema(list) else: raise Exception("Failed to convert schema: %s" % schema)
class Api(object): """Decorator (decorator builder) for webapp2 request handler. Generate routes, schemas and the swagger api doc of an api. """ # api doc `swaggerVersion` attribute swagger_version = "1.2" def __init__(self, host, path, version): """Api constructor. `host`: used for the schema URI. `path`: used a prefix for the route. `version`: used for the api doc `apiVersion` attribute """ if path[0] != "/": raise ValueError("path cannot be relative") self.host = host.rstrip("/") self.path = path.rstrip("/") self.version = version self.resources = {} self._schemas = {} self._resolver = RefResolver(self.schema_path, {}, store={}) @property def base_path(self): return "".join([self.host, self.path]) @property def schema_path(self): return "%s/json-schemas" % self.base_path def api_doc(self): """Generate the api doc (as a dict). It generate a route documentation listing all the resources. """ return { "apiVersion": self.version, "swaggerVersion": self.swagger_version, "apis": sorted([r.summary() for r in self.resources.values()], key=operator.itemgetter("path")), } def _json_handler(self, data, status=200): resp = webapp2.Response(json.dumps(data, sort_keys=True, indent=4)) resp.headers["Content-Type"] = "application/json" resp.status = status return resp def schema_handler(self, request): """http handler for the schema request. """ return self._json_handler(self.schemas()) def api_doc_handler(self, request): """http handler for the route api-doc request. """ return self._json_handler(self.api_doc()) def apis_handler(self, request, path): """http handler for a resource api-doc request. """ resource = self.resources.get("/%s" % path, None) if resource is None: return self._json_handler({"error": "resource not found"}, 404) return self._json_handler(resource.api_doc()) def routes(self): """Return a route collection for an api (including the api-doc and schema): - the request handler routes are define by the `swagger.ApiRequestHandler.path` class attributes. - the api-doc path `<api.path>/api-docs` - the schema path `<api.path>/json-schemas/` """ rel_routes = [] rel_routes.append(webapp2.Route("/api-docs", self.api_doc_handler, methods=["GET"])) rel_routes.append(webapp2.Route("/api-docs/<path:.+>", self.apis_handler, methods=["GET"])) rel_routes.append(webapp2.Route("/json-schemas", self.schema_handler, methods=["GET"])) for resource in self.resources.itervalues(): for api in resource.apis.itervalues(): rel_routes.append(webapp2.Route(api.path, api.handler)) return routes.PathPrefixRoute(self.path, rel_routes) def resource(self, path, desc=None): """Define a new resource. """ if path not in self.resources: self.resources[path] = _Resource(self, path, desc) return self.resources[path] def schema(self, name, properties=None, additional_properties=False, **kw): """Create a new schema definition. The base schema can currently only be defined as objects (swagger only define models as object). """ properties = {} if properties is None else properties kw.setdefault("required", []) for prop_name, prop in properties.iteritems(): if prop.required: kw["required"].append(prop_name) prop.required = None definition = Object(id=name, properties=properties, additional_properties=additional_properties, **kw) self._schemas[name] = definition self._update_resolver() def schemas(self): """Json-schema for all complex type defined in an API. """ schemas = {"id": "%s#" % self.schema_path, "$schema": "http://json-schema.org/draft-04/schema#"} for s_id, s in self._schemas.iteritems(): if s is None: continue schemas[s_id] = s return to_dict(schemas, ctx=_Context(self)) def ref(self, name, required=False): """Return an object with "$ref" attribute. Suitable to be used in json schema document. Use Api.model if the reference is to be used in swagger api document. """ self._schemas.setdefault(name, None) return _Ref(name, required=required) def _update_resolver(self): self._resolver.store[self.schema_path] = self.schemas() def validate(self, schema, data): """Create json-schema validator for a complex type. """ with self._resolver.resolving("#/%s" % schema) as schema: validator = Draft4Validator(schema, resolver=self._resolver) validator.validate(data)
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: 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_retrieves_stored_refs(self): 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.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.requests", None): with mock.patch("jsonschema.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") 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) 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)
"type": "object", "properties": { "name": {"type": "string"}, "mean": {"$ref": '#/evaluationResult'}, "student": {"$ref": '#/evaluationResult'}, }, "required": ["name", "mean", "student"] }, "evaluationResult": { "id": "#evaluation-result", "type": "number", "minimum": -1, "maximum": 1, }, } resolver = RefResolver( _base_uri, {}, store={ '%s/schemas/education.json' % _base_uri: _education_schemas, '%s/schemas/education/portfolio.json' % _base_uri: _portfolio_schemas, } ) with resolver.resolving('schemas/education.json#student') as schema: student = Draft4Validator(schema, resolver=resolver) with resolver.resolving('schemas/education/portfolio.json#portfolio') as schema: portfolio = Draft4Validator(schema, resolver=resolver)
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_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.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.requests", None): with mock.patch("jsonschema.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") 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) 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?")