pax_global_header00006660000000000000000000000064132103233540014506gustar00rootroot0000000000000052 comment=6571d051014a3fc302ecd8d67191cd07a6973fdd validictory-1.1.2/000077500000000000000000000000001321032335400140405ustar00rootroot00000000000000validictory-1.1.2/.coveragerc000066400000000000000000000001311321032335400161540ustar00rootroot00000000000000[run] omit = validictory/tests/* [report] exclude_lines = if __name__ == .__main__.: validictory-1.1.2/.gitignore000066400000000000000000000001001321032335400160170ustar00rootroot00000000000000*.egg-info *.pyc dist build .tox .coverage .ropeproject htmlcov validictory-1.1.2/.travis.yml000066400000000000000000000004121321032335400161460ustar00rootroot00000000000000language: python python: - "2.7" - "3.4" - "3.5" - "3.6" install: pip install pytest pytest-cov coveralls script: py.test --cov validictory --cov-config=.coveragerc after_success: - coveralls notifications: email: - dev@jamesturk.net validictory-1.1.2/AUTHORS.txt000066400000000000000000000022761321032335400157350ustar00rootroot00000000000000Initially derived from jsonschema, by Ian Lewis and Yusuke Muraoka. * James Turk - port from jsonschema & additional features & maintenance * 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 * Lowe Thiderman - improved error message for additionalProperties * Alfredo Deza - improved SchemaError messages * Arthur S. - fix to support Decimal in addition to float * Jesse Printz - doc fix * Nicola Iarocci - disallow_unknown_properties option * Rhett Garber - addition of FieldValidationError * Marc Abramowitz - fix for error message when data doesn't match one of several types, fix to allow dict-like types to validate as dicts * Alon Horev - fix for disallow_unknown_properties bug * Daniel Rech - support for default param * Juan Menéndez & James Clemence - fix for patternProperties w/ additionalProperties validictory-1.1.2/LICENSE.txt000066400000000000000000000021531321032335400156640ustar00rootroot00000000000000Copyright (c) 2013 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. validictory-1.1.2/MANIFEST.in000066400000000000000000000000731321032335400155760ustar00rootroot00000000000000include *.rst include LICENSE.txt include CHANGELOG.txt validictory-1.1.2/README.rst000066400000000000000000000045411321032335400155330ustar00rootroot00000000000000=========== validictory =========== :warning: **:warning: As of 2018 this library is deprecated, please consider using jsonschema (https://pypi.python.org/pypi/jsonschema) instead.** .. image:: https://travis-ci.org/jamesturk/validictory.svg?branch=master :target: https://travis-ci.org/jamesturk/validictory .. image:: https://coveralls.io/repos/jamesturk/validictory/badge.png?branch=master :target: https://coveralls.io/r/jamesturk/validictory .. image:: https://img.shields.io/pypi/v/validictory.svg :target: https://pypi.python.org/pypi/validictory .. image:: https://readthedocs.org/projects/validictory/badge/?version=latest :target: https://readthedocs.org/projects/validictory/?badge=latest :alt: Documentation Status A general purpose Python data validator. Works with Python 2.7 and Python 3.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("something", {"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("something", {"type":"string","minLength":15}) ... except ValueError, error: ... print(error) ... Length of value 'something' for field '_data' must be greater than or equal to 15 You can read more in the official documentation at `Read the Docs `_. validictory-1.1.2/docs/000077500000000000000000000000001321032335400147705ustar00rootroot00000000000000validictory-1.1.2/docs/Makefile000066400000000000000000000110021321032335400164220ustar00rootroot00000000000000# 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." validictory-1.1.2/docs/changelog.rst000066400000000000000000000107671321032335400174640ustar00rootroot00000000000000validictory changelog ===================== 1.1.2 ----- **2017-12-01** * final release w/ deprecation notice * use full path in RequiredFieldValidationError, thanks Marco Mariani 1.1.1 ----- **2017-04-04** * fix for mutable default arguments from Nick Stefan 1.1.0 ----- **2016-10-16** * added support for minProperties/maxProperties, thanks to Heiko Finzel * stop manipulating sys.path in tests, thanks to Hartmut Goebel * fix for SchemaError being raised within list, thanks to Seb James * fix remove_unknown_properties not playing nice w/ patternProperties 1.0.2 ----- **2016-06-21** * bugfix to support microseconds in datetime validation, thanks to Christopher Graham 1.0.1 ----- **2015-09-24** * bugfix for fail_fast w/ type lists 1.0.0 ----- **2015-01-16** * stable release 1.0.0a2 ------- **2014-07-15** * ensure path to field is used in error 1.0.0a1 ------- **2014-07-10** * fix TypeError from format validators * some documentation fixes * enum options are callable (from James McKinney) * switch to py.test * internal changes to how _validate and _error work * initial work on fail_fast=False * initial work on descriptive field names 0.9.3 ----- **2013-11-25** * fix bad 0.9.2 release that didn't have a fix for invalid code from a PR 0.9.2 ----- **2013-11-25** * fix from Marc Abramowitz for validating dict-like things as dicts * fix for patternProperties from Juan Menéndez & James Clemence * include implementation of "default" property from Daniel Rech * drop official support for Python 3.2 * remove a test that relied on dict ordering * updated docs from Mark Grandi * fix where format validators were cleared (also Mark Grandi) 0.9.1 ----- **2013-05-23** * fix for error message when data doesn't match one of multiple subtypes * fix for disallow_unknown_properties 0.9.0 ----- **2013-01-19** * remove optional and requires, deprecated in 0.6 * improved additionalProperties error message * improved schema error message * add long to utc-millisec validation * accept Decimal where float is accepted * add FieldValidationError so that field names can be retrieved from error * a few Python 3 fixes 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 validictory-1.1.2/docs/conf.py000066400000000000000000000155511321032335400162760ustar00rootroot00000000000000# -*- 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'2014, 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 = '1.1' # The full version, including alpha/beta/rc tags. release = '1.1.2' # 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) ] validictory-1.1.2/docs/index.rst000066400000000000000000000021041321032335400166260ustar00rootroot00000000000000validictory |release| ===================== **As of 2018 this library is deprecated, please consider using jsonschema instead.** 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` validictory-1.1.2/docs/usage.rst000066400000000000000000000543251321032335400166370ustar00rootroot00000000000000Using validictory ================= **As of 2018 this library is deprecated, please consider using jsonschema instead.** Normal use of validictory is as simple as calling :func:`validictory.validate`, the only thing to learn is how to craft a schema. Sample 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("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. :: # each key in the 'properties' option matches a key in the object that you are validating, # and the value to each key in the 'properties' option is the schema to validate # the value of the key in the JSON you are verifying. data = json.loads(''' {"obj1": {"obj2": 12}}''' ) schema = { "type": "object", "properties": { "obj1": { "type": "object", "properties": { "obj2": { "type": "integer" } } } } } validictory.validate(data, schema) ``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. :: data = json.loads(''' { "one": "hello", "two": "helloTwo", "thirtyThree": 12 }''') schema = { "type": "object", "properties": { "one": { "type": "string" }, "two": { "type": "string" } }, # each subkey of the 'patternProperties' option is a # regex, and the value is the schema to validate # all values whose keys match said regex. "patternProperties": { "^.+Three$": { "type": "number" } } } ``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. :: data = json.loads(''' { "one": [12, 13], "two": "hello", "three": null, "four": null }''') schema = { "type": "object", "properties": { "one": { "type": "array" }, "two": { "type": "string" } }, # this will match any keys that were not listed in 'properties' "additionalProperties": { "type": "null" } } validictory.validate(data, schema) ``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``) :: # given a schema object, every list will be validated against it. data = json.loads(''' {"results": [1, 2, 3, 4, 5]}''') schema = { "properties": { "results": { "items": { "type": "integer" } } } } validictory.validate(data, schema) # given a list, each item in the list is matched against the schema # at the same index. (entry 0 in the json will be matched against entry 0 # in the schema, etc) dataTwo = json.loads(''' {"results": [1, "a", false, null, 5.3]} ''') schemaTwo = { "properties": { "results": { "items": [ {"type": "integer"}, {"type": "string"}, {"type": "boolean"}, {"type": "null"}, {"type": "number"} ] } } } validictory.validate(dataTwo, schemaTwo) ``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. :: data = json.loads(''' {"results": [1, "a", false, null, null, null]} ''') schema = { "properties": { "results": { "items": [ {"type": "integer"}, {"type": "string"}, {"type": "boolean"} ], # when using 'items' and providing a list (so that values in the list get validated # by the schema at the same index), any extra values get validated using additionalItems "additionalItems": { "type": "null" } } } } validictory.validate(data, 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``. :: data = json.loads(''' {"one": 1, "two": 2}''') schema = { "type": "object", "properties": { "one": { "type": "number", }, "two": { "type": "number", }, # even though "three" is missing, it will pass validation # because required = False "three": { "type": "number", "required": False } } } validictory.validate(data, schema) .. 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. :: data = json.loads(''' {"result": 10, "resultTwo": 12}''') schema = { "properties": { "result": { # passes "minimum": 9, "maximum": 10 }, "resultTwo": { # fails "minimum": 13 } } } ``exclusiveMinimum`` and ``exclusiveMaximum`` If these values are present and set to True, they will modify the ``minimum`` and ``maximum`` tests to be exclusive. :: data = json.loads(''' {"result": 10, "resultTwo": 12, "resultThree": 15}''') schema = { "properties": { "result": { # fails, has to > 10 "exclusiveMaximum": 10 }, "resultTwo": { # fails, has to be > 12 "exclusiveMinimum": 12 }, "resultThree": { # passes "exclusiveMaximum": 20, "exclusiveMinimum": 14 } } } ``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. :: data = json.loads(''' { "one": "12345", "two": "2345", "three": [1, 2, 3, 4, 5]} ''') schema = { "properties": { "one": { # passes "minLength": 4, "maxLength": 6 }, "two": { # fails "minLength": 6 }, "three": { # passes "maxItems": 5 } } } ``uniqueItems`` Indicate that all attributes in a list must be unique. :: data = json.loads(''' {"one": [1, 2, 3, 4], "two": [1, 1, 2]} ''') schema = { "properties": { "one": { # passes "uniqueItems": True }, "two": { # fails "uniqueItems": True } } } ``pattern`` If the value is a string, this provides a regular expression that the string must match to be valid. :: data = json.loads(''' {"twentyOne": "21", "thirtyThree": "33"} ''') schema = { "properties": { "thirtyThree": { "pattern": "^33$" } } } ``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``. :: data = json.loads(''' {"hello": "", "testing": ""}''') schema = { "properties": { "hello": { "blank": True # passes }, "testing": { "blank": False # fails } } } ``enum`` Provides an array that the value must match if present. :: data = json.loads(''' {"today": "monday", "tomorrow": "something"}''') dayList = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"] schema = { "properties": { "today": { "enum": dayList # passes }, "tomorrow": { "enum": dayList # does not pass, 'something' is not in the enum. } } } ``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 * ``ip-address``: IPv4 address, in dotted-quad string format (for example, '123.45.67.89') formats can be provided as a dictionary (of type {"formatString": format_func} ) to the ``format_validators`` argument of ``validictory.validate``. Custom formatting functions have the function signature ``format_func(validator, fieldname, value, format_option):``. * ``validator`` is a reference to the SchemaValidator (or custom validator class if you passed one in for the ``validator_cls`` argument in ``validictory.validate``). * ``fieldname`` is the name of the field whose value you are validating in the JSON. * ``value`` is the actual value that you are validating * ``format_option`` is the name of the format string that was provided in the JSON, useful if you have one format function for multiple format strings. Here is an example of writing a custom format function to validate `UUIDs `_: :: import json import validictory import uuid data = json.loads(''' { "uuidInt": 117574695023396164616661330147169357159, "uuidHex": "fad9d8cc11d64578bff327df93276964"}''') schema = { "title": "My test schema", "properties": { "uuidHex": { "format": "uuid_hex" }, "uuidInt": { "format": "uuid_int" } } } def validate_uuid(validator, fieldname, value, format_option): print("*********************") print("validator:",validator) print("fieldname:", fieldname) print("value", value) print("format_option", format_option) print("*********************") if format_option == "uuid_hex": try: uuid.UUID(hex=value) except Exception as e: raise validictory.FieldValidationError("Could not parse UUID \ from hex string %(uuidstr)s, reason: %(reason)s" % {"uuidstr": value, "reason": e}, fieldname, value) elif format_option == "uuid_int": try: uuid.UUID(int=value) except Exception as e: raise validictory.FieldValidationError("Could not parse UUID \ from int string %(uuidstr)s, reason: %(reason)s" % {"uuidstr": value, "reason": e}, fieldname, value) else: raise validictory.FieldValidationError("Invalid format option for \ 'validate_uuid': %(format)s" % format_option, fieldName, value) try: formatdict = {"uuid_hex": validate_uuid, "uuid_int": validate_uuid} validictory.validate(data, schema, format_validators=formatdict) print("Successfully validated %(data)s!" % {"data": data}) except Exception as e2: print("couldn't validate =( reason: %(reason)s" % {"reason": e}) ``divisibleBy`` Ensures that the data value can be divided (without remainder) by a given divisor (**not 0**). :: data = json.loads('''{"value": 12, "valueTwo": 13} ''') schema = { "properties": { "value": { "divisibleBy": 2 # passes }, "valueTwo": { "divisibleBy": 2 # fails } } } ``title`` and ``description`` These do no validation, but if provided must be strings or a ``~validictory.SchemaError`` will be raised. :: data = json.loads(''' {"hello": "testing"}''') schema = { "title": "My test schema", "properties": { "hello": { "type": "string", "description": Make sure the 'hello' key is a string" } } } Examples -------------- Using a Schema .............. The schema can be either a deserialized JSON document or a literal python object :: data = json.loads(''' {"age": 23, "name": "Steven"} ''') # json string schemaOne = json.loads(''' {"type": "object", "properties": {"age": {"type": "integer"}, "name": {"type": "string"}}} ''') # python object literal schemaTwo = {"type": "object", "properties": {"age": {"type": "integer"}, "name": {"type": "string"}}} validictory.validate(data, schemaOne) validictory.validate(data, schemaTwo) Validating Using Builtin Types ............................... :: data = json.loads(''' { "name": "bob", "age": 23, "siblings": null, "registeredToVote": false, "friends": ["Jane", "Michael"], "heightInInches": 70.2 } ''') schema = { "type": "object", "properties": { "name": { "type": "string" }, "age": { "type": "integer" }, "siblings": { "type": "null" }, "registeredToVote": { "type": "boolean" }, "friends": { "type": "array" } } } validictory.validate(data, schema) the 'number' type can be used when you don't care what type the number is, or 'integer' if you want a non floating point number :: dataTwo = json.loads('''{"valueOne": 12} ''') schemaTwo = { "properties": { "valueOne": { "type": "integer"}} } validictory.validate(dataTwo, schemaTwo) the 'any' type can be used to validate any type. :: dataThree = json.loads(''' {"valueOne": 12, "valueTwo": null, "valueThree": "hello" }''') schemaThree = { "properties": { "valueOne": {"type": "any"}, "valueTwo": {"type": "any"}, "valueThree": {"type": "any"} } } validictory.validate(dataThree, schemaThree) You can list multiple types as well. :: dataFour = json.loads(''' {"valueOne": 12, "valueTwo": null}''') schemaFour = { "properties": { "valueOne": { "type": ["string", "number"] }, "valueTwo": { "type": ["null", "string"] } } } validictory.validate(dataFour, schemaFour) Validating Nested Containers ............................ :: data = json.loads(''' { "results": { "xAxis": [ [0, 1], [1, 3], [2, 5], [3, 1] ], "yAxis": [ [0, "sunday"], [1, "monday"], [2, "tuesday"], [3, "wednesday"] ] } } ''') schema = { "type": "object", "properties": { "results": { "type": "object", "properties": { "xAxis": { "type": "array", "items": { "type": "array", # use a list of schemas, so that the the schema at index 0 # matches the item in the list at index 0, etc. "items": [{"type": "number"}, {"type": "number"}] } }, "yAxis": { "type": "array", "items": { "type": "array", "items": [{"type": "number"}, {"type": "string"}] } } } } } } validictory.validate(data, schema) Specifying Custom Types ....................... If a list is specified for the 'types' option, then you can specify a schema or multiple schemas that each element in the list will be tested against. This also allows you to split up your schema definition for ease of reading, or to share schema definitions between other schemas. :: 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, see `his blog post on validating GeoJSON `_) validictory-1.1.2/docs/validictory.rst000066400000000000000000000005301321032335400200510ustar00rootroot00000000000000validictory module ================== **As of 2018 this library is deprecated, please consider using jsonschema instead.** .. module:: validictory validate -------- .. autofunction:: validate SchemaValidator --------------- .. autoclass:: SchemaValidator Exceptions ---------- .. autoclass:: ValidationError .. autoclass:: SchemaError validictory-1.1.2/setup.cfg000066400000000000000000000000341321032335400156560ustar00rootroot00000000000000[bdist_wheel] universal = 1 validictory-1.1.2/setup.py000077500000000000000000000021711321032335400155560ustar00rootroot00000000000000#!/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='james.p.turk@gmail.com', url='http://github.com/jamesturk/validictory', license="MIT License", platforms=["any"], packages=find_packages(), test_suite="validictory.tests", classifiers=["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Topic :: Software Development :: Libraries :: Python Modules", ], ) validictory-1.1.2/tox.ini000066400000000000000000000003041321032335400153500ustar00rootroot00000000000000[tox] envlist = py27,pypy,py34,py35,flake8 [testenv] commands = py.test deps = setuptools pytest [testenv:flake8] deps = flake8 commands = flake8 validictory [flake8] max-line-length=99 validictory-1.1.2/validictory/000077500000000000000000000000001321032335400163715ustar00rootroot00000000000000validictory-1.1.2/validictory/__init__.py000077500000000000000000000054001321032335400205040ustar00rootroot00000000000000#!/usr/bin/env python from validictory.validator import (SchemaValidator, FieldValidationError, MultipleValidationError, ValidationError, SchemaError) __all__ = ['validate', 'SchemaValidator', 'FieldValidationError', 'MultipleValidationError', 'ValidationError', 'SchemaError'] __version__ = '1.1.2' def validate(data, schema, validator_cls=SchemaValidator, format_validators=None, required_by_default=True, blank_by_default=False, disallow_unknown_properties=False, apply_default_to_data=False, fail_fast=True, remove_unknown_properties=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. :param disallow_unknown_properties: defaults to False, set to True to disallow properties not listed in the schema definition :param apply_default_to_data: defaults to False, set to True to modify the data in case the schema definition includes a "default" property :param fail_fast: defaults to True, set to False if you prefer to get all validation errors back instead of only the first one :param remove_unknown_properties: defaults to False, set to True to filter out properties not listed in the schema definition. Only applies when disallow_unknown_properties is False. ''' v = validator_cls(format_validators, required_by_default, blank_by_default, disallow_unknown_properties, apply_default_to_data, fail_fast, remove_unknown_properties) 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) validictory-1.1.2/validictory/tests/000077500000000000000000000000001321032335400175335ustar00rootroot00000000000000validictory-1.1.2/validictory/tests/__init__.py000066400000000000000000000000001321032335400216320ustar00rootroot00000000000000validictory-1.1.2/validictory/tests/test_apply_default.py000066400000000000000000000061221321032335400237760ustar00rootroot00000000000000from unittest import TestCase import validictory def validate_with_apply_default_to_data(data, schema): return validictory.validate( data, schema, required_by_default=True, apply_default_to_data=True ) class TestItemDefaults(TestCase): """ recognize a "default" keyword in a schema as a fallback for missing properties as described in http://json-schema.org/latest/json-schema-validation.html#anchor101 """ def test_property_default_is_applied(self): schema = { "type": "object", "properties": { "foo": { "default": "bar" }, "baz": { "type": "integer" } } } data = {'baz': 2} validate_with_apply_default_to_data(data, schema) # Note: data was changed! self.assertEqual(data, {"foo": "bar", "baz": 2}) def test_property_default_denied_if_wrong_type_for_default(self): schema = { "type": "object", "properties": { "foo": { "type": "integer", "default": "bar" } } } data = {} # from specification: # "There are no restrictions placed on the value of this keyword." # "It is RECOMMENDED that a default value be # valid against the associated schema." self.assertRaises( validictory.SchemaError, validate_with_apply_default_to_data, data, schema ) # the original data is unchanged self.assertEqual(data, {}) def test_property_default_with_wrong_default_raises_error_if_unused(self): schema = { "type": "object", "properties": { "foo": { "type": "integer", "default": "bar" } } } data = {'foo': 1} # The SchemaError is still raised because the schema is still wrong # even if the property is contained in the data self.assertRaises( validictory.SchemaError, validate_with_apply_default_to_data, data, schema ) # the original data is unchanged self.assertEqual(data, {'foo': 1}) def test_property_default_has_different_memory_id(self): schema = { "type": "object", "properties": { "foo": { "default": [] } } } data = {} validictory.validate( data, schema, required_by_default=True, apply_default_to_data=True ) # correctly apply default self.assertEqual({"foo":[]}, data) # dont re-use the actual array from the schema applied_default_id = id(data["foo"]) original_schema_id = id(schema["properties"]["foo"]["default"]) self.assertNotEqual(applied_default_id, original_schema_id) validictory-1.1.2/validictory/tests/test_disallow_unknown_properties.py000066400000000000000000000072261321032335400270240ustar00rootroot00000000000000from unittest import TestCase import validictory class TestDisallowUnknownProperties(TestCase): def setUp(self): self.data_simple = {"name": "john doe", "age": 42} self.schema_simple = { "type": "object", "properties": { "name": {"type": "string"}, "age": {"type": "integer"} }, } self.data_complex = { "inv_number": "123", "rows": [ { "sku": "ab-456", "desc": "a description", "price": 100.45 }, { "sku": "xy-123", "desc": "another description", "price": 999.00 } ], "data": { "name": "john doe", "age": 42 } } self.schema_complex = { "type": "object", "properties": { "inv_number": {"type": "string"}, "rows": { "type": "array", "items": { "type": "object", "properties": { "sku": {"type": "string"}, "desc": {"type": "string"}, "price": {"type": "number"} } }, }, "data": { "type": ( { "type": "object", "properties": { "name": { "type": "string" }, "hair": { "type": "string" } } }, { "type": "object", "properties": { "name": { "type": "string" }, "age": { "type": "number" } } } ) } } } def test_disallow_unknown_properties_pass(self): try: validictory.validate(self.data_simple, self.schema_simple, disallow_unknown_properties=True) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_disallow_unknown_properties_fail(self): self.data_simple["sex"] = "male" self.assertRaises(validictory.SchemaError, validictory.validate, self.data_simple, self.schema_simple, disallow_unknown_properties=True) def test_disallow_unknown_properties_complex_pass(self): try: validictory.validate(self.data_complex, self.schema_complex, disallow_unknown_properties=True) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_disallow_unknown_properties_complex_fail(self): newrow = {"sku": "789", "desc": "catch me if you can", "price": 1, "rice": 666} self.data_complex["rows"].append(newrow) self.assertRaises(validictory.SchemaError, validictory.validate, self.data_complex, self.schema_complex, disallow_unknown_properties=True) validictory-1.1.2/validictory/tests/test_fail_fast.py000066400000000000000000000101741321032335400230770ustar00rootroot00000000000000from unittest import TestCase import validictory class TestFailFast(TestCase): def test_multi_error(self): schema = { "type": "object", "properties": { "name": {"type": "string"}, "age": {"type": "integer"} }, } data = {"name": 2, "age": "fourty-two"} # ensure it raises an error self.assertRaises(validictory.ValidationError, validictory.validate, data, schema, fail_fast=True) # ensure it raises a MultiError self.assertRaises(validictory.MultipleValidationError, validictory.validate, data, schema, fail_fast=False) # ensure that the MultiError has 2 errors try: validictory.validate(data, schema, fail_fast=False) except validictory.MultipleValidationError as mve: assert len(mve.errors) == 2 def test_multi_error_in_list(self): schema = { "type": "object", "properties": { "words": {"type": "array", "items": {"type": "string"}}, }, } data = {"words": ["word", 32, 2.1, True]} # ensure it raises an error self.assertRaises(validictory.ValidationError, validictory.validate, data, schema, fail_fast=True) # ensure it raises a MultiError self.assertRaises(validictory.MultipleValidationError, validictory.validate, data, schema, fail_fast=False) # ensure that the MultiError has 3 errors since 3 of the items were bad try: validictory.validate(data, schema, fail_fast=False) except validictory.MultipleValidationError as mve: assert len(mve.errors) == 3 def test_multi_error_with_format(self): schema = { "type": "object", "properties": { "date": {"type": "string", "format": "date"}, "time": {"type": "string", "format": "time"} }, } data = {"date": "2011-02-99", "time": "30:00:00"} # ensure it raises an error self.assertRaises(validictory.ValidationError, validictory.validate, data, schema, fail_fast=True) # ensure it raises a MultiError self.assertRaises(validictory.MultipleValidationError, validictory.validate, data, schema, fail_fast=False) # ensure that the MultiError has 2 errors try: validictory.validate(data, schema, fail_fast=False) except validictory.MultipleValidationError as mve: assert len(mve.errors) == 2 def test_no_error_with_type_list(self): schema = { "type": "object", "properties": { "name": {"type": "string"}, "age": {"type": "integer"}, "sibling": {"type": ["string", "null"]} }, } data = {"name": "john doe", "age": 42, "sibling": None} # this should not raise an error validictory.validate(data, schema, fail_fast=True) # and neither should this...fixed by dc78c validictory.validate(data, schema, fail_fast=False) def test_multi_error_with_type_list(self): schema = { "type": "object", "properties": { "name": {"type": "string"}, "age": {"type": "integer"}, "sibling": {"type": ["string", "null"]} }, } data = {"name": 2, "age": "fourty-two", "sibling": 0} # ensure it raises an error self.assertRaises(validictory.ValidationError, validictory.validate, data, schema, fail_fast=True) # ensure it raises a MultiError self.assertRaises(validictory.MultipleValidationError, validictory.validate, data, schema, fail_fast=False) # ensure that the MultiError has 3 errors try: validictory.validate(data, schema, fail_fast=False) except validictory.MultipleValidationError as mve: assert len(mve.errors) == 3 validictory-1.1.2/validictory/tests/test_items.py000066400000000000000000000106641321032335400222740ustar00rootroot00000000000000from unittest import TestCase import pytest 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 position assert '[0]' 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"] pytest.raises(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"] pytest.raises(ValueError, validictory.validate, data, self.schema4) validictory-1.1.2/validictory/tests/test_other.py000066400000000000000000000063571321032335400223000ustar00rootroot00000000000000from unittest import TestCase import validictory class TestSchemaErrors(TestCase): def setUp(self): self.valid_desc = {"description": "My Description for My Schema"} self.invalid_desc = {"description": 1233} self.valid_title = {"title": "My Title for My Schema"} self.invalid_title = {"title": 1233} # doesn't matter what this is self.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) def test_invalid_type(self): expected = "Type for field 'bar' must be 'dict', got: 'str'" data = {'bar': False} schema = {"type": "object", "required": True, "properties": {"bar": "foo"}} try: validictory.validate(data, schema) result = None except Exception as e: result = e.__str__() self.assertEqual(expected, result) class TestFieldValidationErrors(TestCase): def setUp(self): self.schema = {"type": "object", "required": True, "properties": {"bar": {"type": "integer"}}} self.data = {"bar": "faz"} def test(self): try: validictory.validate(self.data, self.schema) except validictory.FieldValidationError as e: self.assertEqual(e.fieldname, "bar") self.assertEqual(e.value, "faz") else: self.fail("No Exception") def test_deep_required_error(): schema = {'type': 'object', 'properties': {'foo': {'type': 'object', 'properties': {'bar': {'type': 'integer', 'required': 'true'}}}}} data = {'foo': {'baz': 3}} try: validictory.validate(data, schema) except validictory.validator.RequiredFieldValidationError as e: estr = str(e) assert 'foo' in estr assert 'bar' in estr def test_deep_error(): schema = {'type': 'object', 'properties': {'foo': {'type': 'object', 'properties': {'bar': {'type': 'array', 'items': {'type': 'object', 'properties': {'baz': {'type': 'string', 'enum': ['a', 'b']}}}}}}}} data = {'foo': {'bar': [{'baz': 'a'}, {'baz': 'x'}]}} try: validictory.validate(data, schema) except validictory.FieldValidationError as e: estr = str(e) assert 'baz' in estr assert 'foo' in estr assert 'bar' in estr assert '1' in estr validictory-1.1.2/validictory/tests/test_properties.py000066400000000000000000000254241321032335400233470ustar00rootroot00000000000000from unittest import TestCase import validictory class TestProperties(TestCase): props = { "prop01": {"type": "string"}, "prop02": {"type": "number", "required": False}, "prop03": {"type": "integer"}, "prop04": {"type": "boolean"}, "prop05": { "type": "object", "required": False, "properties": { "subprop01": {"type": "string"}, "subprop02": {"type": "string", "required": True} } } } 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 TestMinMaxProperties(TestCase): def test_min_properties_pass(self): schema = { 'minProperties': 1, } data = { 'a': 1 } try: validictory.validate(data, schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_max_properties_pass(self): schema = { 'maxProperties': 2, } data = { 'a': 1, 'b': 2, } try: validictory.validate(data, schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_min_properties_fail(self): schema = { 'minProperties': 2, } data = { 'a': 1 } self.assertRaises(ValueError, validictory.validate, data, schema) def test_max_properties_fail(self): schema = { 'maxProperties': 1, } data = { 'a': 1, 'b': 2, } self.assertRaises(ValueError, validictory.validate, data, schema) def test_min_properties_pass_nondict(self): schema = { 'minProperties': 2, } data = 123 try: validictory.validate(data, schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_max_properties_pass_nondict(self): schema = { 'maxProperties': 1, } data = 123 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_with_pattern_properties(self): schema = { "patternProperties": { "[a-c]": { "type": "string"} }, "additionalProperties": False, } data = {"a": "test"} try: validictory.validate(data, schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) # failures data = {"d": "test"} self.assertRaises(ValueError, validictory.validate, data, schema) data = {"a": "test", "d": "test"} 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 TestDependencies(TestCase): props = { "prop01": {"type": "string", "required": False}, "prop02": {"type": "number", "required": False, "dependencies": "prop01"} } schema = {"type": "object", "properties": props} props_array = { "prop01": {"type": "string", "required": False}, "prop02": {"type": "string", "required": False}, "prop03": {"type": "number", "required": False, "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 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) validictory-1.1.2/validictory/tests/test_remove_unknown_properties.py000066400000000000000000000060601321032335400264760ustar00rootroot00000000000000from unittest import TestCase import validictory from copy import deepcopy class TestRemoveUnknownProperties(TestCase): def setUp(self): self.data_simple = {"name": "john doe", "age": 42} self.schema_simple = { "type": "object", "properties": { "name": {"type": "string"}, "age": {"type": "integer"} }, } self.data_complex = { "inv_number": "123", "rows": [ { "sku": "ab-456", "desc": "a description", "price": 100.45 }, { "sku": "xy-123", "desc": "another description", "price": 999.00 } ] } self.schema_complex = { "type": "object", "properties": { "inv_number": {"type": "string"}, "rows": { "type": "array", "items": { "type": "object", "properties": { "sku": {"type": "string"}, "desc": {"type": "string"}, "price": {"type": "number"} } }, } } } def test_remove_unknown_properties_pass(self): extra_data = deepcopy(self.data_simple) extra_data["sex"] = "male" validictory.validate(extra_data, self.schema_simple, remove_unknown_properties=True) self.assertEqual(extra_data, self.data_simple) def test_remove_unknown_properties_patternproperties(self): schema = { "type": "object", "patternProperties": { "[abc]": {"type": "boolean"}, }, "properties": { "d": {"type": "boolean"}, } } orig_data = {'a': True, 'b': False, 'd': True} data = deepcopy(orig_data) validictory.validate(data, schema, remove_unknown_properties=True) self.assertEqual(data, orig_data) def test_remove_unknown_properties_complex_pass(self): try: validictory.validate(self.data_complex, self.schema_complex, remove_unknown_properties=True) except ValueError as e: self.fail("Unexpected failure: %s" % e) def test_remove_unknown_properties_complex_fail(self): extra_data = deepcopy(self.data_complex) newrow_invalid = {"sku": "789", "desc": "catch me if you can", "price": 1, "rice": 666} newrow_valid = {"sku": "789", "desc": "catch me if you can", "price": 1} extra_data["rows"].append(newrow_invalid) validictory.validate(extra_data, self.schema_complex, remove_unknown_properties=True) self.data_complex["rows"].append(newrow_valid) self.assertEqual(extra_data, self.data_complex) validictory-1.1.2/validictory/tests/test_schema_schema.py000066400000000000000000000073071321032335400237330ustar00rootroot00000000000000from 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) validictory-1.1.2/validictory/tests/test_type.py000066400000000000000000000124171321032335400221320ustar00rootroot00000000000000from unittest import TestCase from decimal import Decimal import datetime import sys import validictory if sys.version_info[0] == 3: unicode_str = '\u2603' else: unicode_str = unicode('snowman') 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, Decimal('25.25')] 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_UserDict_is_object(self): # A UserDict (and similar classes) are not dicts, but they're dict-like # and should be treated as objects try: # Python 2 from UserDict import UserDict except ImportError: # Python 3 from collections import UserDict valids = [UserDict({"a": "b"})] invalids = [] self._test_type('object', 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}) validictory-1.1.2/validictory/tests/test_values.py000066400000000000000000000441321321032335400224470ustar00rootroot00000000000000""" Tests that test the value of individual items """ from unittest import TestCase import re 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_blank(self): blank_schema = {"enum": ("test", True, 123, ["???"]), "blank": True} data = ["test", True, 123, ["???"], ""] try: for item in data: validictory.validate(item, blank_schema) 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 test_regex_compiled(self): data = "my.email01@gmail.com" re_schema = {'pattern': re.compile( "^[A-Za-z0-9][A-Za-z0-9\.]*@([A-Za-z0-9]+\.)+[A-Za-z0-9]+$")} try: validictory.validate(data, re_schema) except ValueError as e: self.fail("Unexpected failure: %s" % e) data = "whatever" self.assertRaises(ValueError, validictory.validate, data, re_schema) def validate_format_contains_spaces(validator, fieldname, value, format_option): if ' ' in value: return raise validictory.FieldValidationError( "Value %(value)r of field '%(fieldname)s' does not contain any spaces," "but it should" % locals(), fieldname, value) def validate_format_dict_not_empty(validator, fieldname, value, format_option): if len(value.keys()) > 0: return raise validictory.FieldValidationError( "Value %(value)r of field '%(fieldname)s' should not be an empty" "dict" % locals(), fieldname, value) 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"} schema_non_empty_dict = {"type": "object", "format": "non-empty-dict"} def test_format_datetime_without_microseconds_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_datetime_with_microseconds_pass(self): data = "2011-01-13T10:56:53.0438Z" 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): try: validictory.validate(1294915735, self.schema_utcmillisec) validictory.validate(1294915735.0, 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_datetime_bad_type(self): data = 3 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) def test_format_non_empty_fail(self): data = {} validator = validictory.SchemaValidator( {'non-empty-dict': validate_format_dict_not_empty}) self.assertRaises(ValueError, validator.validate, data, self.schema_non_empty_dict) 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) validictory-1.1.2/validictory/validator.py000077500000000000000000000706271321032335400207470ustar00rootroot00000000000000import re import sys import copy import socket from datetime import datetime from decimal import Decimal 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`) """ class FieldValidationError(ValidationError): """ Validation error that refers to a specific field and has `fieldname` and `value` attributes. """ def __init__(self, message, fieldname, value, path=''): message = "Value {0!r} for field '{1}' {2}".format(value, path, message) super(FieldValidationError, self).__init__(message) self.fieldname = fieldname self.value = value self.path = path class DependencyValidationError(ValidationError): """ Validation error that refers to a missing dependency """ def __init__(self, message): super(DependencyValidationError, self).__init__(message) class RequiredFieldValidationError(ValidationError): """ Validation error that refers to a missing field """ def __init__(self, message): super(RequiredFieldValidationError, self).__init__(message) class MultipleValidationError(ValidationError): def __init__(self, errors): msg = "{0} validation errors:\n{1}".format(len(errors), '\n'.join(str(e) for e in errors)) super(MultipleValidationError, self).__init__(msg) self.errors = errors def _generate_datetime_validator(format_option, dateformat_string): def validate_format_datetime(validator, fieldname, value, format_option): try: # Additions to support date-time with microseconds without breaking # existing date-time validation. # Microseconds will appear specifically separated by a period, as # some variable length decimal number # such as '2015-11-18T19:57:05.061Z' instead of # '2015-11-18T19:57:05Z' Better would be to use # strict_rfc3339 vs datetime.strptime though the user needs the # package installed for the import to succeed. # import strict_rfc3339 # assert strict_rfc3339.validate_rfc3339(value) if format_option == 'date-time' and '.' in value: datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%fZ') else: datetime.strptime(value, dateformat_string) except: msg = "is not in '{format_option}' format" raise FieldValidationError(msg.format(format_option=format_option), fieldname, value) 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_types + (float, Decimal)) or value <= 0: msg = "is not a positive number" raise FieldValidationError(msg, fieldname, value) def validate_format_ip_address(validator, fieldname, value, format_option): try: # Make sure we expect "X.X.X.X" as socket.inet_aton() converts "1" to "0.0.0.1" socket.inet_aton(value) ip = len(value.split('.')) == 4 except: ip = False if not ip: msg = "is not a ip-address" raise FieldValidationError(msg, fieldname, value) 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. :param disallow_unknown_properties: defaults to False, set to True to disallow properties not listed in the schema definition :param apply_default_to_data: defaults to False, set to True to modify the data in case the schema definition includes a "default" property :param fail_fast: defaults to True, set to False if you prefer to get all validation errors back instead of only the first one :param remove_unknown_properties: defaults to False, set to True to filter out properties not listed in the schema definition. Only applies when disallow_unknown_properties is False. ''' def __init__(self, format_validators=None, required_by_default=True, blank_by_default=False, disallow_unknown_properties=False, apply_default_to_data=False, fail_fast=True, remove_unknown_properties=False): self._format_validators = {} self._errors = [] # add the default format validators for key, value in DEFAULT_FORMAT_VALIDATORS.items(): self.register_format_validator(key, value) # register any custom format validators if they were provided if format_validators: for key, value in format_validators.items(): self.register_format_validator(key, value) self.required_by_default = required_by_default self.blank_by_default = blank_by_default self.disallow_unknown_properties = disallow_unknown_properties self.apply_default_to_data = apply_default_to_data self.fail_fast = fail_fast # disallow_unknown_properties takes precedence over remove_unknown_properties self.remove_unknown_properties = remove_unknown_properties def register_format_validator(self, format_name, format_validator_fun): self._format_validators[format_name] = format_validator_fun def get_default(self, value): if isinstance(value, dict) or isinstance(value, list): return copy.deepcopy(value) return value 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, Decimal,) def validate_type_boolean(self, val): return type(val) == bool def validate_type_object(self, val): return isinstance(val, Mapping) or (hasattr(val, 'keys') and hasattr(val, 'items')) 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, exctype=FieldValidationError, path='', **params): params['value'] = value params['fieldname'] = fieldname message = desc.format(**params) if exctype == FieldValidationError: err = FieldValidationError(message, fieldname, value, path) elif exctype == DependencyValidationError or exctype == RequiredFieldValidationError: err = exctype(message) err.fieldname = fieldname err.path = path if self.fail_fast: raise err else: self._errors.append(err) def _validate_unknown_properties(self, schema, data, fieldname, patternProperties): """Raise a SchemaError when unknown fields are found.""" schema_properties = set(schema) data_properties = set(data) delta = data_properties - schema_properties if self.disallow_unknown_properties and delta: unknowns = ', '.join(['"{0}"'.format(x) for x in delta]) raise SchemaError('Unknown properties for field "{fieldname}": {unknowns}'.format( fieldname=fieldname, unknowns=unknowns)) elif self.remove_unknown_properties: patterns = patternProperties.keys() if patternProperties else [] if patterns: delta = [f for f in delta if not any(re.match(p, f) for p in patterns)] for unknown_field in delta: del data[unknown_field] def validate_type(self, x, fieldname, schema, path, 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 errorlist = [] for eachtype in fieldtype: try: # if fail_fast is False, _error will not rais an exception. # need to monitor the _errors list as well errors = self._errors[:] self.validate_type(x, fieldname, eachtype, path, eachtype) if len(self._errors) > len(errors): # an exception was raised. # remove the error from self.errors and raise it here raise self._errors.pop() datavalid = True break except (SchemaError, ValidationError) as err: errorlist.append(err) if not datavalid: self._error("doesn't match any of {numsubtypes} subtypes in {fieldtype}; " "errorlist = {errorlist!r}", value, fieldname, path=path, numsubtypes=len(fieldtype), fieldtype=fieldtype, errorlist=errorlist) elif isinstance(fieldtype, dict): try: self.__validate(fieldname, x, fieldtype, path) except ValueError as e: raise e else: try: type_checker = getattr(self, 'validate_type_' + fieldtype) except AttributeError: raise SchemaError("Field type '{0}' is not supported.".format(fieldtype)) if not type_checker(value): self._error("is not of type {fieldtype}", value, fieldname, path=path, fieldtype=fieldtype) def validate_properties(self, x, fieldname, schema, path, properties=None): ''' Validates properties of a JSON object by processing the object's schema recursively ''' value = x.get(fieldname) if value is not None: if isinstance(value, dict): if isinstance(properties, dict): if self.disallow_unknown_properties or self.remove_unknown_properties: self._validate_unknown_properties(properties, value, fieldname, schema.get('patternProperties')) for property in properties: self.__validate(property, value, properties.get(property), path + '.' + property) else: raise SchemaError("Properties definition of field '{0}' is not an object" .format(fieldname)) def validate_items(self, x, fieldname, schema, path, 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 'additionalItems' not in schema and len(items) != len(value): self._error("is not of same length as schema list", value, fieldname, path=path) else: for index, item in enumerate(items): try: self.__validate("_data", {"_data": value[index]}, item, '{0}[{1}]'.format(path, index)) except FieldValidationError as e: raise type(e)("Failed to validate field '%s' list schema: %s" % (fieldname, e), fieldname, e.value) elif isinstance(items, dict): for index, item in enumerate(value): if ((self.disallow_unknown_properties or self.remove_unknown_properties) and 'properties' in items): self._validate_unknown_properties(items['properties'], item, fieldname, schema.get('patternProperties')) self.__validate("[list item]", {"[list item]": item}, items, '{0}[{1}]'.format(path, index)) else: raise SchemaError("Properties definition of field '{0}' is " "not a list or an object".format(fieldname)) def validate_required(self, x, fieldname, schema, path, 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}' is missing", None, path, path=path, exctype=RequiredFieldValidationError) def validate_blank(self, x, fieldname, schema, path, 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("cannot be blank'", value, fieldname, path=path) def validate_patternProperties(self, x, fieldname, schema, path, patternproperties=None): if patternproperties is 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("_data", {"_data": value}, schema, path) def validate_additionalItems(self, x, fieldname, schema, path, 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']): self._error("is not of same length as schema list", value, fieldname, path=path) remaining = value[len(schema['items']):] if len(remaining) > 0: self.__validate("_data", {"_data": remaining}, {"items": additionalItems}, path) def validate_additionalProperties(self, x, fieldname, schema, path, 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") patterns = schema["patternProperties"].keys() if 'patternProperties' in schema else [] if properties is None: properties = {} if value is None: value = {} for eachProperty in value: if (eachProperty not in properties and not any(re.match(p, eachProperty) for p in patterns)): # If additionalProperties is the boolean value False # then we don't accept any additional properties. if additionalProperties is False: self._error("contains additional property '{prop}' not defined by " "'properties' or 'patternProperties' and additionalProperties " " is False", value, fieldname, prop=eachProperty, path=path) self.__validate(eachProperty, value, additionalProperties, path) else: raise SchemaError("additionalProperties schema definition for " "field '{0}' is not an object".format(fieldname)) def validate_dependencies(self, x, fieldname, schema, path, 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}' is required by field '{fieldname}'", None, fieldname, dependency=dependency, path=path, exctype=DependencyValidationError) 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 '{k}' is required by field '{v}'", None, fieldname, k=k, v=v, exctype=DependencyValidationError, path=path) else: raise SchemaError("'dependencies' must be a string, list of strings, or dict") def validate_minimum(self, x, fieldname, schema, path, 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("is less than minimum value: {minimum}", value, fieldname, minimum=minimum, path=path) def validate_maximum(self, x, fieldname, schema, path, 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("is greater than maximum value: {maximum}", value, fieldname, maximum=maximum, path=path) def validate_maxProperties(self, x, fieldname, schema, path, number=None): ''' Validates that the number of properties of the given object is less than or equal to the specified number ''' value = x.get(fieldname) if isinstance(value, dict) and len(value) > number: self._error("must have number of properties less than or equal to {number}", value, fieldname, number=number, path=path) def validate_minProperties(self, x, fieldname, schema, path, number=None): ''' Validates that the number of properties of the given object is greater than or equal to the specified number ''' value = x.get(fieldname) if isinstance(value, dict) and len(value) < number: self._error("must have number of properties greater than or equal to {number}", value, fieldname, number=number, path=path) def validate_maxLength(self, x, fieldname, schema, path, 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("must have length less than or equal to {length}", value, fieldname, length=length, path=path) def validate_minLength(self, x, fieldname, schema, path, 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("must have length greater than or equal to {length}", value, fieldname, length=length, path=path) validate_minItems = validate_minLength validate_maxItems = validate_maxLength def validate_format(self, x, fieldname, schema, path, format_option=None): ''' Validates the format of primitive data types ''' value = x.get(fieldname, None) format_validator = self._format_validators.get(format_option, None) if format_validator and value is not None: try: format_validator(self, fieldname, value, format_option) except FieldValidationError as fve: if self.fail_fast: raise else: self._errors.append(fve) # TODO: warn about unsupported format ? def validate_pattern(self, x, fieldname, schema, path, pattern=None): ''' Validates that the given field, if a string, matches the given regular expression. ''' value = x.get(fieldname) if (isinstance(value, _str_type) and (isinstance(pattern, _str_type) and not re.match(pattern, value) or not isinstance(pattern, _str_type) and not pattern.match(value))): self._error("does not match regular expression '{pattern}'", value, fieldname, pattern=pattern, path=path) def validate_uniqueItems(self, x, fieldname, schema, path, 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("is not unique", value, fieldname, path=path) else: add(value) def validate_enum(self, x, fieldname, schema, path, 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 callable(options): options = options(x) if not isinstance(options, Container): raise SchemaError("Enumeration {0!r} for field '{1}' must be a container".format( options, fieldname)) if value not in options: if not(value == '' and schema.get('blank', self.blank_by_default)): self._error("is not in the enumeration: {options!r}", value, fieldname, options=options, path=path) def validate_title(self, x, fieldname, schema, path, title=None): if not isinstance(title, (_str_type, type(None))): raise SchemaError("The title for field '{0}' must be a string".format(fieldname)) def validate_description(self, x, fieldname, schema, path, description=None): if not isinstance(description, (_str_type, type(None))): raise SchemaError("The description for field '{0}' must be a string".format(fieldname)) def validate_divisibleBy(self, x, fieldname, schema, path, divisibleBy=None): value = x.get(fieldname) if not self.validate_type_number(value): return if divisibleBy == 0: raise SchemaError("'{0!r}' <- divisibleBy can not be 0".format(schema)) if value % divisibleBy != 0: self._error("is not divisible by '{divisibleBy}'.", x.get(fieldname), fieldname, divisibleBy=divisibleBy, path=path) def validate_disallow(self, x, fieldname, schema, path, disallow=None): ''' Validates that the value of the given field does not match the disallowed type. ''' try: self.validate_type(x, fieldname, schema, path, disallow) except ValidationError: return self._error("is disallowed for field '{fieldname}'", x.get(fieldname), fieldname, disallow=disallow, path=path) def validate(self, data, schema): ''' Validates a piece of json data against the provided json-schema. ''' self.__validate("data", {"data": data}, schema, '') if self._errors: raise MultipleValidationError(self._errors) def __validate(self, fieldname, data, schema, path): if schema is not None: if not isinstance(schema, dict): raise SchemaError("Type for field '%s' must be 'dict', got: '%s'" % (fieldname, type(schema).__name__)) add_required_rule = self.required_by_default and 'required' not in schema add_not_blank_rule = not self.blank_by_default and 'blank' not in schema if add_required_rule or add_not_blank_rule: newschema = copy.copy(schema) if add_required_rule: newschema['required'] = self.required_by_default if add_not_blank_rule: newschema['blank'] = self.blank_by_default else: newschema = schema # add default values first before checking for required fields if self.apply_default_to_data and 'default' in schema: try: self.validate_type(x={'_ds': self.get_default(schema['default'])}, fieldname='_ds', schema=schema, fieldtype=schema['type'] if 'type' in schema else None, path=path) except FieldValidationError as exc: raise SchemaError(exc) if fieldname not in data: data[fieldname] = self.get_default(schema['default']) # iterate over schema and call all validators for schemaprop in newschema: validatorname = "validate_" + schemaprop validator = getattr(self, validatorname, None) if validator: validator(data, fieldname, schema, path, newschema.get(schemaprop)) return data