Package: jsonschema-pyref
jsonschema-pyrefThis package adds support tojsonschema'sRefResolverfor
resolving schema$refs with two new custom URL schemes:py-obj: URLs with this scheme allow loading a schema from a dict in a
Python module/class/etc.  This uses dotted attribute notation for the path
to the schema.  For examplepy-obj:package.submodule.Class.schemaresolves to aschemaclass attribute in the classClassin the modulepackage.submodule.py-pkgdata: URLs with this scheme allow loading a schema from a file
(currently only JSON and YAML are supported) in the package data installed
with a Python package.  For examplepy-pkgdata:package.subpackage/schema.jsonloads the schema from the fileschema.jsonunderpackage/subpackagefrom wherever the package is
installed.Many JSON Schemas have an$idproperty declaring the unique identity of the schema.  Typically this is an
http(s) URL, but there is nothingrequiringit to be.  Often this is just
used for namespacing, and the supplied URL does technically require to
exist.In practice, if your Python package ships with a number of schemas used by
the package, and you would like to reference those schemas either from
another package, or relative to other schemas in the same package, the use
of the http scheme for schema$ids doesn't help with this.  It is
necessary to provide some kind of mapping between the URLs in the schema$ids, and their actual location in the Python package.The use of these custom URL schemes makes it implicit that this schema's
home is in a Python package or module (in the case of schemas declared
directly in Python code) and that it is not located on a network resource.This gives an alternative way to declare the$ids of and$refschemas
installed alongside Python code.Examples>>>importjsonschema>>>fromjsonschema_pyrefimportRefResolver>>>schema={...'properties':{...'a':{'$ref':'py-obj:jsonschema_pyref.examples.schema1'},...'b':{'$ref':'py-pkgdata:jsonschema_pyref.examples/schema1.json'},...'c':{'$ref':'py-pkgdata:jsonschema_pyref.examples/schema2.yml'}...}...}>>>resolver=RefResolver.from_schema(schema)>>>doc={'a':'hello','b':'world','c':123}>>>jsonschema.validate(doc,schema,resolver=resolver)isNoneTrueAs a convenience,jsonschema_pyref.validateis also provided as a drop-in
replacement forjsonschema.validatewhich uses the customRefResolverby
default:>>>fromjsonschema_pyrefimportvalidate>>>validate(doc,schema)isNoneTrueLet's have a look at what's in the referenced schemas to see exactly
what we're validating the document against:>>>frompkgutilimportget_data>>>importjsonschema_pyref.examples>>>jsonschema_pyref.examples.schema1{'type':'string'}>>>defshow_file(filename):...data=get_data('jsonschema_pyref.examples',filename)...print(data.decode('ascii').strip())...>>>show_file('schema1.json'){"type":"string"}>>>show_file('schema2.yml')type:integerThese example schemas may be trivial, but you can easily confirm that this
would work all the same with a more complex schema.Just to prove that the correct schemas are actually being loaded (and the
document is not just being trivially validated) we can also try some
counter-examples:>>>doc1=dict(doc,a=123)>>>jsonschema.validate(doc1,schema,resolver=resolver)Traceback(mostrecentcalllast):...jsonschema.exceptions.ValidationError:123isnotoftype'string'...Failedvalidating'type'inschema['properties']['a']:{'type':'string'}...Oninstance['a']:123>>>doc2=dict(doc,b=123)>>>jsonschema.validate(doc2,schema,resolver=resolver)Traceback(mostrecentcalllast):...jsonschema.exceptions.ValidationError:123isnotoftype'string'...Failedvalidating'type'inschema['properties']['b']:{'type':'string'}...Oninstance['b']:123>>>doc3=dict(doc,c='hello')>>>jsonschema.validate(doc3,schema,resolver=resolver)Traceback(mostrecentcalllast):...jsonschema.exceptions.ValidationError:'hello'isnotoftype'integer'...Failedvalidating'type'inschema['properties']['c']:{'type':'integer'}...Oninstance['c']:'hello'Here is a slightly more complex example demonstrating how relative refs
work withpy-pkgdataURLs, as well as that fragments are correctly
resolved relative to each schema.First we haveschema4.ymlwhich also contains an$idproperty, allowingjsonschema.RefResolverto correctly determine the base URL against which
to resolve relative refs (in the case of{"$ref": "schema3.json"}):>>>show_file('schema4.yml')$id:"py-pkgdata:jsonschema_pyref.examples/schema4.yml"type:"object"properties:foo:{"$ref":"schema3.json"}bar:{"$ref":"#/$defs/bar"}additionalProperties:false$defs:bar:{"type":"integer"}Andschema3.json:>>>show_file('schema3.json'){"$id":"py-pkgdata:jsonschema_pyref.examples/schema3.json","type":"object","properties":{"foo":{"$ref":"#/$defs/foo"}},"additionalProperties":false,"$defs":{"foo":{"type":"string"}}}Here is a document that should validate againstschema4which we'll
load also by$ref:>>>doc={...'foo':{'foo':'hello'},...'bar':123...}...>>>schema={'$ref':'py-pkgdata:jsonschema_pyref.examples/schema4.yml'}>>>resolver=RefResolver.from_schema(schema)>>>jsonschema.validate(doc,schema,resolver=resolver)isNoneTrueAnd again a couple counter-examples:>>>doc1=dict(doc,foo={'foo':123})>>>jsonschema.validate(doc1,schema,resolver=resolver)Traceback(mostrecentcalllast):...jsonschema.exceptions.ValidationError:123isnotoftype'string'...Failedvalidating'type'inschema['properties']['foo']['properties']['foo']:{'type':'string'}...Oninstance['foo']['foo']:123>>>doc2=dict(doc,bar='bar')>>>jsonschema.validate(doc2,schema,resolver=resolver)Traceback(mostrecentcalllast):...jsonschema.exceptions.ValidationError:'bar'isnotoftype'integer'...Failedvalidating'type'inschema['properties']['bar']:{'type':'integer'}...Oninstance['bar']:'bar'API Documentationhttps://jsonschema-pyref.readthedocs.io/en/latest/api.html