flask-jsonschema
flask-jsonschema copied to clipboard
Provide a way to use references alongside schema documents
I've started trying to use flask-jsonschema in my API, and I've run into a problem that I think requires an API change to resolve, specifically how to share schema refs between methods in my API.
I've defined an api.json
file that contains this:
{
"$schema": "http://json-schema.org/schema#",
"definitions": {
"version": {
"type": "object",
"name": {"type": "string"},
"major_version": {"type": "string"},
"required": ["name", "major_version"]
},
"item_revision": {
"type": "object",
"item_name": {"type": "string"},
"version_number": {"type": "integer"},
"required": ["item_name", "version_number"]
}
},
"create": {
"type": "object",
"properties": {
"target_version": {"$ref": "#/definitions/version" },
"item_revision": {"$ref": "#/definitions/item_revision" },
"description": {"type": "string"},
"requester": {"type": "string"}
},
"required": ["item_revision", "description", "requester", "target_version"]
}
}
The create
API I use is annotated thus:
@api.route('/ver', methods=['POST',])
@jsonschema.validate('api', 'create')
def create():
That seems like it should work, except that the references in the create
schema are not resolvable when the JSON document is validated, because the create
subdocument is the only part passed into the schema validator, resulting in an exception that looks like this:
test/test_api.py ...F
=================================== FAILURES ===================================
___________________________ test_create_with_new_version ___________________________
client = <FlaskClient <Flask 'bats_service'>>
def test_create_with_new_version(client):
rv = client.post('/ver', data=json.dumps({
"item_revision": {
"item_name": "test",
"version_number": 1234,
},
"version": {
"name": "Transform",
"major_version": "1.0",
},
"description": "desc",
"requester": "offbyone",
}), headers={
> 'content-type': 'application/json',
})
test/test_api.py:41:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/werkzeug/test.py:784: in post
return self.open(*args, **kw)
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/flask/testing.py:108: in open
follow_redirects=follow_redirects)
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/werkzeug/test.py:742: in open
response = self.run_wsgi_app(environ, buffered=buffered)
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/werkzeug/test.py:659: in run_wsgi_app
rv = run_wsgi_app(self.application, environ, buffered=buffered)
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/werkzeug/test.py:867: in run_wsgi_app
app_iter = app(environ, start_response)
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/flask/app.py:1836: in __call__
return self.wsgi_app(environ, start_response)
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/flask/app.py:1820: in wsgi_app
response = self.make_response(self.handle_exception(e))
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/flask/app.py:1403: in handle_exception
reraise(exc_type, exc_value, tb)
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/flask/app.py:1817: in wsgi_app
response = self.full_dispatch_request()
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/flask/app.py:1477: in full_dispatch_request
rv = self.handle_user_exception(e)
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/flask/app.py:1381: in handle_user_exception
reraise(exc_type, exc_value, tb)
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/flask/app.py:1475: in full_dispatch_request
rv = self.dispatch_request()
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/flask/app.py:1461: in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/flask_jsonschema.py:62: in decorated
_validate(request.json, jschema.get_schema(path))
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/jsonschema/validators.py:428: in validate
cls(schema, *args, **kwargs).validate(instance)
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/jsonschema/validators.py:116: in validate
for error in self.iter_errors(*args, **kwargs):
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/jsonschema/validators.py:95: in iter_errors
for error in errors:
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/jsonschema/_validators.py:283: in properties_draft4
schema_path=property,
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/jsonschema/validators.py:108: in descend
for error in self.iter_errors(instance, schema):
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/jsonschema/validators.py:95: in iter_errors
for error in errors:
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/jsonschema/_validators.py:196: in ref
with validator.resolver.resolving(ref) as resolved:
/usr/lib/python2.7/contextlib.py:17: in __enter__
return self.gen.next()
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/jsonschema/validators.py:297: in resolving
yield self.resolve_fragment(document, fragment)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <jsonschema.validators.RefResolver object at 0x11076cb90>
document = {'properties': {'description': {'type': 'string'}, 'requester': {'type': 'string'}, 'version': {'...'}}, 'required': ['item_revision', 'description', 'requester', 'version'], 'type': 'object'}
fragment = 'definitions/package_major_version'
def resolve_fragment(self, document, fragment):
"""
Resolve a ``fragment`` within the referenced ``document``.
:argument document: the referrant document
:argument str fragment: a URI fragment to resolve within it
"""
fragment = fragment.lstrip(u"/")
parts = unquote(fragment).split(u"/") if fragment else []
for part in parts:
part = part.replace(u"~1", u"/").replace(u"~0", u"~")
if isinstance(document, Sequence):
# Array indexes should be turned into integers
try:
part = int(part)
except ValueError:
pass
try:
document = document[part]
except (TypeError, LookupError):
raise RefResolutionError(
> "Unresolvable JSON pointer: %r" % fragment
)
E RefResolutionError: Unresolvable JSON pointer: u'definitions/package_major_version'
/Users/offbyone/virtualenv/schema/lib/python2.7/site-packages/jsonschema/validators.py:326: RefResolutionError
====================== 1 failed, 3 passed in 6.35 seconds ======================
The upshot of it is that references are in part of the schema document that isn't passed into the validator. Have you any suggestions for how I'd make use of them?
(note: I plan to use the types I'm referencing in more than one API, which is why I factored them out as references in the first place; obviously, I could just inline them, but I'd rather not)
Ran into the exact same problem... Do you have any solution for this OLD question? Thanks!
@offbyone @7mllm7 I created a fork which supports this feature: https://github.com/b-ryan/flask-jsonschema If you still have any use for this let me know if this solution works for you or you have other ideas.