Add a JSON Schema documentation role.
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -14,6 +14,10 @@ MANIFEST
|
||||
coverage
|
||||
htmlcov
|
||||
|
||||
_cache
|
||||
_static
|
||||
_templates
|
||||
|
||||
_trial_temp
|
||||
|
||||
.tox
|
||||
|
||||
@@ -23,8 +23,11 @@ extensions = [
|
||||
'sphinx.ext.doctest',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.viewcode',
|
||||
'jsonschema_role',
|
||||
]
|
||||
|
||||
cache_path = "_cache"
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
@@ -58,7 +61,7 @@ version = release.partition("-")[0]
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
exclude_patterns = ['_build', "_cache", "_static", "_templates"]
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
@@ -17,5 +17,6 @@ Creating Validation Errors
|
||||
Any validating function that validates against a subschema should call
|
||||
:meth:`ValidatorMixin.descend`, rather than :meth:`ValidatorMixin.iter_errors`.
|
||||
If it recurses into the instance, or schema, it should pass one or both of the
|
||||
``path`` or ``schema_path`` arguments to ``descend`` in order to properly
|
||||
maintain where in the instance or schema respsectively the error occurred.
|
||||
``path`` or ``schema_path`` arguments to :meth:`ValidatorMixin.descend` in
|
||||
order to properly maintain where in the instance or schema respsectively the
|
||||
error occurred.
|
||||
|
||||
1
docs/doc-requirements.txt
Normal file
1
docs/doc-requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
lxml
|
||||
@@ -17,7 +17,8 @@ raised or returned, depending on which method or function is used.
|
||||
|
||||
.. attribute:: validator
|
||||
|
||||
The failed validator.
|
||||
The failed `validator
|
||||
<http://json-schema.org/latest/json-schema-validation.html#anchor12>`_.
|
||||
|
||||
.. attribute:: validator_value
|
||||
|
||||
@@ -27,7 +28,7 @@ raised or returned, depending on which method or function is used.
|
||||
|
||||
The full schema that this error came from. This is potentially a
|
||||
subschema from within the schema that was passed into the validator, or
|
||||
even an entirely different schema if a ``$ref`` was followed.
|
||||
even an entirely different schema if a :validator:`$ref` was followed.
|
||||
|
||||
.. attribute:: schema_path
|
||||
|
||||
@@ -53,8 +54,8 @@ raised or returned, depending on which method or function is used.
|
||||
|
||||
If the error was caused by errors in subschemas, the list of errors
|
||||
from the subschemas will be available on this property. The
|
||||
``schema_path`` and ``path`` of these errors will be relative to the
|
||||
parent error.
|
||||
:attr:`.schema_path` and :attr:`.path` of these errors will be relative
|
||||
to the parent error.
|
||||
|
||||
.. attribute:: cause
|
||||
|
||||
@@ -99,9 +100,9 @@ The error messages in this situation are not very helpful on their own:
|
||||
The instance is not valid under any of the given schemas
|
||||
The instance is not valid under any of the given schemas
|
||||
|
||||
If we look at :attr:`ValidationError.path` on each of the errors, we can find
|
||||
If we look at :attr:`~ValidationError.path` on each of the errors, we can find
|
||||
out which elements in the instance correspond to each of the errors. In
|
||||
this example, :attr:`ValidationError.path` will have only one element, which
|
||||
this example, :attr:`~ValidationError.path` will have only one element, which
|
||||
will be the index in our list.
|
||||
|
||||
.. code-block:: python
|
||||
@@ -114,16 +115,16 @@ will be the index in our list.
|
||||
|
||||
Since our schema contained nested subschemas, it can be helpful to look at
|
||||
the specific part of the instance and subschema that caused each of the errors.
|
||||
This can be seen with the :attr:`ValidationError.instance` and
|
||||
:attr:`ValidationError.schema` attributes.
|
||||
This can be seen with the :attr:`~ValidationError.instance` and
|
||||
:attr:`~ValidationError.schema` attributes.
|
||||
|
||||
With validators like ``anyOf``, the :attr:`ValidationError.context` attribute
|
||||
can be used to see the sub-errors which caused the failure. Since these errors
|
||||
actually came from two separate subschemas, it can be helpful to look at the
|
||||
:attr:`ValidationError.schema_path` attribute as well to see where exactly in
|
||||
the schema each of these errors come from. In the case of sub-errors from the
|
||||
:attr:`ValidationError.context` attribute, this path will be relative to the
|
||||
:attr:`ValidationError.schema_path` of the parent error.
|
||||
With validators like :validator:`anyOf`, the :attr:`~ValidationError.context`
|
||||
attribute can be used to see the sub-errors which caused the failure. Since
|
||||
these errors actually came from two separate subschemas, it can be helpful to
|
||||
look at the :attr:`~ValidationError.schema_path` attribute as well to see where
|
||||
exactly in the schema each of these errors come from. In the case of sub-errors
|
||||
from the :attr:`~ValidationError.context` attribute, this path will be relative
|
||||
to the :attr:`~ValidationError.schema_path` of the parent error.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@@ -180,11 +181,11 @@ more easily than by just iterating over the error objects.
|
||||
|
||||
As you can see, :class:`ErrorTree` takes an iterable of
|
||||
:class:`ValidationError`\s when constructing a tree so you can directly pass it
|
||||
the return value of a validator's ``iter_errors`` method.
|
||||
the return value of a validator's :attr:`~IValidator.iter_errors` method.
|
||||
|
||||
:class:`ErrorTree`\s support a number of useful operations. The first one we
|
||||
might want to perform is to check whether a given element in our instance
|
||||
failed validation. We do so using the ``in`` operator:
|
||||
failed validation. We do so using the :keyword:`in` operator:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@@ -199,16 +200,17 @@ did have an error (in fact it had 2), while the 1th index (``2``) did not (i.e.
|
||||
it was valid).
|
||||
|
||||
If we want to see which errors a child had, we index into the tree and look at
|
||||
the ``errors`` attribute.
|
||||
the :attr:`~ErrorTree.errors` attribute.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> sorted(tree[0].errors)
|
||||
['enum', 'type']
|
||||
|
||||
Here we see that the ``enum`` and ``type`` validators failed for index 0. In
|
||||
fact ``errors`` is a dict, whose values are the :class:`ValidationError`\s, so
|
||||
we can get at those directly if we want them.
|
||||
Here we see that the :validator:`enum` and :validator:`type` validators failed
|
||||
for index ``0``. In fact :attr:`~ErrorTree.errors` is a dict, whose values are
|
||||
the :class:`ValidationError`\s, so we can get at those directly if we want
|
||||
them.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@@ -216,7 +218,7 @@ we can get at those directly if we want them.
|
||||
'spam' is not of type 'number'
|
||||
|
||||
Of course this means that if we want to know if a given validator failed for a
|
||||
given index, we check for its presence in ``errors``:
|
||||
given index, we check for its presence in :attr:`~ErrorTree.errors`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@@ -227,9 +229,9 @@ given index, we check for its presence in ``errors``:
|
||||
False
|
||||
|
||||
Finally, if you were paying close enough attention, you'll notice that we
|
||||
haven't seen our ``minItems`` error appear anywhere yet. This is because
|
||||
``minItems`` is an error that applies globally to the instance itself. So it
|
||||
appears in the root node of the tree.
|
||||
haven't seen our :validator:`minItems` error appear anywhere yet. This is
|
||||
because :validator:`minItems` is an error that applies globally to the instance
|
||||
itself. So it appears in the root node of the tree.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@@ -240,5 +242,5 @@ That's all you need to know to use error trees.
|
||||
|
||||
To summarize, each tree contains child trees that can be accessed by indexing
|
||||
the tree to get the corresponding child tree for a given index into the
|
||||
instance. Each tree and child has a ``errors`` attribute, a dict, that maps the
|
||||
failed validator to the corresponding validation error.
|
||||
instance. Each tree and child has a :attr:`~ErrorTree.errors` attribute, a
|
||||
dict, that maps the failed validator to the corresponding validation error.
|
||||
|
||||
111
docs/jsonschema_role.py
Normal file
111
docs/jsonschema_role.py
Normal file
@@ -0,0 +1,111 @@
|
||||
from datetime import datetime
|
||||
from docutils import nodes
|
||||
import errno
|
||||
import os
|
||||
import urllib2
|
||||
|
||||
from lxml import html
|
||||
|
||||
|
||||
VALIDATION_SPEC = "http://json-schema.org/latest/json-schema-validation.html"
|
||||
|
||||
|
||||
def setup(app):
|
||||
"""
|
||||
Install the plugin.
|
||||
|
||||
:argument sphinx.application.Sphinx app: the Sphinx application context
|
||||
|
||||
"""
|
||||
|
||||
app.add_config_value("cache_path", "_cache", "")
|
||||
|
||||
try:
|
||||
os.makedirs(app.config.cache_path)
|
||||
except OSError as error:
|
||||
if error.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
path = os.path.join(app.config.cache_path, "spec.html")
|
||||
spec = fetch_or_load(path)
|
||||
app.add_role("validator", docutils_sucks(spec))
|
||||
|
||||
|
||||
def fetch_or_load(spec_path):
|
||||
"""
|
||||
Fetch a new specification or use the cache if it's current.
|
||||
|
||||
:argument cache_path: the path to a cached specification
|
||||
|
||||
"""
|
||||
|
||||
headers = {}
|
||||
|
||||
try:
|
||||
modified = datetime.utcfromtimestamp(os.path.getmtime(spec_path))
|
||||
date = modified.strftime("%a, %d %b %Y %I:%M:%S UTC")
|
||||
headers["If-Modified-Since"] = date
|
||||
except OSError as error:
|
||||
if error.errno != errno.ENOENT:
|
||||
raise
|
||||
|
||||
request = urllib2.Request(VALIDATION_SPEC, headers=headers)
|
||||
response = urllib2.urlopen(request)
|
||||
|
||||
if response.code == 200:
|
||||
with open(spec_path, "w+") as spec:
|
||||
spec.writelines(response)
|
||||
spec.seek(0)
|
||||
return html.parse(spec)
|
||||
|
||||
with open(spec_path) as spec:
|
||||
return html.parse(spec)
|
||||
|
||||
|
||||
def docutils_sucks(spec):
|
||||
"""
|
||||
Yeah.
|
||||
|
||||
It doesn't allow using a class because it does stupid stuff like try to set
|
||||
attributes on the callable object rather than just keeping a dict.
|
||||
|
||||
"""
|
||||
|
||||
base_url = VALIDATION_SPEC
|
||||
|
||||
def validator(name, raw_text, text, lineno, inliner):
|
||||
"""
|
||||
Link to the JSON Schema documentation for a validator.
|
||||
|
||||
:argument str name: the name of the role in the document
|
||||
:argument str raw_source: the raw text (role with argument)
|
||||
:argument str text: the argument given to the role
|
||||
:argument int lineno: the line number
|
||||
:argument docutils.parsers.rst.states.Inliner inliner: the inliner
|
||||
|
||||
:returns: 2-tuple of nodes to insert into the document and an iterable
|
||||
of system messages, both possibly empty
|
||||
|
||||
"""
|
||||
|
||||
header = spec.xpath(
|
||||
"//h3[re:match(text(), '(^|\W){0}($|\W,)', 'i')]".format(text),
|
||||
namespaces={"re": "http://exslt.org/regular-expressions"},
|
||||
)
|
||||
|
||||
if len(header) == 0:
|
||||
inliner.reporter.warning(
|
||||
"Didn't find a target for {0}".format(text),
|
||||
)
|
||||
uri = base_url
|
||||
else:
|
||||
if len(header) > 1:
|
||||
inliner.reporter.warning(
|
||||
"Found multiple targets for {0}".format(text),
|
||||
)
|
||||
uri = base_url + "#" + header[0].getprevious().attrib["name"]
|
||||
|
||||
reference = nodes.reference(raw_text, text, refuri=uri)
|
||||
return [reference], []
|
||||
|
||||
return validator
|
||||
@@ -14,7 +14,7 @@ The simplest way to validate an instance under a given schema is to use the
|
||||
|
||||
.. autofunction:: validate
|
||||
|
||||
Validate an ``instance`` under the given ``schema``.
|
||||
Validate an instance under the given schema.
|
||||
|
||||
>>> validate([2, 3, 4], {"maxItems" : 2})
|
||||
Traceback (most recent call last):
|
||||
@@ -24,8 +24,9 @@ The simplest way to validate an instance under a given schema is to use the
|
||||
:func:`validate` will first verify that the provided schema is itself
|
||||
valid, since not doing so can lead to less obvious error messages and fail
|
||||
in less obvious or consistent ways. If you know you have a valid schema
|
||||
already or don't care, you might prefer using the ``validate`` method
|
||||
directly on a specific validator (e.g. :meth:`Draft4Validator.validate`).
|
||||
already or don't care, you might prefer using the
|
||||
:meth:`~IValidator.validate` method directly on a specific validator
|
||||
(e.g. :meth:`Draft4Validator.validate`).
|
||||
|
||||
|
||||
:argument instance: the instance to validate
|
||||
@@ -34,12 +35,12 @@ The simplest way to validate an instance under a given schema is to use the
|
||||
the instance.
|
||||
|
||||
If the ``cls`` argument is not provided, two things will happen in
|
||||
accordance with the specification. First, if the ``schema`` has a
|
||||
``$schema`` property containing a known meta-schema (known by a validator
|
||||
registered with :func:`validates`), then the proper validator will be used.
|
||||
The specification recommends that all schemas contain ``$schema``
|
||||
properties for this reason. If no ``$schema`` property is found, the
|
||||
default validator class is :class:`Draft4Validator`.
|
||||
accordance with the specification. First, if the schema has a
|
||||
:validator:`$schema` property containing a known meta-schema [#]_ then the
|
||||
proper validator will be used. The specification recommends that all
|
||||
schemas contain :validator:`$schema` properties for this reason. If no
|
||||
:validator:`$schema` property is found, the default validator class is
|
||||
:class:`Draft4Validator`.
|
||||
|
||||
Any other provided positional and keyword arguments will be passed on when
|
||||
instantiating the ``cls``.
|
||||
@@ -49,6 +50,9 @@ The simplest way to validate an instance under a given schema is to use the
|
||||
|
||||
:exc:`SchemaError` if the schema itself is invalid
|
||||
|
||||
.. rubric:: Footnotes
|
||||
.. [#] known by a validator registered with :func:`validates`
|
||||
|
||||
|
||||
The Validator Interface
|
||||
-----------------------
|
||||
@@ -64,25 +68,25 @@ adhere to.
|
||||
:meth:`IValidator.check_schema` to validate a schema
|
||||
first.
|
||||
:argument types: Override or extend the list of known types when validating
|
||||
the ``type`` property. Should map strings (type names) to
|
||||
class objects that will be checked via ``isinstance``. See
|
||||
:ref:`validating-types` for details.
|
||||
the :validator:`type` property. Should map strings (type
|
||||
names) to class objects that will be checked via
|
||||
:func:`isinstance`. See :ref:`validating-types` for
|
||||
details.
|
||||
:type types: dict or iterable of 2-tuples
|
||||
:argument resolver: an instance of :class:`RefResolver` that will be used
|
||||
to resolve ``$ref`` properties (JSON references). If
|
||||
unprovided, one will be created.
|
||||
:argument format_checker: an object with a ``conform()`` method that will
|
||||
be called to check and see if instances conform
|
||||
to each ``format`` property present in the
|
||||
to resolve :validator:`$ref` properties (JSON
|
||||
references). If unprovided, one will be created.
|
||||
:argument format_checker: an instance of :class:`FormatChecker` whose
|
||||
:meth:`~conforms` method will be called to check
|
||||
and see if instances conform to each
|
||||
:validator:`format` property present in the
|
||||
schema. If unprovided, no validation will be done
|
||||
for ``format``. :class:`FormatChecker` is a
|
||||
concrete implementation of an object of this form
|
||||
that can be used for common formats.
|
||||
for :validator:`format`.
|
||||
|
||||
.. attribute:: DEFAULT_TYPES
|
||||
|
||||
The default mapping of JSON types to Python types used when validating
|
||||
``type`` properties in JSON schemas.
|
||||
:validator:`type` properties in JSON schemas.
|
||||
|
||||
.. attribute:: META_SCHEMA
|
||||
|
||||
@@ -162,20 +166,21 @@ Validating With Additional Types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Occasionally it can be useful to provide additional or alternate types when
|
||||
validating the JSON Schema's ``type`` property. Validators allow this by taking
|
||||
a ``types`` argument on construction that specifies additional types, or which
|
||||
can be used to specify a different set of Python types to map to a given JSON
|
||||
type.
|
||||
validating the JSON Schema's :validator:`type` property. Validators allow this
|
||||
by taking a ``types`` argument on construction that specifies additional types,
|
||||
or which can be used to specify a different set of Python types to map to a
|
||||
given JSON type.
|
||||
|
||||
``jsonschema`` tries to strike a balance between performance in the common case
|
||||
and generality. For instance, JSON defines a ``number`` type, which can be
|
||||
validated with a schema such as ``{"type" : "number"}``. By default, this will
|
||||
accept instances of Python :class:`number.Number`. This includes in particular
|
||||
:class:`int`\s and :class:`float`\s, along with `decimal.Decimal` objects,
|
||||
:class:`complex` numbers etc. See the numbers_ module documentation for more
|
||||
details. For ``integer`` and ``object``, however, rather than checking for
|
||||
``number.Integral`` and ``collections.Mapping``, ``jsonschema`` simply checks
|
||||
for ``int`` and ``dict``, since the former can introduce significant slowdown.
|
||||
:mod:`jsonschema` tries to strike a balance between performance in the common
|
||||
case and generality. For instance, JSON Schema defines a ``number`` type, which
|
||||
can be validated with a schema such as ``{"type" : "number"}``. By default,
|
||||
this will accept instances of Python :class:`number.Number`. This includes in
|
||||
particular :class:`int`\s and :class:`float`\s, along with `decimal.Decimal`
|
||||
objects, :class:`complex` numbers etc. For ``integer`` and ``object``, however,
|
||||
rather than checking for :class:`number.Integral` and
|
||||
:class:`collections.Mapping`, :mod:`jsonschema` simply checks for :class:`int`
|
||||
and :class:`dict`, since the former can introduce significant slowdown in these
|
||||
common cases.
|
||||
|
||||
If you *do* want the generality, or just want to add a few specific additional
|
||||
types as being acceptible for a validator, :class:`IValidator`\s have a
|
||||
@@ -197,8 +202,6 @@ need to specify all types to match if you override one of the existing JSON
|
||||
types, so you may want to access the set of default types when specifying your
|
||||
additional type.
|
||||
|
||||
.. _numbers: http://docs.python.org/3.3/library/numbers.html
|
||||
|
||||
.. _versioned-validators:
|
||||
|
||||
Versioned Validators
|
||||
@@ -217,10 +220,11 @@ implements.
|
||||
Validating Formats
|
||||
------------------
|
||||
|
||||
JSON Schema defines the ``format`` property which can be used to check if
|
||||
primitive types (``str``\s, ``number``\s, ``bool``\s) conform to well-defined
|
||||
formats. By default, no validation is enforced, but optionally, validation can
|
||||
be enabled by hooking in a format-checking object into an :class:`IValidator`.
|
||||
JSON Schema defines the :validator:`format` property which can be used to check
|
||||
if primitive types (``string``\s, ``number``\s, ``boolean``\s) conform to
|
||||
well-defined formats. By default, no validation is enforced, but optionally,
|
||||
validation can be enabled by hooking in a format-checking object into an
|
||||
:class:`IValidator`.
|
||||
|
||||
.. doctest::
|
||||
|
||||
@@ -270,7 +274,7 @@ Checker Notes
|
||||
========== ====================
|
||||
hostname
|
||||
ipv4
|
||||
ipv6 OS must have ``socket.inet_pton`` function
|
||||
ipv6 OS must have :func:`socket.inet_pton` function
|
||||
email
|
||||
uri requires rfc3987_
|
||||
date-time requires isodate_
|
||||
|
||||
Reference in New Issue
Block a user