Add a JSON Schema documentation role.

This commit is contained in:
Julian Berman
2013-04-17 23:39:52 -04:00
parent cc09cafc1d
commit ac9b9c624e
7 changed files with 197 additions and 71 deletions

4
.gitignore vendored
View File

@@ -14,6 +14,10 @@ MANIFEST
coverage
htmlcov
_cache
_static
_templates
_trial_temp
.tox

View File

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

View File

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

View File

@@ -0,0 +1 @@
lxml

View File

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

View File

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