pax_global_header00006660000000000000000000000064117300410570014510gustar00rootroot0000000000000052 comment=6bd2cd20210a9ceb277daf95228821bf5ff4843f sunlightlabs-validictory-046f779/000077500000000000000000000000001173004105700170265ustar00rootroot00000000000000sunlightlabs-validictory-046f779/.gitignore000066400000000000000000000000341173004105700210130ustar00rootroot00000000000000*.egg-info *.pyc dist build sunlightlabs-validictory-046f779/AUTHORS.txt000066400000000000000000000010251173004105700207120ustar00rootroot00000000000000Initially derived from jsonschema, by Ian Lewis and Yusuke Muraoka. * James Turk * Silas Sewell - Cosmetic fixes * Filip Noetzel - lots of work towards being compatible with the draft-03 spec * Kai Lautaportti - blank_by_default * Filod Lin - fix for longs, validate as ints * John Krauss - ignore additionalProperties with non-dicts * Alexandre Conrad - IP address validator * Michael Stephens - support for collections.Mapping * Simon Weber - doc improvements * Ahmed El-Hassany - fix for patternProperties on non-required fields sunlightlabs-validictory-046f779/LICENSE.txt000066400000000000000000000021721173004105700206530ustar00rootroot00000000000000Copyright (c) 2010 Sunlight Labs, James Turk Contains code from jsonschema 0.2, copyright 2008 Ian Lewis, Yusuke Muraoka Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. sunlightlabs-validictory-046f779/MANIFEST.in000066400000000000000000000000731173004105700205640ustar00rootroot00000000000000include *.rst include LICENSE.txt include CHANGELOG.txt sunlightlabs-validictory-046f779/README.rst000066400000000000000000000030361173004105700205170ustar00rootroot00000000000000=========== validictory =========== A general purpose Python data validator. Works with Python 2.6+ (Including Python 3) Schema format based on JSON Schema Proposal (http://json-schema.org) Contains code derived from jsonschema, by Ian Lewis and Yusuke Muraoka. Usage ===== JSON documents and schema must first be loaded into a Python dictionary type before it can be validated. Parsing a simple JSON document:: >>> import validictory >>> >>> validictory.validate("simplejson", {"type":"string"}) Parsing a more complex JSON document:: >>> import simplejson >>> import validictory >>> >>> data = simplejson.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') >>> schema = { ... "type":"array", ... "items":[ ... {"type":"string"}, ... {"type":"object", ... "properties":{ ... "bar":{ ... "items":[ ... {"type":"string"}, ... {"type":"any"}, ... {"type":"number"}, ... {"type":"integer"} ... ] ... } ... } ... } ... ] ... } >>> validictory.validate(data,schema) Catch ValueErrors to handle validation issues:: >>> import validictory >>> >>> try: ... validictory.validate("simplejson", {"type":"string","minLength":15}) ... except ValueError, error: ... print error ... Length of value 'simplejson' for field '_data' must be greater than or equal to 15 sunlightlabs-validictory-046f779/docs/000077500000000000000000000000001173004105700177565ustar00rootroot00000000000000sunlightlabs-validictory-046f779/docs/Makefile000066400000000000000000000110021173004105700214100ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/validictory.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/validictory.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/validictory" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/validictory" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." sunlightlabs-validictory-046f779/docs/changelog.rst000066400000000000000000000045011173004105700224370ustar00rootroot00000000000000validictory changelog ===================== 0.8.3 ----- **2012-03-13** * bugfix for Python 3: fix regression from 0.8.1 in use of long 0.8.2 ----- **2012-03-09** * doc improvements * PEP8 nearly everything * bugfix for patternProperties * ip-address should have been a format, not a type, breaks any code written depending on it in 0.8.1 0.8.1 ----- **2012-03-04** * add GeoJSON example to docs * allow longs in int/number validation * ignore additionalProperties for non-dicts * ip-address type validator 0.8.0 ----- **2012-01-26** * validate_enum accepts any container type * add support for Python 3 * drop support for Python 2.5 and earlier 0.7.2 ----- **2011-09-27** * add blank_by_default argument * more descriptive error message for list items 0.7.1 ----- **2011-05-03** * PEP8 changes to code base * fix for combination of format & required=False * use ABCs to determine types in Python >= 2.6 0.7.0 ----- **2011-03-15** * fix dependencies not really supporting lists * add what might be the draft03 behavior for schema dependencies * add Sphinx documentation 0.6.1 ----- **2011-01-21** * bugfix for uniqueItems 0.6.0 ----- **2011-01-20** * more draft-03 stuff: patternProperties, additionalItems, exclusive{Minimum,Maximum}, divisibleBy * custom format validators * treat tuples as lists * replace requires with dependencies (deprecating requires) * replace optional with required (deprecating optional) * addition of required_by_default parameter 0.5.0 ----- **2011-01-13** * blank false by default * draft-03 stuff: uniqueItems, date formats 0.4.1 ----- **2010-08-27** * test custom types * optional defaults to False correctly * remove raise_errors * add value check in additionalProperties 0.4.0 ----- **2010-08-02** * renamed to validictory * removal of maxDecimal * ignore unknown attributes * differentiate between a schema error and a validation error * filter through _error * combine Items/Length checks * modular type checking * major test refactor 0.3.0 ----- **2010-07-29** * took over abandoned json_schema code * removal of interactive mode * PEP 8 cleanup of source * list/dict checks more flexible * remove identity/options/readonly junk sunlightlabs-validictory-046f779/docs/conf.py000066400000000000000000000155511173004105700212640ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # validictory documentation build configuration file, created by # sphinx-quickstart on Wed Mar 9 12:09:00 2011. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'validictory' copyright = u'2011, James Turk' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '0.8' # The full version, including alpha/beta/rc tags. release = '0.8.3' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'validictorydoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'validictory.tex', u'validictory Documentation', u'James Turk', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'validictory', u'validictory Documentation', [u'James Turk'], 1) ] sunlightlabs-validictory-046f779/docs/index.rst000066400000000000000000000017611173004105700216240ustar00rootroot00000000000000validictory |release| ===================== Overview -------- validictory is a general purpose Python data validator that allows validation of arbitrary Python data structures. Schema format is based on the `JSON Schema proposal `_, so combined with :mod:`json` the library is also useful as a validator for JSON data. Contains code derived from `jsonschema `_ by Ian Lewis and Ysuke Muraoka. Obtaining validictory --------------------- Source is available from `GitHub `_. The latest release is always available on `PyPI `_ and can be installed via `pip `_. Documentation lives at `ReadTheDocs `_. Contents -------- .. toctree:: :maxdepth: 2 usage validictory changelog Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` sunlightlabs-validictory-046f779/docs/usage.rst000066400000000000000000000177441173004105700216310ustar00rootroot00000000000000Using validictory ================= Normal use of validictory is as simple as calling :func:`validictory.validate`, the only thing to learn is how to craft a schema. Examples -------- JSON documents and schema must first be loaded into a Python dictionary type before it can be validated. Parsing a simple JSON document:: >>> import validictory >>> validictory.validate("roast beef", {"type":"string"}) Parsing a more complex JSON document:: >>> import json >>> import validictory >>> data = json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') >>> schema = { ... "type":"array", ... "items":[ ... {"type":"string"}, ... {"type":"object", ... "properties":{ ... "bar":{ ... "items":[ ... {"type":"string"}, ... {"type":"any"}, ... {"type":"number"}, ... {"type":"integer"} ... ] ... } ... } ... } ... ] ... } >>> validictory.validate(data,schema) Catch ValueErrors to handle validation issues:: >>> import validictory >>> try: ... validictory.validate("short", {"type":"string","minLength":15}) ... except ValueError, error: ... print error ... Length of value 'short' for field '_data' must be greater than or equal to 15 For more example usage of all schema options check out the tests within ``validictory/tests``. Schema Options -------------- ``type`` Validate that an item in the data is of a particular type. If a list of values is provided then any of the specified types will be accepted. Provided value can be any combination of the following: * ``string`` - str and unicode objects * ``integer`` - ints * ``number`` - ints and floats * ``boolean`` - bools * ``object`` - dicts * ``array`` - lists and tuples * ``null`` - None * ``any`` - any type is acceptable ``properties`` List of validators for properties of the object. In essence each item in the provided dict for properties is a sub-schema applied against the property (if present) with the same name in the data. ``patternProperties`` Define a set of patterns that validate against subschemas. Similarly to how ``properties`` works, any properties in the data that have a name matching a particular pattern must validate against the provided sub-schema. ``additionalProperties`` Schema for all additional properties not included in properties. Can be ``False`` to disallow any additional properties not in ``properties``, or can be a sub-schema that all properties not included in ``properties`` must match. ``items`` Provide a schema or list of schemas to match against a list. If the provided value is a schema object then every item in the list will be validated against the given schema. If the provided value is a list of schemas then each item in the list must match the schema in the same position of the list. (extra items will be validated according to ``additionalItems``) ``additionalItems`` Used in conjunction with ``items``. If False then no additional items are allowed, if a schema is provided then all additional items must match the provided schema. ``required`` If True, the property must be present to validate. The default value of this parameter is set on the call to :func:`~validictory.validate`. By default it is ``True``. .. note:: If you are following the JSON Schema spec, this diverges from the official spec as of v3. If you want to validate against v3 more correctly, be sure to set ``required_by_default`` to False. ``dependencies`` Can be a single string or list of strings representing properties that must exist if the given property exists. For example:: schema = {"prop01": {"required":False}, "prop02": {"required":False, "dependencies":"prop01"}} # would validate {"prop01": 7} # would fail (missing prop01) {"prop02": 7} ``minimum`` and ``maximum`` If the value is a number (int or float), these methods will validate that the values are less than or greater than the given minimum/maximum. Minimum and maximum values are inclusive by default. ``exclusiveMinimum`` and ``exclusiveMaximum`` If these values are present and set to True, they will modify the ``minimum`` and ``maximum`` tests to be exclusive. ``minItems``, ``minLength``, ``maxItems``, and ``maxLength`` If the value is a list or str, these will test the length of the list or string. There is no difference in implementation between the items/length variants. ``uniqueItems`` Indicate that all attributes in a list must be unique. ``pattern`` If the value is a string, this provides a regular expression that the string must match to be valid. ``blank`` If False, validate that string values are not blank (the empty string). The default value of this parameter is set when initializing `SchemaValidator`. By default it is ``False``. ``enum`` Provides an array that the value must match if present. ``format`` Validate that the value matches a predefined format. By default several formats are recognized: * ``date-time``: 'yyyy-mm-ddhh:mm:ssZ' * ``date``: 'yyyy-mm-dd' * ``time``: 'hh:mm::ss' * ``utc-millisec``: number of seconds since UTC formats can be provided as the ``format_validators`` argument to ``validictory.validate``. ``divisibleBy`` Ensures that the data value can be divided (without remainder) by a given divisor (**not 0**). ``title`` and ``description`` These do no validation, but if provided must be strings or a ``~validictory.SchemaError`` will be raised. Common Recipes -------------- A situation that often arises is the need to validate that all members of a list validate according to one type of object or another. This can be achieved by combining the "items", "type", and "object" schema options. To define a property that you want to be a list of one or more types you'd need to follow the following recipe (filling in foo and bar definition with normal schema definition):: schema = { "type": "object", "properties": { "foo_or_bar_list": { "type": "array", "items": {"type": [ {"type": "object", # foo definition }, {"type": "object", # bar definition }, ]} } } } A common example of this is the GeoJSON spec, which allows for a geometry collection to have a list of geometries (Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon). Simplified GeoJSON example:: # to simplify things we make a few subschema dicts position = { "type": "array", "minItems": 2, "maxItems": 3 } point = { "type": "object", "properties": { "type": { "pattern": "Point" }, "coordinates": { "type": position } } } multipoint = { "type": "object", "properties": { "type": { "pattern": "MultiPoint" }, "coordinates": { "type": "array", "minItems": 2, "items": position } } } # the main schema simplified_geojson_geometry = { "type": "object", "properties": { "type": { "pattern": "GeometryCollection" }, # this defines an array ('geometries') that is a list of objects # which conform to one of the schemas in the type list "geometries": { "type": "array", "items": {"type": [point, multipoint]} } } } (thanks to Jason Sanford for bringing this need to my attention) sunlightlabs-validictory-046f779/docs/validictory.rst000066400000000000000000000004021173004105700230350ustar00rootroot00000000000000validictory module ================== .. module:: validictory validate -------- .. autofunction:: validate SchemaValidator --------------- .. autoclass:: SchemaValidator Exceptions ---------- .. autoclass:: ValidationError .. autoclass:: SchemaError sunlightlabs-validictory-046f779/setup.py000077500000000000000000000011221173004105700205370ustar00rootroot00000000000000#!/usr/bin/env python from setuptools import setup, find_packages from validictory import __version__ DESCRIPTION = "general purpose python data validator" LONG_DESCRIPTION = open('README.rst').read() setup(name='validictory', version=__version__, description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='James Turk', author_email='jturk@sunlightfoundation.com', url='http://github.com/sunlightlabs/validictory', license="MIT License", platforms=["any"], packages=find_packages(), test_suite="validictory.tests", ) sunlightlabs-validictory-046f779/validictory/000077500000000000000000000000001173004105700213575ustar00rootroot00000000000000sunlightlabs-validictory-046f779/validictory/__init__.py000077500000000000000000000033451173004105700235000ustar00rootroot00000000000000#!/usr/bin/env python from validictory.validator import SchemaValidator, ValidationError, SchemaError __all__ = ['validate', 'SchemaValidator', 'ValidationError', 'SchemaError'] __version__ = '0.8.3' def validate(data, schema, validator_cls=SchemaValidator, format_validators=None, required_by_default=True, blank_by_default=False): ''' Validates a parsed json document against the provided schema. If an error is found a :class:`ValidationError` is raised. If there is an issue in the schema a :class:`SchemaError` will be raised. :param data: python data to validate :param schema: python dictionary representing the schema (see `schema format`_) :param validator_cls: optional validator class (default is :class:`SchemaValidator`) :param format_validators: optional dictionary of custom format validators :param required_by_default: defaults to True, set to False to make ``required`` schema attribute False by default. ''' v = validator_cls(format_validators, required_by_default, blank_by_default) return v.validate(data, schema) if __name__ == '__main__': import sys import json if len(sys.argv) == 2: if sys.argv[1] == "--help": raise SystemExit("%s SCHEMAFILE [INFILE]" % (sys.argv[0],)) schemafile = open(sys.argv[1], 'rb') infile = sys.stdin elif len(sys.argv) == 3: schemafile = open(sys.argv[1], 'rb') infile = open(sys.argv[2], 'rb') else: raise SystemExit("%s SCHEMAFILE [INFILE]" % (sys.argv[0],)) try: obj = json.load(infile) schema = json.load(schemafile) validate(obj, schema) except ValueError as e: raise SystemExit(e) sunlightlabs-validictory-046f779/validictory/tests/000077500000000000000000000000001173004105700225215ustar00rootroot00000000000000sunlightlabs-validictory-046f779/validictory/tests/__init__.py000066400000000000000000000000001173004105700246200ustar00rootroot00000000000000sunlightlabs-validictory-046f779/validictory/tests/test_items.py000066400000000000000000000107131173004105700252550ustar00rootroot00000000000000from unittest import TestCase import validictory class TestItems(TestCase): schema1 = { "type": "array", "items": {"type": "string"} } schema2 = { "type": "array", "items": [{"type": "integer"}, {"type": "string"}, {"type": "boolean"}] } schema3 = { "type": "array", "items": ({"type": "integer"}, {"type": "string"}, {"type": "boolean"}) } def test_items_single_pass(self): data = ["string", "another string", "mystring"] data2 = ["JSON Schema is cool", "yet another string"] try: validictory.validate(data, self.schema1) validictory.validate(data2, self.schema1) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_items_single_fail(self): data = ["string", "another string", 1] self.assertRaises(ValueError, validictory.validate, data, self.schema1) def test_items_multiple_pass(self): data = [1, "More strings?", True] data2 = [12482, "Yes, more strings", False] try: validictory.validate(data, self.schema2) validictory.validate(data2, self.schema2) validictory.validate(tuple(data), self.schema3) validictory.validate(tuple(data2), self.schema3) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_items_multiple_fail(self): data = [1294, "Ok. I give up"] data2 = [1294, "Ok. I give up", "Not a boolean"] self.assertRaises(ValueError, validictory.validate, data, self.schema2) self.assertRaises(ValueError, validictory.validate, data2, self.schema2) def test_items_descriptive_fail(self): data = [1294] try: validictory.validate(data, self.schema1) except ValueError as e: # warning should mention list item, not _data assert 'list item' in str(e) class TestAdditionalItems(TestCase): schema1 = { "type": "array", "items": [{"type": "integer"}, {"type": "string"}, {"type": "boolean"}], "additionalItems": False } schema2 = { "type": "array", "items": [{"type": "integer"}, {"type": "string"}, {"type": "boolean"}], "additionalItems": True } schema3 = { "type": "array", "items": [{"type": "integer"}, {"type": "string"}, {"type": "boolean"}], "additionalItems": {"type": "number"} } schema4 = { "type": "array", "items": [{"type": "integer"}, {"type": "string"}, {"type": "boolean"}], "additionalItems": {"type": ["number", "boolean"]} } def test_additionalItems_false_no_additional_items_pass(self): data = [12482, "Yes, more strings", False] try: validictory.validate(data, self.schema1) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_additionalItems_false_additional_items_fail(self): data = [12482, "Yes, more strings", False, "I don't belong here"] self.assertRaises(ValueError, validictory.validate, data, self.schema1) def test_additionalItems_pass(self): data = [12482, "Yes, more strings", False, ["I'm"], {"also": "allowed!"}] try: validictory.validate(data, self.schema2) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_additionalItems_schema_pass(self): data = [12482, "Yes, more strings", False, 13.37, 47.11] try: validictory.validate(data, self.schema3) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_additionalItems_schema_fail(self): data = [12482, "Yes, more strings", False, 13.37, "I'm not allowed"] self.assertRaises(ValueError, validictory.validate, data, self.schema3) def test_additionalItems_multischema_pass(self): data = [12482, "Yes, more strings", False, 13.37, 47.11, True, False] try: validictory.validate(data, self.schema4) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_additionalItems_multischema_fail(self): data = [12482, "Yes, more strings", False, 13.37, True, "I'm not allowed"] self.assertRaises(ValueError, validictory.validate, data, self.schema4) sunlightlabs-validictory-046f779/validictory/tests/test_other.py000066400000000000000000000020051173004105700252500ustar00rootroot00000000000000from unittest import TestCase import validictory class TestSchemaErrors(TestCase): valid_desc = {"description": "My Description for My Schema"} invalid_desc = {"description": 1233} valid_title = {"title": "My Title for My Schema"} invalid_title = {"title": 1233} # doesn't matter what this is data = "whatever" def test_description_pass(self): try: validictory.validate(self.data, self.valid_desc) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_description_fail(self): self.assertRaises(ValueError, validictory.validate, self.data, self.invalid_desc) def test_title_pass(self): try: validictory.validate(self.data, self.valid_title) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_title_fail(self): self.assertRaises(ValueError, validictory.validate, self.data, self.invalid_title) sunlightlabs-validictory-046f779/validictory/tests/test_properties.py000066400000000000000000000245421173004105700263350ustar00rootroot00000000000000from unittest import TestCase import validictory class TestProperties(TestCase): props = { "prop01": {"type": "string"}, "prop02": {"type": "number", "optional": True}, "prop03": {"type": "integer"}, "prop04": {"type": "boolean"}, "prop05": { "type": "object", "optional": True, "properties": { "subprop01": {"type": "string"}, "subprop02": {"type": "string", "optional": False} } } } schema = {"type": "object", "properties": props} def test_properties1(self): data = { "prop01": "test", "prop02": 1.20, "prop03": 1, "prop04": True, "prop05": { "subprop01": "test", "subprop02": "test2", } } try: validictory.validate(data, self.schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_properties2(self): data = { "prop01": "test", "prop02": 1.20, "prop03": 1, "prop04": True } try: validictory.validate(data, self.schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_properties3(self): data = { "prop02": 1.60, "prop05": { "subprop01": "test" } } self.assertRaises(ValueError, validictory.validate, data, self.schema) class TestPatternProperties(TestCase): schema = {'patternProperties': {'[abc]': {'type': 'boolean'}}} def test_patternproperties_pass(self): data = {'a': True} try: validictory.validate(data, self.schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_patternproperties_nonmatch(self): data = {'a': True, 'd': 'foo'} try: validictory.validate(data, self.schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_patternproperties_nested(self): schema = {'patternProperties': {'[abc]': { 'patternProperties': {'[abc]': {'type': 'boolean'}} }}} data = {'a': {'b': False}} try: validictory.validate(data, schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_patternproperties_fail_multiple(self): data = {'a': True, 'b': False, 'c': 'foo'} self.assertRaises(ValueError, validictory.validate, data, self.schema) def test_patternproperties_fail(self): data = {'a': 12} self.assertRaises(ValueError, validictory.validate, data, self.schema) def test_patternproperties_missing(self): schema = {'properties': {'patprops': {'required': False, 'type': 'object', 'patternProperties': {'[abc]': {'required': True, 'type': 'array'}} } } } data = {'id': 1} try: validictory.validate(data, schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) class TestAdditionalProperties(TestCase): def test_no_properties(self): schema = {"additionalProperties": {"type": "integer"}} for x in [1, 89, 48, 32, 49, 42]: try: data = {"prop": x} validictory.validate(data, schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) #failures for x in [1.2, "bad", {"test": "blah"}, [32, 49], None, True]: self.assertRaises(ValueError, validictory.validate, {"prop": x}, schema) def test_with_properties(self): schema = { "properties": { "prop1": {"type": "integer"}, "prop2": {"type": "string"} }, "additionalProperties": {"type": ["string", "number"]} } for x in [1, "test", 48, "ok", 4.9, 42]: try: data = { "prop1": 123, "prop2": "this is prop2", "prop3": x } validictory.validate(data, schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) #failures for x in [{"test": "blah"}, [32, 49], None, True]: data = { "prop1": 123, "prop2": "this is prop2", "prop3": x } self.assertRaises(ValueError, validictory.validate, data, schema) def test_true(self): schema = {"additionalProperties": True} for x in [1.2, 1, {"test": "blah"}, [32, 49], None, True, "blah"]: try: validictory.validate({"prop": x}, schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_false(self): schema = {"additionalProperties": False} for x in ["bad", {"test": "blah"}, [32.42, 494242], None, True, 1.34]: self.assertRaises(ValueError, validictory.validate, {"prop": x}, schema) def test_false_with_type_string(self): schema = { "type": ["object", "string"], "properties": { "key": {"type": "string"} }, "additionalProperties": False } for data in ["foobar", {'key': 'value'}]: try: validictory.validate(data, schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) #failures for data in [['foo', 'bar'], None, True, {'roses': 'red'}]: self.assertRaises(ValueError, validictory.validate, data, schema) class TestRequires(TestCase): ''' "requires" is deprecated in draft-03 and replaced by "dependencies" ''' props = { "prop01": {"type": "string", "optional": True}, "prop02": {"type": "number", "optional": True, "requires": "prop01"} } schema = {"type": "object", "properties": props} def test_requires_pass(self): data1 = {} data2 = {"prop01": "test"} data3 = {"prop01": "test", "prop02": 2} try: validictory.validate(data1, self.schema) validictory.validate(data2, self.schema) validictory.validate(data3, self.schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_requires_fail(self): data = {"prop02": 2} self.assertRaises(ValueError, validictory.validate, data, self.schema) class TestDependencies(TestCase): props = { "prop01": {"type": "string", "optional": True}, "prop02": {"type": "number", "optional": True, "dependencies": "prop01"} } schema = {"type": "object", "properties": props} props_array = { "prop01": {"type": "string", "optional": True}, "prop02": {"type": "string", "optional": True}, "prop03": {"type": "number", "optional": True, "dependencies": ["prop01", "prop02"]} } schema_array = {"type": "object", "properties": props_array} def test_dependencies_pass(self): data1 = {} data2 = {"prop01": "test"} data3 = {"prop01": "test", "prop02": 2} data4 = {"prop01": "a", "prop02": "b", "prop03": 7} try: validictory.validate(data1, self.schema) validictory.validate(data2, self.schema) validictory.validate(data3, self.schema) validictory.validate(data4, self.schema_array) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_dependencies_fail(self): data1 = {"prop02": 2} data2 = {"prop01": "x", "prop03": 7} self.assertRaises(ValueError, validictory.validate, data1, self.schema) self.assertRaises(ValueError, validictory.validate, data2, self.schema_array) class TestOptional(TestCase): props = { "prop01": {"type": "string"}, "prop02": {"type": "number", "optional": True}, "prop03": {"type": "integer"}, "prop04": {"type": "boolean", "optional": False} } schema = {"type": "object", "properties": props} def test_optional_pass(self): x = { "prop01": "test", "prop03": 1, "prop04": False } try: validictory.validate(x, self.schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_optional_fail(self): x = {"prop02": "blah"} self.assertRaises(ValueError, validictory.validate, x, self.schema) x = {"prop04": True} # should still fail self.assertRaises(ValueError, validictory.validate, x, self.schema) class TestRequired(TestCase): props = { "prop_def": {"type": "string"}, "prop_opt": {"type": "number", "required": False}, "prop_req": {"type": "boolean", "required": True} } schema = {"type": "object", "properties": props} def_and_req = {"prop_def": "test", "prop_req": False} req_only = {"prop_req": True} opt_only = {"prop_opt": 7} def test_required_pass(self): try: # should pass if def and req are there validictory.validate(self.def_and_req, self.schema) # should pass if default is missing but req_by_default=False validictory.validate(self.req_only, self.schema, required_by_default=False) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_required_fail(self): # missing required should always fail self.assertRaises(ValueError, validictory.validate, self.opt_only, self.schema) self.assertRaises(ValueError, validictory.validate, self.opt_only, self.schema, required_by_default=False) # missing the default, fail if required_by_default=True self.assertRaises(ValueError, validictory.validate, self.req_only, self.schema) sunlightlabs-validictory-046f779/validictory/tests/test_schema_schema.py000066400000000000000000000073071173004105700267210ustar00rootroot00000000000000from unittest import TestCase import validictory schema = { "$schema": "http://json-schema.org/draft-03/schema#", "id": "http://json-schema.org/draft-03/schema#", "type": "object", "properties": { "type": { "type": ["string", "array"], "items": { "type": ["string", {"$ref": "#"}] }, "uniqueItems": True, "default": "any" }, "properties": { "type": "object", "additionalProperties": {"$ref": "#"}, "default": {} }, "patternProperties": { "type": "object", "additionalProperties": {"$ref": "#"}, "default": {} }, "additionalProperties": { "type": [{"$ref": "#"}, "boolean"], "default": {} }, "items": { "type": [{"$ref": "#"}, "array"], "items": {"$ref": "#"}, "default": {} }, "additionalItems": { "type": [{"$ref": "#"}, "boolean"], "default": {} }, "required": { "type": "boolean", "default": False }, "dependencies": { "type": "object", "additionalProperties": { "type": ["string", "array", {"$ref": "#"}], "items": { "type": "string" } }, "default": {} }, "minimum": { "type": "number" }, "maximum": { "type": "number" }, "exclusiveMinimum": { "type": "boolean", "default": False }, "exclusiveMaximum": { "type": "boolean", "default": False }, "minItems": { "type": "integer", "minimum": 0, "default": 0 }, "maxItems": { "type": "integer", "minimum": 0 }, "uniqueItems": { "type": "boolean", "default": False }, "pattern": { "type": "string", "format": "regex" }, "minLength": { "type": "integer", "minimum": 0, "default": 0 }, "maxLength": { "type": "integer" }, "enum": { "type": "array", "minItems": 1, "uniqueItems": True }, "default": { "type": "any" }, "title": { "type": "string" }, "description": { "type": "string" }, "format": { "type": "string" }, "divisibleBy": { "type": "number", "minimum": 0, "exclusiveMinimum": True, "default": 1 }, "disallow": { "type": ["string", "array"], "items": { "type": ["string", {"$ref": "#"}] }, "uniqueItems": True }, "extends": { "type": [{"$ref": "#"}, "array"], "items": {"$ref": "#"}, "default": {} }, "id": { "type": "string", "format": "uri" }, "$ref": { "type": "string", "format": "uri" }, "$schema": { "type": "string", "format": "uri" } }, "dependencies": { "exclusiveMinimum": "minimum", "exclusiveMaximum": "maximum" }, "default": {} } class TestSchemaSchema(TestCase): def test_schema(self): validictory.validate(schema, schema, required_by_default=False) sunlightlabs-validictory-046f779/validictory/tests/test_type.py000066400000000000000000000114201173004105700251110ustar00rootroot00000000000000from unittest import TestCase import datetime import sys if sys.version_info[0] == 3: unicode_str = '\u2603' else: unicode_str = unicode('snowman') import validictory class TestType(TestCase): def test_schema(self): schema = { "type": [ {"type": "array", "minItems": 10}, {"type": "string", "pattern": "^0+$"} ] } data1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] data2 = "0" data3 = 1203 for x in [data1, data2]: try: validictory.validate(x, schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) self.assertRaises(ValueError, validictory.validate, data3, schema) def _test_type(self, typename, valids, invalids): for x in valids: try: validictory.validate(x, {"type": typename}) except ValueError as e: self.fail("Unexpected failure: %s" % e) for x in invalids: self.assertRaises(ValueError, validictory.validate, x, {"type": typename}) def test_integer(self): valid_ints = [1, -89, 420000] invalid_ints = [1.2, "bad", {"test":"blah"}, [32, 49], None, True] self._test_type('integer', valid_ints, invalid_ints) def test_string(self): valids = ["abc", unicode_str] invalids = [1.2, 1, {"test":"blah"}, [32, 49], None, True] self._test_type('string', valids, invalids) def test_number(self): valids = [1.2, -89.42, 48, -32] invalids = ["bad", {"test":"blah"}, [32.42, 494242], None, True] self._test_type('number', valids, invalids) def test_boolean(self): valids = [True, False] invalids = [1.2, "False", {"test":"blah"}, [32, 49], None, 1, 0] self._test_type('boolean', valids, invalids) def test_object(self): valids = [{"blah": "test"}, {"this":{"blah":"test"}}, {1:2, 10:20}] invalids = [1.2, "bad", 123, [32, 49], None, True] self._test_type('object', valids, invalids) def test_array(self): valids = [[1, 89], [48, {"test":"blah"}, "49", 42], (47, 11)] invalids = [1.2, "bad", {"test":"blah"}, 1234, None, True] self._test_type('array', valids, invalids) def test_null(self): valids = [None] invalids = [1.2, "bad", {"test":"blah"}, [32, 49], 1284, True] self._test_type('null', valids, invalids) def test_any(self): valids = [1.2, "bad", {"test":"blah"}, [32, 49], None, 1284, True] self._test_type('any', valids, []) def test_default(self): # test default value (same as any really) valids = [1.2, "bad", {"test":"blah"}, [32, 49], None, 1284, True] for x in valids: try: validictory.validate(x, {}) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_multi(self): types = ["null", "integer", "string"] valids = [None, 42, "string"] invalids = [1.2, {"test":"blah"}, [32, 49], True] self._test_type(types, valids, invalids) self._test_type(tuple(types), valids, invalids) class TestDisallow(TestType): def _test_type(self, typename, valids, invalids): for x in invalids: try: validictory.validate(x, {"disallow": typename}) except ValueError as e: self.fail("Unexpected failure: %s" % e) for x in valids: self.assertRaises(ValueError, validictory.validate, x, {"disallow": typename}) class DateValidator(validictory.validator.SchemaValidator): def validate_type_date(self, value): return isinstance(value, datetime.date) def validate_type_datetime(self, value): return isinstance(value, datetime.datetime) class TestCustomType(TestCase): def test_date(self): self._test_type('date', [datetime.date.today()], [2010, '2010']) def test_datetime(self): self._test_type('datetime', [datetime.datetime.now()], [2010, '2010', datetime.date.today()]) def test_either(self): self._test_type(['datetime', 'date'], [datetime.date.today(), datetime.datetime.now()], [2010, '2010']) def _test_type(self, typename, valids, invalids): validator = DateValidator() for x in valids: try: validator.validate(x, {"type": typename}) except ValueError as e: self.fail("Unexpected failure: %s" % e) for x in invalids: self.assertRaises(ValueError, validator.validate, x, {"type": typename}) sunlightlabs-validictory-046f779/validictory/tests/test_values.py000066400000000000000000000401331173004105700254320ustar00rootroot00000000000000""" Tests that test the value of individual items """ from unittest import TestCase import validictory class TestEnum(TestCase): schema = {"enum": ["test", True, 123, ["???"]]} schema2 = {"enum": ("test", True, 123, ["???"])} def test_enum_pass(self): data = ["test", True, 123, ["???"]] try: for item in data: validictory.validate(item, self.schema) validictory.validate(item, self.schema2) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_enum_fail(self): data = "unknown" self.assertRaises(ValueError, validictory.validate, data, self.schema) class TestPattern(TestCase): # match simplified regular expression for an e-mail address schema = {"pattern": "^[A-Za-z0-9][A-Za-z0-9\.]*@([A-Za-z0-9]+\.)+[A-Za-z0-9]+$"} def test_pattern_pass(self): data = "my.email01@gmail.com" try: validictory.validate(data, self.schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_pattern_pass_nonstring(self): data = 123 try: validictory.validate(data, self.schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_pattern_fail(self): data = "whatever" self.assertRaises(ValueError, validictory.validate, data, self.schema) def validate_format_contains_spaces(validator, fieldname, value, format_option): if ' ' in value: return raise validictory.ValidationError( "Value %(value)r of field '%(fieldname)s' does not contain any spaces," "but it should" % locals()) class TestFormat(TestCase): schema_datetime = {"format": "date-time"} schema_date = {"format": "date"} schema_time = {"format": "time"} schema_utcmillisec = {"format": "utc-millisec"} schema_ip = {"format": "ip-address"} schema_spaces = {"format": "spaces"} def test_format_datetime_pass(self): data = "2011-01-13T10:56:53Z" try: validictory.validate(data, self.schema_datetime) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_format_date_pass(self): data = "2011-01-13" try: validictory.validate(data, self.schema_date) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_format_time_pass(self): data = "10:56:53" try: validictory.validate(data, self.schema_time) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_format_utcmillisec_pass(self): data = 1294915735 try: validictory.validate(data, self.schema_utcmillisec) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_format_datetime_nonexisting_day_fail(self): data = "2013-13-13T00:00:00Z" self.assertRaises(ValueError, validictory.validate, data, self.schema_datetime) def test_format_datetime_feb29_fail(self): data = "2011-02-29T00:00:00Z" self.assertRaises(ValueError, validictory.validate, data, self.schema_datetime) def test_format_datetime_notutc_fail(self): data = "2011-01-13T10:56:53+01: 00" self.assertRaises(ValueError, validictory.validate, data, self.schema_datetime) def test_format_datetime_fail(self): data = "whatever" self.assertRaises(ValueError, validictory.validate, data, self.schema_datetime) def test_format_date_fail(self): data = "whatever" self.assertRaises(ValueError, validictory.validate, data, self.schema_date) def test_format_time_fail(self): data = "whatever" self.assertRaises(ValueError, validictory.validate, data, self.schema_time) def test_format_utcmillisec_fail(self): data = "whatever" self.assertRaises(ValueError, validictory.validate, data, self.schema_utcmillisec) def test_format_utcmillisec_negative_fail(self): data = -1 self.assertRaises(ValueError, validictory.validate, data, self.schema_utcmillisec) def test_format_ip_pass(self): valids = ["0.0.0.0", "255.255.255.255"] for ip in valids: try: validictory.validate(ip, self.schema_ip) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_format_ip_fail(self): invalids = [1.2, "bad", {"test":"blah"}, [32, 49], 1284, True, "-0.-0.-0.-0", "-1.-1.-1.-1", "256.256.256.256"] for ip in invalids: self.assertRaises(ValueError, validictory.validate, ip, self.schema_ip) def test_format_required_false(self): schema = { 'type': 'object', 'properties': { 'startdate': {'type': 'string', 'format': 'date-time', 'required': False} } } try: validictory.validate({}, schema, required_by_default=False) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_format_custom_unregistered_pass(self): data = 'No-spaces-here' try: # no custom validator installed, so no error validictory.validate(data, self.schema_spaces) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_format_custom_instantiated_pass(self): data = 'Here are spaces' validator = validictory.SchemaValidator( {'spaces': validate_format_contains_spaces}) try: # validator installed, but data validates validator.validate(data, self.schema_spaces) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_format_custom_registered_pass(self): data = 'Here are spaces' validator = validictory.SchemaValidator() validator.register_format_validator('spaces', validate_format_contains_spaces) try: # validator registered, but data validates validator.validate(data, self.schema_spaces) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_format_custom_registered_fail(self): data = 'No-spaces-here' validator = validictory.SchemaValidator( {'spaces': validate_format_contains_spaces}) # validator registered, but data does not conform self.assertRaises(ValueError, validator.validate, data, self.schema_spaces) class TestUniqueItems(TestCase): schema = {"uniqueItems": True} schema_false = {"uniqueItems": False} def test_uniqueitems_pass(self): data = [1, 2, 3] try: validictory.validate(data, self.schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_uniqueitems_pass_string(self): data = ['1', '2', '3'] try: validictory.validate(data, self.schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_uniqueitems_pass_nested_array(self): ''' uniqueItems only applies for the array it was specified on and not to all datastructures nested within. ''' data = [[1, [5, 5]], [2, [5, 5]]] try: validictory.validate(data, self.schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_uniqueitems_pass_not_an_array(self): data = 13 # it's pretty unique try: validictory.validate(data, self.schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_uniqueitems_pass_different_types(self): data = [1, "1"] try: validictory.validate(data, self.schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_uniqueitems_false_pass(self): data = [1, 1, 1] try: validictory.validate(data, self.schema_false) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_uniqueitems_fail(self): data = [1, 1, 1] self.assertRaises(ValueError, validictory.validate, data, self.schema) def test_uniqueitems_fail_nested_arrays(self): data = [[1, 2, 3], [1, 2, 3]] self.assertRaises(ValueError, validictory.validate, data, self.schema) def test_uniqueitems_fail_nested_objects(self): data = [{'one': 1, 'two': 2}, {'one': 1, 'two': 2}] self.assertRaises(ValueError, validictory.validate, data, self.schema) def test_uniqueitems_fail_null(self): data = [None, None] self.assertRaises(ValueError, validictory.validate, data, self.schema) class TestMaximum(TestCase): props = { "prop01": {"type": "number", "maximum": 10}, "prop02": {"type": "integer", "maximum": 20} } props_exclusive = { "prop": {"type": "integer", "maximum": 20, "exclusiveMaximum": True}, } schema = {"type": "object", "properties": props} schema_exclusive = {"type": "object", "properties": props_exclusive} def test_maximum_pass(self): #Test less than data1 = {"prop01": 5, "prop02": 10} #Test equal data2 = {"prop01": 10, "prop02": 20} try: validictory.validate(data1, self.schema) validictory.validate(data2, self.schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_maximum_exclusive_pass(self): #Test less than data = {"prop": 19} try: validictory.validate(data, self.schema_exclusive) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_maximum_fail(self): #Test number data1 = {"prop01": 11, "prop02": 19} #Test integer data2 = {"prop01": 9, "prop02": 21} self.assertRaises(ValueError, validictory.validate, data1, self.schema) self.assertRaises(ValueError, validictory.validate, data2, self.schema) def test_maximum_exclusive_fail(self): #Test equal data = {"prop": 20} self.assertRaises(ValueError, validictory.validate, data, self.schema_exclusive) class TestMinimum(TestCase): props = { "prop01": {"type": "number", "minimum": 10}, "prop02": {"type": "integer", "minimum": 20} } props_exclusive = { "prop": {"type": "integer", "minimum": 20, "exclusiveMinimum": True}, } schema = {"type": "object", "properties": props} schema_exclusive = {"type": "object", "properties": props_exclusive} def test_minimum_pass(self): #Test greater than data1 = {"prop01": 21, "prop02": 21} #Test equal data2 = {"prop01": 10, "prop02": 20} try: validictory.validate(data1, self.schema) validictory.validate(data2, self.schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_minimum_exclusive_pass(self): #Test greater than data = {"prop": 21} try: validictory.validate(data, self.schema_exclusive) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_minimum_fail(self): #Test number data1 = {"prop01": 9, "prop02": 21} #Test integer data2 = {"prop01": 10, "prop02": 19} self.assertRaises(ValueError, validictory.validate, data1, self.schema) self.assertRaises(ValueError, validictory.validate, data2, self.schema) def test_minimum_exclusive_fail(self): #Test equal data = {"prop": 20} self.assertRaises(ValueError, validictory.validate, data, self.schema_exclusive) class TestMinLength(TestCase): schema = {"minLength": 4} def test_minLength_pass(self): # str-equal, str-gt, list-equal, list-gt data = ['test', 'string', [1, 2, 3, 4], [0, 0, 0, 0, 0]] try: for item in data: validictory.validate(item, self.schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_minLength_pass_nonstring(self): #test when data is not a string data1 = 123 try: validictory.validate(data1, self.schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_minLength_fail(self): #test equal data = ["car", [1, 2, 3]] for item in data: self.assertRaises(ValueError, validictory.validate, data, self.schema) class TestMaxLength(TestCase): schema = {"maxLength": 4} def test_maxLength_pass(self): # str-equal, str-lt, list-equal, list-lt data = ["test", "car", [1, 2, 3, 4], [0, 0, 0]] try: for item in data: validictory.validate(item, self.schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_maxLength_pass_nonstring(self): # test when data is not a string data1 = 12345 try: validictory.validate(data1, self.schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_maxLength_fail(self): data = ["string", [1, 2, 3, 4, 5]] for item in data: self.assertRaises(ValueError, validictory.validate, item, self.schema) class TestBlank(TestCase): def test_blank_default_false(self): schema = { "type": "object", "properties": { "key": { "type": "string", "required": True, } } } try: validictory.validate({"key": "value"}, {}, blank_by_default=False) except ValueError as e: self.fail("Unexpected failure: %s" % e) self.assertRaises(ValueError, validictory.validate, {"key": ""}, schema) def test_blank_default_true(self): schema = { "type": "object", "properties": { "key": { "type": "string", "required": True, } } } try: validictory.validate({"key": ""}, schema, blank_by_default=True) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_blank_false(self): schema = {"blank": False} try: validictory.validate("test", schema, blank_by_default=True) except ValueError as e: self.fail("Unexpected failure: %s" % e) self.assertRaises(ValueError, validictory.validate, "", schema) def test_blank_true(self): try: validictory.validate("", {"blank": True}, blank_by_default=False) validictory.validate("test", {"blank": True}, blank_by_default=False) except ValueError as e: self.fail("Unexpected failure: %s" % e) class TestDivisibleBy(TestCase): schema = {'type': 'number', 'divisibleBy': 12} schema0 = {'type': 'number', 'divisibleBy': 0} def test_divisibleBy_pass(self): data = 60 try: validictory.validate(data, self.schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_divisibleBy_fail(self): data = 13 self.assertRaises(ValueError, validictory.validate, data, self.schema) def test_divisibleBy_ZeroDivisionError_fail(self): data = 60 self.assertRaises(ValueError, validictory.validate, data, self.schema0) sunlightlabs-validictory-046f779/validictory/validator.py000077500000000000000000000544371173004105700237360ustar00rootroot00000000000000import re import sys import copy import socket from datetime import datetime import warnings from collections import Mapping, Container if sys.version_info[0] == 3: _str_type = str _int_types = (int,) else: _str_type = basestring _int_types = (int, long) class SchemaError(ValueError): """ errors encountered in processing a schema (subclass of :class:`ValueError`) """ class ValidationError(ValueError): """ validation errors encountered during validation (subclass of :class:`ValueError`) """ def _generate_datetime_validator(format_option, dateformat_string): def validate_format_datetime(validator, fieldname, value, format_option): try: datetime.strptime(value, dateformat_string) except ValueError: raise ValidationError( "Value %(value)r of field '%(fieldname)s' is not in " "'%(format_option)s' format" % locals()) return validate_format_datetime validate_format_date_time = _generate_datetime_validator('date-time', '%Y-%m-%dT%H:%M:%SZ') validate_format_date = _generate_datetime_validator('date', '%Y-%m-%d') validate_format_time = _generate_datetime_validator('time', '%H:%M:%S') def validate_format_utc_millisec(validator, fieldname, value, format_option): if not isinstance(value, (int, float)): raise ValidationError("Value %(value)r of field '%(fieldname)s' is " "not a number" % locals()) if not value > 0: raise ValidationError("Value %(value)r of field '%(fieldname)s' is " "not a positive number" % locals()) def validate_format_ip_address(validator, fieldname, value, format_option): try: socket.inet_aton(value) # Make sure we expect "X.X.X.X" as socket.inet_aton() converts "1" # to "0.0.0.1" ip = len(value.split('.')) == 4 except: ip = False if not ip: raise ValidationError("Value %(value)r of field '%(fieldname)s' is " "not a ip-address" % locals()) DEFAULT_FORMAT_VALIDATORS = { 'date-time': validate_format_date_time, 'date': validate_format_date, 'time': validate_format_time, 'utc-millisec': validate_format_utc_millisec, 'ip-address': validate_format_ip_address, } class SchemaValidator(object): ''' Validator largely based upon the JSON Schema proposal but useful for validating arbitrary python data structures. :param format_validators: optional dictionary of custom format validators :param required_by_default: defaults to True, set to False to make ``required`` schema attribute False by default. :param blank_by_default: defaults to False, set to True to make ``blank`` schema attribute True by default. ''' def __init__(self, format_validators=None, required_by_default=True, blank_by_default=False): if format_validators is None: format_validators = DEFAULT_FORMAT_VALIDATORS.copy() self._format_validators = format_validators self.required_by_default = required_by_default self.blank_by_default = blank_by_default def register_format_validator(self, format_name, format_validator_fun): self._format_validators[format_name] = format_validator_fun def validate_type_string(self, val): return isinstance(val, _str_type) def validate_type_integer(self, val): return type(val) in _int_types def validate_type_number(self, val): return type(val) in _int_types + (float,) def validate_type_boolean(self, val): return type(val) == bool def validate_type_object(self, val): return isinstance(val, Mapping) def validate_type_array(self, val): return isinstance(val, (list, tuple)) def validate_type_null(self, val): return val is None def validate_type_any(self, val): return True def _error(self, desc, value, fieldname, **params): params['value'] = value params['fieldname'] = fieldname message = desc % params raise ValidationError(message) def validate_type(self, x, fieldname, schema, fieldtype=None): ''' Validates that the fieldtype specified is correct for the given data ''' # We need to know if the field exists or if it's just Null fieldexists = True try: value = x[fieldname] except KeyError: fieldexists = False value = None if fieldtype and fieldexists: if isinstance(fieldtype, (list, tuple)): # Match if type matches any one of the types in the list datavalid = False for eachtype in fieldtype: try: self.validate_type(x, fieldname, eachtype, eachtype) datavalid = True break except ValidationError: pass if not datavalid: self._error("Value %(value)r for field '%(fieldname)s' is " "not of type %(fieldtype)s", value, fieldname, fieldtype=fieldtype) elif isinstance(fieldtype, dict): try: self.__validate(fieldname, x, fieldtype) except ValueError as e: raise e else: try: type_checker = getattr(self, 'validate_type_%s' % fieldtype) except AttributeError: raise SchemaError("Field type '%s' is not supported." % fieldtype) if not type_checker(value): self._error("Value %(value)r for field '%(fieldname)s' " "is not of type %(fieldtype)s", value, fieldname, fieldtype=fieldtype) def validate_properties(self, x, fieldname, schema, properties=None): ''' Validates properties of a JSON object by processing the object's schema recursively ''' if x.get(fieldname) is not None: value = x.get(fieldname) if isinstance(value, dict): if isinstance(properties, dict): for eachProp in properties: self.__validate(eachProp, value, properties.get(eachProp)) else: raise SchemaError("Properties definition of field '%s' is " "not an object" % fieldname) def validate_items(self, x, fieldname, schema, items=None): ''' Validates that all items in the list for the given field match the given schema ''' if x.get(fieldname) is not None: value = x.get(fieldname) if isinstance(value, (list, tuple)): if isinstance(items, (list, tuple)): if (not 'additionalItems' in schema and len(items) != len(value)): self._error("Length of list %(value)r for field " "'%(fieldname)s' is not equal to length " "of schema list", value, fieldname) else: for itemIndex in range(len(items)): try: self.validate(value[itemIndex], items[itemIndex]) except ValueError as e: raise type(e)("Failed to validate field '%s' " "list schema: %s" % (fieldname, e)) elif isinstance(items, dict): for eachItem in value: try: self._validate(eachItem, items) except ValueError as e: # a bit of a hack: replace reference to _data # with 'list item' so error messages make sense old_error = str(e).replace("field '_data'", 'list item') raise type(e)("Failed to validate field '%s' list " "schema: %s" % (fieldname, old_error)) else: raise SchemaError("Properties definition of field '%s' is " "not a list or an object" % fieldname) def validate_required(self, x, fieldname, schema, required): ''' Validates that the given field is present if required is True ''' # Make sure the field is present if fieldname not in x and required: self._error("Required field '%(fieldname)s' is missing", None, fieldname) def validate_blank(self, x, fieldname, schema, blank=False): ''' Validates that the given field is not blank if blank=False ''' value = x.get(fieldname) if isinstance(value, _str_type) and not blank and not value: self._error("Value %(value)r for field '%(fieldname)s' cannot be " "blank'", value, fieldname) def validate_patternProperties(self, x, fieldname, schema, patternproperties=None): if patternproperties == None: patternproperties = {} value_obj = x.get(fieldname, {}) for pattern, schema in patternproperties.items(): for key, value in value_obj.items(): if re.match(pattern, key): self.validate(value, schema) def validate_additionalItems(self, x, fieldname, schema, additionalItems=False): value = x.get(fieldname) if not isinstance(value, (list, tuple)): return if isinstance(additionalItems, bool): if additionalItems or 'items' not in schema: return elif len(value) != len(schema['items']): #print locals(), value, len(value), len(schema['items']) self._error("Length of list %(value)r for field " "'%(fieldname)s' is not equal to length of schema " "list", value, fieldname) remaining = value[len(schema['items']):] if len(remaining) > 0: self._validate(remaining, {'items': additionalItems}) def validate_additionalProperties(self, x, fieldname, schema, additionalProperties=None): ''' Validates additional properties of a JSON object that were not specifically defined by the properties property ''' # Shouldn't be validating additionalProperties on non-dicts value = x.get(fieldname) if not isinstance(value, dict): return # If additionalProperties is the boolean value True then we accept # any additional properties. if isinstance(additionalProperties, bool) and additionalProperties: return value = x.get(fieldname) if isinstance(additionalProperties, (dict, bool)): properties = schema.get("properties") if properties is None: properties = {} if value is None: value = {} for eachProperty in value: if eachProperty not in properties: # If additionalProperties is the boolean value False # then we don't accept any additional properties. if (isinstance(additionalProperties, bool) and not additionalProperties): self._error("additional properties not defined by " "'properties' are not allowed in field " "'%(fieldname)s'", None, fieldname) self.__validate(eachProperty, value, additionalProperties) else: raise SchemaError("additionalProperties schema definition for " "field '%s' is not an object" % fieldname) def validate_requires(self, x, fieldname, schema, requires=None): warnings.warn('The "requires" attribute has been replaced by "dependencies"', DeprecationWarning) if x.get(fieldname) is not None: if x.get(requires) is None: self._error("Field '%(requires)s' is required by field '%(fieldname)s'", None, fieldname, requires=requires) def validate_dependencies(self, x, fieldname, schema, dependencies=None): if x.get(fieldname) is not None: # handle cases where dependencies is a string or list of strings if isinstance(dependencies, _str_type): dependencies = [dependencies] if isinstance(dependencies, (list, tuple)): for dependency in dependencies: if dependency not in x: self._error("Field '%(dependency)s' is required by " "field '%(fieldname)s'", None, fieldname, dependency=dependency) elif isinstance(dependencies, dict): # NOTE: the version 3 spec is really unclear on what this means # based on the meta-schema I'm assuming that it should check # that if a key exists, the appropriate value exists for k, v in dependencies.items(): if k in x and v not in x: self._error("Field '%(v)s' is required by field " "'%(k)s'", None, fieldname, k=k, v=v) else: raise SchemaError("'dependencies' must be a string, " "list of strings, or dict") def validate_minimum(self, x, fieldname, schema, minimum=None): ''' Validates that the field is longer than or equal to the minimum length if specified ''' exclusive = schema.get('exclusiveMinimum', False) if x.get(fieldname) is not None: value = x.get(fieldname) if value is not None: if (type(value) in (int, float) and (not exclusive and value < minimum) or (exclusive and value <= minimum)): self._error("Value %(value)r for field '%(fieldname)s' is " "less than minimum value: %(minimum)f", value, fieldname, minimum=minimum) def validate_maximum(self, x, fieldname, schema, maximum=None): ''' Validates that the field is shorter than or equal to the maximum length if specified. ''' exclusive = schema.get('exclusiveMaximum', False) if x.get(fieldname) is not None: value = x.get(fieldname) if value is not None: if (type(value) in (int, float) and (not exclusive and value > maximum) or (exclusive and value >= maximum)): self._error("Value %(value)r for field '%(fieldname)s' is " "greater than maximum value: %(maximum)f", value, fieldname, maximum=maximum) def validate_maxLength(self, x, fieldname, schema, length=None): ''' Validates that the value of the given field is shorter than or equal to the specified length ''' value = x.get(fieldname) if isinstance(value, (_str_type, list, tuple)) and len(value) > length: self._error("Length of value %(value)r for field '%(fieldname)s' " "must be less than or equal to %(length)d", value, fieldname, length=length) def validate_minLength(self, x, fieldname, schema, length=None): ''' Validates that the value of the given field is longer than or equal to the specified length ''' value = x.get(fieldname) if isinstance(value, (_str_type, list, tuple)) and len(value) < length: self._error("Length of value %(value)r for field '%(fieldname)s' " "must be greater than or equal to %(length)d", value, fieldname, length=length) validate_minItems = validate_minLength validate_maxItems = validate_maxLength def validate_format(self, x, fieldname, schema, format_option=None): ''' Validates the format of primitive data types ''' value = x.get(fieldname) format_validator = self._format_validators.get(format_option, None) if format_validator and value: format_validator(self, fieldname, value, format_option) # TODO: warn about unsupported format ? def validate_pattern(self, x, fieldname, schema, pattern=None): ''' Validates that the given field, if a string, matches the given regular expression. ''' value = x.get(fieldname) if isinstance(value, _str_type): if not re.match(pattern, value): self._error("Value %(value)r for field '%(fieldname)s' does " "not match regular expression '%(pattern)s'", value, fieldname, pattern=pattern) def validate_uniqueItems(self, x, fieldname, schema, uniqueItems=False): ''' Validates that all items in an array instance MUST be unique (contains no two identical values). ''' # If additionalProperties is the boolean value True then we accept # any additional properties. if isinstance(uniqueItems, bool) and not uniqueItems: return values = x.get(fieldname) if not isinstance(values, (list, tuple)): return hashables = set() unhashables = [] for value in values: if isinstance(value, (list, dict)): container, add = unhashables, unhashables.append else: container, add = hashables, hashables.add if value in container: self._error( "Value %(value)r for field '%(fieldname)s' is not unique", value, fieldname) else: add(value) def validate_enum(self, x, fieldname, schema, options=None): ''' Validates that the value of the field is equal to one of the specified option values ''' value = x.get(fieldname) if value is not None: if not isinstance(options, Container): raise SchemaError("Enumeration %r for field '%s' must be a " "container", (options, fieldname)) if value not in options: self._error("Value %(value)r for field '%(fieldname)s' is not " "in the enumeration: %(options)r", value, fieldname, options=options) def validate_title(self, x, fieldname, schema, title=None): if not isinstance(title, (_str_type, type(None))): raise SchemaError("The title for field '%s' must be a string" % fieldname) def validate_description(self, x, fieldname, schema, description=None): if not isinstance(description, (_str_type, type(None))): raise SchemaError("The description for field '%s' must be a string" % fieldname) def validate_divisibleBy(self, x, fieldname, schema, divisibleBy=None): value = x.get(fieldname) if not self.validate_type_number(value): return if divisibleBy == 0: raise SchemaError("'%r' <- divisibleBy can not be 0" % schema) if value % divisibleBy != 0: self._error("Value %(value)r field '%(fieldname)s' is not " "divisible by '%(divisibleBy)s'.", x.get(fieldname), fieldname, divisibleBy=divisibleBy) def validate_disallow(self, x, fieldname, schema, disallow=None): ''' Validates that the value of the given field does not match the disallowed type. ''' try: self.validate_type(x, fieldname, schema, disallow) except ValidationError: return self._error("Value %(value)r of type %(disallow)s is disallowed for " "field '%(fieldname)s'", x.get(fieldname), fieldname, disallow=disallow) def validate(self, data, schema): ''' Validates a piece of json data against the provided json-schema. ''' self._validate(data, schema) def _validate(self, data, schema): self.__validate("_data", {"_data": data}, schema) def __validate(self, fieldname, data, schema): if schema is not None: if not isinstance(schema, dict): raise SchemaError("Schema structure is invalid.") newschema = copy.copy(schema) # handle 'optional', replace it with 'required' if 'required' in schema and 'optional' in schema: raise SchemaError('cannot specify optional and required') elif 'optional' in schema: warnings.warn('The "optional" attribute has been replaced by "required"', DeprecationWarning) newschema['required'] = not schema['optional'] elif 'required' not in schema: newschema['required'] = self.required_by_default if 'blank' not in schema: newschema['blank'] = self.blank_by_default for schemaprop in newschema: validatorname = "validate_" + schemaprop validator = getattr(self, validatorname, None) if validator: validator(data, fieldname, schema, newschema.get(schemaprop)) return data __all__ = ['SchemaValidator']