flask-jsonschema icon indicating copy to clipboard operation
flask-jsonschema copied to clipboard

Provide a way to use references alongside schema documents

Open offbyone opened this issue 9 years ago • 2 comments

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)

offbyone avatar Oct 08 '15 15:10 offbyone

Ran into the exact same problem... Do you have any solution for this OLD question? Thanks!

7mllm7 avatar Aug 04 '17 22:08 7mllm7

@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.

b-ryan avatar Mar 16 '18 16:03 b-ryan