summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.coveragerc5
-rw-r--r--.github/FUNDING.yml5
-rw-r--r--.github/SECURITY.md21
-rw-r--r--.github/workflows/ci.yml209
-rw-r--r--.github/workflows/coverage.yml25
-rw-r--r--.github/workflows/packaging.yml38
-rw-r--r--.github/workflows/pre-commit.yml13
-rw-r--r--.gitignore4
-rw-r--r--.pre-commit-config.yaml19
-rw-r--r--.readthedocs.yml15
-rw-r--r--CHANGELOG.rst196
-rw-r--r--COPYING19
-rw-r--r--MANIFEST.in5
-rw-r--r--README.rst136
-rw-r--r--codecov.yml5
-rw-r--r--docs/Makefile227
-rw-r--r--docs/conf.py254
-rw-r--r--docs/creating.rst25
-rw-r--r--docs/errors.rst406
-rw-r--r--docs/faq.rst237
-rw-r--r--docs/index.rst22
-rw-r--r--docs/jsonschema_role.py151
-rw-r--r--docs/make.bat190
-rw-r--r--docs/references.rst13
-rw-r--r--docs/requirements.in3
-rw-r--r--docs/requirements.txt36
-rw-r--r--docs/spelling-wordlist.txt43
-rw-r--r--docs/validate.rst395
-rw-r--r--json/.github/workflows/ci.yml25
-rw-r--r--json/.gitignore1
-rw-r--r--json/LICENSE (renamed from LICENSE)0
-rw-r--r--json/README.md (renamed from README.md)0
-rwxr-xr-xjson/bin/jsonschema_suite (renamed from bin/jsonschema_suite)1
-rw-r--r--json/index.js (renamed from index.js)0
-rw-r--r--json/package.json (renamed from package.json)0
-rw-r--r--json/remotes/baseUriChange/folderInteger.json (renamed from remotes/baseUriChange/folderInteger.json)0
-rw-r--r--json/remotes/baseUriChangeFolder/folderInteger.json (renamed from remotes/baseUriChangeFolder/folderInteger.json)0
-rw-r--r--json/remotes/baseUriChangeFolderInSubschema/folderInteger.json (renamed from remotes/baseUriChangeFolderInSubschema/folderInteger.json)0
-rw-r--r--json/remotes/integer.json (renamed from remotes/integer.json)0
-rw-r--r--json/remotes/name-defs.json (renamed from remotes/name-defs.json)0
-rw-r--r--json/remotes/name.json (renamed from remotes/name.json)0
-rw-r--r--json/remotes/subSchemas-defs.json (renamed from remotes/subSchemas-defs.json)0
-rw-r--r--json/remotes/subSchemas.json (renamed from remotes/subSchemas.json)0
-rw-r--r--json/test-schema.json (renamed from test-schema.json)0
-rw-r--r--json/tests/draft2019-09/additionalItems.json (renamed from tests/draft2019-09/additionalItems.json)0
-rw-r--r--json/tests/draft2019-09/additionalProperties.json (renamed from tests/draft2019-09/additionalProperties.json)0
-rw-r--r--json/tests/draft2019-09/allOf.json (renamed from tests/draft2019-09/allOf.json)0
-rw-r--r--json/tests/draft2019-09/anchor.json (renamed from tests/draft2019-09/anchor.json)0
-rw-r--r--json/tests/draft2019-09/anyOf.json (renamed from tests/draft2019-09/anyOf.json)0
-rw-r--r--json/tests/draft2019-09/boolean_schema.json (renamed from tests/draft2019-09/boolean_schema.json)0
-rw-r--r--json/tests/draft2019-09/const.json (renamed from tests/draft2019-09/const.json)0
-rw-r--r--json/tests/draft2019-09/contains.json (renamed from tests/draft2019-09/contains.json)0
-rw-r--r--json/tests/draft2019-09/default.json (renamed from tests/draft2019-09/default.json)0
-rw-r--r--json/tests/draft2019-09/defs.json (renamed from tests/draft2019-09/defs.json)0
-rw-r--r--json/tests/draft2019-09/dependentRequired.json (renamed from tests/draft2019-09/dependentRequired.json)0
-rw-r--r--json/tests/draft2019-09/dependentSchemas.json (renamed from tests/draft2019-09/dependentSchemas.json)0
-rw-r--r--json/tests/draft2019-09/enum.json (renamed from tests/draft2019-09/enum.json)0
-rw-r--r--json/tests/draft2019-09/exclusiveMaximum.json (renamed from tests/draft2019-09/exclusiveMaximum.json)0
-rw-r--r--json/tests/draft2019-09/exclusiveMinimum.json (renamed from tests/draft2019-09/exclusiveMinimum.json)0
-rw-r--r--json/tests/draft2019-09/format.json (renamed from tests/draft2019-09/format.json)0
-rw-r--r--json/tests/draft2019-09/id.json (renamed from tests/draft2019-09/id.json)0
-rw-r--r--json/tests/draft2019-09/if-then-else.json (renamed from tests/draft2019-09/if-then-else.json)0
-rw-r--r--json/tests/draft2019-09/items.json (renamed from tests/draft2019-09/items.json)0
-rw-r--r--json/tests/draft2019-09/maxContains.json (renamed from tests/draft2019-09/maxContains.json)0
-rw-r--r--json/tests/draft2019-09/maxItems.json (renamed from tests/draft2019-09/maxItems.json)0
-rw-r--r--json/tests/draft2019-09/maxLength.json (renamed from tests/draft2019-09/maxLength.json)0
-rw-r--r--json/tests/draft2019-09/maxProperties.json (renamed from tests/draft2019-09/maxProperties.json)0
-rw-r--r--json/tests/draft2019-09/maximum.json (renamed from tests/draft2019-09/maximum.json)0
-rw-r--r--json/tests/draft2019-09/minContains.json (renamed from tests/draft2019-09/minContains.json)0
-rw-r--r--json/tests/draft2019-09/minItems.json (renamed from tests/draft2019-09/minItems.json)0
-rw-r--r--json/tests/draft2019-09/minLength.json (renamed from tests/draft2019-09/minLength.json)0
-rw-r--r--json/tests/draft2019-09/minProperties.json (renamed from tests/draft2019-09/minProperties.json)0
-rw-r--r--json/tests/draft2019-09/minimum.json (renamed from tests/draft2019-09/minimum.json)0
-rw-r--r--json/tests/draft2019-09/multipleOf.json (renamed from tests/draft2019-09/multipleOf.json)0
-rw-r--r--json/tests/draft2019-09/not.json (renamed from tests/draft2019-09/not.json)0
-rw-r--r--json/tests/draft2019-09/oneOf.json (renamed from tests/draft2019-09/oneOf.json)0
-rw-r--r--json/tests/draft2019-09/optional/bignum.json (renamed from tests/draft2019-09/optional/bignum.json)0
-rw-r--r--json/tests/draft2019-09/optional/content.json (renamed from tests/draft2019-09/optional/content.json)0
-rw-r--r--json/tests/draft2019-09/optional/ecmascript-regex.json (renamed from tests/draft2019-09/optional/ecmascript-regex.json)0
-rw-r--r--json/tests/draft2019-09/optional/format/date-time.json (renamed from tests/draft2019-09/optional/format/date-time.json)0
-rw-r--r--json/tests/draft2019-09/optional/format/date.json (renamed from tests/draft2019-09/optional/format/date.json)0
-rw-r--r--json/tests/draft2019-09/optional/format/duration.json (renamed from tests/draft2019-09/optional/format/duration.json)0
-rw-r--r--json/tests/draft2019-09/optional/format/email.json (renamed from tests/draft2019-09/optional/format/email.json)0
-rw-r--r--json/tests/draft2019-09/optional/format/hostname.json (renamed from tests/draft2019-09/optional/format/hostname.json)0
-rw-r--r--json/tests/draft2019-09/optional/format/idn-email.json (renamed from tests/draft2019-09/optional/format/idn-email.json)0
-rw-r--r--json/tests/draft2019-09/optional/format/idn-hostname.json (renamed from tests/draft2019-09/optional/format/idn-hostname.json)0
-rw-r--r--json/tests/draft2019-09/optional/format/ipv4.json (renamed from tests/draft2019-09/optional/format/ipv4.json)0
-rw-r--r--json/tests/draft2019-09/optional/format/ipv6.json (renamed from tests/draft2019-09/optional/format/ipv6.json)0
-rw-r--r--json/tests/draft2019-09/optional/format/iri-reference.json (renamed from tests/draft2019-09/optional/format/iri-reference.json)0
-rw-r--r--json/tests/draft2019-09/optional/format/iri.json (renamed from tests/draft2019-09/optional/format/iri.json)0
-rw-r--r--json/tests/draft2019-09/optional/format/json-pointer.json (renamed from tests/draft2019-09/optional/format/json-pointer.json)0
-rw-r--r--json/tests/draft2019-09/optional/format/regex.json (renamed from tests/draft2019-09/optional/format/regex.json)0
-rw-r--r--json/tests/draft2019-09/optional/format/relative-json-pointer.json (renamed from tests/draft2019-09/optional/format/relative-json-pointer.json)0
-rw-r--r--json/tests/draft2019-09/optional/format/time.json (renamed from tests/draft2019-09/optional/format/time.json)0
-rw-r--r--json/tests/draft2019-09/optional/format/uri-reference.json (renamed from tests/draft2019-09/optional/format/uri-reference.json)0
-rw-r--r--json/tests/draft2019-09/optional/format/uri-template.json (renamed from tests/draft2019-09/optional/format/uri-template.json)0
-rw-r--r--json/tests/draft2019-09/optional/format/uri.json (renamed from tests/draft2019-09/optional/format/uri.json)0
-rw-r--r--json/tests/draft2019-09/optional/format/uuid.json (renamed from tests/draft2019-09/optional/format/uuid.json)0
-rw-r--r--json/tests/draft2019-09/optional/non-bmp-regex.json (renamed from tests/draft2019-09/optional/non-bmp-regex.json)0
-rw-r--r--json/tests/draft2019-09/optional/refOfUnknownKeyword.json (renamed from tests/draft2019-09/optional/refOfUnknownKeyword.json)0
-rw-r--r--json/tests/draft2019-09/pattern.json (renamed from tests/draft2019-09/pattern.json)0
-rw-r--r--json/tests/draft2019-09/patternProperties.json (renamed from tests/draft2019-09/patternProperties.json)0
-rw-r--r--json/tests/draft2019-09/properties.json (renamed from tests/draft2019-09/properties.json)0
-rw-r--r--json/tests/draft2019-09/propertyNames.json (renamed from tests/draft2019-09/propertyNames.json)0
-rw-r--r--json/tests/draft2019-09/ref.json (renamed from tests/draft2019-09/ref.json)0
-rw-r--r--json/tests/draft2019-09/refRemote.json (renamed from tests/draft2019-09/refRemote.json)0
-rw-r--r--json/tests/draft2019-09/required.json (renamed from tests/draft2019-09/required.json)0
-rw-r--r--json/tests/draft2019-09/type.json (renamed from tests/draft2019-09/type.json)0
-rw-r--r--json/tests/draft2019-09/unevaluatedItems.json (renamed from tests/draft2019-09/unevaluatedItems.json)0
-rw-r--r--json/tests/draft2019-09/unevaluatedProperties.json (renamed from tests/draft2019-09/unevaluatedProperties.json)0
-rw-r--r--json/tests/draft2019-09/uniqueItems.json (renamed from tests/draft2019-09/uniqueItems.json)0
-rw-r--r--json/tests/draft3/additionalItems.json (renamed from tests/draft3/additionalItems.json)0
-rw-r--r--json/tests/draft3/additionalProperties.json (renamed from tests/draft3/additionalProperties.json)0
-rw-r--r--json/tests/draft3/default.json (renamed from tests/draft3/default.json)0
-rw-r--r--json/tests/draft3/dependencies.json (renamed from tests/draft3/dependencies.json)0
-rw-r--r--json/tests/draft3/disallow.json (renamed from tests/draft3/disallow.json)0
-rw-r--r--json/tests/draft3/divisibleBy.json (renamed from tests/draft3/divisibleBy.json)0
-rw-r--r--json/tests/draft3/enum.json (renamed from tests/draft3/enum.json)0
-rw-r--r--json/tests/draft3/extends.json (renamed from tests/draft3/extends.json)0
-rw-r--r--json/tests/draft3/format.json (renamed from tests/draft3/format.json)0
-rw-r--r--json/tests/draft3/items.json (renamed from tests/draft3/items.json)0
-rw-r--r--json/tests/draft3/maxItems.json (renamed from tests/draft3/maxItems.json)0
-rw-r--r--json/tests/draft3/maxLength.json (renamed from tests/draft3/maxLength.json)0
-rw-r--r--json/tests/draft3/maximum.json (renamed from tests/draft3/maximum.json)0
-rw-r--r--json/tests/draft3/minItems.json (renamed from tests/draft3/minItems.json)0
-rw-r--r--json/tests/draft3/minLength.json (renamed from tests/draft3/minLength.json)0
-rw-r--r--json/tests/draft3/minimum.json (renamed from tests/draft3/minimum.json)0
-rw-r--r--json/tests/draft3/optional/bignum.json (renamed from tests/draft3/optional/bignum.json)0
-rw-r--r--json/tests/draft3/optional/ecmascript-regex.json (renamed from tests/draft3/optional/ecmascript-regex.json)0
-rw-r--r--json/tests/draft3/optional/format/color.json (renamed from tests/draft3/optional/format/color.json)0
-rw-r--r--json/tests/draft3/optional/format/date-time.json (renamed from tests/draft3/optional/format/date-time.json)0
-rw-r--r--json/tests/draft3/optional/format/date.json (renamed from tests/draft3/optional/format/date.json)0
-rw-r--r--json/tests/draft3/optional/format/email.json (renamed from tests/draft3/optional/format/email.json)0
-rw-r--r--json/tests/draft3/optional/format/host-name.json (renamed from tests/draft3/optional/format/host-name.json)0
-rw-r--r--json/tests/draft3/optional/format/ip-address.json (renamed from tests/draft3/optional/format/ip-address.json)0
-rw-r--r--json/tests/draft3/optional/format/ipv6.json (renamed from tests/draft3/optional/format/ipv6.json)0
-rw-r--r--json/tests/draft3/optional/format/regex.json (renamed from tests/draft3/optional/format/regex.json)0
-rw-r--r--json/tests/draft3/optional/format/time.json (renamed from tests/draft3/optional/format/time.json)0
-rw-r--r--json/tests/draft3/optional/format/uri.json (renamed from tests/draft3/optional/format/uri.json)0
-rw-r--r--json/tests/draft3/optional/non-bmp-regex.json (renamed from tests/draft3/optional/non-bmp-regex.json)0
-rw-r--r--json/tests/draft3/optional/zeroTerminatedFloats.json (renamed from tests/draft3/optional/zeroTerminatedFloats.json)0
-rw-r--r--json/tests/draft3/pattern.json (renamed from tests/draft3/pattern.json)0
-rw-r--r--json/tests/draft3/patternProperties.json (renamed from tests/draft3/patternProperties.json)0
-rw-r--r--json/tests/draft3/properties.json (renamed from tests/draft3/properties.json)0
-rw-r--r--json/tests/draft3/ref.json (renamed from tests/draft3/ref.json)0
-rw-r--r--json/tests/draft3/refRemote.json (renamed from tests/draft3/refRemote.json)0
-rw-r--r--json/tests/draft3/required.json (renamed from tests/draft3/required.json)0
-rw-r--r--json/tests/draft3/type.json (renamed from tests/draft3/type.json)0
-rw-r--r--json/tests/draft3/uniqueItems.json (renamed from tests/draft3/uniqueItems.json)0
-rw-r--r--json/tests/draft4/additionalItems.json (renamed from tests/draft4/additionalItems.json)0
-rw-r--r--json/tests/draft4/additionalProperties.json (renamed from tests/draft4/additionalProperties.json)0
-rw-r--r--json/tests/draft4/allOf.json (renamed from tests/draft4/allOf.json)0
-rw-r--r--json/tests/draft4/anyOf.json (renamed from tests/draft4/anyOf.json)0
-rw-r--r--json/tests/draft4/default.json (renamed from tests/draft4/default.json)0
-rw-r--r--json/tests/draft4/definitions.json (renamed from tests/draft4/definitions.json)0
-rw-r--r--json/tests/draft4/dependencies.json (renamed from tests/draft4/dependencies.json)0
-rw-r--r--json/tests/draft4/enum.json (renamed from tests/draft4/enum.json)0
-rw-r--r--json/tests/draft4/format.json (renamed from tests/draft4/format.json)0
-rw-r--r--json/tests/draft4/items.json (renamed from tests/draft4/items.json)0
-rw-r--r--json/tests/draft4/maxItems.json (renamed from tests/draft4/maxItems.json)0
-rw-r--r--json/tests/draft4/maxLength.json (renamed from tests/draft4/maxLength.json)0
-rw-r--r--json/tests/draft4/maxProperties.json (renamed from tests/draft4/maxProperties.json)0
-rw-r--r--json/tests/draft4/maximum.json (renamed from tests/draft4/maximum.json)0
-rw-r--r--json/tests/draft4/minItems.json (renamed from tests/draft4/minItems.json)0
-rw-r--r--json/tests/draft4/minLength.json (renamed from tests/draft4/minLength.json)0
-rw-r--r--json/tests/draft4/minProperties.json (renamed from tests/draft4/minProperties.json)0
-rw-r--r--json/tests/draft4/minimum.json (renamed from tests/draft4/minimum.json)0
-rw-r--r--json/tests/draft4/multipleOf.json (renamed from tests/draft4/multipleOf.json)0
-rw-r--r--json/tests/draft4/not.json (renamed from tests/draft4/not.json)0
-rw-r--r--json/tests/draft4/oneOf.json (renamed from tests/draft4/oneOf.json)0
-rw-r--r--json/tests/draft4/optional/bignum.json (renamed from tests/draft4/optional/bignum.json)0
-rw-r--r--json/tests/draft4/optional/ecmascript-regex.json (renamed from tests/draft4/optional/ecmascript-regex.json)0
-rw-r--r--json/tests/draft4/optional/format/date-time.json (renamed from tests/draft4/optional/format/date-time.json)0
-rw-r--r--json/tests/draft4/optional/format/email.json (renamed from tests/draft4/optional/format/email.json)0
-rw-r--r--json/tests/draft4/optional/format/hostname.json (renamed from tests/draft4/optional/format/hostname.json)0
-rw-r--r--json/tests/draft4/optional/format/ipv4.json (renamed from tests/draft4/optional/format/ipv4.json)0
-rw-r--r--json/tests/draft4/optional/format/ipv6.json (renamed from tests/draft4/optional/format/ipv6.json)0
-rw-r--r--json/tests/draft4/optional/format/uri.json (renamed from tests/draft4/optional/format/uri.json)0
-rw-r--r--json/tests/draft4/optional/non-bmp-regex.json (renamed from tests/draft4/optional/non-bmp-regex.json)0
-rw-r--r--json/tests/draft4/optional/zeroTerminatedFloats.json (renamed from tests/draft4/optional/zeroTerminatedFloats.json)0
-rw-r--r--json/tests/draft4/pattern.json (renamed from tests/draft4/pattern.json)0
-rw-r--r--json/tests/draft4/patternProperties.json (renamed from tests/draft4/patternProperties.json)0
-rw-r--r--json/tests/draft4/properties.json (renamed from tests/draft4/properties.json)0
-rw-r--r--json/tests/draft4/ref.json (renamed from tests/draft4/ref.json)0
-rw-r--r--json/tests/draft4/refRemote.json (renamed from tests/draft4/refRemote.json)0
-rw-r--r--json/tests/draft4/required.json (renamed from tests/draft4/required.json)0
-rw-r--r--json/tests/draft4/type.json (renamed from tests/draft4/type.json)0
-rw-r--r--json/tests/draft4/uniqueItems.json (renamed from tests/draft4/uniqueItems.json)0
-rw-r--r--json/tests/draft6/additionalItems.json (renamed from tests/draft6/additionalItems.json)0
-rw-r--r--json/tests/draft6/additionalProperties.json (renamed from tests/draft6/additionalProperties.json)0
-rw-r--r--json/tests/draft6/allOf.json (renamed from tests/draft6/allOf.json)0
-rw-r--r--json/tests/draft6/anyOf.json (renamed from tests/draft6/anyOf.json)0
-rw-r--r--json/tests/draft6/boolean_schema.json (renamed from tests/draft6/boolean_schema.json)0
-rw-r--r--json/tests/draft6/const.json (renamed from tests/draft6/const.json)0
-rw-r--r--json/tests/draft6/contains.json (renamed from tests/draft6/contains.json)0
-rw-r--r--json/tests/draft6/default.json (renamed from tests/draft6/default.json)0
-rw-r--r--json/tests/draft6/definitions.json (renamed from tests/draft6/definitions.json)0
-rw-r--r--json/tests/draft6/dependencies.json (renamed from tests/draft6/dependencies.json)0
-rw-r--r--json/tests/draft6/enum.json (renamed from tests/draft6/enum.json)0
-rw-r--r--json/tests/draft6/exclusiveMaximum.json (renamed from tests/draft6/exclusiveMaximum.json)0
-rw-r--r--json/tests/draft6/exclusiveMinimum.json (renamed from tests/draft6/exclusiveMinimum.json)0
-rw-r--r--json/tests/draft6/format.json (renamed from tests/draft6/format.json)0
-rw-r--r--json/tests/draft6/items.json (renamed from tests/draft6/items.json)0
-rw-r--r--json/tests/draft6/maxItems.json (renamed from tests/draft6/maxItems.json)0
-rw-r--r--json/tests/draft6/maxLength.json (renamed from tests/draft6/maxLength.json)0
-rw-r--r--json/tests/draft6/maxProperties.json (renamed from tests/draft6/maxProperties.json)0
-rw-r--r--json/tests/draft6/maximum.json (renamed from tests/draft6/maximum.json)0
-rw-r--r--json/tests/draft6/minItems.json (renamed from tests/draft6/minItems.json)0
-rw-r--r--json/tests/draft6/minLength.json (renamed from tests/draft6/minLength.json)0
-rw-r--r--json/tests/draft6/minProperties.json (renamed from tests/draft6/minProperties.json)0
-rw-r--r--json/tests/draft6/minimum.json (renamed from tests/draft6/minimum.json)0
-rw-r--r--json/tests/draft6/multipleOf.json (renamed from tests/draft6/multipleOf.json)0
-rw-r--r--json/tests/draft6/not.json (renamed from tests/draft6/not.json)0
-rw-r--r--json/tests/draft6/oneOf.json (renamed from tests/draft6/oneOf.json)0
-rw-r--r--json/tests/draft6/optional/bignum.json (renamed from tests/draft6/optional/bignum.json)0
-rw-r--r--json/tests/draft6/optional/ecmascript-regex.json (renamed from tests/draft6/optional/ecmascript-regex.json)0
-rw-r--r--json/tests/draft6/optional/format/date-time.json (renamed from tests/draft6/optional/format/date-time.json)0
-rw-r--r--json/tests/draft6/optional/format/email.json (renamed from tests/draft6/optional/format/email.json)0
-rw-r--r--json/tests/draft6/optional/format/hostname.json (renamed from tests/draft6/optional/format/hostname.json)0
-rw-r--r--json/tests/draft6/optional/format/ipv4.json (renamed from tests/draft6/optional/format/ipv4.json)0
-rw-r--r--json/tests/draft6/optional/format/ipv6.json (renamed from tests/draft6/optional/format/ipv6.json)0
-rw-r--r--json/tests/draft6/optional/format/json-pointer.json (renamed from tests/draft6/optional/format/json-pointer.json)0
-rw-r--r--json/tests/draft6/optional/format/uri-reference.json (renamed from tests/draft6/optional/format/uri-reference.json)0
-rw-r--r--json/tests/draft6/optional/format/uri-template.json (renamed from tests/draft6/optional/format/uri-template.json)0
-rw-r--r--json/tests/draft6/optional/format/uri.json (renamed from tests/draft6/optional/format/uri.json)0
-rw-r--r--json/tests/draft6/optional/non-bmp-regex.json (renamed from tests/draft6/optional/non-bmp-regex.json)0
-rw-r--r--json/tests/draft6/pattern.json (renamed from tests/draft6/pattern.json)0
-rw-r--r--json/tests/draft6/patternProperties.json (renamed from tests/draft6/patternProperties.json)0
-rw-r--r--json/tests/draft6/properties.json (renamed from tests/draft6/properties.json)0
-rw-r--r--json/tests/draft6/propertyNames.json (renamed from tests/draft6/propertyNames.json)0
-rw-r--r--json/tests/draft6/ref.json (renamed from tests/draft6/ref.json)0
-rw-r--r--json/tests/draft6/refRemote.json (renamed from tests/draft6/refRemote.json)0
-rw-r--r--json/tests/draft6/required.json (renamed from tests/draft6/required.json)0
-rw-r--r--json/tests/draft6/type.json (renamed from tests/draft6/type.json)0
-rw-r--r--json/tests/draft6/uniqueItems.json (renamed from tests/draft6/uniqueItems.json)0
-rw-r--r--json/tests/draft7/additionalItems.json (renamed from tests/draft7/additionalItems.json)0
-rw-r--r--json/tests/draft7/additionalProperties.json (renamed from tests/draft7/additionalProperties.json)0
-rw-r--r--json/tests/draft7/allOf.json (renamed from tests/draft7/allOf.json)0
-rw-r--r--json/tests/draft7/anyOf.json (renamed from tests/draft7/anyOf.json)0
-rw-r--r--json/tests/draft7/boolean_schema.json (renamed from tests/draft7/boolean_schema.json)0
-rw-r--r--json/tests/draft7/const.json (renamed from tests/draft7/const.json)0
-rw-r--r--json/tests/draft7/contains.json (renamed from tests/draft7/contains.json)0
-rw-r--r--json/tests/draft7/default.json (renamed from tests/draft7/default.json)0
-rw-r--r--json/tests/draft7/definitions.json (renamed from tests/draft7/definitions.json)0
-rw-r--r--json/tests/draft7/dependencies.json (renamed from tests/draft7/dependencies.json)0
-rw-r--r--json/tests/draft7/enum.json (renamed from tests/draft7/enum.json)0
-rw-r--r--json/tests/draft7/exclusiveMaximum.json (renamed from tests/draft7/exclusiveMaximum.json)0
-rw-r--r--json/tests/draft7/exclusiveMinimum.json (renamed from tests/draft7/exclusiveMinimum.json)0
-rw-r--r--json/tests/draft7/format.json (renamed from tests/draft7/format.json)0
-rw-r--r--json/tests/draft7/if-then-else.json (renamed from tests/draft7/if-then-else.json)0
-rw-r--r--json/tests/draft7/items.json (renamed from tests/draft7/items.json)0
-rw-r--r--json/tests/draft7/maxItems.json (renamed from tests/draft7/maxItems.json)0
-rw-r--r--json/tests/draft7/maxLength.json (renamed from tests/draft7/maxLength.json)0
-rw-r--r--json/tests/draft7/maxProperties.json (renamed from tests/draft7/maxProperties.json)0
-rw-r--r--json/tests/draft7/maximum.json (renamed from tests/draft7/maximum.json)0
-rw-r--r--json/tests/draft7/minItems.json (renamed from tests/draft7/minItems.json)0
-rw-r--r--json/tests/draft7/minLength.json (renamed from tests/draft7/minLength.json)0
-rw-r--r--json/tests/draft7/minProperties.json (renamed from tests/draft7/minProperties.json)0
-rw-r--r--json/tests/draft7/minimum.json (renamed from tests/draft7/minimum.json)0
-rw-r--r--json/tests/draft7/multipleOf.json (renamed from tests/draft7/multipleOf.json)0
-rw-r--r--json/tests/draft7/not.json (renamed from tests/draft7/not.json)0
-rw-r--r--json/tests/draft7/oneOf.json (renamed from tests/draft7/oneOf.json)0
-rw-r--r--json/tests/draft7/optional/bignum.json (renamed from tests/draft7/optional/bignum.json)0
-rw-r--r--json/tests/draft7/optional/content.json (renamed from tests/draft7/optional/content.json)0
-rw-r--r--json/tests/draft7/optional/ecmascript-regex.json (renamed from tests/draft7/optional/ecmascript-regex.json)0
-rw-r--r--json/tests/draft7/optional/format/date-time.json (renamed from tests/draft7/optional/format/date-time.json)0
-rw-r--r--json/tests/draft7/optional/format/date.json (renamed from tests/draft7/optional/format/date.json)0
-rw-r--r--json/tests/draft7/optional/format/email.json (renamed from tests/draft7/optional/format/email.json)0
-rw-r--r--json/tests/draft7/optional/format/hostname.json (renamed from tests/draft7/optional/format/hostname.json)0
-rw-r--r--json/tests/draft7/optional/format/idn-email.json (renamed from tests/draft7/optional/format/idn-email.json)0
-rw-r--r--json/tests/draft7/optional/format/idn-hostname.json (renamed from tests/draft7/optional/format/idn-hostname.json)0
-rw-r--r--json/tests/draft7/optional/format/ipv4.json (renamed from tests/draft7/optional/format/ipv4.json)0
-rw-r--r--json/tests/draft7/optional/format/ipv6.json (renamed from tests/draft7/optional/format/ipv6.json)0
-rw-r--r--json/tests/draft7/optional/format/iri-reference.json (renamed from tests/draft7/optional/format/iri-reference.json)0
-rw-r--r--json/tests/draft7/optional/format/iri.json (renamed from tests/draft7/optional/format/iri.json)0
-rw-r--r--json/tests/draft7/optional/format/json-pointer.json (renamed from tests/draft7/optional/format/json-pointer.json)0
-rw-r--r--json/tests/draft7/optional/format/regex.json (renamed from tests/draft7/optional/format/regex.json)0
-rw-r--r--json/tests/draft7/optional/format/relative-json-pointer.json (renamed from tests/draft7/optional/format/relative-json-pointer.json)0
-rw-r--r--json/tests/draft7/optional/format/time.json (renamed from tests/draft7/optional/format/time.json)0
-rw-r--r--json/tests/draft7/optional/format/uri-reference.json (renamed from tests/draft7/optional/format/uri-reference.json)0
-rw-r--r--json/tests/draft7/optional/format/uri-template.json (renamed from tests/draft7/optional/format/uri-template.json)0
-rw-r--r--json/tests/draft7/optional/format/uri.json (renamed from tests/draft7/optional/format/uri.json)0
-rw-r--r--json/tests/draft7/optional/non-bmp-regex.json (renamed from tests/draft7/optional/non-bmp-regex.json)0
-rw-r--r--json/tests/draft7/pattern.json (renamed from tests/draft7/pattern.json)0
-rw-r--r--json/tests/draft7/patternProperties.json (renamed from tests/draft7/patternProperties.json)0
-rw-r--r--json/tests/draft7/properties.json (renamed from tests/draft7/properties.json)0
-rw-r--r--json/tests/draft7/propertyNames.json (renamed from tests/draft7/propertyNames.json)0
-rw-r--r--json/tests/draft7/ref.json (renamed from tests/draft7/ref.json)0
-rw-r--r--json/tests/draft7/refRemote.json (renamed from tests/draft7/refRemote.json)0
-rw-r--r--json/tests/draft7/required.json (renamed from tests/draft7/required.json)0
-rw-r--r--json/tests/draft7/type.json (renamed from tests/draft7/type.json)0
-rw-r--r--json/tests/draft7/uniqueItems.json (renamed from tests/draft7/uniqueItems.json)0
l---------json/tests/latest (renamed from tests/latest)0
-rw-r--r--json/tox.ini9
-rw-r--r--jsonschema/__init__.py39
-rw-r--r--jsonschema/__main__.py3
-rw-r--r--jsonschema/_format.py428
-rw-r--r--jsonschema/_legacy_validators.py140
-rw-r--r--jsonschema/_reflect.py149
-rw-r--r--jsonschema/_types.py187
-rw-r--r--jsonschema/_utils.py213
-rw-r--r--jsonschema/_validators.py372
-rw-r--r--jsonschema/benchmarks/__init__.py5
-rw-r--r--jsonschema/benchmarks/issue232.py26
-rw-r--r--jsonschema/benchmarks/issue232/issue.json2653
-rw-r--r--jsonschema/benchmarks/json_schema_test_suite.py13
-rw-r--r--jsonschema/cli.py270
-rw-r--r--jsonschema/exceptions.py358
-rw-r--r--jsonschema/schemas/draft3.json177
-rw-r--r--jsonschema/schemas/draft4.json149
-rw-r--r--jsonschema/schemas/draft6.json153
-rw-r--r--jsonschema/schemas/draft7.json166
-rw-r--r--jsonschema/tests/__init__.py0
-rw-r--r--jsonschema/tests/_helpers.py21
-rw-r--r--jsonschema/tests/_suite.py243
-rw-r--r--jsonschema/tests/test_cli.py821
-rw-r--r--jsonschema/tests/test_exceptions.py459
-rw-r--r--jsonschema/tests/test_format.py88
-rw-r--r--jsonschema/tests/test_jsonschema_test_suite.py489
-rw-r--r--jsonschema/tests/test_types.py192
-rw-r--r--jsonschema/tests/test_validators.py1798
-rw-r--r--jsonschema/validators.py957
-rw-r--r--pyproject.toml16
-rw-r--r--setup.cfg74
-rw-r--r--tox.ini119
325 files changed, 13516 insertions, 15 deletions
diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..0f24d2f
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,5 @@
+# vim: filetype=dosini:
+[run]
+branch = True
+source = jsonschema
+omit = */jsonschema/_reflect.py,*/jsonschema/__main__.py,*/jsonschema/benchmarks/*
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..fc40c00
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,5 @@
+# These are supported funding model platforms
+
+github: "Julian"
+patreon: "JulianWasTaken"
+tidelift: "pypi/jsonschema"
diff --git a/.github/SECURITY.md b/.github/SECURITY.md
new file mode 100644
index 0000000..fd524e9
--- /dev/null
+++ b/.github/SECURITY.md
@@ -0,0 +1,21 @@
+# Security Policy
+
+## Supported Versions
+
+In general, only the latest released ``jsonschema`` version is supported
+and will receive updates.
+
+## Reporting a Vulnerability
+
+To report a security vulnerability, please send an email to
+``Julian+Security@GrayVines.com`` with subject line ``SECURITY
+(jsonschema)``.
+
+I will do my best to respond within 48 hours to acknowledge the message
+and discuss further steps.
+
+If the vulnerability is accepted, an advisory will be sent out via
+GitHub's security advisory functionality.
+
+For non-sensitive discussion related to this policy itself, feel free to
+open an issue on the issue tracker.
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9fdb508..d89d6fd 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,4 +1,4 @@
-name: Test Suite Sanity Checking
+name: CI
on:
push:
@@ -6,20 +6,213 @@ on:
release:
types: [published]
schedule:
- # Daily at 6:42
- - cron: '42 6 * * *'
+ # Daily at 3:21
+ - cron: '21 3 * * *'
jobs:
ci:
- runs-on: ubuntu-latest
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [macos-latest, ubuntu-latest, windows-latest]
+ python-version:
+ - name: pypy3
+ toxenv: pypy3-noextra-build
+ - name: pypy3
+ toxenv: pypy3-noextra-tests
+ - name: pypy3
+ toxenv: pypy3-format-build
+ - name: pypy3
+ toxenv: pypy3-format-tests
+ - name: pypy3
+ toxenv: pypy3-format_nongpl-build
+ - name: pypy3
+ toxenv: pypy3-format_nongpl-tests
+ - name: pypy3
+ toxenv: readme
+ - name: pypy3
+ toxenv: safety
+ - name: pypy3
+ toxenv: secrets
+ - name: pypy3
+ toxenv: style
+ - name: pypy3
+ toxenv: docs-html
+ - name: pypy3
+ toxenv: docs-doctest
+ - name: pypy3
+ toxenv: docs-linkcheck
+ - name: pypy3
+ toxenv: docs-spelling
+ - name: pypy3
+ toxenv: docs-style
+ - name: 3.6
+ toxenv: py36-noextra-build
+ - name: 3.6
+ toxenv: py36-noextra-tests
+ - name: 3.6
+ toxenv: py36-format-build
+ - name: 3.6
+ toxenv: py36-format-tests
+ - name: 3.6
+ toxenv: py36-format_nongpl-build
+ - name: 3.6
+ toxenv: py36-format_nongpl-tests
+ - name: 3.7
+ toxenv: py37-noextra-build
+ - name: 3.7
+ toxenv: py37-noextra-tests
+ - name: 3.7
+ toxenv: py37-format-build
+ - name: 3.7
+ toxenv: py37-format-tests
+ - name: 3.7
+ toxenv: py37-format_nongpl-build
+ - name: 3.7
+ toxenv: py37-format_nongpl-tests
+ - name: 3.8
+ toxenv: py38-noextra-build
+ - name: 3.8
+ toxenv: py38-noextra-tests
+ - name: 3.8
+ toxenv: py38-format-build
+ - name: 3.8
+ toxenv: py38-format-tests
+ - name: 3.8
+ toxenv: py38-format_nongpl-build
+ - name: 3.8
+ toxenv: py38-format_nongpl-tests
+ exclude:
+ - os: windows-latest
+ python-version:
+ name: pypy3
+ toxenv: pypy3-noextra-build
+ - os: windows-latest
+ python-version:
+ name: pypy3
+ toxenv: pypy3-format-build
+ - os: windows-latest
+ python-version:
+ name: pypy3
+ toxenv: pypy3-format_nongpl-build
+ - os: windows-latest
+ python-version:
+ name: pypy3
+ toxenv: pypy3-noextra-tests
+ - os: windows-latest
+ python-version:
+ name: pypy3
+ toxenv: pypy3-format-tests
+ - os: windows-latest
+ python-version:
+ name: pypy3
+ toxenv: pypy3-format_nongpl-tests
+ - os: windows-latest
+ python-version:
+ name: pypy3
+ toxenv: safety
+ - os: windows-latest
+ python-version:
+ name: pypy3
+ toxenv: secrets
+ - os: windows-latest
+ python-version:
+ name: pypy3
+ toxenv: style
+ - os: windows-latest
+ python-version:
+ name: 3.6
+ toxenv: py36-noextra-build
+ - os: windows-latest
+ python-version:
+ name: 3.6
+ toxenv: py36-format-build
+ - os: windows-latest
+ python-version:
+ name: 3.6
+ toxenv: py36-format_nongpl-build
+ - os: windows-latest
+ python-version:
+ name: 3.6
+ toxenv: py36-noextra-tests
+ - os: windows-latest
+ python-version:
+ name: 3.6
+ toxenv: py36-format-tests
+ - os: windows-latest
+ python-version:
+ name: 3.6
+ toxenv: py36-format_nongpl-tests
+ - os: windows-latest
+ python-version:
+ name: 3.7
+ toxenv: py37-noextra-tests
+ - os: windows-latest
+ python-version:
+ name: 3.7
+ toxenv: py37-format-tests
+ - os: windows-latest
+ python-version:
+ name: 3.7
+ toxenv: py37-format_nongpl-tests
+ - os: windows-latest
+ python-version:
+ name: 3.8
+ toxenv: py38-noextra-tests
+ - os: windows-latest
+ python-version:
+ name: 3.8
+ toxenv: py38-format-tests
+ - os: windows-latest
+ python-version:
+ name: 3.8
+ toxenv: py38-format_nongpl-tests
+ - os: windows-latest
+ python-version:
+ name: pypy3
+ toxenv: readme
+ - os: windows-latest
+ python-version:
+ name: pypy3
+ toxenv: docs-html
+ - os: windows-latest
+ python-version:
+ name: pypy3
+ toxenv: docs-doctest
+ - os: windows-latest
+ python-version:
+ name: pypy3
+ toxenv: docs-linkcheck
+ - os: windows-latest
+ python-version:
+ name: pypy3
+ toxenv: docs-spelling
+ - os: windows-latest
+ python-version:
+ name: pypy3
+ toxenv: docs-style
steps:
- uses: actions/checkout@v2
- - name: Set up Python
+ - name: Set up Python ${{ matrix.python-version.name }}
uses: actions/setup-python@v2
with:
- python-version: 3.7
+ python-version: ${{ matrix.python-version.name }}
+ - name: Ensure we have new enough versions to respect python_version
+ run: python -m pip install -U pip setuptools
+ - name: Install dependencies
+ run: >
+ sudo apt-get update &&
+ sudo apt-get install -y libenchant-dev libxml2-dev libxslt-dev
+ if: runner.os == 'Linux' && startsWith(matrix.python-version.toxenv, 'docs-')
+ - name: Install dependencies
+ run: brew install enchant
+ if: runner.os == 'macOS' && startsWith(matrix.python-version.toxenv, 'docs-')
- name: Install tox
run: python -m pip install tox
- - name: Run the sanity checks
- run: python -m tox
+ - name: Use venv on Windows
+ run: python -m pip install tox-venv
+ if: runner.os == 'Windows'
+ - name: Run tox
+ run: python -m tox -e "${{ matrix.python-version.toxenv }}"
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
new file mode 100644
index 0000000..0eae5c8
--- /dev/null
+++ b/.github/workflows/coverage.yml
@@ -0,0 +1,25 @@
+name: Coverage
+
+on:
+ push:
+ pull_request:
+ release:
+ types: [published]
+
+jobs:
+ coverage:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python
+ uses: actions/setup-python@v2
+ with:
+ python-version: pypy3
+ - name: Ensure we have new enough versions to respect python_version
+ run: python -m pip install -U pip setuptools
+ - name: Install tox
+ run: python -m pip install tox
+ - name: Collect & Upload Coverage
+ run: python -m tox -e pypy3-format-codecov
+ env:
+ CODECOV_TOKEN: 2b38dae1-41c4-4435-a29d-79a1299e5617
diff --git a/.github/workflows/packaging.yml b/.github/workflows/packaging.yml
new file mode 100644
index 0000000..decf5f0
--- /dev/null
+++ b/.github/workflows/packaging.yml
@@ -0,0 +1,38 @@
+name: Packaging
+
+on:
+ push:
+ release:
+ types: [published]
+
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [macos-latest, ubuntu-latest]
+ python-version: [pypy3, 3.7, 3.8]
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install dependencies
+ run: python -m pip install pep517
+ - name: Evade 'pypa/pep517#74'
+ run: python -m pip install -U setuptools setuptools-scm pip wheel
+ if: startsWith(matrix.python-version, 'pypy')
+ - name: Create packages
+ run: python -m pep517.build .
+ - uses: actions/upload-artifact@master
+ with:
+ name: dist-${{ matrix.os }}-${{ matrix.python-version }}
+ path: dist
+ - name: Publish package
+ if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && matrix.python-version == 'pypy3' && runner.os == 'Linux'
+ uses: pypa/gh-action-pypi-publish@master
+ with:
+ user: __token__
+ password: ${{ secrets.pypi_password }}
diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
new file mode 100644
index 0000000..306e1ad
--- /dev/null
+++ b/.github/workflows/pre-commit.yml
@@ -0,0 +1,13 @@
+name: pre-commit
+
+on:
+ pull_request:
+ push:
+
+jobs:
+ pre-commit:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-python@v2
+ - uses: pre-commit/action@v2.0.0
diff --git a/.gitignore b/.gitignore
index 1333ed7..31236db 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,5 @@
+_cache
+_static
+_templates
+
TODO
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..bd3e906
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,19 @@
+exclude: json/
+
+repos:
+- repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v3.2.0
+ hooks:
+ - id: check-ast
+ - id: check-docstring-first
+ - id: check-json
+ - id: check-toml
+ - id: check-vcs-permalinks
+ - id: check-yaml
+ - id: debug-statements
+ - id: end-of-file-fixer
+ - id: trailing-whitespace
+- repo: https://github.com/timothycrosley/isort
+ rev: 5.4.2
+ hooks:
+ - id: isort
diff --git a/.readthedocs.yml b/.readthedocs.yml
new file mode 100644
index 0000000..2da1ed5
--- /dev/null
+++ b/.readthedocs.yml
@@ -0,0 +1,15 @@
+version: 2
+
+sphinx:
+ builder: dirhtml
+ configuration: docs/conf.py
+ fail_on_warning: true
+
+formats: all
+
+python:
+ version: 3
+ install:
+ - requirements: docs/requirements.txt
+ - method: pip
+ path: .
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
new file mode 100644
index 0000000..7c1a67e
--- /dev/null
+++ b/CHANGELOG.rst
@@ -0,0 +1,196 @@
+v3.2.0
+------
+
+* Added a ``format_nongpl`` setuptools extra, which installs only ``format``
+ dependencies that are non-GPL (#619).
+
+v3.1.1
+------
+
+* Temporarily revert the switch to ``js-regex`` until #611 and #612 are
+ resolved.
+
+v3.1.0
+------
+
+* Regular expressions throughout schemas now respect the ECMA 262 dialect, as
+ recommended by the specification (#609).
+
+v3.0.2
+------
+
+* Fixed a bug where ``0`` and ``False`` were considered equal by
+ ``const`` and ``enum`` (#575).
+
+v3.0.1
+------
+
+* Fixed a bug where extending validators did not preserve their notion
+ of which validator property contains ``$id`` information.
+
+v3.0.0
+------
+
+* Support for Draft 6 and Draft 7
+* Draft 7 is now the default
+* New ``TypeChecker`` object for more complex type definitions (and overrides)
+* Falling back to isodate for the date-time format checker is no longer
+ attempted, in accordance with the specification
+
+v2.6.0
+------
+
+* Support for Python 2.6 has been dropped.
+* Improve a few error messages for ``uniqueItems`` (#224) and
+ ``additionalProperties`` (#317)
+* Fixed an issue with ``ErrorTree``'s handling of multiple errors (#288)
+
+v2.5.0
+------
+
+* Improved performance on CPython by adding caching around ref resolution
+ (#203)
+
+v2.4.0
+------
+
+* Added a CLI (#134)
+* Added absolute path and absolute schema path to errors (#120)
+* Added ``relevance``
+* Meta-schemas are now loaded via ``pkgutil``
+
+v2.3.0
+------
+
+* Added ``by_relevance`` and ``best_match`` (#91)
+* Fixed ``format`` to allow adding formats for non-strings (#125)
+* Fixed the ``uri`` format to reject URI references (#131)
+
+v2.2.0
+------
+
+* Compile the host name regex (#127)
+* Allow arbitrary objects to be types (#129)
+
+v2.1.0
+------
+
+* Support RFC 3339 datetimes in conformance with the spec
+* Fixed error paths for additionalItems + items (#122)
+* Fixed wording for min / maxProperties (#117)
+
+
+v2.0.0
+------
+
+* Added ``create`` and ``extend`` to ``jsonschema.validators``
+* Removed ``ValidatorMixin``
+* Fixed array indices ref resolution (#95)
+* Fixed unknown scheme defragmenting and handling (#102)
+
+
+v1.3.0
+------
+
+* Better error tracebacks (#83)
+* Raise exceptions in ``ErrorTree``\s for keys not in the instance (#92)
+* __cause__ (#93)
+
+
+v1.2.0
+------
+
+* More attributes for ValidationError (#86)
+* Added ``ValidatorMixin.descend``
+* Fixed bad ``RefResolutionError`` message (#82)
+
+
+v1.1.0
+------
+
+* Canonicalize URIs (#70)
+* Allow attaching exceptions to ``format`` errors (#77)
+
+
+v1.0.0
+------
+
+* Support for Draft 4
+* Support for format
+* Longs are ints too!
+* Fixed a number of issues with ``$ref`` support (#66)
+* Draft4Validator is now the default
+* ``ValidationError.path`` is now in sequential order
+* Added ``ValidatorMixin``
+
+
+v0.8.0
+------
+
+* Full support for JSON References
+* ``validates`` for registering new validators
+* Documentation
+* Bugfixes
+
+ * uniqueItems not so unique (#34)
+ * Improper any (#47)
+
+
+v0.7
+----
+
+* Partial support for (JSON Pointer) ``$ref``
+* Deprecations
+
+ * ``Validator`` is replaced by ``Draft3Validator`` with a slightly different
+ interface
+ * ``validator(meta_validate=False)``
+
+
+v0.6
+----
+
+* Bugfixes
+
+ * Issue #30 - Wrong behavior for the dependencies property validation
+ * Fixed a miswritten test
+
+
+v0.5
+----
+
+* Bugfixes
+
+ * Issue #17 - require path for error objects
+ * Issue #18 - multiple type validation for non-objects
+
+
+v0.4
+----
+
+* Preliminary support for programmatic access to error details (Issue #5).
+ There are certainly some corner cases that don't do the right thing yet, but
+ this works mostly.
+
+ In order to make this happen (and also to clean things up a bit), a number
+ of deprecations are necessary:
+
+ * ``stop_on_error`` is deprecated in ``Validator.__init__``. Use
+ ``Validator.iter_errors()`` instead.
+ * ``number_types`` and ``string_types`` are deprecated there as well.
+ Use ``types={"number" : ..., "string" : ...}`` instead.
+ * ``meta_validate`` is also deprecated, and instead is now accepted as
+ an argument to ``validate``, ``iter_errors`` and ``is_valid``.
+
+* A bugfix or two
+
+
+v0.3
+----
+
+* Default for unknown types and properties is now to *not* error (consistent
+ with the schema).
+* Python 3 support
+* Removed dependency on SecureTypes now that the hash bug has been resolved.
+* "Numerous bug fixes" -- most notably, a divisibleBy error for floats and a
+ bunch of missing typechecks for irrelevant properties.
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..af9cfbd
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,19 @@
+Copyright (c) 2013 Julian Berman
+
+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.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..2eef0b7
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,5 @@
+graft json
+include *.rst
+include COPYING
+include pyproject.toml
+include tox.ini
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..2ad096e
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,136 @@
+==========
+jsonschema
+==========
+
+|PyPI| |Pythons| |CI| |ReadTheDocs|
+
+.. |PyPI| image:: https://img.shields.io/pypi/v/jsonschema.svg
+ :alt: PyPI version
+ :target: https://pypi.org/project/jsonschema/
+
+.. |Pythons| image:: https://img.shields.io/pypi/pyversions/jsonschema.svg
+ :alt: Supported Python versions
+ :target: https://pypi.org/project/jsonschema/
+
+.. |CI| image:: https://github.com/Julian/jsonschema/workflows/CI/badge.svg
+ :alt: Build status
+ :target: https://github.com/Julian/jsonschema/actions?query=workflow%3ACI
+
+.. |ReadTheDocs| image:: https://readthedocs.org/projects/python-jsonschema/badge/?version=stable&style=flat
+ :alt: ReadTheDocs status
+ :target: https://python-jsonschema.readthedocs.io/en/stable/
+
+
+``jsonschema`` is an implementation of `JSON Schema
+<https://json-schema.org>`_ for Python.
+
+.. code-block:: python
+
+ >>> from jsonschema import validate
+
+ >>> # A sample schema, like what we'd get from json.load()
+ >>> schema = {
+ ... "type" : "object",
+ ... "properties" : {
+ ... "price" : {"type" : "number"},
+ ... "name" : {"type" : "string"},
+ ... },
+ ... }
+
+ >>> # If no exception is raised by validate(), the instance is valid.
+ >>> validate(instance={"name" : "Eggs", "price" : 34.99}, schema=schema)
+
+ >>> validate(
+ ... instance={"name" : "Eggs", "price" : "Invalid"}, schema=schema,
+ ... ) # doctest: +IGNORE_EXCEPTION_DETAIL
+ Traceback (most recent call last):
+ ...
+ ValidationError: 'Invalid' is not of type 'number'
+
+It can also be used from console:
+
+.. code-block:: bash
+
+ $ jsonschema --instance sample.json sample.schema
+
+Features
+--------
+
+* Full support for
+ `Draft 7 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft7Validator>`_,
+ `Draft 6 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft6Validator>`_,
+ `Draft 4 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft4Validator>`_
+ and
+ `Draft 3 <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft3Validator>`_
+
+* `Lazy validation <https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.IValidator.iter_errors>`_
+ that can iteratively report *all* validation errors.
+
+* `Programmatic querying <https://python-jsonschema.readthedocs.io/en/latest/errors/>`_
+ of which properties or items failed validation.
+
+
+Installation
+------------
+
+``jsonschema`` is available on `PyPI <https://pypi.org/project/jsonschema/>`_. You can install using `pip <https://pip.pypa.io/en/stable/>`_:
+
+.. code-block:: bash
+
+ $ pip install jsonschema
+
+
+Running the Test Suite
+----------------------
+
+If you have ``tox`` installed (perhaps via ``pip install tox`` or your
+package manager), running ``tox`` in the directory of your source
+checkout will run ``jsonschema``'s test suite on all of the versions
+of Python ``jsonschema`` supports. If you don't have all of the
+versions that ``jsonschema`` is tested under, you'll likely want to run
+using ``tox``'s ``--skip-missing-interpreters`` option.
+
+Of course you're also free to just run the tests on a single version with your
+favorite test runner. The tests live in the ``jsonschema.tests`` package.
+
+
+Benchmarks
+----------
+
+``jsonschema``'s benchmarks make use of `pyperf
+<https://pyperf.readthedocs.io>`_. Running them can be done via::
+
+ $ tox -e perf
+
+
+Community
+---------
+
+The JSON Schema specification has `a Slack
+<https://json-schema.slack.com>`_, with an `invite link on its home page
+<https://json-schema.org/>`_. Many folks knowledgeable on authoring
+schemas can be found there.
+
+Otherwise, asking questions on Stack Overflow is another means of
+getting help if you're stuck.
+
+Contributing
+------------
+
+I'm Julian Berman.
+
+``jsonschema`` is on `GitHub <https://github.com/Julian/jsonschema>`_.
+
+Get in touch, via GitHub or otherwise, if you've got something to contribute,
+it'd be most welcome!
+
+You can also generally find me on Freenode (nick: ``tos9``) in various
+channels, including ``#python``.
+
+If you feel overwhelmingly grateful, you can also woo me with beer money
+via Google Pay with the email in my GitHub profile.
+
+And for companies who appreciate ``jsonschema`` and its continued support
+and growth, ``jsonschema`` is also now supportable via `TideLift
+<https://tidelift.com/subscription/pkg/pypi-jsonschema?utm_source=pypi-j
+sonschema&utm_medium=referral&utm_campaign=readme>`_.
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 0000000..640bd89
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,5 @@
+coverage:
+ status:
+ patch:
+ default:
+ target: 100%
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..f6315df
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,227 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+PYTHON = python
+PAPER =
+BUILDDIR = _build
+SOURCEDIR = $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(SOURCEDIR)
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help
+help:
+ @echo "Please use \`make <target>' where <target> 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 " applehelp to make an Apple Help Book"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " epub3 to make an epub3"
+ @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 " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " texinfo to make Texinfo files"
+ @echo " info to make Texinfo files and run them through makeinfo"
+ @echo " gettext to make PO message catalogs"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " xml to make Docutils-native XML files"
+ @echo " pseudoxml to make pseudoxml-XML files for display purposes"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation"
+ @echo " coverage to run coverage check of the documentation (if enabled)"
+ @echo " spelling to run a spell check of the documentation"
+ @echo " dummy to check syntax errors of document sources"
+
+.PHONY: clean
+clean:
+ rm -rf $(BUILDDIR)/*
+
+.PHONY: html
+html:
+ $(PYTHON) -m sphinx -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+.PHONY: dirhtml
+dirhtml:
+ $(PYTHON) -m sphinx -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+.PHONY: singlehtml
+singlehtml:
+ $(PYTHON) -m sphinx -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+.PHONY: json
+json:
+ $(PYTHON) -m sphinx -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+.PHONY: htmlhelp
+htmlhelp:
+ $(PYTHON) -m sphinx -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+.PHONY: qthelp
+qthelp:
+ $(PYTHON) -m sphinx -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/jsonschema.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/jsonschema.qhc"
+
+.PHONY: applehelp
+applehelp:
+ $(PYTHON) -m sphinx -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
+ @echo
+ @echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
+ @echo "N.B. You won't be able to view it unless you put it in" \
+ "~/Library/Documentation/Help or install it in your application" \
+ "bundle."
+
+.PHONY: devhelp
+devhelp:
+ $(PYTHON) -m sphinx -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/jsonschema"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/jsonschema"
+ @echo "# devhelp"
+
+.PHONY: epub
+epub:
+ $(PYTHON) -m sphinx -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+.PHONY: epub3
+epub3:
+ $(PYTHON) -m sphinx -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
+ @echo
+ @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
+
+.PHONY: latex
+latex:
+ $(PYTHON) -m sphinx -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)."
+
+.PHONY: latexpdf
+latexpdf:
+ $(PYTHON) -m sphinx -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."
+
+.PHONY: latexpdfja
+latexpdfja:
+ $(PYTHON) -m sphinx -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through platex and dvipdfmx..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+.PHONY: text
+text:
+ $(PYTHON) -m sphinx -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+.PHONY: man
+man:
+ $(PYTHON) -m sphinx -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+.PHONY: texinfo
+texinfo:
+ $(PYTHON) -m sphinx -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo
+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+ @echo "Run \`make' in that directory to run these through makeinfo" \
+ "(use \`make info' here to do that automatically)."
+
+.PHONY: info
+info:
+ $(PYTHON) -m sphinx -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo "Running Texinfo files through makeinfo..."
+ make -C $(BUILDDIR)/texinfo info
+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+.PHONY: gettext
+gettext:
+ $(PYTHON) -m sphinx -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+ @echo
+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+.PHONY: changes
+changes:
+ $(PYTHON) -m sphinx -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+.PHONY: linkcheck
+linkcheck:
+ $(PYTHON) -m sphinx -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+.PHONY: doctest
+doctest:
+ $(PYTHON) -m sphinx -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
+
+.PHONY: coverage
+coverage:
+ $(PYTHON) -m sphinx -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
+ @echo "Testing of coverage in the sources finished, look at the " \
+ "results in $(BUILDDIR)/coverage/python.txt."
+
+.PHONY: xml
+xml:
+ $(PYTHON) -m sphinx -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
+ @echo
+ @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
+
+.PHONY: pseudoxml
+pseudoxml:
+ $(PYTHON) -m sphinx -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
+ @echo
+ @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
+
+.PHONY: spelling
+spelling:
+ $(PYTHON) -m sphinx -b spelling $(ALLSPHINXOPTS) $(BUILDDIR)/spelling
+ @echo
+ @echo "Build finished. The spelling files are in $(BUILDDIR)/spelling."
+
+.PHONY: dummy
+dummy:
+ $(PYTHON) -m sphinx -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
+ @echo
+ @echo "Build finished. Dummy builder generates no files."
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..73e0e1f
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,254 @@
+from textwrap import dedent
+import os
+import re
+import sys
+
+import jsonschema
+
+# 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.
+ext_paths = [os.path.abspath(os.path.pardir), os.path.dirname(__file__)]
+sys.path = ext_paths + sys.path
+
+# -- 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",
+ "sphinx.ext.autosectionlabel",
+ "sphinx.ext.coverage",
+ "sphinx.ext.doctest",
+ "sphinx.ext.intersphinx",
+ "sphinx.ext.napoleon",
+ "sphinx.ext.viewcode",
+ "sphinxcontrib.spelling",
+ "jsonschema_role",
+]
+
+cache_path = "_cache"
+
+# 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"jsonschema"
+author = u"Julian Berman"
+copyright = u"2013, " + author
+
+# 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.
+#
+# version: The short X.Y version
+# release: The full version, including alpha/beta/rc tags.
+release = jsonschema.__version__
+version = release.partition("-")[0]
+
+# 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", "_cache", "_static", "_templates"]
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+default_role = "any"
+
+# 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 = []
+
+doctest_global_setup = dedent(
+ """
+ from jsonschema import *
+"""
+)
+
+intersphinx_mapping = {
+ "python": ("https://docs.python.org/3", None),
+}
+
+
+# -- 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 = "pyramid"
+
+# 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
+# "<project> v<release> 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 <link> 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 = "jsonschemadoc"
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+ ("index", "jsonschema.tex", u"jsonschema Documentation", author, "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
+
+# 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", "jsonschema", u"jsonschema Documentation", [author], 1)]
+
+# If true, show URL addresses after external links.
+# man_show_urls = False
+
+
+# -- Options for Texinfo output ------------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ (
+ "index",
+ "jsonschema",
+ u"jsonschema Documentation",
+ author,
+ "jsonschema",
+ "One line description of project.",
+ "Miscellaneous",
+ ),
+]
+
+# Documents to append as an appendix to all manuals.
+# texinfo_appendices = []
+
+# If false, no module index is generated.
+# texinfo_domain_indices = True
+
+# How to display URL addresses: "footnote", "no", or "inline".
+# texinfo_show_urls = "footnote"
+
+# -- Options for the linkcheck builder ------------------------------------
+
+
+def entire_domain(host):
+ return r"http.?://" + re.escape(host) + r"($|/.*)"
+
+
+linkcheck_ignore = [
+ "https://github.com/Julian/jsonschema/actions",
+ "https://github.com/Julian/jsonschema/workflows/CI/badge.svg",
+]
+
+# -- Options for sphinxcontrib-autosectionlabel ---------------------------
+
+autosectionlabel_prefix_document = True
+
+# -- Options for sphinxcontrib-spelling -----------------------------------
+
+spelling_word_list_filename = "spelling-wordlist.txt"
diff --git a/docs/creating.rst b/docs/creating.rst
new file mode 100644
index 0000000..a9f18d1
--- /dev/null
+++ b/docs/creating.rst
@@ -0,0 +1,25 @@
+.. currentmodule:: jsonschema.validators
+
+.. _creating-validators:
+
+=======================================
+Creating or Extending Validator Classes
+=======================================
+
+.. autofunction:: create
+
+.. autofunction:: extend
+
+.. autofunction:: validator_for
+
+.. autofunction:: validates
+
+
+Creating Validation Errors
+--------------------------
+
+Any validating function that validates against a subschema should call
+``descend``, rather than ``iter_errors``. If it recurses into the
+instance, or schema, it should pass one or both of the ``path`` or
+``schema_path`` arguments to ``descend`` in order to properly maintain
+where in the instance or schema respectively the error occurred.
diff --git a/docs/errors.rst b/docs/errors.rst
new file mode 100644
index 0000000..2b2f485
--- /dev/null
+++ b/docs/errors.rst
@@ -0,0 +1,406 @@
+==========================
+Handling Validation Errors
+==========================
+
+.. currentmodule:: jsonschema.exceptions
+
+When an invalid instance is encountered, a `ValidationError` will be
+raised or returned, depending on which method or function is used.
+
+.. autoexception:: ValidationError
+
+ The information carried by an error roughly breaks down into:
+
+ =============== ================= ========================
+ What Happened Why Did It Happen What Was Being Validated
+ =============== ================= ========================
+ `message` `context` `instance`
+
+ `cause` `json_path`
+
+ `path`
+
+ `schema`
+
+ `schema_path`
+
+ `validator`
+
+ `validator_value`
+ =============== ================= ========================
+
+
+ .. attribute:: message
+
+ A human readable message explaining the error.
+
+ .. attribute:: validator
+
+ The name of the failed `validator
+ <https://json-schema.org/draft-04/json-schema-validation.html#rfc.section.5>`_.
+
+ .. attribute:: validator_value
+
+ The value for the failed validator in the schema.
+
+ .. attribute:: schema
+
+ The full schema that this error came from. This is potentially a
+ subschema from within the schema that was passed in originally,
+ or even an entirely different schema if a :validator:`$ref` was
+ followed.
+
+ .. attribute:: relative_schema_path
+
+ A `collections.deque` containing the path to the failed
+ validator within the schema.
+
+ .. attribute:: absolute_schema_path
+
+ A `collections.deque` containing the path to the failed
+ validator within the schema, but always relative to the
+ *original* schema as opposed to any subschema (i.e. the one
+ originally passed into a validator class, *not* `schema`\).
+
+ .. attribute:: schema_path
+
+ Same as `relative_schema_path`.
+
+ .. attribute:: relative_path
+
+ A `collections.deque` containing the path to the
+ offending element within the instance. The deque can be empty if
+ the error happened at the root of the instance.
+
+ .. attribute:: absolute_path
+
+ A `collections.deque` containing the path to the
+ offending element within the instance. The absolute path
+ is always relative to the *original* instance that was
+ validated (i.e. the one passed into a validation method, *not*
+ `instance`\). The deque can be empty if the error happened
+ at the root of the instance.
+
+ .. attribute:: json_path
+
+ A `JSON path <https://goessner.net/articles/JsonPath/index.html>`_
+ to the offending element within the instance.
+
+ .. attribute:: path
+
+ Same as `relative_path`.
+
+ .. attribute:: instance
+
+ The instance that was being validated. This will differ from
+ the instance originally passed into ``validate`` if the
+ validator object was in the process of validating a (possibly
+ nested) element within the top-level instance. The path within
+ the top-level instance (i.e. `ValidationError.path`) could
+ be used to find this object, but it is provided for convenience.
+
+ .. attribute:: context
+
+ If the error was caused by errors in subschemas, the list of errors
+ from the subschemas will be available on this property. The
+ `schema_path` and `path` of these errors will be relative
+ to the parent error.
+
+ .. attribute:: cause
+
+ If the error was caused by a *non*-validation error, the
+ exception object will be here. Currently this is only used
+ for the exception raised by a failed format checker in
+ `jsonschema.FormatChecker.check`.
+
+ .. attribute:: parent
+
+ A validation error which this error is the `context` of.
+ ``None`` if there wasn't one.
+
+
+In case an invalid schema itself is encountered, a `SchemaError` is
+raised.
+
+.. autoexception:: SchemaError
+
+ The same attributes are present as for `ValidationError`\s.
+
+
+These attributes can be clarified with a short example:
+
+.. testcode::
+
+ schema = {
+ "items": {
+ "anyOf": [
+ {"type": "string", "maxLength": 2},
+ {"type": "integer", "minimum": 5}
+ ]
+ }
+ }
+ instance = [{}, 3, "foo"]
+ v = Draft7Validator(schema)
+ errors = sorted(v.iter_errors(instance), key=lambda e: e.path)
+
+The error messages in this situation are not very helpful on their own.
+
+.. testcode::
+
+ for error in errors:
+ print(error.message)
+
+outputs:
+
+.. testoutput::
+
+ {} is not valid under any of the given schemas
+ 3 is not valid under any of the given schemas
+ 'foo' is not valid under any of the given schemas
+
+If we look at `ValidationError.path` on each of the errors, we can find
+out which elements in the instance correspond to each of the errors. In
+this example, `ValidationError.path` will have only one element, which
+will be the index in our list.
+
+.. testcode::
+
+ for error in errors:
+ print(list(error.path))
+
+.. testoutput::
+
+ [0]
+ [1]
+ [2]
+
+Since our schema contained nested subschemas, it can be helpful to look at
+the specific part of the instance and subschema that caused each of the errors.
+This can be seen with the `ValidationError.instance` and
+`ValidationError.schema` attributes.
+
+With validators like :validator:`anyOf`, the `ValidationError.context`
+attribute can be used to see the sub-errors which caused the failure. Since
+these errors actually came from two separate subschemas, it can be helpful to
+look at the `ValidationError.schema_path` attribute as well to see where
+exactly in the schema each of these errors come from. In the case of sub-errors
+from the `ValidationError.context` attribute, this path will be relative
+to the `ValidationError.schema_path` of the parent error.
+
+.. testcode::
+
+ for error in errors:
+ for suberror in sorted(error.context, key=lambda e: e.schema_path):
+ print(list(suberror.schema_path), suberror.message, sep=", ")
+
+.. testoutput::
+
+ [0, 'type'], {} is not of type 'string'
+ [1, 'type'], {} is not of type 'integer'
+ [0, 'type'], 3 is not of type 'string'
+ [1, 'minimum'], 3 is less than the minimum of 5
+ [0, 'maxLength'], 'foo' is too long
+ [1, 'type'], 'foo' is not of type 'integer'
+
+The string representation of an error combines some of these attributes for
+easier debugging.
+
+.. testcode::
+
+ print(errors[1])
+
+.. testoutput::
+
+ 3 is not valid under any of the given schemas
+
+ Failed validating 'anyOf' in schema['items']:
+ {'anyOf': [{'maxLength': 2, 'type': 'string'},
+ {'minimum': 5, 'type': 'integer'}]}
+
+ On instance[1]:
+ 3
+
+
+ErrorTrees
+----------
+
+If you want to programmatically be able to query which properties or validators
+failed when validating a given instance, you probably will want to do so using
+`jsonschema.exceptions.ErrorTree` objects.
+
+.. autoclass:: jsonschema.exceptions.ErrorTree
+ :members:
+ :special-members:
+ :exclude-members: __dict__,__weakref__
+
+ .. attribute:: errors
+
+ The mapping of validator names to the error objects (usually
+ `jsonschema.exceptions.ValidationError`\s) at this level
+ of the tree.
+
+Consider the following example:
+
+.. testcode::
+
+ schema = {
+ "type" : "array",
+ "items" : {"type" : "number", "enum" : [1, 2, 3]},
+ "minItems" : 3,
+ }
+ instance = ["spam", 2]
+
+For clarity's sake, the given instance has three errors under this schema:
+
+.. testcode::
+
+ v = Draft3Validator(schema)
+ for error in sorted(v.iter_errors(["spam", 2]), key=str):
+ print(error.message)
+
+.. testoutput::
+
+ 'spam' is not of type 'number'
+ 'spam' is not one of [1, 2, 3]
+ ['spam', 2] is too short
+
+Let's construct an `jsonschema.exceptions.ErrorTree` so that we
+can query the errors a bit more easily than by just iterating over the
+error objects.
+
+.. testcode::
+
+ tree = ErrorTree(v.iter_errors(instance))
+
+As you can see, `jsonschema.exceptions.ErrorTree` takes an
+iterable of `ValidationError`\s when constructing a tree so
+you can directly pass it the return value of a validator object's
+`jsonschema.IValidator.iter_errors` method.
+
+`ErrorTree`\s support a number of useful operations. The first one we
+might want to perform is to check whether a given element in our instance
+failed validation. We do so using the :keyword:`in` operator:
+
+.. doctest::
+
+ >>> 0 in tree
+ True
+
+ >>> 1 in tree
+ False
+
+The interpretation here is that the 0th index into the instance (``"spam"``)
+did have an error (in fact it had 2), while the 1th index (``2``) did not (i.e.
+it was valid).
+
+If we want to see which errors a child had, we index into the tree and look at
+the `ErrorTree.errors` attribute.
+
+.. doctest::
+
+ >>> sorted(tree[0].errors)
+ ['enum', 'type']
+
+Here we see that the :validator:`enum` and :validator:`type` validators failed
+for index ``0``. In fact `ErrorTree.errors` is a dict, whose values are
+the `ValidationError`\s, so we can get at those directly if we want
+them.
+
+.. doctest::
+
+ >>> print(tree[0].errors["type"].message)
+ 'spam' is not of type 'number'
+
+Of course this means that if we want to know if a given named
+validator failed for a given index, we check for its presence in
+`ErrorTree.errors`:
+
+.. doctest::
+
+ >>> "enum" in tree[0].errors
+ True
+
+ >>> "minimum" in tree[0].errors
+ False
+
+Finally, if you were paying close enough attention, you'll notice that we
+haven't seen our :validator:`minItems` error appear anywhere yet. This is
+because :validator:`minItems` is an error that applies globally to the instance
+itself. So it appears in the root node of the tree.
+
+.. doctest::
+
+ >>> "minItems" in tree.errors
+ True
+
+That's all you need to know to use error trees.
+
+To summarize, each tree contains child trees that can be accessed by
+indexing the tree to get the corresponding child tree for a given index
+into the instance. Each tree and child has a `ErrorTree.errors`
+attribute, a dict, that maps the failed validator name to the
+corresponding validation error.
+
+
+best_match and relevance
+------------------------
+
+The `best_match` function is a simple but useful function for attempting
+to guess the most relevant error in a given bunch.
+
+.. doctest::
+
+ >>> from jsonschema import Draft7Validator
+ >>> from jsonschema.exceptions import best_match
+
+ >>> schema = {
+ ... "type": "array",
+ ... "minItems": 3,
+ ... }
+ >>> print(best_match(Draft7Validator(schema).iter_errors(11)).message)
+ 11 is not of type 'array'
+
+
+.. autofunction:: best_match
+
+
+.. function:: relevance(validation_error)
+
+ A key function that sorts errors based on heuristic relevance.
+
+ If you want to sort a bunch of errors entirely, you can use
+ this function to do so. Using this function as a key to e.g.
+ `sorted` or `max` will cause more relevant errors to be
+ considered greater than less relevant ones.
+
+ Within the different validators that can fail, this function
+ considers :validator:`anyOf` and :validator:`oneOf` to be *weak*
+ validation errors, and will sort them lower than other validators at
+ the same level in the instance.
+
+ If you want to change the set of weak [or strong] validators you can create
+ a custom version of this function with `by_relevance` and provide a
+ different set of each.
+
+.. doctest::
+
+ >>> schema = {
+ ... "properties": {
+ ... "name": {"type": "string"},
+ ... "phones": {
+ ... "properties": {
+ ... "home": {"type": "string"}
+ ... },
+ ... },
+ ... },
+ ... }
+ >>> instance = {"name": 123, "phones": {"home": [123]}}
+ >>> errors = Draft7Validator(schema).iter_errors(instance)
+ >>> [
+ ... e.path[-1]
+ ... for e in sorted(errors, key=exceptions.relevance)
+ ... ]
+ ['home', 'name']
+
+
+.. autofunction:: by_relevance
diff --git a/docs/faq.rst b/docs/faq.rst
new file mode 100644
index 0000000..154ed90
--- /dev/null
+++ b/docs/faq.rst
@@ -0,0 +1,237 @@
+==========================
+Frequently Asked Questions
+==========================
+
+
+My schema specifies format validation. Why do invalid instances seem valid?
+---------------------------------------------------------------------------
+
+The :validator:`format` validator can be a bit of a stumbling block for new
+users working with JSON Schema.
+
+In a schema such as:
+
+.. code-block:: json
+
+ {"type": "string", "format": "date"}
+
+JSON Schema specifications have historically differentiated between the
+:validator:`format` validator and other validators. In particular, the
+:validator:`format` validator was specified to be *informational* as much
+as it may be used for validation.
+
+In other words, for many use cases, schema authors may wish to use
+values for the :validator:`format` validator but have no expectation
+they be validated alongside other required assertions in a schema.
+
+Of course this does not represent all or even most use cases -- many
+schema authors *do* wish to assert that instances conform fully, even to
+the specific format mentioned.
+
+In drafts prior to ``draft2019-09``, the decision on whether to
+automatically enable :validator:`format` validation was left up to
+validation implementations such as this one.
+
+This library made the choice to leave it off by default, for two reasons:
+
+ * for forward compatibility and implementation complexity reasons
+ -- if :validator:`format` validation were on by default, and a
+ future draft of JSON Schema introduced a hard-to-implement format,
+ either the implementation of that format would block releases of
+ this library until it were implemented, or the behavior surrounding
+ :validator:`format` would need to be even more complex than simply
+ defaulting to be on. It therefore was safer to start with it off,
+ and defend against the expectation that a given format would always
+ automatically work.
+
+ * given that a common use of JSON Schema is for portability across
+ languages (and therefore implementations of JSON Schema), so that
+ users be aware of this point itself regarding :validator:`format`
+ validation, and therefore remember to check any *other*
+ implementations they were using to ensure they too were explicitly
+ enabled for :validator:`format` validation.
+
+As of ``draft2019-09`` however, the opt-out by default behavior
+mentioned here is now *required* for all validators.
+
+Difficult as this may sound for new users, at this point it at least
+means they should expect the same behavior that has always been
+implemented here, across any other implementation they encounter.
+
+.. seealso::
+
+ `Draft 2019-09's release notes on format <https://json-schema.org/draft/2019-09/release-notes.html#format-vocabulary>`_
+
+ for upstream details on the behavior of format and how it has changed
+ in ``draft2019-09``
+
+ `validating formats`
+
+ for details on how to enable format validation
+
+ `FormatChecker`
+
+ the object which implements format validation
+
+
+Why doesn't my schema's default property set the default on my instance?
+------------------------------------------------------------------------
+
+The basic answer is that the specification does not require that
+:validator:`default` actually do anything.
+
+For an inkling as to *why* it doesn't actually do anything, consider
+that none of the other validators modify the instance either. More
+importantly, having :validator:`default` modify the instance can produce
+quite peculiar things. It's perfectly valid (and perhaps even useful)
+to have a default that is not valid under the schema it lives in! So an
+instance modified by the default would pass validation the first time,
+but fail the second!
+
+Still, filling in defaults is a thing that is useful. `jsonschema`
+allows you to `define your own validator classes and callables
+<creating>`, so you can easily create an `jsonschema.IValidator` that
+does do default setting. Here's some code to get you started. (In
+this code, we add the default properties to each object *before* the
+properties are validated, so the default values themselves will need to
+be valid under the schema.)
+
+ .. code-block:: python
+
+ from jsonschema import Draft7Validator, validators
+
+
+ def extend_with_default(validator_class):
+ validate_properties = validator_class.VALIDATORS["properties"]
+
+ def set_defaults(validator, properties, instance, schema):
+ for property, subschema in properties.items():
+ if "default" in subschema:
+ instance.setdefault(property, subschema["default"])
+
+ for error in validate_properties(
+ validator, properties, instance, schema,
+ ):
+ yield error
+
+ return validators.extend(
+ validator_class, {"properties" : set_defaults},
+ )
+
+
+ DefaultValidatingDraft7Validator = extend_with_default(Draft7Validator)
+
+
+ # Example usage:
+ obj = {}
+ schema = {'properties': {'foo': {'default': 'bar'}}}
+ # Note jsonschem.validate(obj, schema, cls=DefaultValidatingDraft7Validator)
+ # will not work because the metaschema contains `default` directives.
+ DefaultValidatingDraft7Validator(schema).validate(obj)
+ assert obj == {'foo': 'bar'}
+
+
+See the above-linked document for more info on how this works, but
+basically, it just extends the :validator:`properties` validator on
+a `jsonschema.Draft7Validator` to then go ahead and update all the
+defaults.
+
+.. note::
+
+ If you're interested in a more interesting solution to a larger
+ class of these types of transformations, keep an eye on `Seep
+ <https://github.com/Julian/Seep>`_, which is an experimental
+ data transformation and extraction library written on top of
+ `jsonschema`.
+
+
+.. hint::
+
+ The above code can provide default values for an entire object and
+ all of its properties, but only if your schema provides a default
+ value for the object itself, like so:
+
+ .. code-block:: python
+
+ schema = {
+ "type": "object",
+ "properties": {
+ "outer-object": {
+ "type": "object",
+ "properties" : {
+ "inner-object": {
+ "type": "string",
+ "default": "INNER-DEFAULT"
+ }
+ },
+ "default": {} # <-- MUST PROVIDE DEFAULT OBJECT
+ }
+ }
+ }
+
+ obj = {}
+ DefaultValidatingDraft7Validator(schema).validate(obj)
+ assert obj == {'outer-object': {'inner-object': 'INNER-DEFAULT'}}
+
+ ...but if you don't provide a default value for your object, then
+ it won't be instantiated at all, much less populated with default
+ properties.
+
+ .. code-block:: python
+
+ del schema["properties"]["outer-object"]["default"]
+ obj2 = {}
+ DefaultValidatingDraft7Validator(schema).validate(obj2)
+ assert obj2 == {} # whoops
+
+
+How do jsonschema version numbers work?
+---------------------------------------
+
+``jsonschema`` tries to follow the `Semantic Versioning
+<https://semver.org/>`_ specification.
+
+This means broadly that no backwards-incompatible changes should be made
+in minor releases (and certainly not in dot releases).
+
+The full picture requires defining what constitutes a
+backwards-incompatible change.
+
+The following are simple examples of things considered public API,
+and therefore should *not* be changed without bumping a major version
+number:
+
+ * module names and contents, when not marked private by Python
+ convention (a single leading underscore)
+
+ * function and object signature (parameter order and name)
+
+The following are *not* considered public API and may change without
+notice:
+
+ * the exact wording and contents of error messages; typical reasons
+ to rely on this seem to involve downstream tests in packages using
+ `jsonschema`. These use cases are encouraged to use the extensive
+ introspection provided in `jsonschema.exceptions.ValidationError`\s
+ instead to make meaningful assertions about what failed rather than
+ relying on *how* what failed is explained to a human.
+
+ * the order in which validation errors are returned or raised
+
+ * the contents of the ``jsonschema.tests`` package
+
+ * the contents of the ``jsonschema.benchmarks`` package
+
+ * the specific non-zero error codes presented by the command line
+ interface
+
+ * the exact representation of errors presented by the command line
+ interface, other than that errors represented by the plain outputter
+ will be reported one per line
+
+ * anything marked private
+
+With the exception of the last two of those, flippant changes are
+avoided, but changes can and will be made if there is improvement to be
+had. Feel free to open an issue ticket if there is a specific issue or
+question worth raising.
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..e4d7252
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,22 @@
+.. module:: jsonschema
+.. include:: ../README.rst
+
+
+Contents
+--------
+
+.. toctree::
+ :maxdepth: 2
+
+ validate
+ errors
+ references
+ creating
+ faq
+
+
+Indices and tables
+==================
+
+* `genindex`
+* `search`
diff --git a/docs/jsonschema_role.py b/docs/jsonschema_role.py
new file mode 100644
index 0000000..8a4feaa
--- /dev/null
+++ b/docs/jsonschema_role.py
@@ -0,0 +1,151 @@
+from datetime import datetime
+import errno
+import os
+import urllib.request
+
+from docutils import nodes
+from lxml import html
+import certifi
+
+import jsonschema
+
+__version__ = "1.1.0"
+VALIDATION_SPEC = "https://json-schema.org/draft-07/json-schema-validation.html"
+
+
+def setup(app):
+ """
+ Install the plugin.
+
+ Arguments:
+
+ app (sphinx.application.Sphinx):
+
+ the Sphinx application context
+ """
+
+ app.add_config_value("cache_path", "_cache", "")
+
+ try:
+ os.makedirs(app.config.cache_path)
+ except OSError as error:
+ if error.errno != errno.EEXIST:
+ raise
+
+ path = os.path.join(app.config.cache_path, "spec.html")
+ spec = fetch_or_load(path)
+ app.add_role("validator", docutils_sucks(spec))
+
+ return dict(version=__version__, parallel_read_safe=True)
+
+
+def fetch_or_load(spec_path):
+ """
+ Fetch a new specification or use the cache if it's current.
+
+ Arguments:
+
+ cache_path:
+
+ the path to a cached specification
+ """
+
+ headers = {
+ "User-Agent": "python-jsonschema v{} - documentation build v{}".format(
+ jsonschema.__version__, __version__,
+ ),
+ }
+
+ try:
+ modified = datetime.utcfromtimestamp(os.path.getmtime(spec_path))
+ date = modified.strftime("%a, %d %b %Y %I:%M:%S UTC")
+ headers["If-Modified-Since"] = date
+ except OSError as error:
+ if error.errno != errno.ENOENT:
+ raise
+
+ request = urllib.request.Request(VALIDATION_SPEC, headers=headers)
+ response = urllib.request.urlopen(request, cafile=certifi.where())
+
+ if response.code == 200:
+ with open(spec_path, "w+b") as spec:
+ spec.writelines(response)
+ spec.seek(0)
+ return html.parse(spec)
+
+ with open(spec_path) as spec:
+ return html.parse(spec)
+
+
+def docutils_sucks(spec):
+ """
+ Yeah.
+
+ It doesn't allow using a class because it does stupid stuff like try to set
+ attributes on the callable object rather than just keeping a dict.
+ """
+
+ base_url = VALIDATION_SPEC
+ ref_url = "https://json-schema.org/draft-07/json-schema-core.html#rfc.section.8.3"
+ schema_url = "https://json-schema.org/draft-07/json-schema-core.html#rfc.section.7"
+
+ def validator(name, raw_text, text, lineno, inliner):
+ """
+ Link to the JSON Schema documentation for a validator.
+
+ Arguments:
+
+ name (str):
+
+ the name of the role in the document
+
+ raw_source (str):
+
+ the raw text (role with argument)
+
+ text (str):
+
+ the argument given to the role
+
+ lineno (int):
+
+ the line number
+
+ inliner (docutils.parsers.rst.states.Inliner):
+
+ the inliner
+
+ Returns:
+
+ tuple:
+
+ a 2-tuple of nodes to insert into the document and an
+ iterable of system messages, both possibly empty
+ """
+
+ if text == "$ref":
+ return [nodes.reference(raw_text, text, refuri=ref_url)], []
+ elif text == "$schema":
+ return [nodes.reference(raw_text, text, refuri=schema_url)], []
+
+ # find the header in the validation spec containing matching text
+ header = spec.xpath("//h1[contains(text(), '{0}')]".format(text))
+
+ if len(header) == 0:
+ inliner.reporter.warning(
+ "Didn't find a target for {0}".format(text),
+ )
+ uri = base_url
+ else:
+ if len(header) > 1:
+ inliner.reporter.info(
+ "Found multiple targets for {0}".format(text),
+ )
+
+ # get the href from link in the header
+ uri = base_url + header[0].find("a").attrib["href"]
+
+ reference = nodes.reference(raw_text, text, refuri=uri)
+ return [reference], []
+
+ return validator
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 0000000..fcb914f
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,190 @@
+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=_build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
+set I18NSPHINXOPTS=%SPHINXOPTS% .
+if NOT "%PAPER%" == "" (
+ set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+ set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+ :help
+ echo.Please use `make ^<target^>` where ^<target^> 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. text to make text files
+ echo. man to make manual pages
+ echo. texinfo to make Texinfo files
+ echo. gettext to make PO message catalogs
+ echo. changes to make an overview over 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
+ goto end
+)
+
+if "%1" == "clean" (
+ for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+ del /q /s %BUILDDIR%\*
+ goto end
+)
+
+if "%1" == "html" (
+ %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+ goto end
+)
+
+if "%1" == "dirhtml" (
+ %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+ goto end
+)
+
+if "%1" == "singlehtml" (
+ %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+ goto end
+)
+
+if "%1" == "pickle" (
+ %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the pickle files.
+ goto end
+)
+
+if "%1" == "json" (
+ %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can process the JSON files.
+ goto end
+)
+
+if "%1" == "htmlhelp" (
+ %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+ goto end
+)
+
+if "%1" == "qthelp" (
+ %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+ echo.^> qcollectiongenerator %BUILDDIR%\qthelp\jsonschema.qhcp
+ echo.To view the help file:
+ echo.^> assistant -collectionFile %BUILDDIR%\qthelp\jsonschema.ghc
+ goto end
+)
+
+if "%1" == "devhelp" (
+ %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished.
+ goto end
+)
+
+if "%1" == "epub" (
+ %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The epub file is in %BUILDDIR%/epub.
+ goto end
+)
+
+if "%1" == "latex" (
+ %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+ goto end
+)
+
+if "%1" == "text" (
+ %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The text files are in %BUILDDIR%/text.
+ goto end
+)
+
+if "%1" == "man" (
+ %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The manual pages are in %BUILDDIR%/man.
+ goto end
+)
+
+if "%1" == "texinfo" (
+ %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
+ goto end
+)
+
+if "%1" == "gettext" (
+ %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
+ goto end
+)
+
+if "%1" == "changes" (
+ %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.The overview file is in %BUILDDIR%/changes.
+ goto end
+)
+
+if "%1" == "linkcheck" (
+ %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+ goto end
+)
+
+if "%1" == "doctest" (
+ %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+ if errorlevel 1 exit /b 1
+ echo.
+ echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+ goto end
+)
+
+:end
diff --git a/docs/references.rst b/docs/references.rst
new file mode 100644
index 0000000..9f24299
--- /dev/null
+++ b/docs/references.rst
@@ -0,0 +1,13 @@
+=========================
+Resolving JSON References
+=========================
+
+
+.. currentmodule:: jsonschema
+
+.. autoclass:: RefResolver
+ :members:
+
+.. autoexception:: RefResolutionError
+
+ A JSON reference failed to resolve.
diff --git a/docs/requirements.in b/docs/requirements.in
new file mode 100644
index 0000000..0c4f8bc
--- /dev/null
+++ b/docs/requirements.in
@@ -0,0 +1,3 @@
+lxml
+sphinx
+sphinxcontrib-spelling
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 0000000..2b75db0
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1,36 @@
+#
+# This file is autogenerated by pip-compile
+# To update, run:
+#
+# pip-compile
+#
+alabaster==0.7.12 # via sphinx
+babel==2.8.0 # via sphinx
+certifi==2020.6.20 # via requests
+chardet==3.0.4 # via requests
+docutils==0.16 # via sphinx
+idna==2.10 # via requests
+imagesize==1.2.0 # via sphinx
+jinja2==2.11.2 # via sphinx
+lxml==4.5.2 # via -r requirements.in
+markupsafe==1.1.1 # via jinja2
+packaging==20.4 # via sphinx
+pyenchant==3.1.1 # via sphinxcontrib-spelling
+pygments==2.6.1 # via sphinx
+pyparsing==2.4.7 # via packaging
+pytz==2020.1 # via babel
+requests==2.24.0 # via sphinx
+six==1.15.0 # via packaging
+snowballstemmer==2.0.0 # via sphinx
+sphinx==3.2.1 # via -r requirements.in, sphinxcontrib-spelling
+sphinxcontrib-applehelp==1.0.2 # via sphinx
+sphinxcontrib-devhelp==1.0.2 # via sphinx
+sphinxcontrib-htmlhelp==1.0.3 # via sphinx
+sphinxcontrib-jsmath==1.0.1 # via sphinx
+sphinxcontrib-qthelp==1.0.3 # via sphinx
+sphinxcontrib-serializinghtml==1.1.4 # via sphinx
+sphinxcontrib-spelling==5.3.0 # via -r requirements.in
+urllib3==1.25.10 # via requests
+
+# The following packages are considered to be unsafe in a requirements file:
+# setuptools
diff --git a/docs/spelling-wordlist.txt b/docs/spelling-wordlist.txt
new file mode 100644
index 0000000..e34c39f
--- /dev/null
+++ b/docs/spelling-wordlist.txt
@@ -0,0 +1,43 @@
+# this appears to be misinterpreting Napoleon types as prose, sigh...
+IValidator
+TypeChecker
+UnknownType
+ValidationError
+
+# 0th, sigh...
+th
+callables
+deque
+dereferences
+hostname
+implementers
+indices
+# ipv4/6, sigh...
+ipv
+iterable
+iteratively
+jsonschema
+majorly
+metaschema
+online
+outputter
+pre
+programmatically
+recurses
+regex
+sensical
+subschema
+subschemas
+subscopes
+uri
+validator
+validators
+versioned
+schemas
+
+Zac
+HD
+
+Berman
+Freenode
+GPL
diff --git a/docs/validate.rst b/docs/validate.rst
new file mode 100644
index 0000000..dc46017
--- /dev/null
+++ b/docs/validate.rst
@@ -0,0 +1,395 @@
+=================
+Schema Validation
+=================
+
+
+.. currentmodule:: jsonschema
+
+
+The Basics
+----------
+
+The simplest way to validate an instance under a given schema is to use the
+:func:`validate` function.
+
+.. autofunction:: validate
+
+.. [#] For information on creating JSON schemas to validate
+ your data, there is a good introduction to JSON Schema
+ fundamentals underway at `Understanding JSON Schema
+ <https://json-schema.org/understanding-json-schema/>`_
+
+
+The Validator Interface
+-----------------------
+
+`jsonschema` defines an (informal) interface that all validator
+classes should adhere to.
+
+.. class:: IValidator(schema, types=(), resolver=None, format_checker=None)
+
+ :argument dict schema: the schema that the validator object
+ will validate with. It is assumed to be valid, and providing
+ an invalid schema can lead to undefined behavior. See
+ `IValidator.check_schema` to validate a schema first.
+ :argument resolver: an instance of `RefResolver` that will be
+ used to resolve :validator:`$ref` properties (JSON references). If
+ unprovided, one will be created.
+ :argument format_checker: an instance of `FormatChecker`
+ whose `FormatChecker.conforms` method will be called to
+ check and see if instances conform to each :validator:`format`
+ property present in the schema. If unprovided, no validation
+ will be done for :validator:`format`. Certain formats require
+ additional packages to be installed (ipv5, uri, color, date-time).
+ The required packages can be found at the bottom of this page.
+ :argument types:
+ .. deprecated:: 3.0.0
+
+ Use `TypeChecker.redefine` and
+ `jsonschema.validators.extend` instead of this argument.
+
+ See `validating-types` for details.
+
+ If used, this overrides or extends the list of known types when
+ validating the :validator:`type` property.
+
+ What is provided should map strings (type names) to class objects
+ that will be checked via `isinstance`.
+
+
+ .. attribute:: META_SCHEMA
+
+ An object representing the validator's meta schema (the schema that
+ describes valid schemas in the given version).
+
+ .. attribute:: VALIDATORS
+
+ A mapping of validator names (`str`\s) to functions
+ that validate the validator property with that name. For more
+ information see `creating-validators`.
+
+ .. attribute:: TYPE_CHECKER
+
+ A `TypeChecker` that will be used when validating :validator:`type`
+ properties in JSON schemas.
+
+ .. attribute:: schema
+
+ The schema that was passed in when initializing the object.
+
+ .. attribute:: DEFAULT_TYPES
+
+ .. deprecated:: 3.0.0
+
+ Use of this attribute is deprecated in favor of the new `type
+ checkers <TypeChecker>`.
+
+ See `validating-types` for details.
+
+ For backwards compatibility on existing validator classes, a mapping of
+ JSON types to Python class objects which define the Python types for
+ each JSON type.
+
+ Any existing code using this attribute should likely transition to
+ using `TypeChecker.is_type`.
+
+
+ .. classmethod:: check_schema(schema)
+
+ Validate the given schema against the validator's `META_SCHEMA`.
+
+ :raises: `jsonschema.exceptions.SchemaError` if the schema
+ is invalid
+
+ .. method:: is_type(instance, type)
+
+ Check if the instance is of the given (JSON Schema) type.
+
+ :type type: str
+ :rtype: bool
+ :raises: `jsonschema.exceptions.UnknownType` if ``type``
+ is not a known type.
+
+ .. method:: is_valid(instance)
+
+ Check if the instance is valid under the current `schema`.
+
+ :rtype: bool
+
+ >>> schema = {"maxItems" : 2}
+ >>> Draft3Validator(schema).is_valid([2, 3, 4])
+ False
+
+ .. method:: iter_errors(instance)
+
+ Lazily yield each of the validation errors in the given instance.
+
+ :rtype: an `collections.abc.Iterable` of
+ `jsonschema.exceptions.ValidationError`\s
+
+ >>> schema = {
+ ... "type" : "array",
+ ... "items" : {"enum" : [1, 2, 3]},
+ ... "maxItems" : 2,
+ ... }
+ >>> v = Draft3Validator(schema)
+ >>> for error in sorted(v.iter_errors([2, 3, 4]), key=str):
+ ... print(error.message)
+ 4 is not one of [1, 2, 3]
+ [2, 3, 4] is too long
+
+ .. method:: validate(instance)
+
+ Check if the instance is valid under the current `schema`.
+
+ :raises: `jsonschema.exceptions.ValidationError` if the
+ instance is invalid
+
+ >>> schema = {"maxItems" : 2}
+ >>> Draft3Validator(schema).validate([2, 3, 4])
+ Traceback (most recent call last):
+ ...
+ ValidationError: [2, 3, 4] is too long
+
+
+All of the `versioned validators <versioned-validators>` that are included with
+`jsonschema` adhere to the interface, and implementers of validator classes
+that extend or complement the ones included should adhere to it as well. For
+more information see `creating-validators`.
+
+Type Checking
+-------------
+
+To handle JSON Schema's :validator:`type` property, a `IValidator` uses
+an associated `TypeChecker`. The type checker provides an immutable
+mapping between names of types and functions that can test if an instance is
+of that type. The defaults are suitable for most users - each of the
+`versioned validators <versioned-validators>` that are included with
+`jsonschema` have a `TypeChecker` that can correctly handle their respective
+versions.
+
+.. seealso:: `validating-types`
+
+ For an example of providing a custom type check.
+
+.. autoclass:: TypeChecker
+ :members:
+
+.. autoexception:: jsonschema.exceptions.UndefinedTypeCheck
+
+ Raised when trying to remove a type check that is not known to this
+ TypeChecker, or when calling `jsonschema.TypeChecker.is_type`
+ directly.
+
+.. _validating-types:
+
+Validating With Additional Types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Occasionally it can be useful to provide additional or alternate types when
+validating the JSON Schema's :validator:`type` property.
+
+`jsonschema` tries to strike a balance between performance in the common
+case and generality. For instance, JSON Schema defines a ``number`` type, which
+can be validated with a schema such as ``{"type" : "number"}``. By default,
+this will accept instances of Python `numbers.Number`. This includes in
+particular `int`\s and `float`\s, along with
+`decimal.Decimal` objects, `complex` numbers etc. For
+``integer`` and ``object``, however, rather than checking for
+`numbers.Integral` and `collections.abc.Mapping`,
+`jsonschema` simply checks for `int` and `dict`, since the
+more general instance checks can introduce significant slowdown, especially
+given how common validating these types are.
+
+If you *do* want the generality, or just want to add a few specific additional
+types as being acceptable for a validator object, then you should update an
+existing `TypeChecker` or create a new one. You may then create a new
+`IValidator` via `jsonschema.validators.extend`.
+
+.. code-block:: python
+
+ class MyInteger(object):
+ pass
+
+ def is_my_int(checker, instance):
+ return (
+ Draft3Validator.TYPE_CHECKER.is_type(instance, "number") or
+ isinstance(instance, MyInteger)
+ )
+
+ type_checker = Draft3Validator.TYPE_CHECKER.redefine("number", is_my_int)
+
+ CustomValidator = extend(Draft3Validator, type_checker=type_checker)
+ validator = CustomValidator(schema={"type" : "number"})
+
+
+.. autoexception:: jsonschema.exceptions.UnknownType
+
+.. _versioned-validators:
+
+Versioned Validators
+--------------------
+
+`jsonschema` ships with validator classes for various versions of
+the JSON Schema specification. For details on the methods and attributes
+that each validator class provides see the `IValidator` interface,
+which each included validator class implements.
+
+.. autoclass:: Draft7Validator
+
+.. autoclass:: Draft6Validator
+
+.. autoclass:: Draft4Validator
+
+.. autoclass:: Draft3Validator
+
+
+For example, if you wanted to validate a schema you created against the
+Draft 7 meta-schema, you could use:
+
+.. code-block:: python
+
+ from jsonschema import Draft7Validator
+
+ schema = {
+ "$schema": "http://json-schema.org/draft-07/schema#",
+
+ "type": "object",
+ "properties": {
+ "name": {"type": "string"},
+ "email": {"type": "string"},
+ },
+ "required": ["email"]
+ }
+ Draft7Validator.check_schema(schema)
+
+
+.. _validating formats:
+
+Validating Formats
+------------------
+
+JSON Schema defines the :validator:`format` property which can be used to check
+if primitive types (``string``\s, ``number``\s, ``boolean``\s) conform to
+well-defined formats. By default, no validation is enforced, but optionally,
+validation can be enabled by hooking in a format-checking object into an
+`IValidator`.
+
+.. doctest::
+
+ >>> validate("127.0.0.1", {"format" : "ipv4"})
+ >>> validate(
+ ... instance="-12",
+ ... schema={"format" : "ipv4"},
+ ... format_checker=draft7_format_checker,
+ ... )
+ Traceback (most recent call last):
+ ...
+ ValidationError: "-12" is not a "ipv4"
+
+.. autoclass:: FormatChecker
+ :members:
+ :exclude-members: cls_checks
+
+ .. attribute:: checkers
+
+ A mapping of currently known formats to tuple of functions that
+ validate them and errors that should be caught. New checkers can be
+ added and removed either per-instance or globally for all checkers
+ using the `FormatChecker.checks` or `FormatChecker.cls_checks`
+ decorators respectively.
+
+ .. classmethod:: cls_checks(format, raises=())
+
+ Register a decorated function as *globally* validating a new format.
+
+ Any instance created after this function is called will pick up the
+ supplied checker.
+
+ :argument str format: the format that the decorated function will check
+ :argument Exception raises: the exception(s) raised
+ by the decorated function when an invalid instance is
+ found. The exception object will be accessible as the
+ `jsonschema.exceptions.ValidationError.cause` attribute
+ of the resulting validation error.
+
+
+.. autoexception:: FormatError
+ :members:
+
+
+There are a number of default checkers that `FormatChecker`\s know how
+to validate. Their names can be viewed by inspecting the
+`FormatChecker.checkers` attribute. Certain checkers will only be
+available if an appropriate package is available for use. The easiest way to
+ensure you have what is needed is to install ``jsonschema`` using the
+``format`` or ``format_nongpl`` setuptools extra -- i.e.
+
+.. code-block:: sh
+
+ $ pip install jsonschema[format]
+
+which will install all of the below dependencies for all formats.
+
+Or if you want to install MIT-license compatible dependencies only:
+
+.. code-block:: sh
+
+ $ pip install jsonschema[format_nongpl]
+
+The non-GPL extra is intended to not install any direct dependencies
+that are GPL (but that of course end-users should do their own verification).
+At the moment, it supports all the available checkers except for ``iri`` and
+``iri-reference``.
+
+The more specific list of available checkers, along with their requirement
+(if any,) are listed below.
+
+.. note::
+
+ If the following packages are not installed when using a checker
+ that requires it, validation will succeed without throwing an error,
+ as specified by the JSON Schema specification.
+
+========================= ====================
+Checker Notes
+========================= ====================
+``color`` requires webcolors_
+``date``
+``date-time`` requires strict-rfc3339_ or rfc3339-validator_
+``email``
+``hostname``
+``idn-hostname`` requires idna_
+``ipv4``
+``ipv6`` OS must have `socket.inet_pton` function
+``iri`` requires rfc3987_
+``iri-reference`` requires rfc3987_
+``json-pointer`` requires jsonpointer_
+``regex``
+``relative-json-pointer`` requires jsonpointer_
+``time`` requires strict-rfc3339_ or rfc3339-validator_
+``uri`` requires rfc3987_ or rfc3986-validator_
+``uri-reference`` requires rfc3987_ or rfc3986-validator_
+========================= ====================
+
+
+.. _idna: https://pypi.org/pypi/idna/
+.. _jsonpointer: https://pypi.org/pypi/jsonpointer/
+.. _rfc3987: https://pypi.org/pypi/rfc3987/
+.. _rfc5322: https://tools.ietf.org/html/rfc5322#section-3.4.1
+.. _strict-rfc3339: https://pypi.org/pypi/strict-rfc3339/
+.. _webcolors: https://pypi.org/pypi/webcolors/
+.. _rfc3339-validator: https://pypi.org/project/rfc3339-validator/
+.. _rfc3986-validator: https://pypi.org/project/rfc3986-validator/
+
+.. note::
+
+ Since in most cases "validating" an email address is an attempt
+ instead to confirm that mail sent to it will deliver to a recipient,
+ and that that recipient is the correct one the email is intended
+ for, and since many valid email addresses are in many places
+ incorrectly rejected, and many invalid email addresses are in many
+ places incorrectly accepted, the ``email`` format validator only
+ provides a sanity check, not full rfc5322_ validation.
+
+ The same applies to the ``idn-email`` format.
diff --git a/json/.github/workflows/ci.yml b/json/.github/workflows/ci.yml
new file mode 100644
index 0000000..9fdb508
--- /dev/null
+++ b/json/.github/workflows/ci.yml
@@ -0,0 +1,25 @@
+name: Test Suite Sanity Checking
+
+on:
+ push:
+ pull_request:
+ release:
+ types: [published]
+ schedule:
+ # Daily at 6:42
+ - cron: '42 6 * * *'
+
+jobs:
+ ci:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python
+ uses: actions/setup-python@v2
+ with:
+ python-version: 3.7
+ - name: Install tox
+ run: python -m pip install tox
+ - name: Run the sanity checks
+ run: python -m tox
diff --git a/json/.gitignore b/json/.gitignore
new file mode 100644
index 0000000..1333ed7
--- /dev/null
+++ b/json/.gitignore
@@ -0,0 +1 @@
+TODO
diff --git a/LICENSE b/json/LICENSE
index c28adba..c28adba 100644
--- a/LICENSE
+++ b/json/LICENSE
diff --git a/README.md b/json/README.md
index 2b541ef..2b541ef 100644
--- a/README.md
+++ b/json/README.md
diff --git a/bin/jsonschema_suite b/json/bin/jsonschema_suite
index 5f5d133..628bc5d 100755
--- a/bin/jsonschema_suite
+++ b/json/bin/jsonschema_suite
@@ -1,5 +1,4 @@
#! /usr/bin/env python3
-from __future__ import print_function
from pprint import pformat
import argparse
import errno
diff --git a/index.js b/json/index.js
index 16dc3a5..16dc3a5 100644
--- a/index.js
+++ b/json/index.js
diff --git a/package.json b/json/package.json
index 3980136..3980136 100644
--- a/package.json
+++ b/json/package.json
diff --git a/remotes/baseUriChange/folderInteger.json b/json/remotes/baseUriChange/folderInteger.json
index 8b50ea3..8b50ea3 100644
--- a/remotes/baseUriChange/folderInteger.json
+++ b/json/remotes/baseUriChange/folderInteger.json
diff --git a/remotes/baseUriChangeFolder/folderInteger.json b/json/remotes/baseUriChangeFolder/folderInteger.json
index 8b50ea3..8b50ea3 100644
--- a/remotes/baseUriChangeFolder/folderInteger.json
+++ b/json/remotes/baseUriChangeFolder/folderInteger.json
diff --git a/remotes/baseUriChangeFolderInSubschema/folderInteger.json b/json/remotes/baseUriChangeFolderInSubschema/folderInteger.json
index 8b50ea3..8b50ea3 100644
--- a/remotes/baseUriChangeFolderInSubschema/folderInteger.json
+++ b/json/remotes/baseUriChangeFolderInSubschema/folderInteger.json
diff --git a/remotes/integer.json b/json/remotes/integer.json
index 8b50ea3..8b50ea3 100644
--- a/remotes/integer.json
+++ b/json/remotes/integer.json
diff --git a/remotes/name-defs.json b/json/remotes/name-defs.json
index 1dab4a4..1dab4a4 100644
--- a/remotes/name-defs.json
+++ b/json/remotes/name-defs.json
diff --git a/remotes/name.json b/json/remotes/name.json
index fceacb8..fceacb8 100644
--- a/remotes/name.json
+++ b/json/remotes/name.json
diff --git a/remotes/subSchemas-defs.json b/json/remotes/subSchemas-defs.json
index 50b7b6d..50b7b6d 100644
--- a/remotes/subSchemas-defs.json
+++ b/json/remotes/subSchemas-defs.json
diff --git a/remotes/subSchemas.json b/json/remotes/subSchemas.json
index 9f8030b..9f8030b 100644
--- a/remotes/subSchemas.json
+++ b/json/remotes/subSchemas.json
diff --git a/test-schema.json b/json/test-schema.json
index 11b1e8d..11b1e8d 100644
--- a/test-schema.json
+++ b/json/test-schema.json
diff --git a/tests/draft2019-09/additionalItems.json b/json/tests/draft2019-09/additionalItems.json
index ee46b61..ee46b61 100644
--- a/tests/draft2019-09/additionalItems.json
+++ b/json/tests/draft2019-09/additionalItems.json
diff --git a/tests/draft2019-09/additionalProperties.json b/json/tests/draft2019-09/additionalProperties.json
index 381275a..381275a 100644
--- a/tests/draft2019-09/additionalProperties.json
+++ b/json/tests/draft2019-09/additionalProperties.json
diff --git a/tests/draft2019-09/allOf.json b/json/tests/draft2019-09/allOf.json
index ec9319e..ec9319e 100644
--- a/tests/draft2019-09/allOf.json
+++ b/json/tests/draft2019-09/allOf.json
diff --git a/tests/draft2019-09/anchor.json b/json/tests/draft2019-09/anchor.json
index 42dde7e..42dde7e 100644
--- a/tests/draft2019-09/anchor.json
+++ b/json/tests/draft2019-09/anchor.json
diff --git a/tests/draft2019-09/anyOf.json b/json/tests/draft2019-09/anyOf.json
index ab5eb38..ab5eb38 100644
--- a/tests/draft2019-09/anyOf.json
+++ b/json/tests/draft2019-09/anyOf.json
diff --git a/tests/draft2019-09/boolean_schema.json b/json/tests/draft2019-09/boolean_schema.json
index 6d40f23..6d40f23 100644
--- a/tests/draft2019-09/boolean_schema.json
+++ b/json/tests/draft2019-09/boolean_schema.json
diff --git a/tests/draft2019-09/const.json b/json/tests/draft2019-09/const.json
index 1c2cafc..1c2cafc 100644
--- a/tests/draft2019-09/const.json
+++ b/json/tests/draft2019-09/const.json
diff --git a/tests/draft2019-09/contains.json b/json/tests/draft2019-09/contains.json
index c5471cc..c5471cc 100644
--- a/tests/draft2019-09/contains.json
+++ b/json/tests/draft2019-09/contains.json
diff --git a/tests/draft2019-09/default.json b/json/tests/draft2019-09/default.json
index 1762977..1762977 100644
--- a/tests/draft2019-09/default.json
+++ b/json/tests/draft2019-09/default.json
diff --git a/tests/draft2019-09/defs.json b/json/tests/draft2019-09/defs.json
index f2fbec4..f2fbec4 100644
--- a/tests/draft2019-09/defs.json
+++ b/json/tests/draft2019-09/defs.json
diff --git a/tests/draft2019-09/dependentRequired.json b/json/tests/draft2019-09/dependentRequired.json
index c817120..c817120 100644
--- a/tests/draft2019-09/dependentRequired.json
+++ b/json/tests/draft2019-09/dependentRequired.json
diff --git a/tests/draft2019-09/dependentSchemas.json b/json/tests/draft2019-09/dependentSchemas.json
index e7921d1..e7921d1 100644
--- a/tests/draft2019-09/dependentSchemas.json
+++ b/json/tests/draft2019-09/dependentSchemas.json
diff --git a/tests/draft2019-09/enum.json b/json/tests/draft2019-09/enum.json
index f085097..f085097 100644
--- a/tests/draft2019-09/enum.json
+++ b/json/tests/draft2019-09/enum.json
diff --git a/tests/draft2019-09/exclusiveMaximum.json b/json/tests/draft2019-09/exclusiveMaximum.json
index dc3cd70..dc3cd70 100644
--- a/tests/draft2019-09/exclusiveMaximum.json
+++ b/json/tests/draft2019-09/exclusiveMaximum.json
diff --git a/tests/draft2019-09/exclusiveMinimum.json b/json/tests/draft2019-09/exclusiveMinimum.json
index b38d7ec..b38d7ec 100644
--- a/tests/draft2019-09/exclusiveMinimum.json
+++ b/json/tests/draft2019-09/exclusiveMinimum.json
diff --git a/tests/draft2019-09/format.json b/json/tests/draft2019-09/format.json
index dddea86..dddea86 100644
--- a/tests/draft2019-09/format.json
+++ b/json/tests/draft2019-09/format.json
diff --git a/tests/draft2019-09/id.json b/json/tests/draft2019-09/id.json
index cd97d59..cd97d59 100644
--- a/tests/draft2019-09/id.json
+++ b/json/tests/draft2019-09/id.json
diff --git a/tests/draft2019-09/if-then-else.json b/json/tests/draft2019-09/if-then-else.json
index e0b873e..e0b873e 100644
--- a/tests/draft2019-09/if-then-else.json
+++ b/json/tests/draft2019-09/if-then-else.json
diff --git a/tests/draft2019-09/items.json b/json/tests/draft2019-09/items.json
index 6e98ee8..6e98ee8 100644
--- a/tests/draft2019-09/items.json
+++ b/json/tests/draft2019-09/items.json
diff --git a/tests/draft2019-09/maxContains.json b/json/tests/draft2019-09/maxContains.json
index 3c42fb3..3c42fb3 100644
--- a/tests/draft2019-09/maxContains.json
+++ b/json/tests/draft2019-09/maxContains.json
diff --git a/tests/draft2019-09/maxItems.json b/json/tests/draft2019-09/maxItems.json
index 3b53a6b..3b53a6b 100644
--- a/tests/draft2019-09/maxItems.json
+++ b/json/tests/draft2019-09/maxItems.json
diff --git a/tests/draft2019-09/maxLength.json b/json/tests/draft2019-09/maxLength.json
index 811d35b..811d35b 100644
--- a/tests/draft2019-09/maxLength.json
+++ b/json/tests/draft2019-09/maxLength.json
diff --git a/tests/draft2019-09/maxProperties.json b/json/tests/draft2019-09/maxProperties.json
index aa7209f..aa7209f 100644
--- a/tests/draft2019-09/maxProperties.json
+++ b/json/tests/draft2019-09/maxProperties.json
diff --git a/tests/draft2019-09/maximum.json b/json/tests/draft2019-09/maximum.json
index 6844a39..6844a39 100644
--- a/tests/draft2019-09/maximum.json
+++ b/json/tests/draft2019-09/maximum.json
diff --git a/tests/draft2019-09/minContains.json b/json/tests/draft2019-09/minContains.json
index f359de0..f359de0 100644
--- a/tests/draft2019-09/minContains.json
+++ b/json/tests/draft2019-09/minContains.json
diff --git a/tests/draft2019-09/minItems.json b/json/tests/draft2019-09/minItems.json
index ed51188..ed51188 100644
--- a/tests/draft2019-09/minItems.json
+++ b/json/tests/draft2019-09/minItems.json
diff --git a/tests/draft2019-09/minLength.json b/json/tests/draft2019-09/minLength.json
index 3f09158..3f09158 100644
--- a/tests/draft2019-09/minLength.json
+++ b/json/tests/draft2019-09/minLength.json
diff --git a/tests/draft2019-09/minProperties.json b/json/tests/draft2019-09/minProperties.json
index 49a0726..49a0726 100644
--- a/tests/draft2019-09/minProperties.json
+++ b/json/tests/draft2019-09/minProperties.json
diff --git a/tests/draft2019-09/minimum.json b/json/tests/draft2019-09/minimum.json
index 21ae50e..21ae50e 100644
--- a/tests/draft2019-09/minimum.json
+++ b/json/tests/draft2019-09/minimum.json
diff --git a/tests/draft2019-09/multipleOf.json b/json/tests/draft2019-09/multipleOf.json
index ca3b761..ca3b761 100644
--- a/tests/draft2019-09/multipleOf.json
+++ b/json/tests/draft2019-09/multipleOf.json
diff --git a/tests/draft2019-09/not.json b/json/tests/draft2019-09/not.json
index 98de0ed..98de0ed 100644
--- a/tests/draft2019-09/not.json
+++ b/json/tests/draft2019-09/not.json
diff --git a/tests/draft2019-09/oneOf.json b/json/tests/draft2019-09/oneOf.json
index eeb7ae8..eeb7ae8 100644
--- a/tests/draft2019-09/oneOf.json
+++ b/json/tests/draft2019-09/oneOf.json
diff --git a/tests/draft2019-09/optional/bignum.json b/json/tests/draft2019-09/optional/bignum.json
index fac275e..fac275e 100644
--- a/tests/draft2019-09/optional/bignum.json
+++ b/json/tests/draft2019-09/optional/bignum.json
diff --git a/tests/draft2019-09/optional/content.json b/json/tests/draft2019-09/optional/content.json
index 3f5a743..3f5a743 100644
--- a/tests/draft2019-09/optional/content.json
+++ b/json/tests/draft2019-09/optional/content.json
diff --git a/tests/draft2019-09/optional/ecmascript-regex.json b/json/tests/draft2019-09/optional/ecmascript-regex.json
index 6ed6cbe..6ed6cbe 100644
--- a/tests/draft2019-09/optional/ecmascript-regex.json
+++ b/json/tests/draft2019-09/optional/ecmascript-regex.json
diff --git a/tests/draft2019-09/optional/format/date-time.json b/json/tests/draft2019-09/optional/format/date-time.json
index 900fcb7..900fcb7 100644
--- a/tests/draft2019-09/optional/format/date-time.json
+++ b/json/tests/draft2019-09/optional/format/date-time.json
diff --git a/tests/draft2019-09/optional/format/date.json b/json/tests/draft2019-09/optional/format/date.json
index 453b51d..453b51d 100644
--- a/tests/draft2019-09/optional/format/date.json
+++ b/json/tests/draft2019-09/optional/format/date.json
diff --git a/tests/draft2019-09/optional/format/duration.json b/json/tests/draft2019-09/optional/format/duration.json
index 4514738..4514738 100644
--- a/tests/draft2019-09/optional/format/duration.json
+++ b/json/tests/draft2019-09/optional/format/duration.json
diff --git a/tests/draft2019-09/optional/format/email.json b/json/tests/draft2019-09/optional/format/email.json
index 02396d2..02396d2 100644
--- a/tests/draft2019-09/optional/format/email.json
+++ b/json/tests/draft2019-09/optional/format/email.json
diff --git a/tests/draft2019-09/optional/format/hostname.json b/json/tests/draft2019-09/optional/format/hostname.json
index 476541a..476541a 100644
--- a/tests/draft2019-09/optional/format/hostname.json
+++ b/json/tests/draft2019-09/optional/format/hostname.json
diff --git a/tests/draft2019-09/optional/format/idn-email.json b/json/tests/draft2019-09/optional/format/idn-email.json
index 552d106..552d106 100644
--- a/tests/draft2019-09/optional/format/idn-email.json
+++ b/json/tests/draft2019-09/optional/format/idn-email.json
diff --git a/tests/draft2019-09/optional/format/idn-hostname.json b/json/tests/draft2019-09/optional/format/idn-hostname.json
index bbb257b..bbb257b 100644
--- a/tests/draft2019-09/optional/format/idn-hostname.json
+++ b/json/tests/draft2019-09/optional/format/idn-hostname.json
diff --git a/tests/draft2019-09/optional/format/ipv4.json b/json/tests/draft2019-09/optional/format/ipv4.json
index 8b99b9f..8b99b9f 100644
--- a/tests/draft2019-09/optional/format/ipv4.json
+++ b/json/tests/draft2019-09/optional/format/ipv4.json
diff --git a/tests/draft2019-09/optional/format/ipv6.json b/json/tests/draft2019-09/optional/format/ipv6.json
index 2a08cb4..2a08cb4 100644
--- a/tests/draft2019-09/optional/format/ipv6.json
+++ b/json/tests/draft2019-09/optional/format/ipv6.json
diff --git a/tests/draft2019-09/optional/format/iri-reference.json b/json/tests/draft2019-09/optional/format/iri-reference.json
index 1fd779c..1fd779c 100644
--- a/tests/draft2019-09/optional/format/iri-reference.json
+++ b/json/tests/draft2019-09/optional/format/iri-reference.json
diff --git a/tests/draft2019-09/optional/format/iri.json b/json/tests/draft2019-09/optional/format/iri.json
index ed54094..ed54094 100644
--- a/tests/draft2019-09/optional/format/iri.json
+++ b/json/tests/draft2019-09/optional/format/iri.json
diff --git a/tests/draft2019-09/optional/format/json-pointer.json b/json/tests/draft2019-09/optional/format/json-pointer.json
index 65c2f06..65c2f06 100644
--- a/tests/draft2019-09/optional/format/json-pointer.json
+++ b/json/tests/draft2019-09/optional/format/json-pointer.json
diff --git a/tests/draft2019-09/optional/format/regex.json b/json/tests/draft2019-09/optional/format/regex.json
index d99d021..d99d021 100644
--- a/tests/draft2019-09/optional/format/regex.json
+++ b/json/tests/draft2019-09/optional/format/regex.json
diff --git a/tests/draft2019-09/optional/format/relative-json-pointer.json b/json/tests/draft2019-09/optional/format/relative-json-pointer.json
index 17816c9..17816c9 100644
--- a/tests/draft2019-09/optional/format/relative-json-pointer.json
+++ b/json/tests/draft2019-09/optional/format/relative-json-pointer.json
diff --git a/tests/draft2019-09/optional/format/time.json b/json/tests/draft2019-09/optional/format/time.json
index 4ec8a01..4ec8a01 100644
--- a/tests/draft2019-09/optional/format/time.json
+++ b/json/tests/draft2019-09/optional/format/time.json
diff --git a/tests/draft2019-09/optional/format/uri-reference.json b/json/tests/draft2019-09/optional/format/uri-reference.json
index e4c9eef..e4c9eef 100644
--- a/tests/draft2019-09/optional/format/uri-reference.json
+++ b/json/tests/draft2019-09/optional/format/uri-reference.json
diff --git a/tests/draft2019-09/optional/format/uri-template.json b/json/tests/draft2019-09/optional/format/uri-template.json
index 33ab76e..33ab76e 100644
--- a/tests/draft2019-09/optional/format/uri-template.json
+++ b/json/tests/draft2019-09/optional/format/uri-template.json
diff --git a/tests/draft2019-09/optional/format/uri.json b/json/tests/draft2019-09/optional/format/uri.json
index 4306a68..4306a68 100644
--- a/tests/draft2019-09/optional/format/uri.json
+++ b/json/tests/draft2019-09/optional/format/uri.json
diff --git a/tests/draft2019-09/optional/format/uuid.json b/json/tests/draft2019-09/optional/format/uuid.json
index 45bf349..45bf349 100644
--- a/tests/draft2019-09/optional/format/uuid.json
+++ b/json/tests/draft2019-09/optional/format/uuid.json
diff --git a/tests/draft2019-09/optional/non-bmp-regex.json b/json/tests/draft2019-09/optional/non-bmp-regex.json
index dd67af2..dd67af2 100644
--- a/tests/draft2019-09/optional/non-bmp-regex.json
+++ b/json/tests/draft2019-09/optional/non-bmp-regex.json
diff --git a/tests/draft2019-09/optional/refOfUnknownKeyword.json b/json/tests/draft2019-09/optional/refOfUnknownKeyword.json
index 5b150df..5b150df 100644
--- a/tests/draft2019-09/optional/refOfUnknownKeyword.json
+++ b/json/tests/draft2019-09/optional/refOfUnknownKeyword.json
diff --git a/tests/draft2019-09/pattern.json b/json/tests/draft2019-09/pattern.json
index 92db0f9..92db0f9 100644
--- a/tests/draft2019-09/pattern.json
+++ b/json/tests/draft2019-09/pattern.json
diff --git a/tests/draft2019-09/patternProperties.json b/json/tests/draft2019-09/patternProperties.json
index c10ffcc..c10ffcc 100644
--- a/tests/draft2019-09/patternProperties.json
+++ b/json/tests/draft2019-09/patternProperties.json
diff --git a/tests/draft2019-09/properties.json b/json/tests/draft2019-09/properties.json
index b86c181..b86c181 100644
--- a/tests/draft2019-09/properties.json
+++ b/json/tests/draft2019-09/properties.json
diff --git a/tests/draft2019-09/propertyNames.json b/json/tests/draft2019-09/propertyNames.json
index 8423690..8423690 100644
--- a/tests/draft2019-09/propertyNames.json
+++ b/json/tests/draft2019-09/propertyNames.json
diff --git a/tests/draft2019-09/ref.json b/json/tests/draft2019-09/ref.json
index 97f7054..97f7054 100644
--- a/tests/draft2019-09/ref.json
+++ b/json/tests/draft2019-09/ref.json
diff --git a/tests/draft2019-09/refRemote.json b/json/tests/draft2019-09/refRemote.json
index b9c6a28..b9c6a28 100644
--- a/tests/draft2019-09/refRemote.json
+++ b/json/tests/draft2019-09/refRemote.json
diff --git a/tests/draft2019-09/required.json b/json/tests/draft2019-09/required.json
index abf18f3..abf18f3 100644
--- a/tests/draft2019-09/required.json
+++ b/json/tests/draft2019-09/required.json
diff --git a/tests/draft2019-09/type.json b/json/tests/draft2019-09/type.json
index 8304647..8304647 100644
--- a/tests/draft2019-09/type.json
+++ b/json/tests/draft2019-09/type.json
diff --git a/tests/draft2019-09/unevaluatedItems.json b/json/tests/draft2019-09/unevaluatedItems.json
index 01c07be..01c07be 100644
--- a/tests/draft2019-09/unevaluatedItems.json
+++ b/json/tests/draft2019-09/unevaluatedItems.json
diff --git a/tests/draft2019-09/unevaluatedProperties.json b/json/tests/draft2019-09/unevaluatedProperties.json
index b634be5..b634be5 100644
--- a/tests/draft2019-09/unevaluatedProperties.json
+++ b/json/tests/draft2019-09/unevaluatedProperties.json
diff --git a/tests/draft2019-09/uniqueItems.json b/json/tests/draft2019-09/uniqueItems.json
index 4846c77..4846c77 100644
--- a/tests/draft2019-09/uniqueItems.json
+++ b/json/tests/draft2019-09/uniqueItems.json
diff --git a/tests/draft3/additionalItems.json b/json/tests/draft3/additionalItems.json
index 1e8ebdc..1e8ebdc 100644
--- a/tests/draft3/additionalItems.json
+++ b/json/tests/draft3/additionalItems.json
diff --git a/tests/draft3/additionalProperties.json b/json/tests/draft3/additionalProperties.json
index 4620618..4620618 100644
--- a/tests/draft3/additionalProperties.json
+++ b/json/tests/draft3/additionalProperties.json
diff --git a/tests/draft3/default.json b/json/tests/draft3/default.json
index 1762977..1762977 100644
--- a/tests/draft3/default.json
+++ b/json/tests/draft3/default.json
diff --git a/tests/draft3/dependencies.json b/json/tests/draft3/dependencies.json
index 0ffa6bf..0ffa6bf 100644
--- a/tests/draft3/dependencies.json
+++ b/json/tests/draft3/dependencies.json
diff --git a/tests/draft3/disallow.json b/json/tests/draft3/disallow.json
index a5c9d90..a5c9d90 100644
--- a/tests/draft3/disallow.json
+++ b/json/tests/draft3/disallow.json
diff --git a/tests/draft3/divisibleBy.json b/json/tests/draft3/divisibleBy.json
index ef7cc14..ef7cc14 100644
--- a/tests/draft3/divisibleBy.json
+++ b/json/tests/draft3/divisibleBy.json
diff --git a/tests/draft3/enum.json b/json/tests/draft3/enum.json
index 5a1ab3b..5a1ab3b 100644
--- a/tests/draft3/enum.json
+++ b/json/tests/draft3/enum.json
diff --git a/tests/draft3/extends.json b/json/tests/draft3/extends.json
index 909bce5..909bce5 100644
--- a/tests/draft3/extends.json
+++ b/json/tests/draft3/extends.json
diff --git a/tests/draft3/format.json b/json/tests/draft3/format.json
index 8279336..8279336 100644
--- a/tests/draft3/format.json
+++ b/json/tests/draft3/format.json
diff --git a/tests/draft3/items.json b/json/tests/draft3/items.json
index f5e18a1..f5e18a1 100644
--- a/tests/draft3/items.json
+++ b/json/tests/draft3/items.json
diff --git a/tests/draft3/maxItems.json b/json/tests/draft3/maxItems.json
index 3b53a6b..3b53a6b 100644
--- a/tests/draft3/maxItems.json
+++ b/json/tests/draft3/maxItems.json
diff --git a/tests/draft3/maxLength.json b/json/tests/draft3/maxLength.json
index 4de42bc..4de42bc 100644
--- a/tests/draft3/maxLength.json
+++ b/json/tests/draft3/maxLength.json
diff --git a/tests/draft3/maximum.json b/json/tests/draft3/maximum.json
index ccb79c6..ccb79c6 100644
--- a/tests/draft3/maximum.json
+++ b/json/tests/draft3/maximum.json
diff --git a/tests/draft3/minItems.json b/json/tests/draft3/minItems.json
index ed51188..ed51188 100644
--- a/tests/draft3/minItems.json
+++ b/json/tests/draft3/minItems.json
diff --git a/tests/draft3/minLength.json b/json/tests/draft3/minLength.json
index 3f09158..3f09158 100644
--- a/tests/draft3/minLength.json
+++ b/json/tests/draft3/minLength.json
diff --git a/tests/draft3/minimum.json b/json/tests/draft3/minimum.json
index d579536..d579536 100644
--- a/tests/draft3/minimum.json
+++ b/json/tests/draft3/minimum.json
diff --git a/tests/draft3/optional/bignum.json b/json/tests/draft3/optional/bignum.json
index ccc7c17..ccc7c17 100644
--- a/tests/draft3/optional/bignum.json
+++ b/json/tests/draft3/optional/bignum.json
diff --git a/tests/draft3/optional/ecmascript-regex.json b/json/tests/draft3/optional/ecmascript-regex.json
index 03fe977..03fe977 100644
--- a/tests/draft3/optional/ecmascript-regex.json
+++ b/json/tests/draft3/optional/ecmascript-regex.json
diff --git a/tests/draft3/optional/format/color.json b/json/tests/draft3/optional/format/color.json
index e80cb69..e80cb69 100644
--- a/tests/draft3/optional/format/color.json
+++ b/json/tests/draft3/optional/format/color.json
diff --git a/tests/draft3/optional/format/date-time.json b/json/tests/draft3/optional/format/date-time.json
index 58261fa..58261fa 100644
--- a/tests/draft3/optional/format/date-time.json
+++ b/json/tests/draft3/optional/format/date-time.json
diff --git a/tests/draft3/optional/format/date.json b/json/tests/draft3/optional/format/date.json
index 4842b48..4842b48 100644
--- a/tests/draft3/optional/format/date.json
+++ b/json/tests/draft3/optional/format/date.json
diff --git a/tests/draft3/optional/format/email.json b/json/tests/draft3/optional/format/email.json
index 02396d2..02396d2 100644
--- a/tests/draft3/optional/format/email.json
+++ b/json/tests/draft3/optional/format/email.json
diff --git a/tests/draft3/optional/format/host-name.json b/json/tests/draft3/optional/format/host-name.json
index 8f6e28a..8f6e28a 100644
--- a/tests/draft3/optional/format/host-name.json
+++ b/json/tests/draft3/optional/format/host-name.json
diff --git a/tests/draft3/optional/format/ip-address.json b/json/tests/draft3/optional/format/ip-address.json
index c868bfb..c868bfb 100644
--- a/tests/draft3/optional/format/ip-address.json
+++ b/json/tests/draft3/optional/format/ip-address.json
diff --git a/tests/draft3/optional/format/ipv6.json b/json/tests/draft3/optional/format/ipv6.json
index 131edb8..131edb8 100644
--- a/tests/draft3/optional/format/ipv6.json
+++ b/json/tests/draft3/optional/format/ipv6.json
diff --git a/tests/draft3/optional/format/regex.json b/json/tests/draft3/optional/format/regex.json
index d99d021..d99d021 100644
--- a/tests/draft3/optional/format/regex.json
+++ b/json/tests/draft3/optional/format/regex.json
diff --git a/tests/draft3/optional/format/time.json b/json/tests/draft3/optional/format/time.json
index e97160d..e97160d 100644
--- a/tests/draft3/optional/format/time.json
+++ b/json/tests/draft3/optional/format/time.json
diff --git a/tests/draft3/optional/format/uri.json b/json/tests/draft3/optional/format/uri.json
index 9c4de35..9c4de35 100644
--- a/tests/draft3/optional/format/uri.json
+++ b/json/tests/draft3/optional/format/uri.json
diff --git a/tests/draft3/optional/non-bmp-regex.json b/json/tests/draft3/optional/non-bmp-regex.json
index dd67af2..dd67af2 100644
--- a/tests/draft3/optional/non-bmp-regex.json
+++ b/json/tests/draft3/optional/non-bmp-regex.json
diff --git a/tests/draft3/optional/zeroTerminatedFloats.json b/json/tests/draft3/optional/zeroTerminatedFloats.json
index 9b50ea2..9b50ea2 100644
--- a/tests/draft3/optional/zeroTerminatedFloats.json
+++ b/json/tests/draft3/optional/zeroTerminatedFloats.json
diff --git a/tests/draft3/pattern.json b/json/tests/draft3/pattern.json
index 92db0f9..92db0f9 100644
--- a/tests/draft3/pattern.json
+++ b/json/tests/draft3/pattern.json
diff --git a/tests/draft3/patternProperties.json b/json/tests/draft3/patternProperties.json
index 2ca9aae..2ca9aae 100644
--- a/tests/draft3/patternProperties.json
+++ b/json/tests/draft3/patternProperties.json
diff --git a/tests/draft3/properties.json b/json/tests/draft3/properties.json
index a830c67..a830c67 100644
--- a/tests/draft3/properties.json
+++ b/json/tests/draft3/properties.json
diff --git a/tests/draft3/ref.json b/json/tests/draft3/ref.json
index 77b2947..77b2947 100644
--- a/tests/draft3/ref.json
+++ b/json/tests/draft3/ref.json
diff --git a/tests/draft3/refRemote.json b/json/tests/draft3/refRemote.json
index de0cb43..de0cb43 100644
--- a/tests/draft3/refRemote.json
+++ b/json/tests/draft3/refRemote.json
diff --git a/tests/draft3/required.json b/json/tests/draft3/required.json
index aaaf024..aaaf024 100644
--- a/tests/draft3/required.json
+++ b/json/tests/draft3/required.json
diff --git a/tests/draft3/type.json b/json/tests/draft3/type.json
index f962cc3..f962cc3 100644
--- a/tests/draft3/type.json
+++ b/json/tests/draft3/type.json
diff --git a/tests/draft3/uniqueItems.json b/json/tests/draft3/uniqueItems.json
index fd4b849..fd4b849 100644
--- a/tests/draft3/uniqueItems.json
+++ b/json/tests/draft3/uniqueItems.json
diff --git a/tests/draft4/additionalItems.json b/json/tests/draft4/additionalItems.json
index ee46b61..ee46b61 100644
--- a/tests/draft4/additionalItems.json
+++ b/json/tests/draft4/additionalItems.json
diff --git a/tests/draft4/additionalProperties.json b/json/tests/draft4/additionalProperties.json
index 381275a..381275a 100644
--- a/tests/draft4/additionalProperties.json
+++ b/json/tests/draft4/additionalProperties.json
diff --git a/tests/draft4/allOf.json b/json/tests/draft4/allOf.json
index fc7dec5..fc7dec5 100644
--- a/tests/draft4/allOf.json
+++ b/json/tests/draft4/allOf.json
diff --git a/tests/draft4/anyOf.json b/json/tests/draft4/anyOf.json
index f8d82e8..f8d82e8 100644
--- a/tests/draft4/anyOf.json
+++ b/json/tests/draft4/anyOf.json
diff --git a/tests/draft4/default.json b/json/tests/draft4/default.json
index 1762977..1762977 100644
--- a/tests/draft4/default.json
+++ b/json/tests/draft4/default.json
diff --git a/tests/draft4/definitions.json b/json/tests/draft4/definitions.json
index cf935a3..cf935a3 100644
--- a/tests/draft4/definitions.json
+++ b/json/tests/draft4/definitions.json
diff --git a/tests/draft4/dependencies.json b/json/tests/draft4/dependencies.json
index 51eeddf..51eeddf 100644
--- a/tests/draft4/dependencies.json
+++ b/json/tests/draft4/dependencies.json
diff --git a/tests/draft4/enum.json b/json/tests/draft4/enum.json
index f085097..f085097 100644
--- a/tests/draft4/enum.json
+++ b/json/tests/draft4/enum.json
diff --git a/tests/draft4/format.json b/json/tests/draft4/format.json
index 61e4b62..61e4b62 100644
--- a/tests/draft4/format.json
+++ b/json/tests/draft4/format.json
diff --git a/tests/draft4/items.json b/json/tests/draft4/items.json
index 7bf9f02..7bf9f02 100644
--- a/tests/draft4/items.json
+++ b/json/tests/draft4/items.json
diff --git a/tests/draft4/maxItems.json b/json/tests/draft4/maxItems.json
index 3b53a6b..3b53a6b 100644
--- a/tests/draft4/maxItems.json
+++ b/json/tests/draft4/maxItems.json
diff --git a/tests/draft4/maxLength.json b/json/tests/draft4/maxLength.json
index 811d35b..811d35b 100644
--- a/tests/draft4/maxLength.json
+++ b/json/tests/draft4/maxLength.json
diff --git a/tests/draft4/maxProperties.json b/json/tests/draft4/maxProperties.json
index aa7209f..aa7209f 100644
--- a/tests/draft4/maxProperties.json
+++ b/json/tests/draft4/maxProperties.json
diff --git a/tests/draft4/maximum.json b/json/tests/draft4/maximum.json
index ccb79c6..ccb79c6 100644
--- a/tests/draft4/maximum.json
+++ b/json/tests/draft4/maximum.json
diff --git a/tests/draft4/minItems.json b/json/tests/draft4/minItems.json
index ed51188..ed51188 100644
--- a/tests/draft4/minItems.json
+++ b/json/tests/draft4/minItems.json
diff --git a/tests/draft4/minLength.json b/json/tests/draft4/minLength.json
index 3f09158..3f09158 100644
--- a/tests/draft4/minLength.json
+++ b/json/tests/draft4/minLength.json
diff --git a/tests/draft4/minProperties.json b/json/tests/draft4/minProperties.json
index 49a0726..49a0726 100644
--- a/tests/draft4/minProperties.json
+++ b/json/tests/draft4/minProperties.json
diff --git a/tests/draft4/minimum.json b/json/tests/draft4/minimum.json
index 22d310e..22d310e 100644
--- a/tests/draft4/minimum.json
+++ b/json/tests/draft4/minimum.json
diff --git a/tests/draft4/multipleOf.json b/json/tests/draft4/multipleOf.json
index ca3b761..ca3b761 100644
--- a/tests/draft4/multipleOf.json
+++ b/json/tests/draft4/multipleOf.json
diff --git a/tests/draft4/not.json b/json/tests/draft4/not.json
index cbb7f46..cbb7f46 100644
--- a/tests/draft4/not.json
+++ b/json/tests/draft4/not.json
diff --git a/tests/draft4/oneOf.json b/json/tests/draft4/oneOf.json
index fb63b08..fb63b08 100644
--- a/tests/draft4/oneOf.json
+++ b/json/tests/draft4/oneOf.json
diff --git a/tests/draft4/optional/bignum.json b/json/tests/draft4/optional/bignum.json
index ccc7c17..ccc7c17 100644
--- a/tests/draft4/optional/bignum.json
+++ b/json/tests/draft4/optional/bignum.json
diff --git a/tests/draft4/optional/ecmascript-regex.json b/json/tests/draft4/optional/ecmascript-regex.json
index 6ed6cbe..6ed6cbe 100644
--- a/tests/draft4/optional/ecmascript-regex.json
+++ b/json/tests/draft4/optional/ecmascript-regex.json
diff --git a/tests/draft4/optional/format/date-time.json b/json/tests/draft4/optional/format/date-time.json
index 900fcb7..900fcb7 100644
--- a/tests/draft4/optional/format/date-time.json
+++ b/json/tests/draft4/optional/format/date-time.json
diff --git a/tests/draft4/optional/format/email.json b/json/tests/draft4/optional/format/email.json
index 02396d2..02396d2 100644
--- a/tests/draft4/optional/format/email.json
+++ b/json/tests/draft4/optional/format/email.json
diff --git a/tests/draft4/optional/format/hostname.json b/json/tests/draft4/optional/format/hostname.json
index e913aba..e913aba 100644
--- a/tests/draft4/optional/format/hostname.json
+++ b/json/tests/draft4/optional/format/hostname.json
diff --git a/tests/draft4/optional/format/ipv4.json b/json/tests/draft4/optional/format/ipv4.json
index 8b99b9f..8b99b9f 100644
--- a/tests/draft4/optional/format/ipv4.json
+++ b/json/tests/draft4/optional/format/ipv4.json
diff --git a/tests/draft4/optional/format/ipv6.json b/json/tests/draft4/optional/format/ipv6.json
index 2a08cb4..2a08cb4 100644
--- a/tests/draft4/optional/format/ipv6.json
+++ b/json/tests/draft4/optional/format/ipv6.json
diff --git a/tests/draft4/optional/format/uri.json b/json/tests/draft4/optional/format/uri.json
index 4306a68..4306a68 100644
--- a/tests/draft4/optional/format/uri.json
+++ b/json/tests/draft4/optional/format/uri.json
diff --git a/tests/draft4/optional/non-bmp-regex.json b/json/tests/draft4/optional/non-bmp-regex.json
index dd67af2..dd67af2 100644
--- a/tests/draft4/optional/non-bmp-regex.json
+++ b/json/tests/draft4/optional/non-bmp-regex.json
diff --git a/tests/draft4/optional/zeroTerminatedFloats.json b/json/tests/draft4/optional/zeroTerminatedFloats.json
index 9b50ea2..9b50ea2 100644
--- a/tests/draft4/optional/zeroTerminatedFloats.json
+++ b/json/tests/draft4/optional/zeroTerminatedFloats.json
diff --git a/tests/draft4/pattern.json b/json/tests/draft4/pattern.json
index 92db0f9..92db0f9 100644
--- a/tests/draft4/pattern.json
+++ b/json/tests/draft4/pattern.json
diff --git a/tests/draft4/patternProperties.json b/json/tests/draft4/patternProperties.json
index 5f741df..5f741df 100644
--- a/tests/draft4/patternProperties.json
+++ b/json/tests/draft4/patternProperties.json
diff --git a/tests/draft4/properties.json b/json/tests/draft4/properties.json
index 688527b..688527b 100644
--- a/tests/draft4/properties.json
+++ b/json/tests/draft4/properties.json
diff --git a/tests/draft4/ref.json b/json/tests/draft4/ref.json
index f88e963..f88e963 100644
--- a/tests/draft4/ref.json
+++ b/json/tests/draft4/ref.json
diff --git a/tests/draft4/refRemote.json b/json/tests/draft4/refRemote.json
index ce5e99a..ce5e99a 100644
--- a/tests/draft4/refRemote.json
+++ b/json/tests/draft4/refRemote.json
diff --git a/tests/draft4/required.json b/json/tests/draft4/required.json
index 9b05318..9b05318 100644
--- a/tests/draft4/required.json
+++ b/json/tests/draft4/required.json
diff --git a/tests/draft4/type.json b/json/tests/draft4/type.json
index df46677..df46677 100644
--- a/tests/draft4/type.json
+++ b/json/tests/draft4/type.json
diff --git a/tests/draft4/uniqueItems.json b/json/tests/draft4/uniqueItems.json
index 4846c77..4846c77 100644
--- a/tests/draft4/uniqueItems.json
+++ b/json/tests/draft4/uniqueItems.json
diff --git a/tests/draft6/additionalItems.json b/json/tests/draft6/additionalItems.json
index ee46b61..ee46b61 100644
--- a/tests/draft6/additionalItems.json
+++ b/json/tests/draft6/additionalItems.json
diff --git a/tests/draft6/additionalProperties.json b/json/tests/draft6/additionalProperties.json
index 381275a..381275a 100644
--- a/tests/draft6/additionalProperties.json
+++ b/json/tests/draft6/additionalProperties.json
diff --git a/tests/draft6/allOf.json b/json/tests/draft6/allOf.json
index ec9319e..ec9319e 100644
--- a/tests/draft6/allOf.json
+++ b/json/tests/draft6/allOf.json
diff --git a/tests/draft6/anyOf.json b/json/tests/draft6/anyOf.json
index b720afa..b720afa 100644
--- a/tests/draft6/anyOf.json
+++ b/json/tests/draft6/anyOf.json
diff --git a/tests/draft6/boolean_schema.json b/json/tests/draft6/boolean_schema.json
index 6d40f23..6d40f23 100644
--- a/tests/draft6/boolean_schema.json
+++ b/json/tests/draft6/boolean_schema.json
diff --git a/tests/draft6/const.json b/json/tests/draft6/const.json
index 1c2cafc..1c2cafc 100644
--- a/tests/draft6/const.json
+++ b/json/tests/draft6/const.json
diff --git a/tests/draft6/contains.json b/json/tests/draft6/contains.json
index c5471cc..c5471cc 100644
--- a/tests/draft6/contains.json
+++ b/json/tests/draft6/contains.json
diff --git a/tests/draft6/default.json b/json/tests/draft6/default.json
index 1762977..1762977 100644
--- a/tests/draft6/default.json
+++ b/json/tests/draft6/default.json
diff --git a/tests/draft6/definitions.json b/json/tests/draft6/definitions.json
index 7f3b899..7f3b899 100644
--- a/tests/draft6/definitions.json
+++ b/json/tests/draft6/definitions.json
diff --git a/tests/draft6/dependencies.json b/json/tests/draft6/dependencies.json
index a5e5428..a5e5428 100644
--- a/tests/draft6/dependencies.json
+++ b/json/tests/draft6/dependencies.json
diff --git a/tests/draft6/enum.json b/json/tests/draft6/enum.json
index f085097..f085097 100644
--- a/tests/draft6/enum.json
+++ b/json/tests/draft6/enum.json
diff --git a/tests/draft6/exclusiveMaximum.json b/json/tests/draft6/exclusiveMaximum.json
index dc3cd70..dc3cd70 100644
--- a/tests/draft6/exclusiveMaximum.json
+++ b/json/tests/draft6/exclusiveMaximum.json
diff --git a/tests/draft6/exclusiveMinimum.json b/json/tests/draft6/exclusiveMinimum.json
index b38d7ec..b38d7ec 100644
--- a/tests/draft6/exclusiveMinimum.json
+++ b/json/tests/draft6/exclusiveMinimum.json
diff --git a/tests/draft6/format.json b/json/tests/draft6/format.json
index 32e8152..32e8152 100644
--- a/tests/draft6/format.json
+++ b/json/tests/draft6/format.json
diff --git a/tests/draft6/items.json b/json/tests/draft6/items.json
index 67f1184..67f1184 100644
--- a/tests/draft6/items.json
+++ b/json/tests/draft6/items.json
diff --git a/tests/draft6/maxItems.json b/json/tests/draft6/maxItems.json
index 3b53a6b..3b53a6b 100644
--- a/tests/draft6/maxItems.json
+++ b/json/tests/draft6/maxItems.json
diff --git a/tests/draft6/maxLength.json b/json/tests/draft6/maxLength.json
index 811d35b..811d35b 100644
--- a/tests/draft6/maxLength.json
+++ b/json/tests/draft6/maxLength.json
diff --git a/tests/draft6/maxProperties.json b/json/tests/draft6/maxProperties.json
index aa7209f..aa7209f 100644
--- a/tests/draft6/maxProperties.json
+++ b/json/tests/draft6/maxProperties.json
diff --git a/tests/draft6/maximum.json b/json/tests/draft6/maximum.json
index 6844a39..6844a39 100644
--- a/tests/draft6/maximum.json
+++ b/json/tests/draft6/maximum.json
diff --git a/tests/draft6/minItems.json b/json/tests/draft6/minItems.json
index ed51188..ed51188 100644
--- a/tests/draft6/minItems.json
+++ b/json/tests/draft6/minItems.json
diff --git a/tests/draft6/minLength.json b/json/tests/draft6/minLength.json
index 3f09158..3f09158 100644
--- a/tests/draft6/minLength.json
+++ b/json/tests/draft6/minLength.json
diff --git a/tests/draft6/minProperties.json b/json/tests/draft6/minProperties.json
index 49a0726..49a0726 100644
--- a/tests/draft6/minProperties.json
+++ b/json/tests/draft6/minProperties.json
diff --git a/tests/draft6/minimum.json b/json/tests/draft6/minimum.json
index 21ae50e..21ae50e 100644
--- a/tests/draft6/minimum.json
+++ b/json/tests/draft6/minimum.json
diff --git a/tests/draft6/multipleOf.json b/json/tests/draft6/multipleOf.json
index ca3b761..ca3b761 100644
--- a/tests/draft6/multipleOf.json
+++ b/json/tests/draft6/multipleOf.json
diff --git a/tests/draft6/not.json b/json/tests/draft6/not.json
index 98de0ed..98de0ed 100644
--- a/tests/draft6/not.json
+++ b/json/tests/draft6/not.json
diff --git a/tests/draft6/oneOf.json b/json/tests/draft6/oneOf.json
index eeb7ae8..eeb7ae8 100644
--- a/tests/draft6/oneOf.json
+++ b/json/tests/draft6/oneOf.json
diff --git a/tests/draft6/optional/bignum.json b/json/tests/draft6/optional/bignum.json
index fac275e..fac275e 100644
--- a/tests/draft6/optional/bignum.json
+++ b/json/tests/draft6/optional/bignum.json
diff --git a/tests/draft6/optional/ecmascript-regex.json b/json/tests/draft6/optional/ecmascript-regex.json
index 6ed6cbe..6ed6cbe 100644
--- a/tests/draft6/optional/ecmascript-regex.json
+++ b/json/tests/draft6/optional/ecmascript-regex.json
diff --git a/tests/draft6/optional/format/date-time.json b/json/tests/draft6/optional/format/date-time.json
index a6320a9..a6320a9 100644
--- a/tests/draft6/optional/format/date-time.json
+++ b/json/tests/draft6/optional/format/date-time.json
diff --git a/tests/draft6/optional/format/email.json b/json/tests/draft6/optional/format/email.json
index 02396d2..02396d2 100644
--- a/tests/draft6/optional/format/email.json
+++ b/json/tests/draft6/optional/format/email.json
diff --git a/tests/draft6/optional/format/hostname.json b/json/tests/draft6/optional/format/hostname.json
index d7654e0..d7654e0 100644
--- a/tests/draft6/optional/format/hostname.json
+++ b/json/tests/draft6/optional/format/hostname.json
diff --git a/tests/draft6/optional/format/ipv4.json b/json/tests/draft6/optional/format/ipv4.json
index 8b99b9f..8b99b9f 100644
--- a/tests/draft6/optional/format/ipv4.json
+++ b/json/tests/draft6/optional/format/ipv4.json
diff --git a/tests/draft6/optional/format/ipv6.json b/json/tests/draft6/optional/format/ipv6.json
index 2a08cb4..2a08cb4 100644
--- a/tests/draft6/optional/format/ipv6.json
+++ b/json/tests/draft6/optional/format/ipv6.json
diff --git a/tests/draft6/optional/format/json-pointer.json b/json/tests/draft6/optional/format/json-pointer.json
index 65c2f06..65c2f06 100644
--- a/tests/draft6/optional/format/json-pointer.json
+++ b/json/tests/draft6/optional/format/json-pointer.json
diff --git a/tests/draft6/optional/format/uri-reference.json b/json/tests/draft6/optional/format/uri-reference.json
index e4c9eef..e4c9eef 100644
--- a/tests/draft6/optional/format/uri-reference.json
+++ b/json/tests/draft6/optional/format/uri-reference.json
diff --git a/tests/draft6/optional/format/uri-template.json b/json/tests/draft6/optional/format/uri-template.json
index 33ab76e..33ab76e 100644
--- a/tests/draft6/optional/format/uri-template.json
+++ b/json/tests/draft6/optional/format/uri-template.json
diff --git a/tests/draft6/optional/format/uri.json b/json/tests/draft6/optional/format/uri.json
index 4306a68..4306a68 100644
--- a/tests/draft6/optional/format/uri.json
+++ b/json/tests/draft6/optional/format/uri.json
diff --git a/tests/draft6/optional/non-bmp-regex.json b/json/tests/draft6/optional/non-bmp-regex.json
index dd67af2..dd67af2 100644
--- a/tests/draft6/optional/non-bmp-regex.json
+++ b/json/tests/draft6/optional/non-bmp-regex.json
diff --git a/tests/draft6/pattern.json b/json/tests/draft6/pattern.json
index 92db0f9..92db0f9 100644
--- a/tests/draft6/pattern.json
+++ b/json/tests/draft6/pattern.json
diff --git a/tests/draft6/patternProperties.json b/json/tests/draft6/patternProperties.json
index c10ffcc..c10ffcc 100644
--- a/tests/draft6/patternProperties.json
+++ b/json/tests/draft6/patternProperties.json
diff --git a/tests/draft6/properties.json b/json/tests/draft6/properties.json
index b86c181..b86c181 100644
--- a/tests/draft6/properties.json
+++ b/json/tests/draft6/properties.json
diff --git a/tests/draft6/propertyNames.json b/json/tests/draft6/propertyNames.json
index 8423690..8423690 100644
--- a/tests/draft6/propertyNames.json
+++ b/json/tests/draft6/propertyNames.json
diff --git a/tests/draft6/ref.json b/json/tests/draft6/ref.json
index 0138382..0138382 100644
--- a/tests/draft6/ref.json
+++ b/json/tests/draft6/ref.json
diff --git a/tests/draft6/refRemote.json b/json/tests/draft6/refRemote.json
index 74a7862..74a7862 100644
--- a/tests/draft6/refRemote.json
+++ b/json/tests/draft6/refRemote.json
diff --git a/tests/draft6/required.json b/json/tests/draft6/required.json
index abf18f3..abf18f3 100644
--- a/tests/draft6/required.json
+++ b/json/tests/draft6/required.json
diff --git a/tests/draft6/type.json b/json/tests/draft6/type.json
index 8304647..8304647 100644
--- a/tests/draft6/type.json
+++ b/json/tests/draft6/type.json
diff --git a/tests/draft6/uniqueItems.json b/json/tests/draft6/uniqueItems.json
index 4846c77..4846c77 100644
--- a/tests/draft6/uniqueItems.json
+++ b/json/tests/draft6/uniqueItems.json
diff --git a/tests/draft7/additionalItems.json b/json/tests/draft7/additionalItems.json
index ee46b61..ee46b61 100644
--- a/tests/draft7/additionalItems.json
+++ b/json/tests/draft7/additionalItems.json
diff --git a/tests/draft7/additionalProperties.json b/json/tests/draft7/additionalProperties.json
index 381275a..381275a 100644
--- a/tests/draft7/additionalProperties.json
+++ b/json/tests/draft7/additionalProperties.json
diff --git a/tests/draft7/allOf.json b/json/tests/draft7/allOf.json
index ec9319e..ec9319e 100644
--- a/tests/draft7/allOf.json
+++ b/json/tests/draft7/allOf.json
diff --git a/tests/draft7/anyOf.json b/json/tests/draft7/anyOf.json
index b720afa..b720afa 100644
--- a/tests/draft7/anyOf.json
+++ b/json/tests/draft7/anyOf.json
diff --git a/tests/draft7/boolean_schema.json b/json/tests/draft7/boolean_schema.json
index 6d40f23..6d40f23 100644
--- a/tests/draft7/boolean_schema.json
+++ b/json/tests/draft7/boolean_schema.json
diff --git a/tests/draft7/const.json b/json/tests/draft7/const.json
index 1c2cafc..1c2cafc 100644
--- a/tests/draft7/const.json
+++ b/json/tests/draft7/const.json
diff --git a/tests/draft7/contains.json b/json/tests/draft7/contains.json
index c5471cc..c5471cc 100644
--- a/tests/draft7/contains.json
+++ b/json/tests/draft7/contains.json
diff --git a/tests/draft7/default.json b/json/tests/draft7/default.json
index 1762977..1762977 100644
--- a/tests/draft7/default.json
+++ b/json/tests/draft7/default.json
diff --git a/tests/draft7/definitions.json b/json/tests/draft7/definitions.json
index 4360406..4360406 100644
--- a/tests/draft7/definitions.json
+++ b/json/tests/draft7/definitions.json
diff --git a/tests/draft7/dependencies.json b/json/tests/draft7/dependencies.json
index a5e5428..a5e5428 100644
--- a/tests/draft7/dependencies.json
+++ b/json/tests/draft7/dependencies.json
diff --git a/tests/draft7/enum.json b/json/tests/draft7/enum.json
index f085097..f085097 100644
--- a/tests/draft7/enum.json
+++ b/json/tests/draft7/enum.json
diff --git a/tests/draft7/exclusiveMaximum.json b/json/tests/draft7/exclusiveMaximum.json
index dc3cd70..dc3cd70 100644
--- a/tests/draft7/exclusiveMaximum.json
+++ b/json/tests/draft7/exclusiveMaximum.json
diff --git a/tests/draft7/exclusiveMinimum.json b/json/tests/draft7/exclusiveMinimum.json
index b38d7ec..b38d7ec 100644
--- a/tests/draft7/exclusiveMinimum.json
+++ b/json/tests/draft7/exclusiveMinimum.json
diff --git a/tests/draft7/format.json b/json/tests/draft7/format.json
index 93305f5..93305f5 100644
--- a/tests/draft7/format.json
+++ b/json/tests/draft7/format.json
diff --git a/tests/draft7/if-then-else.json b/json/tests/draft7/if-then-else.json
index e0b873e..e0b873e 100644
--- a/tests/draft7/if-then-else.json
+++ b/json/tests/draft7/if-then-else.json
diff --git a/tests/draft7/items.json b/json/tests/draft7/items.json
index 67f1184..67f1184 100644
--- a/tests/draft7/items.json
+++ b/json/tests/draft7/items.json
diff --git a/tests/draft7/maxItems.json b/json/tests/draft7/maxItems.json
index 3b53a6b..3b53a6b 100644
--- a/tests/draft7/maxItems.json
+++ b/json/tests/draft7/maxItems.json
diff --git a/tests/draft7/maxLength.json b/json/tests/draft7/maxLength.json
index 811d35b..811d35b 100644
--- a/tests/draft7/maxLength.json
+++ b/json/tests/draft7/maxLength.json
diff --git a/tests/draft7/maxProperties.json b/json/tests/draft7/maxProperties.json
index aa7209f..aa7209f 100644
--- a/tests/draft7/maxProperties.json
+++ b/json/tests/draft7/maxProperties.json
diff --git a/tests/draft7/maximum.json b/json/tests/draft7/maximum.json
index 6844a39..6844a39 100644
--- a/tests/draft7/maximum.json
+++ b/json/tests/draft7/maximum.json
diff --git a/tests/draft7/minItems.json b/json/tests/draft7/minItems.json
index ed51188..ed51188 100644
--- a/tests/draft7/minItems.json
+++ b/json/tests/draft7/minItems.json
diff --git a/tests/draft7/minLength.json b/json/tests/draft7/minLength.json
index 3f09158..3f09158 100644
--- a/tests/draft7/minLength.json
+++ b/json/tests/draft7/minLength.json
diff --git a/tests/draft7/minProperties.json b/json/tests/draft7/minProperties.json
index 49a0726..49a0726 100644
--- a/tests/draft7/minProperties.json
+++ b/json/tests/draft7/minProperties.json
diff --git a/tests/draft7/minimum.json b/json/tests/draft7/minimum.json
index 21ae50e..21ae50e 100644
--- a/tests/draft7/minimum.json
+++ b/json/tests/draft7/minimum.json
diff --git a/tests/draft7/multipleOf.json b/json/tests/draft7/multipleOf.json
index ca3b761..ca3b761 100644
--- a/tests/draft7/multipleOf.json
+++ b/json/tests/draft7/multipleOf.json
diff --git a/tests/draft7/not.json b/json/tests/draft7/not.json
index 98de0ed..98de0ed 100644
--- a/tests/draft7/not.json
+++ b/json/tests/draft7/not.json
diff --git a/tests/draft7/oneOf.json b/json/tests/draft7/oneOf.json
index eeb7ae8..eeb7ae8 100644
--- a/tests/draft7/oneOf.json
+++ b/json/tests/draft7/oneOf.json
diff --git a/tests/draft7/optional/bignum.json b/json/tests/draft7/optional/bignum.json
index fac275e..fac275e 100644
--- a/tests/draft7/optional/bignum.json
+++ b/json/tests/draft7/optional/bignum.json
diff --git a/tests/draft7/optional/content.json b/json/tests/draft7/optional/content.json
index 3f5a743..3f5a743 100644
--- a/tests/draft7/optional/content.json
+++ b/json/tests/draft7/optional/content.json
diff --git a/tests/draft7/optional/ecmascript-regex.json b/json/tests/draft7/optional/ecmascript-regex.json
index 6ed6cbe..6ed6cbe 100644
--- a/tests/draft7/optional/ecmascript-regex.json
+++ b/json/tests/draft7/optional/ecmascript-regex.json
diff --git a/tests/draft7/optional/format/date-time.json b/json/tests/draft7/optional/format/date-time.json
index 900fcb7..900fcb7 100644
--- a/tests/draft7/optional/format/date-time.json
+++ b/json/tests/draft7/optional/format/date-time.json
diff --git a/tests/draft7/optional/format/date.json b/json/tests/draft7/optional/format/date.json
index 453b51d..453b51d 100644
--- a/tests/draft7/optional/format/date.json
+++ b/json/tests/draft7/optional/format/date.json
diff --git a/tests/draft7/optional/format/email.json b/json/tests/draft7/optional/format/email.json
index 02396d2..02396d2 100644
--- a/tests/draft7/optional/format/email.json
+++ b/json/tests/draft7/optional/format/email.json
diff --git a/tests/draft7/optional/format/hostname.json b/json/tests/draft7/optional/format/hostname.json
index 476541a..476541a 100644
--- a/tests/draft7/optional/format/hostname.json
+++ b/json/tests/draft7/optional/format/hostname.json
diff --git a/tests/draft7/optional/format/idn-email.json b/json/tests/draft7/optional/format/idn-email.json
index 552d106..552d106 100644
--- a/tests/draft7/optional/format/idn-email.json
+++ b/json/tests/draft7/optional/format/idn-email.json
diff --git a/tests/draft7/optional/format/idn-hostname.json b/json/tests/draft7/optional/format/idn-hostname.json
index 7f10bd8..7f10bd8 100644
--- a/tests/draft7/optional/format/idn-hostname.json
+++ b/json/tests/draft7/optional/format/idn-hostname.json
diff --git a/tests/draft7/optional/format/ipv4.json b/json/tests/draft7/optional/format/ipv4.json
index 8b99b9f..8b99b9f 100644
--- a/tests/draft7/optional/format/ipv4.json
+++ b/json/tests/draft7/optional/format/ipv4.json
diff --git a/tests/draft7/optional/format/ipv6.json b/json/tests/draft7/optional/format/ipv6.json
index 2a08cb4..2a08cb4 100644
--- a/tests/draft7/optional/format/ipv6.json
+++ b/json/tests/draft7/optional/format/ipv6.json
diff --git a/tests/draft7/optional/format/iri-reference.json b/json/tests/draft7/optional/format/iri-reference.json
index 1fd779c..1fd779c 100644
--- a/tests/draft7/optional/format/iri-reference.json
+++ b/json/tests/draft7/optional/format/iri-reference.json
diff --git a/tests/draft7/optional/format/iri.json b/json/tests/draft7/optional/format/iri.json
index ed54094..ed54094 100644
--- a/tests/draft7/optional/format/iri.json
+++ b/json/tests/draft7/optional/format/iri.json
diff --git a/tests/draft7/optional/format/json-pointer.json b/json/tests/draft7/optional/format/json-pointer.json
index 65c2f06..65c2f06 100644
--- a/tests/draft7/optional/format/json-pointer.json
+++ b/json/tests/draft7/optional/format/json-pointer.json
diff --git a/tests/draft7/optional/format/regex.json b/json/tests/draft7/optional/format/regex.json
index d99d021..d99d021 100644
--- a/tests/draft7/optional/format/regex.json
+++ b/json/tests/draft7/optional/format/regex.json
diff --git a/tests/draft7/optional/format/relative-json-pointer.json b/json/tests/draft7/optional/format/relative-json-pointer.json
index 17816c9..17816c9 100644
--- a/tests/draft7/optional/format/relative-json-pointer.json
+++ b/json/tests/draft7/optional/format/relative-json-pointer.json
diff --git a/tests/draft7/optional/format/time.json b/json/tests/draft7/optional/format/time.json
index 4ec8a01..4ec8a01 100644
--- a/tests/draft7/optional/format/time.json
+++ b/json/tests/draft7/optional/format/time.json
diff --git a/tests/draft7/optional/format/uri-reference.json b/json/tests/draft7/optional/format/uri-reference.json
index e4c9eef..e4c9eef 100644
--- a/tests/draft7/optional/format/uri-reference.json
+++ b/json/tests/draft7/optional/format/uri-reference.json
diff --git a/tests/draft7/optional/format/uri-template.json b/json/tests/draft7/optional/format/uri-template.json
index 33ab76e..33ab76e 100644
--- a/tests/draft7/optional/format/uri-template.json
+++ b/json/tests/draft7/optional/format/uri-template.json
diff --git a/tests/draft7/optional/format/uri.json b/json/tests/draft7/optional/format/uri.json
index 4306a68..4306a68 100644
--- a/tests/draft7/optional/format/uri.json
+++ b/json/tests/draft7/optional/format/uri.json
diff --git a/tests/draft7/optional/non-bmp-regex.json b/json/tests/draft7/optional/non-bmp-regex.json
index dd67af2..dd67af2 100644
--- a/tests/draft7/optional/non-bmp-regex.json
+++ b/json/tests/draft7/optional/non-bmp-regex.json
diff --git a/tests/draft7/pattern.json b/json/tests/draft7/pattern.json
index 92db0f9..92db0f9 100644
--- a/tests/draft7/pattern.json
+++ b/json/tests/draft7/pattern.json
diff --git a/tests/draft7/patternProperties.json b/json/tests/draft7/patternProperties.json
index c10ffcc..c10ffcc 100644
--- a/tests/draft7/patternProperties.json
+++ b/json/tests/draft7/patternProperties.json
diff --git a/tests/draft7/properties.json b/json/tests/draft7/properties.json
index b86c181..b86c181 100644
--- a/tests/draft7/properties.json
+++ b/json/tests/draft7/properties.json
diff --git a/tests/draft7/propertyNames.json b/json/tests/draft7/propertyNames.json
index 8423690..8423690 100644
--- a/tests/draft7/propertyNames.json
+++ b/json/tests/draft7/propertyNames.json
diff --git a/tests/draft7/ref.json b/json/tests/draft7/ref.json
index 01ba9fa..01ba9fa 100644
--- a/tests/draft7/ref.json
+++ b/json/tests/draft7/ref.json
diff --git a/tests/draft7/refRemote.json b/json/tests/draft7/refRemote.json
index 74a7862..74a7862 100644
--- a/tests/draft7/refRemote.json
+++ b/json/tests/draft7/refRemote.json
diff --git a/tests/draft7/required.json b/json/tests/draft7/required.json
index abf18f3..abf18f3 100644
--- a/tests/draft7/required.json
+++ b/json/tests/draft7/required.json
diff --git a/tests/draft7/type.json b/json/tests/draft7/type.json
index 8304647..8304647 100644
--- a/tests/draft7/type.json
+++ b/json/tests/draft7/type.json
diff --git a/tests/draft7/uniqueItems.json b/json/tests/draft7/uniqueItems.json
index 4846c77..4846c77 100644
--- a/tests/draft7/uniqueItems.json
+++ b/json/tests/draft7/uniqueItems.json
diff --git a/tests/latest b/json/tests/latest
index 90f70db..90f70db 120000
--- a/tests/latest
+++ b/json/tests/latest
diff --git a/json/tox.ini b/json/tox.ini
new file mode 100644
index 0000000..72fd562
--- /dev/null
+++ b/json/tox.ini
@@ -0,0 +1,9 @@
+[tox]
+minversion = 1.6
+envlist = sanity
+skipsdist = True
+
+[testenv:sanity]
+# used just for validating the structure of the test case files themselves
+deps = jsonschema>=3.2.0
+commands = {envpython} bin/jsonschema_suite check
diff --git a/jsonschema/__init__.py b/jsonschema/__init__.py
new file mode 100644
index 0000000..619a7ea
--- /dev/null
+++ b/jsonschema/__init__.py
@@ -0,0 +1,39 @@
+"""
+An implementation of JSON Schema for Python
+
+The main functionality is provided by the validator classes for each of the
+supported JSON Schema versions.
+
+Most commonly, `validate` is the quickest way to simply validate a given
+instance under a schema, and will create a validator for you.
+"""
+
+from jsonschema._format import (
+ FormatChecker,
+ draft3_format_checker,
+ draft4_format_checker,
+ draft6_format_checker,
+ draft7_format_checker,
+)
+from jsonschema._types import TypeChecker
+from jsonschema.exceptions import (
+ ErrorTree,
+ FormatError,
+ RefResolutionError,
+ SchemaError,
+ ValidationError,
+)
+from jsonschema.validators import (
+ Draft3Validator,
+ Draft4Validator,
+ Draft6Validator,
+ Draft7Validator,
+ RefResolver,
+ validate,
+)
+
+try:
+ from importlib import metadata
+except ImportError: # for Python<3.8
+ import importlib_metadata as metadata
+__version__ = metadata.version("jsonschema")
diff --git a/jsonschema/__main__.py b/jsonschema/__main__.py
new file mode 100644
index 0000000..fdc21e2
--- /dev/null
+++ b/jsonschema/__main__.py
@@ -0,0 +1,3 @@
+from jsonschema.cli import main
+
+main()
diff --git a/jsonschema/_format.py b/jsonschema/_format.py
new file mode 100644
index 0000000..ce47864
--- /dev/null
+++ b/jsonschema/_format.py
@@ -0,0 +1,428 @@
+import datetime
+import ipaddress
+import re
+
+from jsonschema.exceptions import FormatError
+
+
+class FormatChecker(object):
+ """
+ A ``format`` property checker.
+
+ JSON Schema does not mandate that the ``format`` property actually do any
+ validation. If validation is desired however, instances of this class can
+ be hooked into validators to enable format validation.
+
+ `FormatChecker` objects always return ``True`` when asked about
+ formats that they do not know how to validate.
+
+ To check a custom format using a function that takes an instance and
+ returns a ``bool``, use the `FormatChecker.checks` or
+ `FormatChecker.cls_checks` decorators.
+
+ Arguments:
+
+ formats (~collections.abc.Iterable):
+
+ The known formats to validate. This argument can be used to
+ limit which formats will be used during validation.
+ """
+
+ checkers = {}
+
+ def __init__(self, formats=None):
+ if formats is None:
+ self.checkers = self.checkers.copy()
+ else:
+ self.checkers = dict((k, self.checkers[k]) for k in formats)
+
+ def __repr__(self):
+ return "<FormatChecker checkers={}>".format(sorted(self.checkers))
+
+ def checks(self, format, raises=()):
+ """
+ Register a decorated function as validating a new format.
+
+ Arguments:
+
+ format (str):
+
+ The format that the decorated function will check.
+
+ raises (Exception):
+
+ The exception(s) raised by the decorated function when an
+ invalid instance is found.
+
+ The exception object will be accessible as the
+ `jsonschema.exceptions.ValidationError.cause` attribute of the
+ resulting validation error.
+ """
+
+ def _checks(func):
+ self.checkers[format] = (func, raises)
+ return func
+ return _checks
+
+ cls_checks = classmethod(checks)
+
+ def check(self, instance, format):
+ """
+ Check whether the instance conforms to the given format.
+
+ Arguments:
+
+ instance (*any primitive type*, i.e. str, number, bool):
+
+ The instance to check
+
+ format (str):
+
+ The format that instance should conform to
+
+
+ Raises:
+
+ FormatError: if the instance does not conform to ``format``
+ """
+
+ if format not in self.checkers:
+ return
+
+ func, raises = self.checkers[format]
+ result, cause = None, None
+ try:
+ result = func(instance)
+ except raises as e:
+ cause = e
+ if not result:
+ raise FormatError(
+ "%r is not a %r" % (instance, format), cause=cause,
+ )
+
+ def conforms(self, instance, format):
+ """
+ Check whether the instance conforms to the given format.
+
+ Arguments:
+
+ instance (*any primitive type*, i.e. str, number, bool):
+
+ The instance to check
+
+ format (str):
+
+ The format that instance should conform to
+
+ Returns:
+
+ bool: whether it conformed
+ """
+
+ try:
+ self.check(instance, format)
+ except FormatError:
+ return False
+ else:
+ return True
+
+
+draft3_format_checker = FormatChecker()
+draft4_format_checker = FormatChecker()
+draft6_format_checker = FormatChecker()
+draft7_format_checker = FormatChecker()
+
+
+_draft_checkers = dict(
+ draft3=draft3_format_checker,
+ draft4=draft4_format_checker,
+ draft6=draft6_format_checker,
+ draft7=draft7_format_checker,
+)
+
+
+def _checks_drafts(
+ name=None,
+ draft3=None,
+ draft4=None,
+ draft6=None,
+ draft7=None,
+ raises=(),
+):
+ draft3 = draft3 or name
+ draft4 = draft4 or name
+ draft6 = draft6 or name
+ draft7 = draft7 or name
+
+ def wrap(func):
+ if draft3:
+ func = _draft_checkers["draft3"].checks(draft3, raises)(func)
+ if draft4:
+ func = _draft_checkers["draft4"].checks(draft4, raises)(func)
+ if draft6:
+ func = _draft_checkers["draft6"].checks(draft6, raises)(func)
+ if draft7:
+ func = _draft_checkers["draft7"].checks(draft7, raises)(func)
+
+ # Oy. This is bad global state, but relied upon for now, until
+ # deprecation. See https://github.com/Julian/jsonschema/issues/519
+ # and test_format_checkers_come_with_defaults
+ FormatChecker.cls_checks(draft7 or draft6 or draft4 or draft3, raises)(
+ func,
+ )
+ return func
+ return wrap
+
+
+@_checks_drafts(name="idn-email")
+@_checks_drafts(name="email")
+def is_email(instance):
+ if not isinstance(instance, str):
+ return True
+ return "@" in instance
+
+
+@_checks_drafts(
+ draft3="ip-address",
+ draft4="ipv4",
+ draft6="ipv4",
+ draft7="ipv4",
+ raises=ipaddress.AddressValueError,
+)
+def is_ipv4(instance):
+ if not isinstance(instance, str):
+ return True
+ return ipaddress.IPv4Address(instance)
+
+
+@_checks_drafts(name="ipv6", raises=ipaddress.AddressValueError)
+def is_ipv6(instance):
+ if not isinstance(instance, str):
+ return True
+ return ipaddress.IPv6Address(instance)
+
+
+try:
+ from fqdn import FQDN
+except ImportError:
+ pass
+else:
+ @_checks_drafts(
+ draft3="host-name",
+ draft4="hostname",
+ draft6="hostname",
+ draft7="hostname",
+ )
+ def is_host_name(instance):
+ if not isinstance(instance, str):
+ return True
+ return FQDN(instance).is_valid
+
+
+try:
+ # The built-in `idna` codec only implements RFC 3890, so we go elsewhere.
+ import idna
+except ImportError:
+ pass
+else:
+ @_checks_drafts(
+ draft7="idn-hostname",
+ raises=(idna.IDNAError, UnicodeError),
+ )
+ def is_idn_host_name(instance):
+ if not isinstance(instance, str):
+ return True
+ idna.encode(instance)
+ return True
+
+
+try:
+ import rfc3987
+except ImportError:
+ try:
+ from rfc3986_validator import validate_rfc3986
+ except ImportError:
+ pass
+ else:
+ @_checks_drafts(name="uri")
+ def is_uri(instance):
+ if not isinstance(instance, str):
+ return True
+ return validate_rfc3986(instance, rule="URI")
+
+ @_checks_drafts(
+ draft6="uri-reference",
+ draft7="uri-reference",
+ raises=ValueError,
+ )
+ def is_uri_reference(instance):
+ if not isinstance(instance, str):
+ return True
+ return validate_rfc3986(instance, rule="URI_reference")
+
+else:
+ @_checks_drafts(draft7="iri", raises=ValueError)
+ def is_iri(instance):
+ if not isinstance(instance, str):
+ return True
+ return rfc3987.parse(instance, rule="IRI")
+
+ @_checks_drafts(draft7="iri-reference", raises=ValueError)
+ def is_iri_reference(instance):
+ if not isinstance(instance, str):
+ return True
+ return rfc3987.parse(instance, rule="IRI_reference")
+
+ @_checks_drafts(name="uri", raises=ValueError)
+ def is_uri(instance):
+ if not isinstance(instance, str):
+ return True
+ return rfc3987.parse(instance, rule="URI")
+
+ @_checks_drafts(
+ draft6="uri-reference",
+ draft7="uri-reference",
+ raises=ValueError,
+ )
+ def is_uri_reference(instance):
+ if not isinstance(instance, str):
+ return True
+ return rfc3987.parse(instance, rule="URI_reference")
+
+
+try:
+ from strict_rfc3339 import validate_rfc3339
+except ImportError:
+ try:
+ from rfc3339_validator import validate_rfc3339
+ except ImportError:
+ validate_rfc3339 = None
+
+if validate_rfc3339:
+ @_checks_drafts(name="date-time")
+ def is_datetime(instance):
+ if not isinstance(instance, str):
+ return True
+ return validate_rfc3339(instance)
+
+ @_checks_drafts(draft7="time")
+ def is_time(instance):
+ if not isinstance(instance, str):
+ return True
+ return is_datetime("1970-01-01T" + instance)
+
+
+@_checks_drafts(name="regex", raises=re.error)
+def is_regex(instance):
+ if not isinstance(instance, str):
+ return True
+ return re.compile(instance)
+
+
+if hasattr(datetime.date, "fromisoformat"):
+ _is_date = datetime.date.fromisoformat
+else:
+ def _is_date(instance):
+ return datetime.datetime.strptime(instance, "%Y-%m-%d")
+
+
+@_checks_drafts(draft3="date", draft7="date", raises=ValueError)
+def is_date(instance):
+ if not isinstance(instance, str):
+ return True
+ return _is_date(instance)
+
+
+@_checks_drafts(draft3="time", raises=ValueError)
+def is_draft3_time(instance):
+ if not isinstance(instance, str):
+ return True
+ return datetime.datetime.strptime(instance, "%H:%M:%S")
+
+
+try: # webcolors>=1.11
+ from webcolors import CSS21_NAMES_TO_HEX
+ import webcolors
+except ImportError:
+ try: # webcolors<1.11
+ from webcolors import css21_names_to_hex as CSS21_NAMES_TO_HEX
+ import webcolors
+ except ImportError:
+ pass
+else:
+ def is_css_color_code(instance):
+ return webcolors.normalize_hex(instance)
+
+ @_checks_drafts(draft3="color", raises=(ValueError, TypeError))
+ def is_css21_color(instance):
+ if (
+ not isinstance(instance, str) or
+ instance.lower() in CSS21_NAMES_TO_HEX
+ ):
+ return True
+ return is_css_color_code(instance)
+
+ def is_css3_color(instance):
+ if instance.lower() in webcolors.css3_names_to_hex:
+ return True
+ return is_css_color_code(instance)
+
+
+try:
+ import jsonpointer
+except ImportError:
+ pass
+else:
+ @_checks_drafts(
+ draft6="json-pointer",
+ draft7="json-pointer",
+ raises=jsonpointer.JsonPointerException,
+ )
+ def is_json_pointer(instance):
+ if not isinstance(instance, str):
+ return True
+ return jsonpointer.JsonPointer(instance)
+
+ # TODO: I don't want to maintain this, so it
+ # needs to go either into jsonpointer (pending
+ # https://github.com/stefankoegl/python-json-pointer/issues/34) or
+ # into a new external library.
+ @_checks_drafts(
+ draft7="relative-json-pointer",
+ raises=jsonpointer.JsonPointerException,
+ )
+ def is_relative_json_pointer(instance):
+ # Definition taken from:
+ # https://tools.ietf.org/html/draft-handrews-relative-json-pointer-01#section-3
+ if not isinstance(instance, str):
+ return True
+ non_negative_integer, rest = [], ""
+ for i, character in enumerate(instance):
+ if character.isdigit():
+ non_negative_integer.append(character)
+ continue
+
+ if not non_negative_integer:
+ return False
+
+ rest = instance[i:]
+ break
+ return (rest == "#") or jsonpointer.JsonPointer(rest)
+
+
+try:
+ import uritemplate.exceptions
+except ImportError:
+ pass
+else:
+ @_checks_drafts(
+ draft6="uri-template",
+ draft7="uri-template",
+ raises=uritemplate.exceptions.InvalidTemplate,
+ )
+ def is_uri_template(
+ instance,
+ template_validator=uritemplate.Validator().force_balanced_braces(),
+ ):
+ template = uritemplate.URITemplate(instance)
+ return template_validator.validate(template)
diff --git a/jsonschema/_legacy_validators.py b/jsonschema/_legacy_validators.py
new file mode 100644
index 0000000..80f798b
--- /dev/null
+++ b/jsonschema/_legacy_validators.py
@@ -0,0 +1,140 @@
+from jsonschema import _utils
+from jsonschema.exceptions import ValidationError
+
+
+def dependencies_draft3(validator, dependencies, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+
+ for property, dependency in dependencies.items():
+ if property not in instance:
+ continue
+
+ if validator.is_type(dependency, "object"):
+ for error in validator.descend(
+ instance, dependency, schema_path=property,
+ ):
+ yield error
+ elif validator.is_type(dependency, "string"):
+ if dependency not in instance:
+ yield ValidationError(
+ "%r is a dependency of %r" % (dependency, property)
+ )
+ else:
+ for each in dependency:
+ if each not in instance:
+ message = "%r is a dependency of %r"
+ yield ValidationError(message % (each, property))
+
+
+def disallow_draft3(validator, disallow, instance, schema):
+ for disallowed in _utils.ensure_list(disallow):
+ if validator.is_valid(instance, {"type": [disallowed]}):
+ yield ValidationError(
+ "%r is disallowed for %r" % (disallowed, instance)
+ )
+
+
+def extends_draft3(validator, extends, instance, schema):
+ if validator.is_type(extends, "object"):
+ for error in validator.descend(instance, extends):
+ yield error
+ return
+ for index, subschema in enumerate(extends):
+ for error in validator.descend(instance, subschema, schema_path=index):
+ yield error
+
+
+def items_draft3_draft4(validator, items, instance, schema):
+ if not validator.is_type(instance, "array"):
+ return
+
+ if validator.is_type(items, "object"):
+ for index, item in enumerate(instance):
+ for error in validator.descend(item, items, path=index):
+ yield error
+ else:
+ for (index, item), subschema in zip(enumerate(instance), items):
+ for error in validator.descend(
+ item, subschema, path=index, schema_path=index,
+ ):
+ yield error
+
+
+def minimum_draft3_draft4(validator, minimum, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if schema.get("exclusiveMinimum", False):
+ failed = instance <= minimum
+ cmp = "less than or equal to"
+ else:
+ failed = instance < minimum
+ cmp = "less than"
+
+ if failed:
+ yield ValidationError(
+ "%r is %s the minimum of %r" % (instance, cmp, minimum)
+ )
+
+
+def maximum_draft3_draft4(validator, maximum, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if schema.get("exclusiveMaximum", False):
+ failed = instance >= maximum
+ cmp = "greater than or equal to"
+ else:
+ failed = instance > maximum
+ cmp = "greater than"
+
+ if failed:
+ yield ValidationError(
+ "%r is %s the maximum of %r" % (instance, cmp, maximum)
+ )
+
+
+def properties_draft3(validator, properties, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+
+ for property, subschema in properties.items():
+ if property in instance:
+ for error in validator.descend(
+ instance[property],
+ subschema,
+ path=property,
+ schema_path=property,
+ ):
+ yield error
+ elif subschema.get("required", False):
+ error = ValidationError("%r is a required property" % property)
+ error._set(
+ validator="required",
+ validator_value=subschema["required"],
+ instance=instance,
+ schema=schema,
+ )
+ error.path.appendleft(property)
+ error.schema_path.extend([property, "required"])
+ yield error
+
+
+def type_draft3(validator, types, instance, schema):
+ types = _utils.ensure_list(types)
+
+ all_errors = []
+ for index, type in enumerate(types):
+ if validator.is_type(type, "object"):
+ errors = list(validator.descend(instance, type, schema_path=index))
+ if not errors:
+ return
+ all_errors.extend(errors)
+ else:
+ if validator.is_type(instance, type):
+ return
+ else:
+ yield ValidationError(
+ _utils.types_msg(instance, types), context=all_errors,
+ )
diff --git a/jsonschema/_reflect.py b/jsonschema/_reflect.py
new file mode 100644
index 0000000..39ee7a6
--- /dev/null
+++ b/jsonschema/_reflect.py
@@ -0,0 +1,149 @@
+# -*- test-case-name: twisted.test.test_reflect -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Standardized versions of various cool and/or strange things that you can do
+with Python's reflection capabilities.
+"""
+
+import sys
+
+
+class _NoModuleFound(Exception):
+ """
+ No module was found because none exists.
+ """
+
+
+
+class InvalidName(ValueError):
+ """
+ The given name is not a dot-separated list of Python objects.
+ """
+
+
+
+class ModuleNotFound(InvalidName):
+ """
+ The module associated with the given name doesn't exist and it can't be
+ imported.
+ """
+
+
+
+class ObjectNotFound(InvalidName):
+ """
+ The object associated with the given name doesn't exist and it can't be
+ imported.
+ """
+
+
+
+def reraise(exception, traceback):
+ raise exception.with_traceback(traceback)
+
+reraise.__doc__ = """
+Re-raise an exception, with an optional traceback, in a way that is compatible
+with both Python 2 and Python 3.
+
+Note that on Python 3, re-raised exceptions will be mutated, with their
+C{__traceback__} attribute being set.
+
+@param exception: The exception instance.
+@param traceback: The traceback to use, or C{None} indicating a new traceback.
+"""
+
+
+def _importAndCheckStack(importName):
+ """
+ Import the given name as a module, then walk the stack to determine whether
+ the failure was the module not existing, or some code in the module (for
+ example a dependent import) failing. This can be helpful to determine
+ whether any actual application code was run. For example, to distiguish
+ administrative error (entering the wrong module name), from programmer
+ error (writing buggy code in a module that fails to import).
+
+ @param importName: The name of the module to import.
+ @type importName: C{str}
+ @raise Exception: if something bad happens. This can be any type of
+ exception, since nobody knows what loading some arbitrary code might
+ do.
+ @raise _NoModuleFound: if no module was found.
+ """
+ try:
+ return __import__(importName)
+ except ImportError:
+ excType, excValue, excTraceback = sys.exc_info()
+ while excTraceback:
+ execName = excTraceback.tb_frame.f_globals["__name__"]
+ # in Python 2 execName is None when an ImportError is encountered,
+ # where in Python 3 execName is equal to the importName.
+ if execName is None or execName == importName:
+ reraise(excValue, excTraceback)
+ excTraceback = excTraceback.tb_next
+ raise _NoModuleFound()
+
+
+
+def namedAny(name):
+ """
+ Retrieve a Python object by its fully qualified name from the global Python
+ module namespace. The first part of the name, that describes a module,
+ will be discovered and imported. Each subsequent part of the name is
+ treated as the name of an attribute of the object specified by all of the
+ name which came before it. For example, the fully-qualified name of this
+ object is 'twisted.python.reflect.namedAny'.
+
+ @type name: L{str}
+ @param name: The name of the object to return.
+
+ @raise InvalidName: If the name is an empty string, starts or ends with
+ a '.', or is otherwise syntactically incorrect.
+
+ @raise ModuleNotFound: If the name is syntactically correct but the
+ module it specifies cannot be imported because it does not appear to
+ exist.
+
+ @raise ObjectNotFound: If the name is syntactically correct, includes at
+ least one '.', but the module it specifies cannot be imported because
+ it does not appear to exist.
+
+ @raise AttributeError: If an attribute of an object along the way cannot be
+ accessed, or a module along the way is not found.
+
+ @return: the Python object identified by 'name'.
+ """
+ if not name:
+ raise InvalidName('Empty module name')
+
+ names = name.split('.')
+
+ # if the name starts or ends with a '.' or contains '..', the __import__
+ # will raise an 'Empty module name' error. This will provide a better error
+ # message.
+ if '' in names:
+ raise InvalidName(
+ "name must be a string giving a '.'-separated list of Python "
+ "identifiers, not %r" % (name,))
+
+ topLevelPackage = None
+ moduleNames = names[:]
+ while not topLevelPackage:
+ if moduleNames:
+ trialname = '.'.join(moduleNames)
+ try:
+ topLevelPackage = _importAndCheckStack(trialname)
+ except _NoModuleFound:
+ moduleNames.pop()
+ else:
+ if len(names) == 1:
+ raise ModuleNotFound("No module named %r" % (name,))
+ else:
+ raise ObjectNotFound('%r does not name an object' % (name,))
+
+ obj = topLevelPackage
+ for n in names[1:]:
+ obj = getattr(obj, n)
+
+ return obj
diff --git a/jsonschema/_types.py b/jsonschema/_types.py
new file mode 100644
index 0000000..50bcf99
--- /dev/null
+++ b/jsonschema/_types.py
@@ -0,0 +1,187 @@
+import numbers
+
+from pyrsistent import pmap
+import attr
+
+from jsonschema.exceptions import UndefinedTypeCheck
+
+
+def is_array(checker, instance):
+ return isinstance(instance, list)
+
+
+def is_bool(checker, instance):
+ return isinstance(instance, bool)
+
+
+def is_integer(checker, instance):
+ # bool inherits from int, so ensure bools aren't reported as ints
+ if isinstance(instance, bool):
+ return False
+ return isinstance(instance, int)
+
+
+def is_null(checker, instance):
+ return instance is None
+
+
+def is_number(checker, instance):
+ # bool inherits from int, so ensure bools aren't reported as ints
+ if isinstance(instance, bool):
+ return False
+ return isinstance(instance, numbers.Number)
+
+
+def is_object(checker, instance):
+ return isinstance(instance, dict)
+
+
+def is_string(checker, instance):
+ return isinstance(instance, str)
+
+
+def is_any(checker, instance):
+ return True
+
+
+@attr.s(frozen=True)
+class TypeChecker(object):
+ """
+ A ``type`` property checker.
+
+ A `TypeChecker` performs type checking for an `IValidator`. Type
+ checks to perform are updated using `TypeChecker.redefine` or
+ `TypeChecker.redefine_many` and removed via `TypeChecker.remove`.
+ Each of these return a new `TypeChecker` object.
+
+ Arguments:
+
+ type_checkers (dict):
+
+ The initial mapping of types to their checking functions.
+ """
+ _type_checkers = attr.ib(default=pmap(), converter=pmap)
+
+ def is_type(self, instance, type):
+ """
+ Check if the instance is of the appropriate type.
+
+ Arguments:
+
+ instance (object):
+
+ The instance to check
+
+ type (str):
+
+ The name of the type that is expected.
+
+ Returns:
+
+ bool: Whether it conformed.
+
+
+ Raises:
+
+ `jsonschema.exceptions.UndefinedTypeCheck`:
+ if type is unknown to this object.
+ """
+ try:
+ fn = self._type_checkers[type]
+ except KeyError:
+ raise UndefinedTypeCheck(type)
+
+ return fn(self, instance)
+
+ def redefine(self, type, fn):
+ """
+ Produce a new checker with the given type redefined.
+
+ Arguments:
+
+ type (str):
+
+ The name of the type to check.
+
+ fn (collections.abc.Callable):
+
+ A function taking exactly two parameters - the type
+ checker calling the function and the instance to check.
+ The function should return true if instance is of this
+ type and false otherwise.
+
+ Returns:
+
+ A new `TypeChecker` instance.
+ """
+ return self.redefine_many({type: fn})
+
+ def redefine_many(self, definitions=()):
+ """
+ Produce a new checker with the given types redefined.
+
+ Arguments:
+
+ definitions (dict):
+
+ A dictionary mapping types to their checking functions.
+
+ Returns:
+
+ A new `TypeChecker` instance.
+ """
+ return attr.evolve(
+ self, type_checkers=self._type_checkers.update(definitions),
+ )
+
+ def remove(self, *types):
+ """
+ Produce a new checker with the given types forgotten.
+
+ Arguments:
+
+ types (~collections.abc.Iterable):
+
+ the names of the types to remove.
+
+ Returns:
+
+ A new `TypeChecker` instance
+
+ Raises:
+
+ `jsonschema.exceptions.UndefinedTypeCheck`:
+
+ if any given type is unknown to this object
+ """
+
+ checkers = self._type_checkers
+ for each in types:
+ try:
+ checkers = checkers.remove(each)
+ except KeyError:
+ raise UndefinedTypeCheck(each)
+ return attr.evolve(self, type_checkers=checkers)
+
+
+draft3_type_checker = TypeChecker(
+ {
+ u"any": is_any,
+ u"array": is_array,
+ u"boolean": is_bool,
+ u"integer": is_integer,
+ u"object": is_object,
+ u"null": is_null,
+ u"number": is_number,
+ u"string": is_string,
+ },
+)
+draft4_type_checker = draft3_type_checker.remove(u"any")
+draft6_type_checker = draft4_type_checker.redefine(
+ u"integer",
+ lambda checker, instance: (
+ is_integer(checker, instance) or
+ isinstance(instance, float) and instance.is_integer()
+ ),
+)
+draft7_type_checker = draft6_type_checker
diff --git a/jsonschema/_utils.py b/jsonschema/_utils.py
new file mode 100644
index 0000000..a320b2d
--- /dev/null
+++ b/jsonschema/_utils.py
@@ -0,0 +1,213 @@
+from collections.abc import MutableMapping
+from urllib.parse import urlsplit
+import itertools
+import json
+import pkgutil
+import re
+
+
+class URIDict(MutableMapping):
+ """
+ Dictionary which uses normalized URIs as keys.
+ """
+
+ def normalize(self, uri):
+ return urlsplit(uri).geturl()
+
+ def __init__(self, *args, **kwargs):
+ self.store = dict()
+ self.store.update(*args, **kwargs)
+
+ def __getitem__(self, uri):
+ return self.store[self.normalize(uri)]
+
+ def __setitem__(self, uri, value):
+ self.store[self.normalize(uri)] = value
+
+ def __delitem__(self, uri):
+ del self.store[self.normalize(uri)]
+
+ def __iter__(self):
+ return iter(self.store)
+
+ def __len__(self):
+ return len(self.store)
+
+ def __repr__(self):
+ return repr(self.store)
+
+
+class Unset(object):
+ """
+ An as-of-yet unset attribute or unprovided default parameter.
+ """
+
+ def __repr__(self):
+ return "<unset>"
+
+
+def load_schema(name):
+ """
+ Load a schema from ./schemas/``name``.json and return it.
+ """
+
+ data = pkgutil.get_data("jsonschema", "schemas/{0}.json".format(name))
+ return json.loads(data.decode("utf-8"))
+
+
+def indent(string, times=1):
+ """
+ A dumb version of `textwrap.indent` from Python 3.3.
+ """
+
+ return "\n".join(" " * (4 * times) + line for line in string.splitlines())
+
+
+def format_as_index(indices):
+ """
+ Construct a single string containing indexing operations for the indices.
+
+ For example, [1, 2, "foo"] -> [1][2]["foo"]
+
+ Arguments:
+
+ indices (sequence):
+
+ The indices to format.
+ """
+
+ if not indices:
+ return ""
+ return "[%s]" % "][".join(repr(index) for index in indices)
+
+
+def find_additional_properties(instance, schema):
+ """
+ Return the set of additional properties for the given ``instance``.
+
+ Weeds out properties that should have been validated by ``properties`` and
+ / or ``patternProperties``.
+
+ Assumes ``instance`` is dict-like already.
+ """
+
+ properties = schema.get("properties", {})
+ patterns = "|".join(schema.get("patternProperties", {}))
+ for property in instance:
+ if property not in properties:
+ if patterns and re.search(patterns, property):
+ continue
+ yield property
+
+
+def extras_msg(extras):
+ """
+ Create an error message for extra items or properties.
+ """
+
+ if len(extras) == 1:
+ verb = "was"
+ else:
+ verb = "were"
+ return ", ".join(repr(extra) for extra in extras), verb
+
+
+def types_msg(instance, types):
+ """
+ Create an error message for a failure to match the given types.
+
+ If the ``instance`` is an object and contains a ``name`` property, it will
+ be considered to be a description of that object and used as its type.
+
+ Otherwise the message is simply the reprs of the given ``types``.
+ """
+
+ reprs = []
+ for type in types:
+ try:
+ reprs.append(repr(type["name"]))
+ except Exception:
+ reprs.append(repr(type))
+ return "%r is not of type %s" % (instance, ", ".join(reprs))
+
+
+def flatten(suitable_for_isinstance):
+ """
+ isinstance() can accept a bunch of really annoying different types:
+
+ * a single type
+ * a tuple of types
+ * an arbitrary nested tree of tuples
+
+ Return a flattened tuple of the given argument.
+ """
+
+ types = set()
+
+ if not isinstance(suitable_for_isinstance, tuple):
+ suitable_for_isinstance = (suitable_for_isinstance,)
+ for thing in suitable_for_isinstance:
+ if isinstance(thing, tuple):
+ types.update(flatten(thing))
+ else:
+ types.add(thing)
+ return tuple(types)
+
+
+def ensure_list(thing):
+ """
+ Wrap ``thing`` in a list if it's a single str.
+
+ Otherwise, return it unchanged.
+ """
+
+ if isinstance(thing, str):
+ return [thing]
+ return thing
+
+
+def equal(one, two):
+ """
+ Check if two things are equal, but evade booleans and ints being equal.
+ """
+ return unbool(one) == unbool(two)
+
+
+def unbool(element, true=object(), false=object()):
+ """
+ A hack to make True and 1 and False and 0 unique for ``uniq``.
+ """
+
+ if element is True:
+ return true
+ elif element is False:
+ return false
+ return element
+
+
+def uniq(container):
+ """
+ Check if all of a container's elements are unique.
+
+ Successively tries first to rely that the elements are hashable, then
+ falls back on them being sortable, and finally falls back on brute
+ force.
+ """
+
+ try:
+ return len(set(unbool(i) for i in container)) == len(container)
+ except TypeError:
+ try:
+ sort = sorted(unbool(i) for i in container)
+ sliced = itertools.islice(sort, 1, None)
+ for i, j in zip(sort, sliced):
+ if i == j:
+ return False
+ except (NotImplementedError, TypeError):
+ seen = []
+ for e in container:
+ e = unbool(e)
+ if e in seen:
+ return False
+ seen.append(e)
+ return True
diff --git a/jsonschema/_validators.py b/jsonschema/_validators.py
new file mode 100644
index 0000000..d7097ff
--- /dev/null
+++ b/jsonschema/_validators.py
@@ -0,0 +1,372 @@
+import re
+
+from jsonschema._utils import (
+ ensure_list,
+ equal,
+ extras_msg,
+ find_additional_properties,
+ types_msg,
+ unbool,
+ uniq,
+)
+from jsonschema.exceptions import FormatError, ValidationError
+
+
+def patternProperties(validator, patternProperties, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+
+ for pattern, subschema in patternProperties.items():
+ for k, v in instance.items():
+ if re.search(pattern, k):
+ for error in validator.descend(
+ v, subschema, path=k, schema_path=pattern,
+ ):
+ yield error
+
+
+def propertyNames(validator, propertyNames, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+
+ for property in instance:
+ for error in validator.descend(
+ instance=property,
+ schema=propertyNames,
+ ):
+ yield error
+
+
+def additionalProperties(validator, aP, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+
+ extras = set(find_additional_properties(instance, schema))
+
+ if validator.is_type(aP, "object"):
+ for extra in extras:
+ for error in validator.descend(instance[extra], aP, path=extra):
+ yield error
+ elif not aP and extras:
+ if "patternProperties" in schema:
+ patterns = sorted(schema["patternProperties"])
+ if len(extras) == 1:
+ verb = "does"
+ else:
+ verb = "do"
+ error = "%s %s not match any of the regexes: %s" % (
+ ", ".join(map(repr, sorted(extras))),
+ verb,
+ ", ".join(map(repr, patterns)),
+ )
+ yield ValidationError(error)
+ else:
+ error = "Additional properties are not allowed (%s %s unexpected)"
+ yield ValidationError(error % extras_msg(extras))
+
+
+def items(validator, items, instance, schema):
+ if not validator.is_type(instance, "array"):
+ return
+
+ if validator.is_type(items, "array"):
+ for (index, item), subschema in zip(enumerate(instance), items):
+ for error in validator.descend(
+ item, subschema, path=index, schema_path=index,
+ ):
+ yield error
+ else:
+ for index, item in enumerate(instance):
+ for error in validator.descend(item, items, path=index):
+ yield error
+
+
+def additionalItems(validator, aI, instance, schema):
+ if (
+ not validator.is_type(instance, "array") or
+ validator.is_type(schema.get("items", {}), "object")
+ ):
+ return
+
+ len_items = len(schema.get("items", []))
+ if validator.is_type(aI, "object"):
+ for index, item in enumerate(instance[len_items:], start=len_items):
+ for error in validator.descend(item, aI, path=index):
+ yield error
+ elif not aI and len(instance) > len(schema.get("items", [])):
+ error = "Additional items are not allowed (%s %s unexpected)"
+ yield ValidationError(
+ error %
+ extras_msg(instance[len(schema.get("items", [])):])
+ )
+
+
+def const(validator, const, instance, schema):
+ if not equal(instance, const):
+ yield ValidationError("%r was expected" % (const,))
+
+
+def contains(validator, contains, instance, schema):
+ if not validator.is_type(instance, "array"):
+ return
+
+ if not any(validator.is_valid(element, contains) for element in instance):
+ yield ValidationError(
+ "None of %r are valid under the given schema" % (instance,)
+ )
+
+
+def exclusiveMinimum(validator, minimum, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if instance <= minimum:
+ yield ValidationError(
+ "%r is less than or equal to the minimum of %r" % (
+ instance, minimum,
+ ),
+ )
+
+
+def exclusiveMaximum(validator, maximum, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if instance >= maximum:
+ yield ValidationError(
+ "%r is greater than or equal to the maximum of %r" % (
+ instance, maximum,
+ ),
+ )
+
+
+def minimum(validator, minimum, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if instance < minimum:
+ yield ValidationError(
+ "%r is less than the minimum of %r" % (instance, minimum)
+ )
+
+
+def maximum(validator, maximum, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if instance > maximum:
+ yield ValidationError(
+ "%r is greater than the maximum of %r" % (instance, maximum)
+ )
+
+
+def multipleOf(validator, dB, instance, schema):
+ if not validator.is_type(instance, "number"):
+ return
+
+ if isinstance(dB, float):
+ quotient = instance / dB
+ failed = int(quotient) != quotient
+ else:
+ failed = instance % dB
+
+ if failed:
+ yield ValidationError("%r is not a multiple of %r" % (instance, dB))
+
+
+def minItems(validator, mI, instance, schema):
+ if validator.is_type(instance, "array") and len(instance) < mI:
+ yield ValidationError("%r is too short" % (instance,))
+
+
+def maxItems(validator, mI, instance, schema):
+ if validator.is_type(instance, "array") and len(instance) > mI:
+ yield ValidationError("%r is too long" % (instance,))
+
+
+def uniqueItems(validator, uI, instance, schema):
+ if (
+ uI and
+ validator.is_type(instance, "array") and
+ not uniq(instance)
+ ):
+ yield ValidationError("%r has non-unique elements" % (instance,))
+
+
+def pattern(validator, patrn, instance, schema):
+ if (
+ validator.is_type(instance, "string") and
+ not re.search(patrn, instance)
+ ):
+ yield ValidationError("%r does not match %r" % (instance, patrn))
+
+
+def format(validator, format, instance, schema):
+ if validator.format_checker is not None:
+ try:
+ validator.format_checker.check(instance, format)
+ except FormatError as error:
+ yield ValidationError(error.message, cause=error.cause)
+
+
+def minLength(validator, mL, instance, schema):
+ if validator.is_type(instance, "string") and len(instance) < mL:
+ yield ValidationError("%r is too short" % (instance,))
+
+
+def maxLength(validator, mL, instance, schema):
+ if validator.is_type(instance, "string") and len(instance) > mL:
+ yield ValidationError("%r is too long" % (instance,))
+
+
+def dependencies(validator, dependencies, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+
+ for property, dependency in dependencies.items():
+ if property not in instance:
+ continue
+
+ if validator.is_type(dependency, "array"):
+ for each in dependency:
+ if each not in instance:
+ message = "%r is a dependency of %r"
+ yield ValidationError(message % (each, property))
+ else:
+ for error in validator.descend(
+ instance, dependency, schema_path=property,
+ ):
+ yield error
+
+
+def enum(validator, enums, instance, schema):
+ if instance == 0 or instance == 1:
+ unbooled = unbool(instance)
+ if all(unbooled != unbool(each) for each in enums):
+ yield ValidationError("%r is not one of %r" % (instance, enums))
+ elif instance not in enums:
+ yield ValidationError("%r is not one of %r" % (instance, enums))
+
+
+def ref(validator, ref, instance, schema):
+ resolve = getattr(validator.resolver, "resolve", None)
+ if resolve is None:
+ with validator.resolver.resolving(ref) as resolved:
+ for error in validator.descend(instance, resolved):
+ yield error
+ else:
+ scope, resolved = validator.resolver.resolve(ref)
+ validator.resolver.push_scope(scope)
+
+ try:
+ for error in validator.descend(instance, resolved):
+ yield error
+ finally:
+ validator.resolver.pop_scope()
+
+
+def type(validator, types, instance, schema):
+ types = ensure_list(types)
+
+ if not any(validator.is_type(instance, type) for type in types):
+ yield ValidationError(types_msg(instance, types))
+
+
+def properties(validator, properties, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+
+ for property, subschema in properties.items():
+ if property in instance:
+ for error in validator.descend(
+ instance[property],
+ subschema,
+ path=property,
+ schema_path=property,
+ ):
+ yield error
+
+
+def required(validator, required, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+ for property in required:
+ if property not in instance:
+ yield ValidationError("%r is a required property" % property)
+
+
+def minProperties(validator, mP, instance, schema):
+ if validator.is_type(instance, "object") and len(instance) < mP:
+ yield ValidationError(
+ "%r does not have enough properties" % (instance,)
+ )
+
+
+def maxProperties(validator, mP, instance, schema):
+ if not validator.is_type(instance, "object"):
+ return
+ if validator.is_type(instance, "object") and len(instance) > mP:
+ yield ValidationError("%r has too many properties" % (instance,))
+
+
+def allOf(validator, allOf, instance, schema):
+ for index, subschema in enumerate(allOf):
+ for error in validator.descend(instance, subschema, schema_path=index):
+ yield error
+
+
+def anyOf(validator, anyOf, instance, schema):
+ all_errors = []
+ for index, subschema in enumerate(anyOf):
+ errs = list(validator.descend(instance, subschema, schema_path=index))
+ if not errs:
+ break
+ all_errors.extend(errs)
+ else:
+ yield ValidationError(
+ "%r is not valid under any of the given schemas" % (instance,),
+ context=all_errors,
+ )
+
+
+def oneOf(validator, oneOf, instance, schema):
+ subschemas = enumerate(oneOf)
+ all_errors = []
+ for index, subschema in subschemas:
+ errs = list(validator.descend(instance, subschema, schema_path=index))
+ if not errs:
+ first_valid = subschema
+ break
+ all_errors.extend(errs)
+ else:
+ yield ValidationError(
+ "%r is not valid under any of the given schemas" % (instance,),
+ context=all_errors,
+ )
+
+ more_valid = [s for i, s in subschemas if validator.is_valid(instance, s)]
+ if more_valid:
+ more_valid.append(first_valid)
+ reprs = ", ".join(repr(schema) for schema in more_valid)
+ yield ValidationError(
+ "%r is valid under each of %s" % (instance, reprs)
+ )
+
+
+def not_(validator, not_schema, instance, schema):
+ if validator.is_valid(instance, not_schema):
+ yield ValidationError(
+ "%r is not allowed for %r" % (not_schema, instance)
+ )
+
+
+def if_(validator, if_schema, instance, schema):
+ if validator.is_valid(instance, if_schema):
+ if u"then" in schema:
+ then = schema[u"then"]
+ for error in validator.descend(instance, then, schema_path="then"):
+ yield error
+ elif u"else" in schema:
+ else_ = schema[u"else"]
+ for error in validator.descend(instance, else_, schema_path="else"):
+ yield error
diff --git a/jsonschema/benchmarks/__init__.py b/jsonschema/benchmarks/__init__.py
new file mode 100644
index 0000000..e3dcc68
--- /dev/null
+++ b/jsonschema/benchmarks/__init__.py
@@ -0,0 +1,5 @@
+"""
+Benchmarks for validation.
+
+This package is *not* public API.
+"""
diff --git a/jsonschema/benchmarks/issue232.py b/jsonschema/benchmarks/issue232.py
new file mode 100644
index 0000000..e08dff9
--- /dev/null
+++ b/jsonschema/benchmarks/issue232.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+"""
+A performance benchmark using the example from issue #232.
+
+See https://github.com/Julian/jsonschema/pull/232.
+"""
+from pathlib import Path
+
+from pyperf import Runner
+from pyrsistent import m
+
+from jsonschema.tests._suite import Version
+import jsonschema
+
+issue232 = Version(
+ path=Path(__file__).parent / "issue232",
+ remotes=m(),
+ name="issue232",
+)
+
+
+if __name__ == "__main__":
+ issue232.benchmark(
+ runner=Runner(),
+ Validator=jsonschema.Draft4Validator,
+ )
diff --git a/jsonschema/benchmarks/issue232/issue.json b/jsonschema/benchmarks/issue232/issue.json
new file mode 100644
index 0000000..804c340
--- /dev/null
+++ b/jsonschema/benchmarks/issue232/issue.json
@@ -0,0 +1,2653 @@
+[
+ {
+ "description": "Petstore",
+ "schema": {
+ "title": "A JSON Schema for Swagger 2.0 API.",
+ "id": "http://swagger.io/v2/schema.json#",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "type": "object",
+ "required": [
+ "swagger",
+ "info",
+ "paths"
+ ],
+ "additionalProperties": false,
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ },
+ "properties": {
+ "swagger": {
+ "type": "string",
+ "enum": [
+ "2.0"
+ ],
+ "description": "The Swagger version of this document."
+ },
+ "info": {
+ "$ref": "#/definitions/info"
+ },
+ "host": {
+ "type": "string",
+ "pattern": "^[^{}/ :\\\\]+(?::\\d+)?$",
+ "description": "The host (name or ip) of the API. Example: 'swagger.io'"
+ },
+ "basePath": {
+ "type": "string",
+ "pattern": "^/",
+ "description": "The base path to the API. Example: '/api'."
+ },
+ "schemes": {
+ "$ref": "#/definitions/schemesList"
+ },
+ "consumes": {
+ "description": "A list of MIME types accepted by the API.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/mediaTypeList"
+ }
+ ]
+ },
+ "produces": {
+ "description": "A list of MIME types the API can produce.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/mediaTypeList"
+ }
+ ]
+ },
+ "paths": {
+ "$ref": "#/definitions/paths"
+ },
+ "definitions": {
+ "$ref": "#/definitions/definitions"
+ },
+ "parameters": {
+ "$ref": "#/definitions/parameterDefinitions"
+ },
+ "responses": {
+ "$ref": "#/definitions/responseDefinitions"
+ },
+ "security": {
+ "$ref": "#/definitions/security"
+ },
+ "securityDefinitions": {
+ "$ref": "#/definitions/securityDefinitions"
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/tag"
+ },
+ "uniqueItems": true
+ },
+ "externalDocs": {
+ "$ref": "#/definitions/externalDocs"
+ }
+ },
+ "definitions": {
+ "info": {
+ "type": "object",
+ "description": "General information about the API.",
+ "required": [
+ "version",
+ "title"
+ ],
+ "additionalProperties": false,
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ },
+ "properties": {
+ "title": {
+ "type": "string",
+ "description": "A unique and precise title of the API."
+ },
+ "version": {
+ "type": "string",
+ "description": "A semantic version number of the API."
+ },
+ "description": {
+ "type": "string",
+ "description": "A longer description of the API. Should be different from the title. GitHub Flavored Markdown is allowed."
+ },
+ "termsOfService": {
+ "type": "string",
+ "description": "The terms of service for the API."
+ },
+ "contact": {
+ "$ref": "#/definitions/contact"
+ },
+ "license": {
+ "$ref": "#/definitions/license"
+ }
+ }
+ },
+ "contact": {
+ "type": "object",
+ "description": "Contact information for the owners of the API.",
+ "additionalProperties": false,
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "The identifying name of the contact person/organization."
+ },
+ "url": {
+ "type": "string",
+ "description": "The URL pointing to the contact information.",
+ "format": "uri"
+ },
+ "email": {
+ "type": "string",
+ "description": "The email address of the contact person/organization.",
+ "format": "email"
+ }
+ },
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ }
+ },
+ "license": {
+ "type": "object",
+ "required": [
+ "name"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "The name of the license type. It's encouraged to use an OSI compatible license."
+ },
+ "url": {
+ "type": "string",
+ "description": "The URL pointing to the license.",
+ "format": "uri"
+ }
+ },
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ }
+ },
+ "paths": {
+ "type": "object",
+ "description": "Relative paths to the individual endpoints. They must be relative to the 'basePath'.",
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ },
+ "^/": {
+ "$ref": "#/definitions/pathItem"
+ }
+ },
+ "additionalProperties": false
+ },
+ "definitions": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/definitions/schema"
+ },
+ "description": "One or more JSON objects describing the schemas being consumed and produced by the API."
+ },
+ "parameterDefinitions": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/definitions/parameter"
+ },
+ "description": "One or more JSON representations for parameters"
+ },
+ "responseDefinitions": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/definitions/response"
+ },
+ "description": "One or more JSON representations for parameters"
+ },
+ "externalDocs": {
+ "type": "object",
+ "additionalProperties": false,
+ "description": "information about external documentation",
+ "required": [
+ "url"
+ ],
+ "properties": {
+ "description": {
+ "type": "string"
+ },
+ "url": {
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ }
+ },
+ "examples": {
+ "type": "object",
+ "additionalProperties": true
+ },
+ "mimeType": {
+ "type": "string",
+ "description": "The MIME type of the HTTP message."
+ },
+ "operation": {
+ "type": "object",
+ "required": [
+ "responses"
+ ],
+ "additionalProperties": false,
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ },
+ "properties": {
+ "tags": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "uniqueItems": true
+ },
+ "summary": {
+ "type": "string",
+ "description": "A brief summary of the operation."
+ },
+ "description": {
+ "type": "string",
+ "description": "A longer description of the operation, GitHub Flavored Markdown is allowed."
+ },
+ "externalDocs": {
+ "$ref": "#/definitions/externalDocs"
+ },
+ "operationId": {
+ "type": "string",
+ "description": "A unique identifier of the operation."
+ },
+ "produces": {
+ "description": "A list of MIME types the API can produce.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/mediaTypeList"
+ }
+ ]
+ },
+ "consumes": {
+ "description": "A list of MIME types the API can consume.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/mediaTypeList"
+ }
+ ]
+ },
+ "parameters": {
+ "$ref": "#/definitions/parametersList"
+ },
+ "responses": {
+ "$ref": "#/definitions/responses"
+ },
+ "schemes": {
+ "$ref": "#/definitions/schemesList"
+ },
+ "deprecated": {
+ "type": "boolean",
+ "default": false
+ },
+ "security": {
+ "$ref": "#/definitions/security"
+ }
+ }
+ },
+ "pathItem": {
+ "type": "object",
+ "additionalProperties": false,
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ },
+ "properties": {
+ "$ref": {
+ "type": "string"
+ },
+ "get": {
+ "$ref": "#/definitions/operation"
+ },
+ "put": {
+ "$ref": "#/definitions/operation"
+ },
+ "post": {
+ "$ref": "#/definitions/operation"
+ },
+ "delete": {
+ "$ref": "#/definitions/operation"
+ },
+ "options": {
+ "$ref": "#/definitions/operation"
+ },
+ "head": {
+ "$ref": "#/definitions/operation"
+ },
+ "patch": {
+ "$ref": "#/definitions/operation"
+ },
+ "parameters": {
+ "$ref": "#/definitions/parametersList"
+ }
+ }
+ },
+ "responses": {
+ "type": "object",
+ "description": "Response objects names can either be any valid HTTP status code or 'default'.",
+ "minProperties": 1,
+ "additionalProperties": false,
+ "patternProperties": {
+ "^([0-9]{3})$|^(default)$": {
+ "$ref": "#/definitions/responseValue"
+ },
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ },
+ "not": {
+ "type": "object",
+ "additionalProperties": false,
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ }
+ }
+ },
+ "responseValue": {
+ "oneOf": [
+ {
+ "$ref": "#/definitions/response"
+ },
+ {
+ "$ref": "#/definitions/jsonReference"
+ }
+ ]
+ },
+ "response": {
+ "type": "object",
+ "required": [
+ "description"
+ ],
+ "properties": {
+ "description": {
+ "type": "string"
+ },
+ "schema": {
+ "oneOf": [
+ {
+ "$ref": "#/definitions/schema"
+ },
+ {
+ "$ref": "#/definitions/fileSchema"
+ }
+ ]
+ },
+ "headers": {
+ "$ref": "#/definitions/headers"
+ },
+ "examples": {
+ "$ref": "#/definitions/examples"
+ }
+ },
+ "additionalProperties": false,
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ }
+ },
+ "headers": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/definitions/header"
+ }
+ },
+ "header": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "type"
+ ],
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "string",
+ "number",
+ "integer",
+ "boolean",
+ "array"
+ ]
+ },
+ "format": {
+ "type": "string"
+ },
+ "items": {
+ "$ref": "#/definitions/primitivesItems"
+ },
+ "collectionFormat": {
+ "$ref": "#/definitions/collectionFormat"
+ },
+ "default": {
+ "$ref": "#/definitions/default"
+ },
+ "maximum": {
+ "$ref": "#/definitions/maximum"
+ },
+ "exclusiveMaximum": {
+ "$ref": "#/definitions/exclusiveMaximum"
+ },
+ "minimum": {
+ "$ref": "#/definitions/minimum"
+ },
+ "exclusiveMinimum": {
+ "$ref": "#/definitions/exclusiveMinimum"
+ },
+ "maxLength": {
+ "$ref": "#/definitions/maxLength"
+ },
+ "minLength": {
+ "$ref": "#/definitions/minLength"
+ },
+ "pattern": {
+ "$ref": "#/definitions/pattern"
+ },
+ "maxItems": {
+ "$ref": "#/definitions/maxItems"
+ },
+ "minItems": {
+ "$ref": "#/definitions/minItems"
+ },
+ "uniqueItems": {
+ "$ref": "#/definitions/uniqueItems"
+ },
+ "enum": {
+ "$ref": "#/definitions/enum"
+ },
+ "multipleOf": {
+ "$ref": "#/definitions/multipleOf"
+ },
+ "description": {
+ "type": "string"
+ }
+ },
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ }
+ },
+ "vendorExtension": {
+ "description": "Any property starting with x- is valid.",
+ "additionalProperties": true,
+ "additionalItems": true
+ },
+ "bodyParameter": {
+ "type": "object",
+ "required": [
+ "name",
+ "in",
+ "schema"
+ ],
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ },
+ "properties": {
+ "description": {
+ "type": "string",
+ "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed."
+ },
+ "name": {
+ "type": "string",
+ "description": "The name of the parameter."
+ },
+ "in": {
+ "type": "string",
+ "description": "Determines the location of the parameter.",
+ "enum": [
+ "body"
+ ]
+ },
+ "required": {
+ "type": "boolean",
+ "description": "Determines whether or not this parameter is required or optional.",
+ "default": false
+ },
+ "schema": {
+ "$ref": "#/definitions/schema"
+ }
+ },
+ "additionalProperties": false
+ },
+ "headerParameterSubSchema": {
+ "additionalProperties": false,
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ },
+ "properties": {
+ "required": {
+ "type": "boolean",
+ "description": "Determines whether or not this parameter is required or optional.",
+ "default": false
+ },
+ "in": {
+ "type": "string",
+ "description": "Determines the location of the parameter.",
+ "enum": [
+ "header"
+ ]
+ },
+ "description": {
+ "type": "string",
+ "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed."
+ },
+ "name": {
+ "type": "string",
+ "description": "The name of the parameter."
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "string",
+ "number",
+ "boolean",
+ "integer",
+ "array"
+ ]
+ },
+ "format": {
+ "type": "string"
+ },
+ "items": {
+ "$ref": "#/definitions/primitivesItems"
+ },
+ "collectionFormat": {
+ "$ref": "#/definitions/collectionFormat"
+ },
+ "default": {
+ "$ref": "#/definitions/default"
+ },
+ "maximum": {
+ "$ref": "#/definitions/maximum"
+ },
+ "exclusiveMaximum": {
+ "$ref": "#/definitions/exclusiveMaximum"
+ },
+ "minimum": {
+ "$ref": "#/definitions/minimum"
+ },
+ "exclusiveMinimum": {
+ "$ref": "#/definitions/exclusiveMinimum"
+ },
+ "maxLength": {
+ "$ref": "#/definitions/maxLength"
+ },
+ "minLength": {
+ "$ref": "#/definitions/minLength"
+ },
+ "pattern": {
+ "$ref": "#/definitions/pattern"
+ },
+ "maxItems": {
+ "$ref": "#/definitions/maxItems"
+ },
+ "minItems": {
+ "$ref": "#/definitions/minItems"
+ },
+ "uniqueItems": {
+ "$ref": "#/definitions/uniqueItems"
+ },
+ "enum": {
+ "$ref": "#/definitions/enum"
+ },
+ "multipleOf": {
+ "$ref": "#/definitions/multipleOf"
+ }
+ }
+ },
+ "queryParameterSubSchema": {
+ "additionalProperties": false,
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ },
+ "properties": {
+ "required": {
+ "type": "boolean",
+ "description": "Determines whether or not this parameter is required or optional.",
+ "default": false
+ },
+ "in": {
+ "type": "string",
+ "description": "Determines the location of the parameter.",
+ "enum": [
+ "query"
+ ]
+ },
+ "description": {
+ "type": "string",
+ "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed."
+ },
+ "name": {
+ "type": "string",
+ "description": "The name of the parameter."
+ },
+ "allowEmptyValue": {
+ "type": "boolean",
+ "default": false,
+ "description": "allows sending a parameter by name only or with an empty value."
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "string",
+ "number",
+ "boolean",
+ "integer",
+ "array"
+ ]
+ },
+ "format": {
+ "type": "string"
+ },
+ "items": {
+ "$ref": "#/definitions/primitivesItems"
+ },
+ "collectionFormat": {
+ "$ref": "#/definitions/collectionFormatWithMulti"
+ },
+ "default": {
+ "$ref": "#/definitions/default"
+ },
+ "maximum": {
+ "$ref": "#/definitions/maximum"
+ },
+ "exclusiveMaximum": {
+ "$ref": "#/definitions/exclusiveMaximum"
+ },
+ "minimum": {
+ "$ref": "#/definitions/minimum"
+ },
+ "exclusiveMinimum": {
+ "$ref": "#/definitions/exclusiveMinimum"
+ },
+ "maxLength": {
+ "$ref": "#/definitions/maxLength"
+ },
+ "minLength": {
+ "$ref": "#/definitions/minLength"
+ },
+ "pattern": {
+ "$ref": "#/definitions/pattern"
+ },
+ "maxItems": {
+ "$ref": "#/definitions/maxItems"
+ },
+ "minItems": {
+ "$ref": "#/definitions/minItems"
+ },
+ "uniqueItems": {
+ "$ref": "#/definitions/uniqueItems"
+ },
+ "enum": {
+ "$ref": "#/definitions/enum"
+ },
+ "multipleOf": {
+ "$ref": "#/definitions/multipleOf"
+ }
+ }
+ },
+ "formDataParameterSubSchema": {
+ "additionalProperties": false,
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ },
+ "properties": {
+ "required": {
+ "type": "boolean",
+ "description": "Determines whether or not this parameter is required or optional.",
+ "default": false
+ },
+ "in": {
+ "type": "string",
+ "description": "Determines the location of the parameter.",
+ "enum": [
+ "formData"
+ ]
+ },
+ "description": {
+ "type": "string",
+ "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed."
+ },
+ "name": {
+ "type": "string",
+ "description": "The name of the parameter."
+ },
+ "allowEmptyValue": {
+ "type": "boolean",
+ "default": false,
+ "description": "allows sending a parameter by name only or with an empty value."
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "string",
+ "number",
+ "boolean",
+ "integer",
+ "array",
+ "file"
+ ]
+ },
+ "format": {
+ "type": "string"
+ },
+ "items": {
+ "$ref": "#/definitions/primitivesItems"
+ },
+ "collectionFormat": {
+ "$ref": "#/definitions/collectionFormatWithMulti"
+ },
+ "default": {
+ "$ref": "#/definitions/default"
+ },
+ "maximum": {
+ "$ref": "#/definitions/maximum"
+ },
+ "exclusiveMaximum": {
+ "$ref": "#/definitions/exclusiveMaximum"
+ },
+ "minimum": {
+ "$ref": "#/definitions/minimum"
+ },
+ "exclusiveMinimum": {
+ "$ref": "#/definitions/exclusiveMinimum"
+ },
+ "maxLength": {
+ "$ref": "#/definitions/maxLength"
+ },
+ "minLength": {
+ "$ref": "#/definitions/minLength"
+ },
+ "pattern": {
+ "$ref": "#/definitions/pattern"
+ },
+ "maxItems": {
+ "$ref": "#/definitions/maxItems"
+ },
+ "minItems": {
+ "$ref": "#/definitions/minItems"
+ },
+ "uniqueItems": {
+ "$ref": "#/definitions/uniqueItems"
+ },
+ "enum": {
+ "$ref": "#/definitions/enum"
+ },
+ "multipleOf": {
+ "$ref": "#/definitions/multipleOf"
+ }
+ }
+ },
+ "pathParameterSubSchema": {
+ "additionalProperties": false,
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ },
+ "required": [
+ "required"
+ ],
+ "properties": {
+ "required": {
+ "type": "boolean",
+ "enum": [
+ true
+ ],
+ "description": "Determines whether or not this parameter is required or optional."
+ },
+ "in": {
+ "type": "string",
+ "description": "Determines the location of the parameter.",
+ "enum": [
+ "path"
+ ]
+ },
+ "description": {
+ "type": "string",
+ "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed."
+ },
+ "name": {
+ "type": "string",
+ "description": "The name of the parameter."
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "string",
+ "number",
+ "boolean",
+ "integer",
+ "array"
+ ]
+ },
+ "format": {
+ "type": "string"
+ },
+ "items": {
+ "$ref": "#/definitions/primitivesItems"
+ },
+ "collectionFormat": {
+ "$ref": "#/definitions/collectionFormat"
+ },
+ "default": {
+ "$ref": "#/definitions/default"
+ },
+ "maximum": {
+ "$ref": "#/definitions/maximum"
+ },
+ "exclusiveMaximum": {
+ "$ref": "#/definitions/exclusiveMaximum"
+ },
+ "minimum": {
+ "$ref": "#/definitions/minimum"
+ },
+ "exclusiveMinimum": {
+ "$ref": "#/definitions/exclusiveMinimum"
+ },
+ "maxLength": {
+ "$ref": "#/definitions/maxLength"
+ },
+ "minLength": {
+ "$ref": "#/definitions/minLength"
+ },
+ "pattern": {
+ "$ref": "#/definitions/pattern"
+ },
+ "maxItems": {
+ "$ref": "#/definitions/maxItems"
+ },
+ "minItems": {
+ "$ref": "#/definitions/minItems"
+ },
+ "uniqueItems": {
+ "$ref": "#/definitions/uniqueItems"
+ },
+ "enum": {
+ "$ref": "#/definitions/enum"
+ },
+ "multipleOf": {
+ "$ref": "#/definitions/multipleOf"
+ }
+ }
+ },
+ "nonBodyParameter": {
+ "type": "object",
+ "required": [
+ "name",
+ "in",
+ "type"
+ ],
+ "oneOf": [
+ {
+ "$ref": "#/definitions/headerParameterSubSchema"
+ },
+ {
+ "$ref": "#/definitions/formDataParameterSubSchema"
+ },
+ {
+ "$ref": "#/definitions/queryParameterSubSchema"
+ },
+ {
+ "$ref": "#/definitions/pathParameterSubSchema"
+ }
+ ]
+ },
+ "parameter": {
+ "oneOf": [
+ {
+ "$ref": "#/definitions/bodyParameter"
+ },
+ {
+ "$ref": "#/definitions/nonBodyParameter"
+ }
+ ]
+ },
+ "schema": {
+ "type": "object",
+ "description": "A deterministic version of a JSON Schema object.",
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ },
+ "properties": {
+ "$ref": {
+ "type": "string"
+ },
+ "format": {
+ "type": "string"
+ },
+ "title": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/title"
+ },
+ "description": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/description"
+ },
+ "default": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/default"
+ },
+ "multipleOf": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/multipleOf"
+ },
+ "maximum": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/maximum"
+ },
+ "exclusiveMaximum": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum"
+ },
+ "minimum": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/minimum"
+ },
+ "exclusiveMinimum": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum"
+ },
+ "maxLength": {
+ "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger"
+ },
+ "minLength": {
+ "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
+ },
+ "pattern": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/pattern"
+ },
+ "maxItems": {
+ "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger"
+ },
+ "minItems": {
+ "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
+ },
+ "uniqueItems": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/uniqueItems"
+ },
+ "maxProperties": {
+ "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger"
+ },
+ "minProperties": {
+ "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
+ },
+ "required": {
+ "$ref": "http://json-schema.org/draft-04/schema#/definitions/stringArray"
+ },
+ "enum": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/enum"
+ },
+ "additionalProperties": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/schema"
+ },
+ {
+ "type": "boolean"
+ }
+ ],
+ "default": {}
+ },
+ "type": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/type"
+ },
+ "items": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/schema"
+ },
+ {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/definitions/schema"
+ }
+ }
+ ],
+ "default": {}
+ },
+ "allOf": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/definitions/schema"
+ }
+ },
+ "properties": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/definitions/schema"
+ },
+ "default": {}
+ },
+ "discriminator": {
+ "type": "string"
+ },
+ "readOnly": {
+ "type": "boolean",
+ "default": false
+ },
+ "xml": {
+ "$ref": "#/definitions/xml"
+ },
+ "externalDocs": {
+ "$ref": "#/definitions/externalDocs"
+ },
+ "example": {}
+ },
+ "additionalProperties": false
+ },
+ "fileSchema": {
+ "type": "object",
+ "description": "A deterministic version of a JSON Schema object.",
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "properties": {
+ "format": {
+ "type": "string"
+ },
+ "title": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/title"
+ },
+ "description": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/description"
+ },
+ "default": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/default"
+ },
+ "required": {
+ "$ref": "http://json-schema.org/draft-04/schema#/definitions/stringArray"
+ },
+ "type": {
+ "type": "string",
+ "enum": [
+ "file"
+ ]
+ },
+ "readOnly": {
+ "type": "boolean",
+ "default": false
+ },
+ "externalDocs": {
+ "$ref": "#/definitions/externalDocs"
+ },
+ "example": {}
+ },
+ "additionalProperties": false
+ },
+ "primitivesItems": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "string",
+ "number",
+ "integer",
+ "boolean",
+ "array"
+ ]
+ },
+ "format": {
+ "type": "string"
+ },
+ "items": {
+ "$ref": "#/definitions/primitivesItems"
+ },
+ "collectionFormat": {
+ "$ref": "#/definitions/collectionFormat"
+ },
+ "default": {
+ "$ref": "#/definitions/default"
+ },
+ "maximum": {
+ "$ref": "#/definitions/maximum"
+ },
+ "exclusiveMaximum": {
+ "$ref": "#/definitions/exclusiveMaximum"
+ },
+ "minimum": {
+ "$ref": "#/definitions/minimum"
+ },
+ "exclusiveMinimum": {
+ "$ref": "#/definitions/exclusiveMinimum"
+ },
+ "maxLength": {
+ "$ref": "#/definitions/maxLength"
+ },
+ "minLength": {
+ "$ref": "#/definitions/minLength"
+ },
+ "pattern": {
+ "$ref": "#/definitions/pattern"
+ },
+ "maxItems": {
+ "$ref": "#/definitions/maxItems"
+ },
+ "minItems": {
+ "$ref": "#/definitions/minItems"
+ },
+ "uniqueItems": {
+ "$ref": "#/definitions/uniqueItems"
+ },
+ "enum": {
+ "$ref": "#/definitions/enum"
+ },
+ "multipleOf": {
+ "$ref": "#/definitions/multipleOf"
+ }
+ },
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ }
+ },
+ "security": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/securityRequirement"
+ },
+ "uniqueItems": true
+ },
+ "securityRequirement": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "uniqueItems": true
+ }
+ },
+ "xml": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "namespace": {
+ "type": "string"
+ },
+ "prefix": {
+ "type": "string"
+ },
+ "attribute": {
+ "type": "boolean",
+ "default": false
+ },
+ "wrapped": {
+ "type": "boolean",
+ "default": false
+ }
+ },
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ }
+ },
+ "tag": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "name"
+ ],
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "externalDocs": {
+ "$ref": "#/definitions/externalDocs"
+ }
+ },
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ }
+ },
+ "securityDefinitions": {
+ "type": "object",
+ "additionalProperties": {
+ "oneOf": [
+ {
+ "$ref": "#/definitions/basicAuthenticationSecurity"
+ },
+ {
+ "$ref": "#/definitions/apiKeySecurity"
+ },
+ {
+ "$ref": "#/definitions/oauth2ImplicitSecurity"
+ },
+ {
+ "$ref": "#/definitions/oauth2PasswordSecurity"
+ },
+ {
+ "$ref": "#/definitions/oauth2ApplicationSecurity"
+ },
+ {
+ "$ref": "#/definitions/oauth2AccessCodeSecurity"
+ }
+ ]
+ }
+ },
+ "basicAuthenticationSecurity": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "type"
+ ],
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "basic"
+ ]
+ },
+ "description": {
+ "type": "string"
+ }
+ },
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ }
+ },
+ "apiKeySecurity": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "type",
+ "name",
+ "in"
+ ],
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "apiKey"
+ ]
+ },
+ "name": {
+ "type": "string"
+ },
+ "in": {
+ "type": "string",
+ "enum": [
+ "header",
+ "query"
+ ]
+ },
+ "description": {
+ "type": "string"
+ }
+ },
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ }
+ },
+ "oauth2ImplicitSecurity": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "type",
+ "flow",
+ "authorizationUrl"
+ ],
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "oauth2"
+ ]
+ },
+ "flow": {
+ "type": "string",
+ "enum": [
+ "implicit"
+ ]
+ },
+ "scopes": {
+ "$ref": "#/definitions/oauth2Scopes"
+ },
+ "authorizationUrl": {
+ "type": "string",
+ "format": "uri"
+ },
+ "description": {
+ "type": "string"
+ }
+ },
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ }
+ },
+ "oauth2PasswordSecurity": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "type",
+ "flow",
+ "tokenUrl"
+ ],
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "oauth2"
+ ]
+ },
+ "flow": {
+ "type": "string",
+ "enum": [
+ "password"
+ ]
+ },
+ "scopes": {
+ "$ref": "#/definitions/oauth2Scopes"
+ },
+ "tokenUrl": {
+ "type": "string",
+ "format": "uri"
+ },
+ "description": {
+ "type": "string"
+ }
+ },
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ }
+ },
+ "oauth2ApplicationSecurity": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "type",
+ "flow",
+ "tokenUrl"
+ ],
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "oauth2"
+ ]
+ },
+ "flow": {
+ "type": "string",
+ "enum": [
+ "application"
+ ]
+ },
+ "scopes": {
+ "$ref": "#/definitions/oauth2Scopes"
+ },
+ "tokenUrl": {
+ "type": "string",
+ "format": "uri"
+ },
+ "description": {
+ "type": "string"
+ }
+ },
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ }
+ },
+ "oauth2AccessCodeSecurity": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "type",
+ "flow",
+ "authorizationUrl",
+ "tokenUrl"
+ ],
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "oauth2"
+ ]
+ },
+ "flow": {
+ "type": "string",
+ "enum": [
+ "accessCode"
+ ]
+ },
+ "scopes": {
+ "$ref": "#/definitions/oauth2Scopes"
+ },
+ "authorizationUrl": {
+ "type": "string",
+ "format": "uri"
+ },
+ "tokenUrl": {
+ "type": "string",
+ "format": "uri"
+ },
+ "description": {
+ "type": "string"
+ }
+ },
+ "patternProperties": {
+ "^x-": {
+ "$ref": "#/definitions/vendorExtension"
+ }
+ }
+ },
+ "oauth2Scopes": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "mediaTypeList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/mimeType"
+ },
+ "uniqueItems": true
+ },
+ "parametersList": {
+ "type": "array",
+ "description": "The parameters needed to send a valid API call.",
+ "additionalItems": false,
+ "items": {
+ "oneOf": [
+ {
+ "$ref": "#/definitions/parameter"
+ },
+ {
+ "$ref": "#/definitions/jsonReference"
+ }
+ ]
+ },
+ "uniqueItems": true
+ },
+ "schemesList": {
+ "type": "array",
+ "description": "The transfer protocol of the API.",
+ "items": {
+ "type": "string",
+ "enum": [
+ "http",
+ "https",
+ "ws",
+ "wss"
+ ]
+ },
+ "uniqueItems": true
+ },
+ "collectionFormat": {
+ "type": "string",
+ "enum": [
+ "csv",
+ "ssv",
+ "tsv",
+ "pipes"
+ ],
+ "default": "csv"
+ },
+ "collectionFormatWithMulti": {
+ "type": "string",
+ "enum": [
+ "csv",
+ "ssv",
+ "tsv",
+ "pipes",
+ "multi"
+ ],
+ "default": "csv"
+ },
+ "title": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/title"
+ },
+ "description": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/description"
+ },
+ "default": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/default"
+ },
+ "multipleOf": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/multipleOf"
+ },
+ "maximum": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/maximum"
+ },
+ "exclusiveMaximum": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum"
+ },
+ "minimum": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/minimum"
+ },
+ "exclusiveMinimum": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum"
+ },
+ "maxLength": {
+ "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger"
+ },
+ "minLength": {
+ "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
+ },
+ "pattern": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/pattern"
+ },
+ "maxItems": {
+ "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger"
+ },
+ "minItems": {
+ "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
+ },
+ "uniqueItems": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/uniqueItems"
+ },
+ "enum": {
+ "$ref": "http://json-schema.org/draft-04/schema#/properties/enum"
+ },
+ "jsonReference": {
+ "type": "object",
+ "required": [
+ "$ref"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "$ref": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ "tests": [
+ {
+ "description": "Example petsore",
+ "data": {
+ "swagger": "2.0",
+ "info": {
+ "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.",
+ "version": "1.0.0",
+ "title": "Swagger Petstore",
+ "termsOfService": "http://swagger.io/terms/",
+ "contact": {
+ "email": "apiteam@swagger.io"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
+ }
+ },
+ "host": "petstore.swagger.io",
+ "basePath": "/v2",
+ "tags": [
+ {
+ "name": "pet",
+ "description": "Everything about your Pets",
+ "externalDocs": {
+ "description": "Find out more",
+ "url": "http://swagger.io"
+ }
+ },
+ {
+ "name": "store",
+ "description": "Access to Petstore orders"
+ },
+ {
+ "name": "user",
+ "description": "Operations about user",
+ "externalDocs": {
+ "description": "Find out more about our store",
+ "url": "http://swagger.io"
+ }
+ }
+ ],
+ "schemes": [
+ "http"
+ ],
+ "paths": {
+ "/pet": {
+ "post": {
+ "tags": [
+ "pet"
+ ],
+ "summary": "Add a new pet to the store",
+ "description": "",
+ "operationId": "addPet",
+ "consumes": [
+ "application/json",
+ "application/xml"
+ ],
+ "produces": [
+ "application/xml",
+ "application/json"
+ ],
+ "parameters": [
+ {
+ "in": "body",
+ "name": "body",
+ "description": "Pet object that needs to be added to the store",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/Pet"
+ }
+ }
+ ],
+ "responses": {
+ "405": {
+ "description": "Invalid input"
+ }
+ },
+ "security": [
+ {
+ "petstore_auth": [
+ "write:pets",
+ "read:pets"
+ ]
+ }
+ ]
+ },
+ "put": {
+ "tags": [
+ "pet"
+ ],
+ "summary": "Update an existing pet",
+ "description": "",
+ "operationId": "updatePet",
+ "consumes": [
+ "application/json",
+ "application/xml"
+ ],
+ "produces": [
+ "application/xml",
+ "application/json"
+ ],
+ "parameters": [
+ {
+ "in": "body",
+ "name": "body",
+ "description": "Pet object that needs to be added to the store",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/Pet"
+ }
+ }
+ ],
+ "responses": {
+ "400": {
+ "description": "Invalid ID supplied"
+ },
+ "404": {
+ "description": "Pet not found"
+ },
+ "405": {
+ "description": "Validation exception"
+ }
+ },
+ "security": [
+ {
+ "petstore_auth": [
+ "write:pets",
+ "read:pets"
+ ]
+ }
+ ]
+ }
+ },
+ "/pet/findByStatus": {
+ "get": {
+ "tags": [
+ "pet"
+ ],
+ "summary": "Finds Pets by status",
+ "description": "Multiple status values can be provided with comma separated strings",
+ "operationId": "findPetsByStatus",
+ "produces": [
+ "application/xml",
+ "application/json"
+ ],
+ "parameters": [
+ {
+ "name": "status",
+ "in": "query",
+ "description": "Status values that need to be considered for filter",
+ "required": true,
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "available",
+ "pending",
+ "sold"
+ ],
+ "default": "available"
+ },
+ "collectionFormat": "multi"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "successful operation",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Pet"
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid status value"
+ }
+ },
+ "security": [
+ {
+ "petstore_auth": [
+ "write:pets",
+ "read:pets"
+ ]
+ }
+ ]
+ }
+ },
+ "/pet/findByTags": {
+ "get": {
+ "tags": [
+ "pet"
+ ],
+ "summary": "Finds Pets by tags",
+ "description": "Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.",
+ "operationId": "findPetsByTags",
+ "produces": [
+ "application/xml",
+ "application/json"
+ ],
+ "parameters": [
+ {
+ "name": "tags",
+ "in": "query",
+ "description": "Tags to filter by",
+ "required": true,
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "collectionFormat": "multi"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "successful operation",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Pet"
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid tag value"
+ }
+ },
+ "security": [
+ {
+ "petstore_auth": [
+ "write:pets",
+ "read:pets"
+ ]
+ }
+ ],
+ "deprecated": true
+ }
+ },
+ "/pet/{petId}": {
+ "get": {
+ "tags": [
+ "pet"
+ ],
+ "summary": "Find pet by ID",
+ "description": "Returns a single pet",
+ "operationId": "getPetById",
+ "produces": [
+ "application/xml",
+ "application/json"
+ ],
+ "parameters": [
+ {
+ "name": "petId",
+ "in": "path",
+ "description": "ID of pet to return",
+ "required": true,
+ "type": "integer",
+ "format": "int64"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "successful operation",
+ "schema": {
+ "$ref": "#/definitions/Pet"
+ }
+ },
+ "400": {
+ "description": "Invalid ID supplied"
+ },
+ "404": {
+ "description": "Pet not found"
+ }
+ },
+ "security": [
+ {
+ "api_key": []
+ }
+ ]
+ },
+ "post": {
+ "tags": [
+ "pet"
+ ],
+ "summary": "Updates a pet in the store with form data",
+ "description": "",
+ "operationId": "updatePetWithForm",
+ "consumes": [
+ "application/x-www-form-urlencoded"
+ ],
+ "produces": [
+ "application/xml",
+ "application/json"
+ ],
+ "parameters": [
+ {
+ "name": "petId",
+ "in": "path",
+ "description": "ID of pet that needs to be updated",
+ "required": true,
+ "type": "integer",
+ "format": "int64"
+ },
+ {
+ "name": "name",
+ "in": "formData",
+ "description": "Updated name of the pet",
+ "required": false,
+ "type": "string"
+ },
+ {
+ "name": "status",
+ "in": "formData",
+ "description": "Updated status of the pet",
+ "required": false,
+ "type": "string"
+ }
+ ],
+ "responses": {
+ "405": {
+ "description": "Invalid input"
+ }
+ },
+ "security": [
+ {
+ "petstore_auth": [
+ "write:pets",
+ "read:pets"
+ ]
+ }
+ ]
+ },
+ "delete": {
+ "tags": [
+ "pet"
+ ],
+ "summary": "Deletes a pet",
+ "description": "",
+ "operationId": "deletePet",
+ "produces": [
+ "application/xml",
+ "application/json"
+ ],
+ "parameters": [
+ {
+ "name": "api_key",
+ "in": "header",
+ "required": false,
+ "type": "string"
+ },
+ {
+ "name": "petId",
+ "in": "path",
+ "description": "Pet id to delete",
+ "required": true,
+ "type": "integer",
+ "format": "int64"
+ }
+ ],
+ "responses": {
+ "400": {
+ "description": "Invalid ID supplied"
+ },
+ "404": {
+ "description": "Pet not found"
+ }
+ },
+ "security": [
+ {
+ "petstore_auth": [
+ "write:pets",
+ "read:pets"
+ ]
+ }
+ ]
+ }
+ },
+ "/pet/{petId}/uploadImage": {
+ "post": {
+ "tags": [
+ "pet"
+ ],
+ "summary": "uploads an image",
+ "description": "",
+ "operationId": "uploadFile",
+ "consumes": [
+ "multipart/form-data"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "parameters": [
+ {
+ "name": "petId",
+ "in": "path",
+ "description": "ID of pet to update",
+ "required": true,
+ "type": "integer",
+ "format": "int64"
+ },
+ {
+ "name": "additionalMetadata",
+ "in": "formData",
+ "description": "Additional data to pass to server",
+ "required": false,
+ "type": "string"
+ },
+ {
+ "name": "file",
+ "in": "formData",
+ "description": "file to upload",
+ "required": false,
+ "type": "file"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "successful operation",
+ "schema": {
+ "$ref": "#/definitions/ApiResponse"
+ }
+ }
+ },
+ "security": [
+ {
+ "petstore_auth": [
+ "write:pets",
+ "read:pets"
+ ]
+ }
+ ]
+ }
+ },
+ "/store/inventory": {
+ "get": {
+ "tags": [
+ "store"
+ ],
+ "summary": "Returns pet inventories by status",
+ "description": "Returns a map of status codes to quantities",
+ "operationId": "getInventory",
+ "produces": [
+ "application/json"
+ ],
+ "parameters": [],
+ "responses": {
+ "200": {
+ "description": "successful operation",
+ "schema": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "integer",
+ "format": "int32"
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "api_key": []
+ }
+ ]
+ }
+ },
+ "/store/order": {
+ "post": {
+ "tags": [
+ "store"
+ ],
+ "summary": "Place an order for a pet",
+ "description": "",
+ "operationId": "placeOrder",
+ "produces": [
+ "application/xml",
+ "application/json"
+ ],
+ "parameters": [
+ {
+ "in": "body",
+ "name": "body",
+ "description": "order placed for purchasing the pet",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/Order"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "successful operation",
+ "schema": {
+ "$ref": "#/definitions/Order"
+ }
+ },
+ "400": {
+ "description": "Invalid Order"
+ }
+ }
+ }
+ },
+ "/store/order/{orderId}": {
+ "get": {
+ "tags": [
+ "store"
+ ],
+ "summary": "Find purchase order by ID",
+ "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions",
+ "operationId": "getOrderById",
+ "produces": [
+ "application/xml",
+ "application/json"
+ ],
+ "parameters": [
+ {
+ "name": "orderId",
+ "in": "path",
+ "description": "ID of pet that needs to be fetched",
+ "required": true,
+ "type": "integer",
+ "maximum": 10.0,
+ "minimum": 1.0,
+ "format": "int64"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "successful operation",
+ "schema": {
+ "$ref": "#/definitions/Order"
+ }
+ },
+ "400": {
+ "description": "Invalid ID supplied"
+ },
+ "404": {
+ "description": "Order not found"
+ }
+ }
+ },
+ "delete": {
+ "tags": [
+ "store"
+ ],
+ "summary": "Delete purchase order by ID",
+ "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors",
+ "operationId": "deleteOrder",
+ "produces": [
+ "application/xml",
+ "application/json"
+ ],
+ "parameters": [
+ {
+ "name": "orderId",
+ "in": "path",
+ "description": "ID of the order that needs to be deleted",
+ "required": true,
+ "type": "integer",
+ "minimum": 1.0,
+ "format": "int64"
+ }
+ ],
+ "responses": {
+ "400": {
+ "description": "Invalid ID supplied"
+ },
+ "404": {
+ "description": "Order not found"
+ }
+ }
+ }
+ },
+ "/user": {
+ "post": {
+ "tags": [
+ "user"
+ ],
+ "summary": "Create user",
+ "description": "This can only be done by the logged in user.",
+ "operationId": "createUser",
+ "produces": [
+ "application/xml",
+ "application/json"
+ ],
+ "parameters": [
+ {
+ "in": "body",
+ "name": "body",
+ "description": "Created user object",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/User"
+ }
+ }
+ ],
+ "responses": {
+ "default": {
+ "description": "successful operation"
+ }
+ }
+ }
+ },
+ "/user/createWithArray": {
+ "post": {
+ "tags": [
+ "user"
+ ],
+ "summary": "Creates list of users with given input array",
+ "description": "",
+ "operationId": "createUsersWithArrayInput",
+ "produces": [
+ "application/xml",
+ "application/json"
+ ],
+ "parameters": [
+ {
+ "in": "body",
+ "name": "body",
+ "description": "List of user object",
+ "required": true,
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/User"
+ }
+ }
+ }
+ ],
+ "responses": {
+ "default": {
+ "description": "successful operation"
+ }
+ }
+ }
+ },
+ "/user/createWithList": {
+ "post": {
+ "tags": [
+ "user"
+ ],
+ "summary": "Creates list of users with given input array",
+ "description": "",
+ "operationId": "createUsersWithListInput",
+ "produces": [
+ "application/xml",
+ "application/json"
+ ],
+ "parameters": [
+ {
+ "in": "body",
+ "name": "body",
+ "description": "List of user object",
+ "required": true,
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/User"
+ }
+ }
+ }
+ ],
+ "responses": {
+ "default": {
+ "description": "successful operation"
+ }
+ }
+ }
+ },
+ "/user/login": {
+ "get": {
+ "tags": [
+ "user"
+ ],
+ "summary": "Logs user into the system",
+ "description": "",
+ "operationId": "loginUser",
+ "produces": [
+ "application/xml",
+ "application/json"
+ ],
+ "parameters": [
+ {
+ "name": "username",
+ "in": "query",
+ "description": "The user name for login",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "name": "password",
+ "in": "query",
+ "description": "The password for login in clear text",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "successful operation",
+ "schema": {
+ "type": "string"
+ },
+ "headers": {
+ "X-Rate-Limit": {
+ "type": "integer",
+ "format": "int32",
+ "description": "calls per hour allowed by the user"
+ },
+ "X-Expires-After": {
+ "type": "string",
+ "format": "date-time",
+ "description": "date in UTC when token expires"
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid username/password supplied"
+ }
+ }
+ }
+ },
+ "/user/logout": {
+ "get": {
+ "tags": [
+ "user"
+ ],
+ "summary": "Logs out current logged in user session",
+ "description": "",
+ "operationId": "logoutUser",
+ "produces": [
+ "application/xml",
+ "application/json"
+ ],
+ "parameters": [],
+ "responses": {
+ "default": {
+ "description": "successful operation"
+ }
+ }
+ }
+ },
+ "/user/{username}": {
+ "get": {
+ "tags": [
+ "user"
+ ],
+ "summary": "Get user by user name",
+ "description": "",
+ "operationId": "getUserByName",
+ "produces": [
+ "application/xml",
+ "application/json"
+ ],
+ "parameters": [
+ {
+ "name": "username",
+ "in": "path",
+ "description": "The name that needs to be fetched. Use user1 for testing. ",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "successful operation",
+ "schema": {
+ "$ref": "#/definitions/User"
+ }
+ },
+ "400": {
+ "description": "Invalid username supplied"
+ },
+ "404": {
+ "description": "User not found"
+ }
+ }
+ },
+ "put": {
+ "tags": [
+ "user"
+ ],
+ "summary": "Updated user",
+ "description": "This can only be done by the logged in user.",
+ "operationId": "updateUser",
+ "produces": [
+ "application/xml",
+ "application/json"
+ ],
+ "parameters": [
+ {
+ "name": "username",
+ "in": "path",
+ "description": "name that need to be updated",
+ "required": true,
+ "type": "string"
+ },
+ {
+ "in": "body",
+ "name": "body",
+ "description": "Updated user object",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/User"
+ }
+ }
+ ],
+ "responses": {
+ "400": {
+ "description": "Invalid user supplied"
+ },
+ "404": {
+ "description": "User not found"
+ }
+ }
+ },
+ "delete": {
+ "tags": [
+ "user"
+ ],
+ "summary": "Delete user",
+ "description": "This can only be done by the logged in user.",
+ "operationId": "deleteUser",
+ "produces": [
+ "application/xml",
+ "application/json"
+ ],
+ "parameters": [
+ {
+ "name": "username",
+ "in": "path",
+ "description": "The name that needs to be deleted",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "responses": {
+ "400": {
+ "description": "Invalid username supplied"
+ },
+ "404": {
+ "description": "User not found"
+ }
+ }
+ }
+ }
+ },
+ "securityDefinitions": {
+ "petstore_auth": {
+ "type": "oauth2",
+ "authorizationUrl": "http://petstore.swagger.io/oauth/dialog",
+ "flow": "implicit",
+ "scopes": {
+ "write:pets": "modify pets in your account",
+ "read:pets": "read your pets"
+ }
+ },
+ "api_key": {
+ "type": "apiKey",
+ "name": "api_key",
+ "in": "header"
+ }
+ },
+ "definitions": {
+ "Order": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "petId": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "quantity": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "shipDate": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "status": {
+ "type": "string",
+ "description": "Order Status",
+ "enum": [
+ "placed",
+ "approved",
+ "delivered"
+ ]
+ },
+ "complete": {
+ "type": "boolean",
+ "default": false
+ }
+ },
+ "xml": {
+ "name": "Order"
+ }
+ },
+ "Category": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "xml": {
+ "name": "Category"
+ }
+ },
+ "User": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "username": {
+ "type": "string"
+ },
+ "firstName": {
+ "type": "string"
+ },
+ "lastName": {
+ "type": "string"
+ },
+ "email": {
+ "type": "string"
+ },
+ "password": {
+ "type": "string"
+ },
+ "phone": {
+ "type": "string"
+ },
+ "userStatus": {
+ "type": "integer",
+ "format": "int32",
+ "description": "User Status"
+ }
+ },
+ "xml": {
+ "name": "User"
+ }
+ },
+ "Tag": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "xml": {
+ "name": "Tag"
+ }
+ },
+ "Pet": {
+ "type": "object",
+ "required": [
+ "name",
+ "photoUrls"
+ ],
+ "properties": {
+ "id": {
+ "type": "integer",
+ "format": "int64"
+ },
+ "category": {
+ "$ref": "#/definitions/Category"
+ },
+ "name": {
+ "type": "string",
+ "example": "doggie"
+ },
+ "photoUrls": {
+ "type": "array",
+ "xml": {
+ "name": "photoUrl",
+ "wrapped": true
+ },
+ "items": {
+ "type": "string"
+ }
+ },
+ "tags": {
+ "type": "array",
+ "xml": {
+ "name": "tag",
+ "wrapped": true
+ },
+ "items": {
+ "$ref": "#/definitions/Tag"
+ }
+ },
+ "status": {
+ "type": "string",
+ "description": "pet status in the store",
+ "enum": [
+ "available",
+ "pending",
+ "sold"
+ ]
+ }
+ },
+ "xml": {
+ "name": "Pet"
+ }
+ },
+ "ApiResponse": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "type": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "externalDocs": {
+ "description": "Find out more about Swagger",
+ "url": "http://swagger.io"
+ }
+ },
+ "valid": true
+ }
+ ]
+ }
+]
diff --git a/jsonschema/benchmarks/json_schema_test_suite.py b/jsonschema/benchmarks/json_schema_test_suite.py
new file mode 100644
index 0000000..4126fdc
--- /dev/null
+++ b/jsonschema/benchmarks/json_schema_test_suite.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env python
+"""
+A performance benchmark using the official test suite.
+
+This benchmarks jsonschema using every valid example in the
+JSON-Schema-Test-Suite. It will take some time to complete.
+"""
+from pyperf import Runner
+
+from jsonschema.tests._suite import Suite
+
+if __name__ == "__main__":
+ Suite().benchmark(runner=Runner())
diff --git a/jsonschema/cli.py b/jsonschema/cli.py
new file mode 100644
index 0000000..7131939
--- /dev/null
+++ b/jsonschema/cli.py
@@ -0,0 +1,270 @@
+"""
+The ``jsonschema`` command line.
+"""
+
+from json import JSONDecodeError
+from textwrap import dedent
+import argparse
+import errno
+import json
+import sys
+import traceback
+
+import attr
+
+from jsonschema import __version__
+from jsonschema._reflect import namedAny
+from jsonschema.exceptions import SchemaError
+from jsonschema.validators import validator_for
+
+
+class _CannotLoadFile(Exception):
+ pass
+
+
+@attr.s
+class _Outputter(object):
+
+ _formatter = attr.ib()
+ _stdout = attr.ib()
+ _stderr = attr.ib()
+
+ @classmethod
+ def from_arguments(cls, arguments, stdout, stderr):
+ if arguments["output"] == "plain":
+ formatter = _PlainFormatter(arguments["error_format"])
+ elif arguments["output"] == "pretty":
+ formatter = _PrettyFormatter()
+ return cls(formatter=formatter, stdout=stdout, stderr=stderr)
+
+ def load(self, path):
+ try:
+ file = open(path)
+ except (IOError, OSError) as error:
+ if error.errno != errno.ENOENT:
+ raise
+ self.filenotfound_error(path=path, exc_info=sys.exc_info())
+ raise _CannotLoadFile()
+
+ with file:
+ try:
+ return json.load(file)
+ except JSONDecodeError:
+ self.parsing_error(path=path, exc_info=sys.exc_info())
+ raise _CannotLoadFile()
+
+ def filenotfound_error(self, **kwargs):
+ self._stderr.write(self._formatter.filenotfound_error(**kwargs))
+
+ def parsing_error(self, **kwargs):
+ self._stderr.write(self._formatter.parsing_error(**kwargs))
+
+ def validation_error(self, **kwargs):
+ self._stderr.write(self._formatter.validation_error(**kwargs))
+
+ def validation_success(self, **kwargs):
+ self._stdout.write(self._formatter.validation_success(**kwargs))
+
+
+@attr.s
+class _PrettyFormatter(object):
+
+ _ERROR_MSG = dedent(
+ """\
+ ===[{type}]===({path})===
+
+ {body}
+ -----------------------------
+ """,
+ )
+ _SUCCESS_MSG = "===[SUCCESS]===({path})===\n"
+
+ def filenotfound_error(self, path, exc_info):
+ return self._ERROR_MSG.format(
+ path=path,
+ type="FileNotFoundError",
+ body="{!r} does not exist.".format(path),
+ )
+
+ def parsing_error(self, path, exc_info):
+ exc_type, exc_value, exc_traceback = exc_info
+ exc_lines = "".join(
+ traceback.format_exception(exc_type, exc_value, exc_traceback),
+ )
+ return self._ERROR_MSG.format(
+ path=path,
+ type=exc_type.__name__,
+ body=exc_lines,
+ )
+
+ def validation_error(self, instance_path, error):
+ return self._ERROR_MSG.format(
+ path=instance_path,
+ type=error.__class__.__name__,
+ body=error,
+ )
+
+ def validation_success(self, instance_path):
+ return self._SUCCESS_MSG.format(path=instance_path)
+
+
+@attr.s
+class _PlainFormatter(object):
+
+ _error_format = attr.ib()
+
+ def filenotfound_error(self, path, exc_info):
+ return "{!r} does not exist.\n".format(path)
+
+ def parsing_error(self, path, exc_info):
+ return "Failed to parse {}: {}\n".format(
+ "<stdin>" if path == "<stdin>" else repr(path),
+ exc_info[1],
+ )
+
+ def validation_error(self, instance_path, error):
+ return self._error_format.format(file_name=instance_path, error=error)
+
+ def validation_success(self, instance_path):
+ return ""
+
+
+def _namedAnyWithDefault(name):
+ if "." not in name:
+ name = "jsonschema." + name
+ return namedAny(name)
+
+
+parser = argparse.ArgumentParser(
+ description="JSON Schema Validation CLI",
+)
+parser.add_argument(
+ "-i", "--instance",
+ action="append",
+ dest="instances",
+ help="""
+ a path to a JSON instance (i.e. filename.json) to validate (may
+ be specified multiple times). If no instances are provided via this
+ option, one will be expected on standard input.
+ """,
+)
+parser.add_argument(
+ "-F", "--error-format",
+ help="""
+ the format to use for each validation error message, specified
+ in a form suitable for str.format. This string will be passed
+ one formatted object named 'error' for each ValidationError.
+ Only provide this option when using --output=plain, which is the
+ default. If this argument is unprovided and --output=plain is
+ used, a simple default representation will be used."
+ """,
+)
+parser.add_argument(
+ "-o", "--output",
+ choices=["plain", "pretty"],
+ default="plain",
+ help="""
+ an output format to use. 'plain' (default) will produce minimal
+ text with one line for each error, while 'pretty' will produce
+ more detailed human-readable output on multiple lines.
+ """,
+)
+parser.add_argument(
+ "-V", "--validator",
+ type=_namedAnyWithDefault,
+ help="""
+ the fully qualified object name of a validator to use, or, for
+ validators that are registered with jsonschema, simply the name
+ of the class.
+ """,
+)
+parser.add_argument(
+ "--version",
+ action="version",
+ version=__version__,
+)
+parser.add_argument(
+ "schema",
+ help="the path to a JSON Schema to validate with (i.e. schema.json)",
+)
+
+
+def parse_args(args):
+ arguments = vars(parser.parse_args(args=args or ["--help"]))
+ if arguments["output"] != "plain" and arguments["error_format"]:
+ raise parser.error(
+ "--error-format can only be used with --output plain"
+ )
+ if arguments["output"] == "plain" and arguments["error_format"] is None:
+ arguments["error_format"] = "{error.instance}: {error.message}\n"
+ return arguments
+
+
+def _validate_instance(instance_path, instance, validator, outputter):
+ invalid = False
+ for error in validator.iter_errors(instance):
+ invalid = True
+ outputter.validation_error(instance_path=instance_path, error=error)
+
+ if not invalid:
+ outputter.validation_success(instance_path=instance_path)
+ return invalid
+
+
+def main(args=sys.argv[1:]):
+ sys.exit(run(arguments=parse_args(args=args)))
+
+
+def run(arguments, stdout=sys.stdout, stderr=sys.stderr, stdin=sys.stdin):
+ outputter = _Outputter.from_arguments(
+ arguments=arguments,
+ stdout=stdout,
+ stderr=stderr,
+ )
+
+ try:
+ schema = outputter.load(arguments["schema"])
+ except _CannotLoadFile:
+ return 1
+
+ if arguments["validator"] is None:
+ arguments["validator"] = validator_for(schema)
+
+ try:
+ arguments["validator"].check_schema(schema)
+ except SchemaError as error:
+ outputter.validation_error(
+ instance_path=arguments["schema"],
+ error=error,
+ )
+ return 1
+
+ if arguments["instances"]:
+ load, instances = outputter.load, arguments["instances"]
+ else:
+ def load(_):
+ try:
+ return json.load(stdin)
+ except JSONDecodeError:
+ outputter.parsing_error(
+ path="<stdin>", exc_info=sys.exc_info(),
+ )
+ raise _CannotLoadFile()
+ instances = ["<stdin>"]
+
+ validator = arguments["validator"](schema)
+ exit_code = 0
+ for each in instances:
+ try:
+ instance = load(each)
+ except _CannotLoadFile:
+ exit_code = 1
+ else:
+ exit_code |= _validate_instance(
+ instance_path=each,
+ instance=instance,
+ validator=validator,
+ outputter=outputter,
+ )
+
+ return exit_code
diff --git a/jsonschema/exceptions.py b/jsonschema/exceptions.py
new file mode 100644
index 0000000..46ffced
--- /dev/null
+++ b/jsonschema/exceptions.py
@@ -0,0 +1,358 @@
+"""
+Validation errors, and some surrounding helpers.
+"""
+from collections import defaultdict, deque
+import itertools
+import pprint
+import textwrap
+
+import attr
+
+from jsonschema import _utils
+
+WEAK_MATCHES = frozenset(["anyOf", "oneOf"])
+STRONG_MATCHES = frozenset()
+
+_unset = _utils.Unset()
+
+
+class _Error(Exception):
+ def __init__(
+ self,
+ message,
+ validator=_unset,
+ path=(),
+ cause=None,
+ context=(),
+ validator_value=_unset,
+ instance=_unset,
+ schema=_unset,
+ schema_path=(),
+ parent=None,
+ ):
+ super(_Error, self).__init__(
+ message,
+ validator,
+ path,
+ cause,
+ context,
+ validator_value,
+ instance,
+ schema,
+ schema_path,
+ parent,
+ )
+ self.message = message
+ self.path = self.relative_path = deque(path)
+ self.schema_path = self.relative_schema_path = deque(schema_path)
+ self.context = list(context)
+ self.cause = self.__cause__ = cause
+ self.validator = validator
+ self.validator_value = validator_value
+ self.instance = instance
+ self.schema = schema
+ self.parent = parent
+
+ for error in context:
+ error.parent = self
+
+ def __repr__(self):
+ return "<%s: %r>" % (self.__class__.__name__, self.message)
+
+ def __str__(self):
+ essential_for_verbose = (
+ self.validator, self.validator_value, self.instance, self.schema,
+ )
+ if any(m is _unset for m in essential_for_verbose):
+ return self.message
+
+ pschema = pprint.pformat(self.schema, width=72)
+ pinstance = pprint.pformat(self.instance, width=72)
+ return self.message + textwrap.dedent("""
+
+ Failed validating %r in %s%s:
+ %s
+
+ On %s%s:
+ %s
+ """.rstrip()
+ ) % (
+ self.validator,
+ self._word_for_schema_in_error_message,
+ _utils.format_as_index(list(self.relative_schema_path)[:-1]),
+ _utils.indent(pschema),
+ self._word_for_instance_in_error_message,
+ _utils.format_as_index(self.relative_path),
+ _utils.indent(pinstance),
+ )
+
+ @classmethod
+ def create_from(cls, other):
+ return cls(**other._contents())
+
+ @property
+ def absolute_path(self):
+ parent = self.parent
+ if parent is None:
+ return self.relative_path
+
+ path = deque(self.relative_path)
+ path.extendleft(reversed(parent.absolute_path))
+ return path
+
+ @property
+ def absolute_schema_path(self):
+ parent = self.parent
+ if parent is None:
+ return self.relative_schema_path
+
+ path = deque(self.relative_schema_path)
+ path.extendleft(reversed(parent.absolute_schema_path))
+ return path
+
+ @property
+ def json_path(self):
+ path = '$'
+ for elem in self.absolute_path:
+ if isinstance(elem, int):
+ path += '[' + str(elem) + ']'
+ else:
+ path += '.' + elem
+ return path
+
+ def _set(self, **kwargs):
+ for k, v in kwargs.items():
+ if getattr(self, k) is _unset:
+ setattr(self, k, v)
+
+ def _contents(self):
+ attrs = (
+ "message", "cause", "context", "validator", "validator_value",
+ "path", "schema_path", "instance", "schema", "parent",
+ )
+ return dict((attr, getattr(self, attr)) for attr in attrs)
+
+
+class ValidationError(_Error):
+ """
+ An instance was invalid under a provided schema.
+ """
+
+ _word_for_schema_in_error_message = "schema"
+ _word_for_instance_in_error_message = "instance"
+
+
+class SchemaError(_Error):
+ """
+ A schema was invalid under its corresponding metaschema.
+ """
+
+ _word_for_schema_in_error_message = "metaschema"
+ _word_for_instance_in_error_message = "schema"
+
+
+@attr.s(hash=True)
+class RefResolutionError(Exception):
+ """
+ A ref could not be resolved.
+ """
+
+ _cause = attr.ib()
+
+ def __str__(self):
+ return str(self._cause)
+
+
+class UndefinedTypeCheck(Exception):
+ """
+ A type checker was asked to check a type it did not have registered.
+ """
+
+ def __init__(self, type):
+ self.type = type
+
+ def __str__(self):
+ return "Type %r is unknown to this type checker" % self.type
+
+
+class UnknownType(Exception):
+ """
+ A validator was asked to validate an instance against an unknown type.
+ """
+
+ def __init__(self, type, instance, schema):
+ self.type = type
+ self.instance = instance
+ self.schema = schema
+
+ def __str__(self):
+ pschema = pprint.pformat(self.schema, width=72)
+ pinstance = pprint.pformat(self.instance, width=72)
+ return textwrap.dedent("""
+ Unknown type %r for validator with schema:
+ %s
+
+ While checking instance:
+ %s
+ """.rstrip()
+ ) % (self.type, _utils.indent(pschema), _utils.indent(pinstance))
+
+
+class FormatError(Exception):
+ """
+ Validating a format failed.
+ """
+
+ def __init__(self, message, cause=None):
+ super(FormatError, self).__init__(message, cause)
+ self.message = message
+ self.cause = self.__cause__ = cause
+
+ def __str__(self):
+ return self.message
+
+
+class ErrorTree(object):
+ """
+ ErrorTrees make it easier to check which validations failed.
+ """
+
+ _instance = _unset
+
+ def __init__(self, errors=()):
+ self.errors = {}
+ self._contents = defaultdict(self.__class__)
+
+ for error in errors:
+ container = self
+ for element in error.path:
+ container = container[element]
+ container.errors[error.validator] = error
+
+ container._instance = error.instance
+
+ def __contains__(self, index):
+ """
+ Check whether ``instance[index]`` has any errors.
+ """
+
+ return index in self._contents
+
+ def __getitem__(self, index):
+ """
+ Retrieve the child tree one level down at the given ``index``.
+
+ If the index is not in the instance that this tree corresponds
+ to and is not known by this tree, whatever error would be raised
+ by ``instance.__getitem__`` will be propagated (usually this is
+ some subclass of `LookupError`.
+ """
+
+ if self._instance is not _unset and index not in self:
+ self._instance[index]
+ return self._contents[index]
+
+ def __setitem__(self, index, value):
+ """
+ Add an error to the tree at the given ``index``.
+ """
+ self._contents[index] = value
+
+ def __iter__(self):
+ """
+ Iterate (non-recursively) over the indices in the instance with errors.
+ """
+
+ return iter(self._contents)
+
+ def __len__(self):
+ """
+ Return the `total_errors`.
+ """
+ return self.total_errors
+
+ def __repr__(self):
+ return "<%s (%s total errors)>" % (self.__class__.__name__, len(self))
+
+ @property
+ def total_errors(self):
+ """
+ The total number of errors in the entire tree, including children.
+ """
+
+ child_errors = sum(len(tree) for _, tree in self._contents.items())
+ return len(self.errors) + child_errors
+
+
+def by_relevance(weak=WEAK_MATCHES, strong=STRONG_MATCHES):
+ """
+ Create a key function that can be used to sort errors by relevance.
+
+ Arguments:
+ weak (set):
+ a collection of validator names to consider to be "weak".
+ If there are two errors at the same level of the instance
+ and one is in the set of weak validator names, the other
+ error will take priority. By default, :validator:`anyOf` and
+ :validator:`oneOf` are considered weak validators and will
+ be superseded by other same-level validation errors.
+
+ strong (set):
+ a collection of validator names to consider to be "strong"
+ """
+ def relevance(error):
+ validator = error.validator
+ return -len(error.path), validator not in weak, validator in strong
+ return relevance
+
+
+relevance = by_relevance()
+
+
+def best_match(errors, key=relevance):
+ """
+ Try to find an error that appears to be the best match among given errors.
+
+ In general, errors that are higher up in the instance (i.e. for which
+ `ValidationError.path` is shorter) are considered better matches,
+ since they indicate "more" is wrong with the instance.
+
+ If the resulting match is either :validator:`oneOf` or :validator:`anyOf`,
+ the *opposite* assumption is made -- i.e. the deepest error is picked,
+ since these validators only need to match once, and any other errors may
+ not be relevant.
+
+ Arguments:
+ errors (collections.abc.Iterable):
+
+ the errors to select from. Do not provide a mixture of
+ errors from different validation attempts (i.e. from
+ different instances or schemas), since it won't produce
+ sensical output.
+
+ key (collections.abc.Callable):
+
+ the key to use when sorting errors. See `relevance` and
+ transitively `by_relevance` for more details (the default is
+ to sort with the defaults of that function). Changing the
+ default is only useful if you want to change the function
+ that rates errors but still want the error context descent
+ done by this function.
+
+ Returns:
+ the best matching error, or ``None`` if the iterable was empty
+
+ .. note::
+
+ This function is a heuristic. Its return value may change for a given
+ set of inputs from version to version if better heuristics are added.
+ """
+ errors = iter(errors)
+ best = next(errors, None)
+ if best is None:
+ return
+ best = max(itertools.chain([best], errors), key=key)
+
+ while best.context:
+ best = min(best.context, key=key)
+ return best
diff --git a/jsonschema/schemas/draft3.json b/jsonschema/schemas/draft3.json
new file mode 100644
index 0000000..23d59b6
--- /dev/null
+++ b/jsonschema/schemas/draft3.json
@@ -0,0 +1,177 @@
+{
+ "$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" : "#", "type" : "object"},
+ "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" : ["string", "array", "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
+ },
+
+ "maxDecimal": {
+ "minimum": 0,
+ "type": "number"
+ },
+
+ "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"
+ },
+
+ "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" : {}
+}
diff --git a/jsonschema/schemas/draft4.json b/jsonschema/schemas/draft4.json
new file mode 100644
index 0000000..ba0c117
--- /dev/null
+++ b/jsonschema/schemas/draft4.json
@@ -0,0 +1,149 @@
+{
+ "id": "http://json-schema.org/draft-04/schema#",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Core schema meta-schema",
+ "definitions": {
+ "schemaArray": {
+ "type": "array",
+ "minItems": 1,
+ "items": { "$ref": "#" }
+ },
+ "positiveInteger": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "positiveIntegerDefault0": {
+ "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
+ },
+ "simpleTypes": {
+ "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
+ },
+ "stringArray": {
+ "type": "array",
+ "items": { "type": "string" },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ },
+ "type": "object",
+ "properties": {
+ "id": {
+ "format": "uri",
+ "type": "string"
+ },
+ "$schema": {
+ "type": "string",
+ "format": "uri"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "default": {},
+ "multipleOf": {
+ "type": "number",
+ "minimum": 0,
+ "exclusiveMinimum": true
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "exclusiveMaximum": {
+ "type": "boolean",
+ "default": false
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "exclusiveMinimum": {
+ "type": "boolean",
+ "default": false
+ },
+ "maxLength": { "$ref": "#/definitions/positiveInteger" },
+ "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
+ "pattern": {
+ "type": "string",
+ "format": "regex"
+ },
+ "additionalItems": {
+ "anyOf": [
+ { "type": "boolean" },
+ { "$ref": "#" }
+ ],
+ "default": {}
+ },
+ "items": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/schemaArray" }
+ ],
+ "default": {}
+ },
+ "maxItems": { "$ref": "#/definitions/positiveInteger" },
+ "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
+ "uniqueItems": {
+ "type": "boolean",
+ "default": false
+ },
+ "maxProperties": { "$ref": "#/definitions/positiveInteger" },
+ "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
+ "required": { "$ref": "#/definitions/stringArray" },
+ "additionalProperties": {
+ "anyOf": [
+ { "type": "boolean" },
+ { "$ref": "#" }
+ ],
+ "default": {}
+ },
+ "definitions": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "properties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "patternProperties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "dependencies": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/stringArray" }
+ ]
+ }
+ },
+ "enum": {
+ "type": "array"
+ },
+ "type": {
+ "anyOf": [
+ { "$ref": "#/definitions/simpleTypes" },
+ {
+ "type": "array",
+ "items": { "$ref": "#/definitions/simpleTypes" },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ ]
+ },
+ "format": { "type": "string" },
+ "allOf": { "$ref": "#/definitions/schemaArray" },
+ "anyOf": { "$ref": "#/definitions/schemaArray" },
+ "oneOf": { "$ref": "#/definitions/schemaArray" },
+ "not": { "$ref": "#" }
+ },
+ "dependencies": {
+ "exclusiveMaximum": [ "maximum" ],
+ "exclusiveMinimum": [ "minimum" ]
+ },
+ "default": {}
+}
diff --git a/jsonschema/schemas/draft6.json b/jsonschema/schemas/draft6.json
new file mode 100644
index 0000000..a0d2bf7
--- /dev/null
+++ b/jsonschema/schemas/draft6.json
@@ -0,0 +1,153 @@
+{
+ "$schema": "http://json-schema.org/draft-06/schema#",
+ "$id": "http://json-schema.org/draft-06/schema#",
+ "title": "Core schema meta-schema",
+ "definitions": {
+ "schemaArray": {
+ "type": "array",
+ "minItems": 1,
+ "items": { "$ref": "#" }
+ },
+ "nonNegativeInteger": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "nonNegativeIntegerDefault0": {
+ "allOf": [
+ { "$ref": "#/definitions/nonNegativeInteger" },
+ { "default": 0 }
+ ]
+ },
+ "simpleTypes": {
+ "enum": [
+ "array",
+ "boolean",
+ "integer",
+ "null",
+ "number",
+ "object",
+ "string"
+ ]
+ },
+ "stringArray": {
+ "type": "array",
+ "items": { "type": "string" },
+ "uniqueItems": true,
+ "default": []
+ }
+ },
+ "type": ["object", "boolean"],
+ "properties": {
+ "$id": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$schema": {
+ "type": "string",
+ "format": "uri"
+ },
+ "$ref": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "default": {},
+ "examples": {
+ "type": "array",
+ "items": {}
+ },
+ "multipleOf": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "exclusiveMaximum": {
+ "type": "number"
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "exclusiveMinimum": {
+ "type": "number"
+ },
+ "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "pattern": {
+ "type": "string",
+ "format": "regex"
+ },
+ "additionalItems": { "$ref": "#" },
+ "items": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/schemaArray" }
+ ],
+ "default": {}
+ },
+ "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "uniqueItems": {
+ "type": "boolean",
+ "default": false
+ },
+ "contains": { "$ref": "#" },
+ "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "required": { "$ref": "#/definitions/stringArray" },
+ "additionalProperties": { "$ref": "#" },
+ "definitions": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "properties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "patternProperties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "propertyNames": { "format": "regex" },
+ "default": {}
+ },
+ "dependencies": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/stringArray" }
+ ]
+ }
+ },
+ "propertyNames": { "$ref": "#" },
+ "const": {},
+ "enum": {
+ "type": "array"
+ },
+ "type": {
+ "anyOf": [
+ { "$ref": "#/definitions/simpleTypes" },
+ {
+ "type": "array",
+ "items": { "$ref": "#/definitions/simpleTypes" },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ ]
+ },
+ "format": { "type": "string" },
+ "allOf": { "$ref": "#/definitions/schemaArray" },
+ "anyOf": { "$ref": "#/definitions/schemaArray" },
+ "oneOf": { "$ref": "#/definitions/schemaArray" },
+ "not": { "$ref": "#" }
+ },
+ "default": {}
+}
diff --git a/jsonschema/schemas/draft7.json b/jsonschema/schemas/draft7.json
new file mode 100644
index 0000000..746cde9
--- /dev/null
+++ b/jsonschema/schemas/draft7.json
@@ -0,0 +1,166 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "http://json-schema.org/draft-07/schema#",
+ "title": "Core schema meta-schema",
+ "definitions": {
+ "schemaArray": {
+ "type": "array",
+ "minItems": 1,
+ "items": { "$ref": "#" }
+ },
+ "nonNegativeInteger": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "nonNegativeIntegerDefault0": {
+ "allOf": [
+ { "$ref": "#/definitions/nonNegativeInteger" },
+ { "default": 0 }
+ ]
+ },
+ "simpleTypes": {
+ "enum": [
+ "array",
+ "boolean",
+ "integer",
+ "null",
+ "number",
+ "object",
+ "string"
+ ]
+ },
+ "stringArray": {
+ "type": "array",
+ "items": { "type": "string" },
+ "uniqueItems": true,
+ "default": []
+ }
+ },
+ "type": ["object", "boolean"],
+ "properties": {
+ "$id": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$schema": {
+ "type": "string",
+ "format": "uri"
+ },
+ "$ref": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$comment": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "default": true,
+ "readOnly": {
+ "type": "boolean",
+ "default": false
+ },
+ "examples": {
+ "type": "array",
+ "items": true
+ },
+ "multipleOf": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "exclusiveMaximum": {
+ "type": "number"
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "exclusiveMinimum": {
+ "type": "number"
+ },
+ "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "pattern": {
+ "type": "string",
+ "format": "regex"
+ },
+ "additionalItems": { "$ref": "#" },
+ "items": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/schemaArray" }
+ ],
+ "default": true
+ },
+ "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "uniqueItems": {
+ "type": "boolean",
+ "default": false
+ },
+ "contains": { "$ref": "#" },
+ "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "required": { "$ref": "#/definitions/stringArray" },
+ "additionalProperties": { "$ref": "#" },
+ "definitions": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "properties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "patternProperties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "propertyNames": { "format": "regex" },
+ "default": {}
+ },
+ "dependencies": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/stringArray" }
+ ]
+ }
+ },
+ "propertyNames": { "$ref": "#" },
+ "const": true,
+ "enum": {
+ "type": "array",
+ "items": true
+ },
+ "type": {
+ "anyOf": [
+ { "$ref": "#/definitions/simpleTypes" },
+ {
+ "type": "array",
+ "items": { "$ref": "#/definitions/simpleTypes" },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ ]
+ },
+ "format": { "type": "string" },
+ "contentMediaType": { "type": "string" },
+ "contentEncoding": { "type": "string" },
+ "if": {"$ref": "#"},
+ "then": {"$ref": "#"},
+ "else": {"$ref": "#"},
+ "allOf": { "$ref": "#/definitions/schemaArray" },
+ "anyOf": { "$ref": "#/definitions/schemaArray" },
+ "oneOf": { "$ref": "#/definitions/schemaArray" },
+ "not": { "$ref": "#" }
+ },
+ "default": true
+}
diff --git a/jsonschema/tests/__init__.py b/jsonschema/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/jsonschema/tests/__init__.py
diff --git a/jsonschema/tests/_helpers.py b/jsonschema/tests/_helpers.py
new file mode 100644
index 0000000..51fff4f
--- /dev/null
+++ b/jsonschema/tests/_helpers.py
@@ -0,0 +1,21 @@
+from contextlib import contextmanager
+from io import StringIO
+import sys
+
+
+def bug(issue=None):
+ message = "A known bug."
+ if issue is not None:
+ message += " See issue #{issue}.".format(issue=issue)
+ return message
+
+
+@contextmanager
+def captured_output():
+ new_out, new_err = StringIO(), StringIO()
+ old_out, old_err = sys.stdout, sys.stderr
+ try:
+ sys.stdout, sys.stderr = new_out, new_err
+ yield sys.stdout, sys.stderr
+ finally:
+ sys.stdout, sys.stderr = old_out, old_err
diff --git a/jsonschema/tests/_suite.py b/jsonschema/tests/_suite.py
new file mode 100644
index 0000000..cba4f87
--- /dev/null
+++ b/jsonschema/tests/_suite.py
@@ -0,0 +1,243 @@
+"""
+Python representations of the JSON Schema Test Suite tests.
+"""
+
+from functools import partial
+import json
+import os
+import re
+import subprocess
+import sys
+import unittest
+
+try:
+ from pathlib import Path
+except ImportError:
+ from pathlib2 import Path
+
+import attr
+
+from jsonschema.validators import validators
+import jsonschema
+
+
+def _find_suite():
+ root = os.environ.get("JSON_SCHEMA_TEST_SUITE")
+ if root is not None:
+ return Path(root)
+
+ root = Path(jsonschema.__file__).parent.parent / "json"
+ if not root.is_dir(): # pragma: no cover
+ raise ValueError(
+ (
+ "Can't find the JSON-Schema-Test-Suite directory. "
+ "Set the 'JSON_SCHEMA_TEST_SUITE' environment "
+ "variable or run the tests from alongside a checkout "
+ "of the suite."
+ ),
+ )
+ return root
+
+
+@attr.s(hash=True)
+class Suite(object):
+
+ _root = attr.ib(default=attr.Factory(_find_suite))
+
+ def _remotes(self):
+ jsonschema_suite = self._root.joinpath("bin", "jsonschema_suite")
+ remotes = subprocess.check_output(
+ [sys.executable, str(jsonschema_suite), "remotes"],
+ )
+ return {
+ "http://localhost:1234/" + name: schema
+ for name, schema in json.loads(remotes.decode("utf-8")).items()
+ }
+
+ def benchmark(self, runner): # pragma: no cover
+ for name in validators:
+ self.version(name=name).benchmark(
+ runner=runner,
+ Validator=validators[name],
+ )
+
+ def version(self, name):
+ return Version(
+ name=name,
+ path=self._root.joinpath("tests", name),
+ remotes=self._remotes(),
+ )
+
+
+@attr.s(hash=True)
+class Version(object):
+
+ _path = attr.ib()
+ _remotes = attr.ib()
+
+ name = attr.ib()
+
+ def benchmark(self, runner, **kwargs): # pragma: no cover
+ for suite in self.tests():
+ for test in suite:
+ runner.bench_func(
+ test.fully_qualified_name,
+ partial(test.validate_ignoring_errors, **kwargs),
+ )
+
+ def tests(self):
+ return (
+ test
+ for child in self._path.glob("*.json")
+ for test in self._tests_in(
+ subject=child.name[:-5],
+ path=child,
+ )
+ )
+
+ def format_tests(self):
+ path = self._path.joinpath("optional", "format")
+ return (
+ test
+ for child in path.glob("*.json")
+ for test in self._tests_in(
+ subject=child.name[:-5],
+ path=child,
+ )
+ )
+
+ def tests_of(self, name):
+ return self._tests_in(
+ subject=name,
+ path=self._path / (name + ".json"),
+ )
+
+ def optional_tests_of(self, name):
+ return self._tests_in(
+ subject=name,
+ path=self._path.joinpath("optional", name + ".json"),
+ )
+
+ def to_unittest_testcase(self, *suites, **kwargs):
+ name = kwargs.pop("name", "Test" + self.name.title())
+ methods = {
+ test.method_name: test.to_unittest_method(**kwargs)
+ for suite in suites
+ for tests in suite
+ for test in tests
+ }
+ cls = type(name, (unittest.TestCase,), methods)
+
+ try:
+ cls.__module__ = _someone_save_us_the_module_of_the_caller()
+ except Exception: # pragma: no cover
+ # We're doing crazy things, so if they go wrong, like a function
+ # behaving differently on some other interpreter, just make them
+ # not happen.
+ pass
+
+ return cls
+
+ def _tests_in(self, subject, path):
+ for each in json.loads(path.read_text(encoding="utf-8")):
+ yield (
+ _Test(
+ version=self,
+ subject=subject,
+ case_description=each["description"],
+ schema=each["schema"],
+ remotes=self._remotes,
+ **test
+ ) for test in each["tests"]
+ )
+
+
+@attr.s(hash=True, repr=False)
+class _Test(object):
+
+ version = attr.ib()
+
+ subject = attr.ib()
+ case_description = attr.ib()
+ description = attr.ib()
+
+ data = attr.ib()
+ schema = attr.ib(repr=False)
+
+ valid = attr.ib()
+
+ _remotes = attr.ib()
+
+ comment = attr.ib(default=None)
+
+ def __repr__(self): # pragma: no cover
+ return "<Test {}>".format(self.fully_qualified_name)
+
+ @property
+ def fully_qualified_name(self): # pragma: no cover
+ return " > ".join(
+ [
+ self.version.name,
+ self.subject,
+ self.case_description,
+ self.description,
+ ]
+ )
+
+ @property
+ def method_name(self):
+ delimiters = r"[\W\- ]+"
+ return "test_%s_%s_%s" % (
+ re.sub(delimiters, "_", self.subject),
+ re.sub(delimiters, "_", self.case_description),
+ re.sub(delimiters, "_", self.description),
+ )
+
+ def to_unittest_method(self, skip=lambda test: None, **kwargs):
+ if self.valid:
+ def fn(this):
+ self.validate(**kwargs)
+ else:
+ def fn(this):
+ with this.assertRaises(jsonschema.ValidationError):
+ self.validate(**kwargs)
+
+ fn.__name__ = self.method_name
+ reason = skip(self)
+ return unittest.skipIf(reason is not None, reason)(fn)
+
+ def validate(self, Validator, **kwargs):
+ resolver = jsonschema.RefResolver.from_schema(
+ schema=self.schema,
+ store=self._remotes,
+ id_of=Validator.ID_OF,
+ )
+ jsonschema.validate(
+ instance=self.data,
+ schema=self.schema,
+ cls=Validator,
+ resolver=resolver,
+ **kwargs
+ )
+
+ def validate_ignoring_errors(self, Validator): # pragma: no cover
+ try:
+ self.validate(Validator=Validator)
+ except jsonschema.ValidationError:
+ pass
+
+
+def _someone_save_us_the_module_of_the_caller():
+ """
+ The FQON of the module 2nd stack frames up from here.
+
+ This is intended to allow us to dynamicallly return test case classes that
+ are indistinguishable from being defined in the module that wants them.
+
+ Otherwise, trial will mis-print the FQON, and copy pasting it won't re-run
+ the class that really is running.
+
+ Save us all, this is all so so so so so terrible.
+ """
+
+ return sys._getframe(2).f_globals["__name__"]
diff --git a/jsonschema/tests/test_cli.py b/jsonschema/tests/test_cli.py
new file mode 100644
index 0000000..4216759
--- /dev/null
+++ b/jsonschema/tests/test_cli.py
@@ -0,0 +1,821 @@
+from io import StringIO
+from json import JSONDecodeError
+from textwrap import dedent
+from unittest import TestCase
+import errno
+import json
+import os
+import subprocess
+import sys
+
+from jsonschema import Draft4Validator, Draft7Validator, __version__, cli
+from jsonschema.exceptions import SchemaError, ValidationError
+from jsonschema.tests._helpers import captured_output
+from jsonschema.validators import _LATEST_VERSION, validate
+
+
+def fake_validator(*errors):
+ errors = list(reversed(errors))
+
+ class FakeValidator(object):
+ def __init__(self, *args, **kwargs):
+ pass
+
+ def iter_errors(self, instance):
+ if errors:
+ return errors.pop()
+ return []
+
+ @classmethod
+ def check_schema(self, schema):
+ pass
+
+ return FakeValidator
+
+
+def fake_open(all_contents):
+ def open(path):
+ contents = all_contents.get(path)
+ if contents is None:
+ raise OSError(errno.ENOENT, os.strerror(errno.ENOENT), path)
+ return StringIO(contents)
+ return open
+
+
+def _message_for(non_json):
+ try:
+ json.loads(non_json)
+ except JSONDecodeError as error:
+ return str(error)
+ else: # pragma: no cover
+ raise RuntimeError("Tried and failed to capture a JSON dump error.")
+
+
+class TestCLI(TestCase):
+ def run_cli(
+ self, argv, files={}, stdin=StringIO(), exit_code=0, **override
+ ):
+ arguments = cli.parse_args(argv)
+ arguments.update(override)
+
+ self.assertFalse(hasattr(cli, "open"))
+ cli.open = fake_open(files)
+ try:
+ stdout, stderr = StringIO(), StringIO()
+ actual_exit_code = cli.run(
+ arguments,
+ stdin=stdin,
+ stdout=stdout,
+ stderr=stderr,
+ )
+ finally:
+ del cli.open
+
+ self.assertEqual(
+ actual_exit_code, exit_code, msg=dedent(
+ """
+ Expected an exit code of {} != {}.
+
+ stdout: {}
+
+ stderr: {}
+ """.format(
+ exit_code,
+ actual_exit_code,
+ stdout.getvalue(),
+ stderr.getvalue(),
+ ),
+ ),
+ )
+ return stdout.getvalue(), stderr.getvalue()
+
+ def assertOutputs(self, stdout="", stderr="", **kwargs):
+ self.assertEqual(
+ self.run_cli(**kwargs),
+ (dedent(stdout), dedent(stderr)),
+ )
+
+ def test_invalid_instance(self):
+ error = ValidationError("I am an error!", instance=12)
+ self.assertOutputs(
+ files=dict(
+ some_schema='{"does not": "matter since it is stubbed"}',
+ some_instance=json.dumps(error.instance),
+ ),
+ validator=fake_validator([error]),
+
+ argv=["-i", "some_instance", "some_schema"],
+
+ exit_code=1,
+ stderr="12: I am an error!\n",
+ )
+
+ def test_invalid_instance_pretty_output(self):
+ error = ValidationError("I am an error!", instance=12)
+ self.assertOutputs(
+ files=dict(
+ some_schema='{"does not": "matter since it is stubbed"}',
+ some_instance=json.dumps(error.instance),
+ ),
+ validator=fake_validator([error]),
+
+ argv=["-i", "some_instance", "--output", "pretty", "some_schema"],
+
+ exit_code=1,
+ stderr="""\
+ ===[ValidationError]===(some_instance)===
+
+ I am an error!
+ -----------------------------
+ """,
+ ),
+
+ def test_invalid_instance_explicit_plain_output(self):
+ error = ValidationError("I am an error!", instance=12)
+ self.assertOutputs(
+ files=dict(
+ some_schema='{"does not": "matter since it is stubbed"}',
+ some_instance=json.dumps(error.instance),
+ ),
+ validator=fake_validator([error]),
+
+ argv=["--output", "plain", "-i", "some_instance", "some_schema"],
+
+ exit_code=1,
+ stderr="12: I am an error!\n",
+ )
+
+ def test_invalid_instance_multiple_errors(self):
+ instance = 12
+ first = ValidationError("First error", instance=instance)
+ second = ValidationError("Second error", instance=instance)
+
+ self.assertOutputs(
+ files=dict(
+ some_schema='{"does not": "matter since it is stubbed"}',
+ some_instance=json.dumps(instance),
+ ),
+ validator=fake_validator([first, second]),
+
+ argv=["-i", "some_instance", "some_schema"],
+
+ exit_code=1,
+ stderr="""\
+ 12: First error
+ 12: Second error
+ """,
+ )
+
+ def test_invalid_instance_multiple_errors_pretty_output(self):
+ instance = 12
+ first = ValidationError("First error", instance=instance)
+ second = ValidationError("Second error", instance=instance)
+
+ self.assertOutputs(
+ files=dict(
+ some_schema='{"does not": "matter since it is stubbed"}',
+ some_instance=json.dumps(instance),
+ ),
+ validator=fake_validator([first, second]),
+
+ argv=["-i", "some_instance", "--output", "pretty", "some_schema"],
+
+ exit_code=1,
+ stderr="""\
+ ===[ValidationError]===(some_instance)===
+
+ First error
+ -----------------------------
+ ===[ValidationError]===(some_instance)===
+
+ Second error
+ -----------------------------
+ """,
+ )
+
+ def test_multiple_invalid_instances(self):
+ first_instance = 12
+ first_errors = [
+ ValidationError("An error", instance=first_instance),
+ ValidationError("Another error", instance=first_instance),
+ ]
+ second_instance = "foo"
+ second_errors = [ValidationError("BOOM", instance=second_instance)]
+
+ self.assertOutputs(
+ files=dict(
+ some_schema='{"does not": "matter since it is stubbed"}',
+ some_first_instance=json.dumps(first_instance),
+ some_second_instance=json.dumps(second_instance),
+ ),
+ validator=fake_validator(first_errors, second_errors),
+
+ argv=[
+ "-i", "some_first_instance",
+ "-i", "some_second_instance",
+ "some_schema",
+ ],
+
+ exit_code=1,
+ stderr="""\
+ 12: An error
+ 12: Another error
+ foo: BOOM
+ """,
+ )
+
+ def test_multiple_invalid_instances_pretty_output(self):
+ first_instance = 12
+ first_errors = [
+ ValidationError("An error", instance=first_instance),
+ ValidationError("Another error", instance=first_instance),
+ ]
+ second_instance = "foo"
+ second_errors = [ValidationError("BOOM", instance=second_instance)]
+
+ self.assertOutputs(
+ files=dict(
+ some_schema='{"does not": "matter since it is stubbed"}',
+ some_first_instance=json.dumps(first_instance),
+ some_second_instance=json.dumps(second_instance),
+ ),
+ validator=fake_validator(first_errors, second_errors),
+
+ argv=[
+ "--output", "pretty",
+ "-i", "some_first_instance",
+ "-i", "some_second_instance",
+ "some_schema",
+ ],
+
+ exit_code=1,
+ stderr="""\
+ ===[ValidationError]===(some_first_instance)===
+
+ An error
+ -----------------------------
+ ===[ValidationError]===(some_first_instance)===
+
+ Another error
+ -----------------------------
+ ===[ValidationError]===(some_second_instance)===
+
+ BOOM
+ -----------------------------
+ """,
+ )
+
+ def test_custom_error_format(self):
+ first_instance = 12
+ first_errors = [
+ ValidationError("An error", instance=first_instance),
+ ValidationError("Another error", instance=first_instance),
+ ]
+ second_instance = "foo"
+ second_errors = [ValidationError("BOOM", instance=second_instance)]
+
+ self.assertOutputs(
+ files=dict(
+ some_schema='{"does not": "matter since it is stubbed"}',
+ some_first_instance=json.dumps(first_instance),
+ some_second_instance=json.dumps(second_instance),
+ ),
+ validator=fake_validator(first_errors, second_errors),
+
+ argv=[
+ "--error-format", ":{error.message}._-_.{error.instance}:",
+ "-i", "some_first_instance",
+ "-i", "some_second_instance",
+ "some_schema",
+ ],
+
+ exit_code=1,
+ stderr=":An error._-_.12::Another error._-_.12::BOOM._-_.foo:",
+ )
+
+ def test_invalid_schema(self):
+ self.assertOutputs(
+ files=dict(some_schema='{"type": 12}'),
+ argv=["some_schema"],
+
+ exit_code=1,
+ stderr="""\
+ 12: 12 is not valid under any of the given schemas
+ """,
+ )
+
+ def test_invalid_schema_pretty_output(self):
+ schema = {"type": 12}
+
+ with self.assertRaises(SchemaError) as e:
+ validate(schema=schema, instance="")
+ error = str(e.exception)
+
+ self.assertOutputs(
+ files=dict(some_schema=json.dumps(schema)),
+ argv=["--output", "pretty", "some_schema"],
+
+ exit_code=1,
+ stderr=(
+ "===[SchemaError]===(some_schema)===\n\n"
+ + str(error)
+ + "\n-----------------------------\n"
+ ),
+ )
+
+ def test_invalid_schema_multiple_errors(self):
+ self.assertOutputs(
+ files=dict(some_schema='{"type": 12, "items": 57}'),
+ argv=["some_schema"],
+
+ exit_code=1,
+ stderr="""\
+ 57: 57 is not valid under any of the given schemas
+ """,
+ )
+
+ def test_invalid_schema_multiple_errors_pretty_output(self):
+ schema = {"type": 12, "items": 57}
+
+ with self.assertRaises(SchemaError) as e:
+ validate(schema=schema, instance="")
+ error = str(e.exception)
+
+ self.assertOutputs(
+ files=dict(some_schema=json.dumps(schema)),
+ argv=["--output", "pretty", "some_schema"],
+
+ exit_code=1,
+ stderr=(
+ "===[SchemaError]===(some_schema)===\n\n"
+ + str(error)
+ + "\n-----------------------------\n"
+ ),
+ )
+
+ def test_invalid_schema_with_invalid_instance(self):
+ """
+ "Validating" an instance that's invalid under an invalid schema
+ just shows the schema error.
+ """
+ self.assertOutputs(
+ files=dict(
+ some_schema='{"type": 12, "minimum": 30}',
+ some_instance="13",
+ ),
+ argv=["-i", "some_instance", "some_schema"],
+
+ exit_code=1,
+ stderr="""\
+ 12: 12 is not valid under any of the given schemas
+ """,
+ )
+
+ def test_invalid_schema_with_invalid_instance_pretty_output(self):
+ instance, schema = 13, {"type": 12, "minimum": 30}
+
+ with self.assertRaises(SchemaError) as e:
+ validate(schema=schema, instance=instance)
+ error = str(e.exception)
+
+ self.assertOutputs(
+ files=dict(
+ some_schema=json.dumps(schema),
+ some_instance=json.dumps(instance),
+ ),
+ argv=["--output", "pretty", "-i", "some_instance", "some_schema"],
+
+ exit_code=1,
+ stderr=(
+ "===[SchemaError]===(some_schema)===\n\n"
+ + str(error)
+ + "\n-----------------------------\n"
+ ),
+ )
+
+ def test_invalid_instance_continues_with_the_rest(self):
+ self.assertOutputs(
+ files=dict(
+ some_schema='{"minimum": 30}',
+ first_instance="not valid JSON!",
+ second_instance="12",
+ ),
+ argv=[
+ "-i", "first_instance",
+ "-i", "second_instance",
+ "some_schema",
+ ],
+
+ exit_code=1,
+ stderr="""\
+ Failed to parse 'first_instance': {}
+ 12: 12 is less than the minimum of 30
+ """.format(_message_for("not valid JSON!")),
+ )
+
+ def test_custom_error_format_applies_to_schema_errors(self):
+ instance, schema = 13, {"type": 12, "minimum": 30}
+
+ with self.assertRaises(SchemaError):
+ validate(schema=schema, instance=instance)
+
+ self.assertOutputs(
+ files=dict(some_schema=json.dumps(schema)),
+
+ argv=[
+ "--error-format", ":{error.message}._-_.{error.instance}:",
+ "some_schema",
+ ],
+
+ exit_code=1,
+ stderr=":12 is not valid under any of the given schemas._-_.12:",
+ )
+
+ def test_instance_is_invalid_JSON(self):
+ instance = "not valid JSON!"
+
+ self.assertOutputs(
+ files=dict(some_schema="{}", some_instance=instance),
+ argv=["-i", "some_instance", "some_schema"],
+
+ exit_code=1,
+ stderr="""\
+ Failed to parse 'some_instance': {}
+ """.format(_message_for(instance)),
+ )
+
+ def test_instance_is_invalid_JSON_pretty_output(self):
+ stdout, stderr = self.run_cli(
+ files=dict(
+ some_schema="{}",
+ some_instance="not valid JSON!",
+ ),
+
+ argv=["--output", "pretty", "-i", "some_instance", "some_schema"],
+
+ exit_code=1,
+ )
+ self.assertFalse(stdout)
+ self.assertIn(
+ "(some_instance)===\n\nTraceback (most recent call last):\n",
+ stderr,
+ )
+ self.assertNotIn("some_schema", stderr)
+
+ def test_instance_is_invalid_JSON_on_stdin(self):
+ instance = "not valid JSON!"
+
+ self.assertOutputs(
+ files=dict(some_schema="{}"),
+ stdin=StringIO(instance),
+
+ argv=["some_schema"],
+
+ exit_code=1,
+ stderr="""\
+ Failed to parse <stdin>: {}
+ """.format(_message_for(instance)),
+ )
+
+ def test_instance_is_invalid_JSON_on_stdin_pretty_output(self):
+ stdout, stderr = self.run_cli(
+ files=dict(some_schema="{}"),
+ stdin=StringIO("not valid JSON!"),
+
+ argv=["--output", "pretty", "some_schema"],
+
+ exit_code=1,
+ )
+ self.assertFalse(stdout)
+ self.assertIn(
+ "(<stdin>)===\n\nTraceback (most recent call last):\n",
+ stderr,
+ )
+ self.assertNotIn("some_schema", stderr)
+
+ def test_schema_is_invalid_JSON(self):
+ schema = "not valid JSON!"
+
+ self.assertOutputs(
+ files=dict(some_schema=schema),
+
+ argv=["some_schema"],
+
+ exit_code=1,
+ stderr="""\
+ Failed to parse 'some_schema': {}
+ """.format(_message_for(schema)),
+ )
+
+ def test_schema_is_invalid_JSON_pretty_output(self):
+ stdout, stderr = self.run_cli(
+ files=dict(some_schema="not valid JSON!"),
+
+ argv=["--output", "pretty", "some_schema"],
+
+ exit_code=1,
+ )
+ self.assertFalse(stdout)
+ self.assertIn(
+ "(some_schema)===\n\nTraceback (most recent call last):\n",
+ stderr,
+ )
+
+ def test_schema_and_instance_are_both_invalid_JSON(self):
+ """
+ Only the schema error is reported, as we abort immediately.
+ """
+ schema, instance = "not valid JSON!", "also not valid JSON!"
+ self.assertOutputs(
+ files=dict(some_schema=schema, some_instance=instance),
+
+ argv=["some_schema"],
+
+ exit_code=1,
+ stderr="""\
+ Failed to parse 'some_schema': {}
+ """.format(_message_for(schema)),
+ )
+
+ def test_schema_and_instance_are_both_invalid_JSON_pretty_output(self):
+ """
+ Only the schema error is reported, as we abort immediately.
+ """
+ stdout, stderr = self.run_cli(
+ files=dict(
+ some_schema="not valid JSON!",
+ some_instance="also not valid JSON!",
+ ),
+
+ argv=["--output", "pretty", "-i", "some_instance", "some_schema"],
+
+ exit_code=1,
+ )
+ self.assertFalse(stdout)
+ self.assertIn(
+ "(some_schema)===\n\nTraceback (most recent call last):\n",
+ stderr,
+ )
+ self.assertNotIn("some_instance", stderr)
+
+ def test_instance_does_not_exist(self):
+ self.assertOutputs(
+ files=dict(some_schema="{}"),
+ argv=["-i", "nonexisting_instance", "some_schema"],
+
+ exit_code=1,
+ stderr="""\
+ 'nonexisting_instance' does not exist.
+ """,
+ )
+
+ def test_instance_does_not_exist_pretty_output(self):
+ self.assertOutputs(
+ files=dict(some_schema="{}"),
+ argv=[
+ "--output", "pretty",
+ "-i", "nonexisting_instance",
+ "some_schema",
+ ],
+
+ exit_code=1,
+ stderr="""\
+ ===[FileNotFoundError]===(nonexisting_instance)===
+
+ 'nonexisting_instance' does not exist.
+ -----------------------------
+ """,
+ )
+
+ def test_schema_does_not_exist(self):
+ self.assertOutputs(
+ argv=["nonexisting_schema"],
+
+ exit_code=1,
+ stderr="'nonexisting_schema' does not exist.\n",
+ )
+
+ def test_schema_does_not_exist_pretty_output(self):
+ self.assertOutputs(
+ argv=["--output", "pretty", "nonexisting_schema"],
+
+ exit_code=1,
+ stderr="""\
+ ===[FileNotFoundError]===(nonexisting_schema)===
+
+ 'nonexisting_schema' does not exist.
+ -----------------------------
+ """,
+ )
+
+ def test_neither_instance_nor_schema_exist(self):
+ self.assertOutputs(
+ argv=["-i", "nonexisting_instance", "nonexisting_schema"],
+
+ exit_code=1,
+ stderr="'nonexisting_schema' does not exist.\n",
+ )
+
+ def test_neither_instance_nor_schema_exist_pretty_output(self):
+ self.assertOutputs(
+ argv=[
+ "--output", "pretty",
+ "-i", "nonexisting_instance",
+ "nonexisting_schema",
+ ],
+
+ exit_code=1,
+ stderr="""\
+ ===[FileNotFoundError]===(nonexisting_schema)===
+
+ 'nonexisting_schema' does not exist.
+ -----------------------------
+ """,
+ )
+
+ def test_successful_validation(self):
+ self.assertOutputs(
+ files=dict(some_schema="{}", some_instance="{}"),
+ argv=["-i", "some_instance", "some_schema"],
+ stdout="",
+ stderr="",
+ )
+
+ def test_successful_validation_pretty_output(self):
+ self.assertOutputs(
+ files=dict(some_schema="{}", some_instance="{}"),
+ argv=["--output", "pretty", "-i", "some_instance", "some_schema"],
+ stdout="===[SUCCESS]===(some_instance)===\n",
+ stderr="",
+ )
+
+ def test_successful_validation_of_stdin(self):
+ self.assertOutputs(
+ files=dict(some_schema="{}"),
+ stdin=StringIO("{}"),
+ argv=["some_schema"],
+ stdout="",
+ stderr="",
+ )
+
+ def test_successful_validation_of_stdin_pretty_output(self):
+ self.assertOutputs(
+ files=dict(some_schema="{}"),
+ stdin=StringIO("{}"),
+ argv=["--output", "pretty", "some_schema"],
+ stdout="===[SUCCESS]===(<stdin>)===\n",
+ stderr="",
+ )
+
+ def test_successful_validation_of_just_the_schema(self):
+ self.assertOutputs(
+ files=dict(some_schema="{}", some_instance="{}"),
+ argv=["-i", "some_instance", "some_schema"],
+ stdout="",
+ stderr="",
+ )
+
+ def test_successful_validation_of_just_the_schema_pretty_output(self):
+ self.assertOutputs(
+ files=dict(some_schema="{}", some_instance="{}"),
+ argv=["--output", "pretty", "-i", "some_instance", "some_schema"],
+ stdout="===[SUCCESS]===(some_instance)===\n",
+ stderr="",
+ )
+
+ def test_it_validates_using_the_latest_validator_when_unspecified(self):
+ # There isn't a better way now I can think of to ensure that the
+ # latest version was used, given that the call to validator_for
+ # is hidden inside the CLI, so guard that that's the case, and
+ # this test will have to be updated when versions change until
+ # we can think of a better way to ensure this behavior.
+ self.assertIs(Draft7Validator, _LATEST_VERSION)
+
+ self.assertOutputs(
+ files=dict(some_schema='{"const": "check"}', some_instance='"a"'),
+ argv=["-i", "some_instance", "some_schema"],
+ exit_code=1,
+ stdout="",
+ stderr="a: 'check' was expected\n",
+ )
+
+ def test_it_validates_using_draft7_when_specified(self):
+ """
+ Specifically, `const` validation applies for Draft 7.
+ """
+ schema = """
+ {
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "const": "check"
+ }
+ """
+ instance = '"foo"'
+ self.assertOutputs(
+ files=dict(some_schema=schema, some_instance=instance),
+ argv=["-i", "some_instance", "some_schema"],
+ exit_code=1,
+ stdout="",
+ stderr="foo: 'check' was expected\n",
+ )
+
+ def test_it_validates_using_draft4_when_specified(self):
+ """
+ Specifically, `const` validation *does not* apply for Draft 4.
+ """
+ schema = """
+ {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "const": "check"
+ }
+ """
+ instance = '"foo"'
+ self.assertOutputs(
+ files=dict(some_schema=schema, some_instance=instance),
+ argv=["-i", "some_instance", "some_schema"],
+ stdout="",
+ stderr="",
+ )
+
+
+class TestParser(TestCase):
+
+ FakeValidator = fake_validator()
+
+ def test_find_validator_by_fully_qualified_object_name(self):
+ arguments = cli.parse_args(
+ [
+ "--validator",
+ "jsonschema.tests.test_cli.TestParser.FakeValidator",
+ "--instance", "mem://some/instance",
+ "mem://some/schema",
+ ]
+ )
+ self.assertIs(arguments["validator"], self.FakeValidator)
+
+ def test_find_validator_in_jsonschema(self):
+ arguments = cli.parse_args(
+ [
+ "--validator", "Draft4Validator",
+ "--instance", "mem://some/instance",
+ "mem://some/schema",
+ ]
+ )
+ self.assertIs(arguments["validator"], Draft4Validator)
+
+ def test_unknown_output(self):
+ # Avoid the help message on stdout
+ with captured_output() as (stdout, stderr):
+ with self.assertRaises(SystemExit):
+ cli.parse_args(
+ [
+ "--output", "foo",
+ "mem://some/schema",
+ ]
+ )
+ self.assertIn("invalid choice: 'foo'", stderr.getvalue())
+ self.assertFalse(stdout.getvalue())
+
+ def test_useless_error_format(self):
+ # Avoid the help message on stdout
+ with captured_output() as (stdout, stderr):
+ with self.assertRaises(SystemExit):
+ cli.parse_args(
+ [
+ "--output", "pretty",
+ "--error-format", "foo",
+ "mem://some/schema",
+ ]
+ )
+ self.assertIn(
+ "--error-format can only be used with --output plain",
+ stderr.getvalue(),
+ )
+ self.assertFalse(stdout.getvalue())
+
+
+class TestCLIIntegration(TestCase):
+ def test_license(self):
+ output = subprocess.check_output(
+ [sys.executable, "-m", "pip", "show", "jsonschema"],
+ stderr=subprocess.STDOUT,
+ )
+ self.assertIn(b"License: MIT", output)
+
+ def test_version(self):
+ version = subprocess.check_output(
+ [sys.executable, "-m", "jsonschema", "--version"],
+ stderr=subprocess.STDOUT,
+ )
+ version = version.decode("utf-8").strip()
+ self.assertEqual(version, __version__)
+
+ def test_no_arguments_shows_usage_notes(self):
+ output = subprocess.check_output(
+ [sys.executable, "-m", "jsonschema"],
+ stderr=subprocess.STDOUT,
+ )
+ output_for_help = subprocess.check_output(
+ [sys.executable, "-m", "jsonschema", "--help"],
+ stderr=subprocess.STDOUT,
+ )
+ self.assertEqual(output, output_for_help)
diff --git a/jsonschema/tests/test_exceptions.py b/jsonschema/tests/test_exceptions.py
new file mode 100644
index 0000000..a285550
--- /dev/null
+++ b/jsonschema/tests/test_exceptions.py
@@ -0,0 +1,459 @@
+from unittest import TestCase
+import textwrap
+
+from jsonschema import Draft4Validator, exceptions
+
+
+class TestBestMatch(TestCase):
+ def best_match(self, errors):
+ errors = list(errors)
+ best = exceptions.best_match(errors)
+ reversed_best = exceptions.best_match(reversed(errors))
+ msg = "Didn't return a consistent best match!\nGot: {0}\n\nThen: {1}"
+ self.assertEqual(
+ best._contents(), reversed_best._contents(),
+ msg=msg.format(best, reversed_best),
+ )
+ return best
+
+ def test_shallower_errors_are_better_matches(self):
+ validator = Draft4Validator(
+ {
+ "properties": {
+ "foo": {
+ "minProperties": 2,
+ "properties": {"bar": {"type": "object"}},
+ },
+ },
+ },
+ )
+ best = self.best_match(validator.iter_errors({"foo": {"bar": []}}))
+ self.assertEqual(best.validator, "minProperties")
+
+ def test_oneOf_and_anyOf_are_weak_matches(self):
+ """
+ A property you *must* match is probably better than one you have to
+ match a part of.
+ """
+
+ validator = Draft4Validator(
+ {
+ "minProperties": 2,
+ "anyOf": [{"type": "string"}, {"type": "number"}],
+ "oneOf": [{"type": "string"}, {"type": "number"}],
+ }
+ )
+ best = self.best_match(validator.iter_errors({}))
+ self.assertEqual(best.validator, "minProperties")
+
+ def test_if_the_most_relevant_error_is_anyOf_it_is_traversed(self):
+ """
+ If the most relevant error is an anyOf, then we traverse its context
+ and select the otherwise *least* relevant error, since in this case
+ that means the most specific, deep, error inside the instance.
+
+ I.e. since only one of the schemas must match, we look for the most
+ relevant one.
+ """
+
+ validator = Draft4Validator(
+ {
+ "properties": {
+ "foo": {
+ "anyOf": [
+ {"type": "string"},
+ {"properties": {"bar": {"type": "array"}}},
+ ],
+ },
+ },
+ },
+ )
+ best = self.best_match(validator.iter_errors({"foo": {"bar": 12}}))
+ self.assertEqual(best.validator_value, "array")
+
+ def test_if_the_most_relevant_error_is_oneOf_it_is_traversed(self):
+ """
+ If the most relevant error is an oneOf, then we traverse its context
+ and select the otherwise *least* relevant error, since in this case
+ that means the most specific, deep, error inside the instance.
+
+ I.e. since only one of the schemas must match, we look for the most
+ relevant one.
+ """
+
+ validator = Draft4Validator(
+ {
+ "properties": {
+ "foo": {
+ "oneOf": [
+ {"type": "string"},
+ {"properties": {"bar": {"type": "array"}}},
+ ],
+ },
+ },
+ },
+ )
+ best = self.best_match(validator.iter_errors({"foo": {"bar": 12}}))
+ self.assertEqual(best.validator_value, "array")
+
+ def test_if_the_most_relevant_error_is_allOf_it_is_traversed(self):
+ """
+ Now, if the error is allOf, we traverse but select the *most* relevant
+ error from the context, because all schemas here must match anyways.
+ """
+
+ validator = Draft4Validator(
+ {
+ "properties": {
+ "foo": {
+ "allOf": [
+ {"type": "string"},
+ {"properties": {"bar": {"type": "array"}}},
+ ],
+ },
+ },
+ },
+ )
+ best = self.best_match(validator.iter_errors({"foo": {"bar": 12}}))
+ self.assertEqual(best.validator_value, "string")
+
+ def test_nested_context_for_oneOf(self):
+ validator = Draft4Validator(
+ {
+ "properties": {
+ "foo": {
+ "oneOf": [
+ {"type": "string"},
+ {
+ "oneOf": [
+ {"type": "string"},
+ {
+ "properties": {
+ "bar": {"type": "array"},
+ },
+ },
+ ],
+ },
+ ],
+ },
+ },
+ },
+ )
+ best = self.best_match(validator.iter_errors({"foo": {"bar": 12}}))
+ self.assertEqual(best.validator_value, "array")
+
+ def test_one_error(self):
+ validator = Draft4Validator({"minProperties": 2})
+ error, = validator.iter_errors({})
+ self.assertEqual(
+ exceptions.best_match(validator.iter_errors({})).validator,
+ "minProperties",
+ )
+
+ def test_no_errors(self):
+ validator = Draft4Validator({})
+ self.assertIsNone(exceptions.best_match(validator.iter_errors({})))
+
+
+class TestByRelevance(TestCase):
+ def test_short_paths_are_better_matches(self):
+ shallow = exceptions.ValidationError("Oh no!", path=["baz"])
+ deep = exceptions.ValidationError("Oh yes!", path=["foo", "bar"])
+ match = max([shallow, deep], key=exceptions.relevance)
+ self.assertIs(match, shallow)
+
+ match = max([deep, shallow], key=exceptions.relevance)
+ self.assertIs(match, shallow)
+
+ def test_global_errors_are_even_better_matches(self):
+ shallow = exceptions.ValidationError("Oh no!", path=[])
+ deep = exceptions.ValidationError("Oh yes!", path=["foo"])
+
+ errors = sorted([shallow, deep], key=exceptions.relevance)
+ self.assertEqual(
+ [list(error.path) for error in errors],
+ [["foo"], []],
+ )
+
+ errors = sorted([deep, shallow], key=exceptions.relevance)
+ self.assertEqual(
+ [list(error.path) for error in errors],
+ [["foo"], []],
+ )
+
+ def test_weak_validators_are_lower_priority(self):
+ weak = exceptions.ValidationError("Oh no!", path=[], validator="a")
+ normal = exceptions.ValidationError("Oh yes!", path=[], validator="b")
+
+ best_match = exceptions.by_relevance(weak="a")
+
+ match = max([weak, normal], key=best_match)
+ self.assertIs(match, normal)
+
+ match = max([normal, weak], key=best_match)
+ self.assertIs(match, normal)
+
+ def test_strong_validators_are_higher_priority(self):
+ weak = exceptions.ValidationError("Oh no!", path=[], validator="a")
+ normal = exceptions.ValidationError("Oh yes!", path=[], validator="b")
+ strong = exceptions.ValidationError("Oh fine!", path=[], validator="c")
+
+ best_match = exceptions.by_relevance(weak="a", strong="c")
+
+ match = max([weak, normal, strong], key=best_match)
+ self.assertIs(match, strong)
+
+ match = max([strong, normal, weak], key=best_match)
+ self.assertIs(match, strong)
+
+
+class TestErrorTree(TestCase):
+ def test_it_knows_how_many_total_errors_it_contains(self):
+ # FIXME: https://github.com/Julian/jsonschema/issues/442
+ errors = [
+ exceptions.ValidationError("Something", validator=i)
+ for i in range(8)
+ ]
+ tree = exceptions.ErrorTree(errors)
+ self.assertEqual(tree.total_errors, 8)
+
+ def test_it_contains_an_item_if_the_item_had_an_error(self):
+ errors = [exceptions.ValidationError("a message", path=["bar"])]
+ tree = exceptions.ErrorTree(errors)
+ self.assertIn("bar", tree)
+
+ def test_it_does_not_contain_an_item_if_the_item_had_no_error(self):
+ errors = [exceptions.ValidationError("a message", path=["bar"])]
+ tree = exceptions.ErrorTree(errors)
+ self.assertNotIn("foo", tree)
+
+ def test_validators_that_failed_appear_in_errors_dict(self):
+ error = exceptions.ValidationError("a message", validator="foo")
+ tree = exceptions.ErrorTree([error])
+ self.assertEqual(tree.errors, {"foo": error})
+
+ def test_it_creates_a_child_tree_for_each_nested_path(self):
+ errors = [
+ exceptions.ValidationError("a bar message", path=["bar"]),
+ exceptions.ValidationError("a bar -> 0 message", path=["bar", 0]),
+ ]
+ tree = exceptions.ErrorTree(errors)
+ self.assertIn(0, tree["bar"])
+ self.assertNotIn(1, tree["bar"])
+
+ def test_children_have_their_errors_dicts_built(self):
+ e1, e2 = (
+ exceptions.ValidationError("1", validator="foo", path=["bar", 0]),
+ exceptions.ValidationError("2", validator="quux", path=["bar", 0]),
+ )
+ tree = exceptions.ErrorTree([e1, e2])
+ self.assertEqual(tree["bar"][0].errors, {"foo": e1, "quux": e2})
+
+ def test_multiple_errors_with_instance(self):
+ e1, e2 = (
+ exceptions.ValidationError(
+ "1",
+ validator="foo",
+ path=["bar", "bar2"],
+ instance="i1"),
+ exceptions.ValidationError(
+ "2",
+ validator="quux",
+ path=["foobar", 2],
+ instance="i2"),
+ )
+ exceptions.ErrorTree([e1, e2])
+
+ def test_it_does_not_contain_subtrees_that_are_not_in_the_instance(self):
+ error = exceptions.ValidationError("123", validator="foo", instance=[])
+ tree = exceptions.ErrorTree([error])
+
+ with self.assertRaises(IndexError):
+ tree[0]
+
+ def test_if_its_in_the_tree_anyhow_it_does_not_raise_an_error(self):
+ """
+ If a validator is dumb (like :validator:`required` in draft 3) and
+ refers to a path that isn't in the instance, the tree still properly
+ returns a subtree for that path.
+ """
+
+ error = exceptions.ValidationError(
+ "a message", validator="foo", instance={}, path=["foo"],
+ )
+ tree = exceptions.ErrorTree([error])
+ self.assertIsInstance(tree["foo"], exceptions.ErrorTree)
+
+
+class TestErrorInitReprStr(TestCase):
+ def make_error(self, **kwargs):
+ defaults = dict(
+ message=u"hello",
+ validator=u"type",
+ validator_value=u"string",
+ instance=5,
+ schema={u"type": u"string"},
+ )
+ defaults.update(kwargs)
+ return exceptions.ValidationError(**defaults)
+
+ def assertShows(self, expected, **kwargs):
+ expected = textwrap.dedent(expected).rstrip("\n")
+
+ error = self.make_error(**kwargs)
+ message_line, _, rest = str(error).partition("\n")
+ self.assertEqual(message_line, error.message)
+ self.assertEqual(rest, expected)
+
+ def test_it_calls_super_and_sets_args(self):
+ error = self.make_error()
+ self.assertGreater(len(error.args), 1)
+
+ def test_repr(self):
+ self.assertEqual(
+ repr(exceptions.ValidationError(message="Hello!")),
+ "<ValidationError: %r>" % "Hello!",
+ )
+
+ def test_unset_error(self):
+ error = exceptions.ValidationError("message")
+ self.assertEqual(str(error), "message")
+
+ kwargs = {
+ "validator": "type",
+ "validator_value": "string",
+ "instance": 5,
+ "schema": {"type": "string"},
+ }
+ # Just the message should show if any of the attributes are unset
+ for attr in kwargs:
+ k = dict(kwargs)
+ del k[attr]
+ error = exceptions.ValidationError("message", **k)
+ self.assertEqual(str(error), "message")
+
+ def test_empty_paths(self):
+ self.assertShows(
+ """
+ Failed validating 'type' in schema:
+ {'type': 'string'}
+
+ On instance:
+ 5
+ """,
+ path=[],
+ schema_path=[],
+ )
+
+ def test_one_item_paths(self):
+ self.assertShows(
+ """
+ Failed validating 'type' in schema:
+ {'type': 'string'}
+
+ On instance[0]:
+ 5
+ """,
+ path=[0],
+ schema_path=["items"],
+ )
+
+ def test_multiple_item_paths(self):
+ self.assertShows(
+ """
+ Failed validating 'type' in schema['items'][0]:
+ {'type': 'string'}
+
+ On instance[0]['a']:
+ 5
+ """,
+ path=[0, u"a"],
+ schema_path=[u"items", 0, 1],
+ )
+
+ def test_uses_pprint(self):
+ self.assertShows(
+ """
+ Failed validating 'maxLength' in schema:
+ {0: 0,
+ 1: 1,
+ 2: 2,
+ 3: 3,
+ 4: 4,
+ 5: 5,
+ 6: 6,
+ 7: 7,
+ 8: 8,
+ 9: 9,
+ 10: 10,
+ 11: 11,
+ 12: 12,
+ 13: 13,
+ 14: 14,
+ 15: 15,
+ 16: 16,
+ 17: 17,
+ 18: 18,
+ 19: 19}
+
+ On instance:
+ [0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24]
+ """,
+ instance=list(range(25)),
+ schema=dict(zip(range(20), range(20))),
+ validator=u"maxLength",
+ )
+
+ def test_str_works_with_instances_having_overriden_eq_operator(self):
+ """
+ Check for https://github.com/Julian/jsonschema/issues/164 which
+ rendered exceptions unusable when a `ValidationError` involved
+ instances with an `__eq__` method that returned truthy values.
+ """
+
+ class DontEQMeBro(object):
+ def __eq__(this, other): # pragma: no cover
+ self.fail("Don't!")
+
+ def __ne__(this, other): # pragma: no cover
+ self.fail("Don't!")
+
+ instance = DontEQMeBro()
+ error = exceptions.ValidationError(
+ "a message",
+ validator="foo",
+ instance=instance,
+ validator_value="some",
+ schema="schema",
+ )
+ self.assertIn(repr(instance), str(error))
+
+
+class TestHashable(TestCase):
+ def test_hashable(self):
+ set([exceptions.ValidationError("")])
+ set([exceptions.SchemaError("")])
diff --git a/jsonschema/tests/test_format.py b/jsonschema/tests/test_format.py
new file mode 100644
index 0000000..6dba484
--- /dev/null
+++ b/jsonschema/tests/test_format.py
@@ -0,0 +1,88 @@
+"""
+Tests for the parts of jsonschema related to the :validator:`format` property.
+"""
+
+from unittest import TestCase
+
+from jsonschema import FormatChecker, FormatError, ValidationError
+from jsonschema.validators import Draft4Validator
+
+BOOM = ValueError("Boom!")
+BANG = ZeroDivisionError("Bang!")
+
+
+def boom(thing):
+ if thing == "bang":
+ raise BANG
+ raise BOOM
+
+
+class TestFormatChecker(TestCase):
+ def test_it_can_validate_no_formats(self):
+ checker = FormatChecker(formats=())
+ self.assertFalse(checker.checkers)
+
+ def test_it_raises_a_key_error_for_unknown_formats(self):
+ with self.assertRaises(KeyError):
+ FormatChecker(formats=["o noes"])
+
+ def test_it_can_register_cls_checkers(self):
+ original = dict(FormatChecker.checkers)
+ self.addCleanup(FormatChecker.checkers.pop, "boom")
+ FormatChecker.cls_checks("boom")(boom)
+ self.assertEqual(
+ FormatChecker.checkers,
+ dict(original, boom=(boom, ())),
+ )
+
+ def test_it_can_register_checkers(self):
+ checker = FormatChecker()
+ checker.checks("boom")(boom)
+ self.assertEqual(
+ checker.checkers,
+ dict(FormatChecker.checkers, boom=(boom, ()))
+ )
+
+ def test_it_catches_registered_errors(self):
+ checker = FormatChecker()
+ checker.checks("boom", raises=type(BOOM))(boom)
+
+ with self.assertRaises(FormatError) as cm:
+ checker.check(instance=12, format="boom")
+
+ self.assertIs(cm.exception.cause, BOOM)
+ self.assertIs(cm.exception.__cause__, BOOM)
+
+ # Unregistered errors should not be caught
+ with self.assertRaises(type(BANG)):
+ checker.check(instance="bang", format="boom")
+
+ def test_format_error_causes_become_validation_error_causes(self):
+ checker = FormatChecker()
+ checker.checks("boom", raises=ValueError)(boom)
+ validator = Draft4Validator({"format": "boom"}, format_checker=checker)
+
+ with self.assertRaises(ValidationError) as cm:
+ validator.validate("BOOM")
+
+ self.assertIs(cm.exception.cause, BOOM)
+ self.assertIs(cm.exception.__cause__, BOOM)
+
+ def test_format_checkers_come_with_defaults(self):
+ # This is bad :/ but relied upon.
+ # The docs for quite awhile recommended people do things like
+ # validate(..., format_checker=FormatChecker())
+ # We should change that, but we can't without deprecation...
+ checker = FormatChecker()
+ with self.assertRaises(FormatError):
+ checker.check(instance="not-an-ipv4", format="ipv4")
+
+ def test_repr(self):
+ checker = FormatChecker(formats=())
+ checker.checks("foo")(lambda thing: True)
+ checker.checks("bar")(lambda thing: True)
+ checker.checks("baz")(lambda thing: True)
+ self.assertEqual(
+ repr(checker),
+ "<FormatChecker checkers=['bar', 'baz', 'foo']>",
+ )
diff --git a/jsonschema/tests/test_jsonschema_test_suite.py b/jsonschema/tests/test_jsonschema_test_suite.py
new file mode 100644
index 0000000..0f9698a
--- /dev/null
+++ b/jsonschema/tests/test_jsonschema_test_suite.py
@@ -0,0 +1,489 @@
+"""
+Test runner for the JSON Schema official test suite
+
+Tests comprehensive correctness of each draft's validator.
+
+See https://github.com/json-schema-org/JSON-Schema-Test-Suite for details.
+"""
+
+import sys
+import warnings
+
+from jsonschema import (
+ Draft3Validator,
+ Draft4Validator,
+ Draft6Validator,
+ Draft7Validator,
+ draft3_format_checker,
+ draft4_format_checker,
+ draft6_format_checker,
+ draft7_format_checker,
+)
+from jsonschema.tests._helpers import bug
+from jsonschema.tests._suite import Suite
+from jsonschema.validators import _DEPRECATED_DEFAULT_TYPES, create
+
+SUITE = Suite()
+DRAFT3 = SUITE.version(name="draft3")
+DRAFT4 = SUITE.version(name="draft4")
+DRAFT6 = SUITE.version(name="draft6")
+DRAFT7 = SUITE.version(name="draft7")
+
+
+def skip(message, **kwargs):
+ def skipper(test):
+ if all(value == getattr(test, attr) for attr, value in kwargs.items()):
+ return message
+ return skipper
+
+
+def missing_format(checker):
+ def missing_format(test):
+ schema = test.schema
+ if (
+ schema is True
+ or schema is False
+ or "format" not in schema
+ or schema["format"] in checker.checkers
+ or test.valid
+ ):
+ return
+
+ return "Format checker {0!r} not found.".format(schema["format"])
+ return missing_format
+
+
+def complex_email_validation(test):
+ if test.subject != "email":
+ return
+
+ message = "Complex email validation is (intentionally) unsupported."
+ return skip(
+ message=message,
+ description="dot after local part is not valid",
+ )(test) or skip(
+ message=message,
+ description="dot before local part is not valid",
+ )(test) or skip(
+ message=message,
+ description="two subsequent dots inside local part are not valid",
+ )(test)
+
+
+is_narrow_build = sys.maxunicode == 2 ** 16 - 1
+if is_narrow_build: # pragma: no cover
+ message = "Not running surrogate Unicode case, this Python is narrow."
+
+ def narrow_unicode_build(test): # pragma: no cover
+ return skip(
+ message=message,
+ description="one supplementary Unicode code point is not long enough",
+ )(test) or skip(
+ message=message,
+ description="two supplementary Unicode code points is long enough",
+ )(test)
+else:
+ def narrow_unicode_build(test): # pragma: no cover
+ return
+
+
+if sys.version_info < (3, 7):
+ message = "datetime.date.fromisoformat is new in 3.7+"
+
+ def missing_date_fromisoformat(test):
+ return skip(
+ message=message,
+ subject="date",
+ description="invalidates non-padded month dates",
+ )(test) or skip(
+ message=message,
+ subject="date",
+ description="invalidates non-padded day dates",
+ )(test)
+else:
+ def missing_date_fromisoformat(test):
+ return
+
+
+TestDraft3 = DRAFT3.to_unittest_testcase(
+ DRAFT3.tests(),
+ DRAFT3.format_tests(),
+ DRAFT3.optional_tests_of(name="bignum"),
+ DRAFT3.optional_tests_of(name="non-bmp-regex"),
+ DRAFT3.optional_tests_of(name="zeroTerminatedFloats"),
+ Validator=Draft3Validator,
+ format_checker=draft3_format_checker,
+ skip=lambda test: (
+ narrow_unicode_build(test)
+ or missing_date_fromisoformat(test)
+ or missing_format(draft3_format_checker)(test)
+ or complex_email_validation(test)
+ or skip(
+ message="Upstream bug in strict_rfc3339",
+ subject="date-time",
+ description="case-insensitive T and Z",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description="[0] and [false] are unique",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description="[1] and [true] are unique",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description="nested [0] and [false] are unique",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description="nested [1] and [true] are unique",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description='{"a": false} and {"a": 0} are unique',
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description='{"a": true} and {"a": 1} are unique',
+ )(test)
+ ),
+)
+
+
+TestDraft4 = DRAFT4.to_unittest_testcase(
+ DRAFT4.tests(),
+ DRAFT4.format_tests(),
+ DRAFT4.optional_tests_of(name="bignum"),
+ DRAFT4.optional_tests_of(name="non-bmp-regex"),
+ DRAFT4.optional_tests_of(name="zeroTerminatedFloats"),
+ Validator=Draft4Validator,
+ format_checker=draft4_format_checker,
+ skip=lambda test: (
+ narrow_unicode_build(test)
+ or missing_date_fromisoformat(test)
+ or missing_format(draft4_format_checker)(test)
+ or complex_email_validation(test)
+ or skip(
+ message=bug(),
+ subject="ref",
+ case_description="Recursive references between schemas",
+ )(test)
+ or skip(
+ message=bug(371),
+ subject="ref",
+ case_description="Location-independent identifier",
+ )(test)
+ or skip(
+ message=bug(371),
+ subject="ref",
+ case_description=(
+ "Location-independent identifier with absolute URI"
+ ),
+ )(test)
+ or skip(
+ message=bug(371),
+ subject="ref",
+ case_description=(
+ "Location-independent identifier with base URI change in subschema"
+ ),
+ )(test)
+ or skip(
+ message=bug(),
+ subject="refRemote",
+ case_description="base URI change - change folder in subschema",
+ )(test)
+ or skip(
+ message="Upstream bug in strict_rfc3339",
+ subject="date-time",
+ description="case-insensitive T and Z",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description="[0] and [false] are unique",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description="[1] and [true] are unique",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description="nested [0] and [false] are unique",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description="nested [1] and [true] are unique",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description='{"a": false} and {"a": 0} are unique',
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description='{"a": true} and {"a": 1} are unique',
+ )(test)
+ ),
+)
+
+
+TestDraft6 = DRAFT6.to_unittest_testcase(
+ DRAFT6.tests(),
+ DRAFT6.format_tests(),
+ DRAFT6.optional_tests_of(name="bignum"),
+ DRAFT6.optional_tests_of(name="non-bmp-regex"),
+ Validator=Draft6Validator,
+ format_checker=draft6_format_checker,
+ skip=lambda test: (
+ narrow_unicode_build(test)
+ or missing_date_fromisoformat(test)
+ or missing_format(draft6_format_checker)(test)
+ or complex_email_validation(test)
+ or skip(
+ message=bug(),
+ subject="ref",
+ case_description="Recursive references between schemas",
+ )(test)
+ or skip(
+ message=bug(371),
+ subject="ref",
+ case_description="Location-independent identifier",
+ )(test)
+ or skip(
+ message=bug(371),
+ subject="ref",
+ case_description=(
+ "Location-independent identifier with absolute URI"
+ ),
+ )(test)
+ or skip(
+ message=bug(371),
+ subject="ref",
+ case_description=(
+ "Location-independent identifier with base URI change in subschema"
+ ),
+ )(test)
+ or skip(
+ message=bug(),
+ subject="refRemote",
+ case_description="base URI change - change folder in subschema",
+ )(test)
+ or skip(
+ message="Upstream bug in strict_rfc3339",
+ subject="date-time",
+ description="case-insensitive T and Z",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description="[0] and [false] are unique",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description="[1] and [true] are unique",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description="nested [0] and [false] are unique",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description="nested [1] and [true] are unique",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description='{"a": false} and {"a": 0} are unique',
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description='{"a": true} and {"a": 1} are unique',
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="const",
+ case_description="const with [false] does not match [0]",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="const",
+ case_description="const with [true] does not match [1]",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="const",
+ case_description='const with {"a": false} does not match {"a": 0}',
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="const",
+ case_description='const with {"a": true} does not match {"a": 1}',
+ )(test)
+ ),
+)
+
+
+TestDraft7 = DRAFT7.to_unittest_testcase(
+ DRAFT7.tests(),
+ DRAFT7.format_tests(),
+ DRAFT7.optional_tests_of(name="bignum"),
+ DRAFT7.optional_tests_of(name="content"),
+ DRAFT7.optional_tests_of(name="non-bmp-regex"),
+ Validator=Draft7Validator,
+ format_checker=draft7_format_checker,
+ skip=lambda test: (
+ narrow_unicode_build(test)
+ or missing_date_fromisoformat(test)
+ or missing_format(draft7_format_checker)(test)
+ or complex_email_validation(test)
+ or skip(
+ message=bug(),
+ subject="ref",
+ case_description="Recursive references between schemas",
+ )(test)
+ or skip(
+ message=bug(371),
+ subject="ref",
+ case_description="Location-independent identifier",
+ )(test)
+ or skip(
+ message=bug(371),
+ subject="ref",
+ case_description=(
+ "Location-independent identifier with absolute URI"
+ ),
+ )(test)
+ or skip(
+ message=bug(371),
+ subject="ref",
+ case_description=(
+ "Location-independent identifier with base URI change in subschema"
+ ),
+ )(test)
+ or skip(
+ message=bug(),
+ subject="refRemote",
+ case_description="base URI change - change folder in subschema",
+ )(test)
+ or skip(
+ message="Upstream bug in strict_rfc3339",
+ subject="date-time",
+ description="case-insensitive T and Z",
+ )(test)
+ or skip(
+ message=bug(593),
+ subject="content",
+ valid=False,
+ case_description=(
+ "validation of string-encoded content based on media type"
+ ),
+ )(test)
+ or skip(
+ message=bug(593),
+ subject="content",
+ valid=False,
+ case_description="validation of binary string-encoding",
+ )(test)
+ or skip(
+ message=bug(593),
+ subject="content",
+ valid=False,
+ case_description=(
+ "validation of binary-encoded media type documents"
+ ),
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description="[0] and [false] are unique",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description="[1] and [true] are unique",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description="nested [0] and [false] are unique",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description="nested [1] and [true] are unique",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description='{"a": false} and {"a": 0} are unique',
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="uniqueItems",
+ description='{"a": true} and {"a": 1} are unique',
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="const",
+ case_description="const with [false] does not match [0]",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="const",
+ case_description="const with [true] does not match [1]",
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="const",
+ case_description='const with {"a": false} does not match {"a": 0}',
+ )(test)
+ or skip(
+ message=bug(686),
+ subject="const",
+ case_description='const with {"a": true} does not match {"a": 1}',
+ )(test)
+ ),
+)
+
+
+with warnings.catch_warnings():
+ warnings.simplefilter("ignore", DeprecationWarning)
+
+ TestDraft3LegacyTypeCheck = DRAFT3.to_unittest_testcase(
+ # Interestingly the any part couldn't really be done w/the old API.
+ (
+ (test for test in each if test.schema != {"type": "any"})
+ for each in DRAFT3.tests_of(name="type")
+ ),
+ name="TestDraft3LegacyTypeCheck",
+ Validator=create(
+ meta_schema=Draft3Validator.META_SCHEMA,
+ validators=Draft3Validator.VALIDATORS,
+ default_types=_DEPRECATED_DEFAULT_TYPES,
+ ),
+ )
+
+ TestDraft4LegacyTypeCheck = DRAFT4.to_unittest_testcase(
+ DRAFT4.tests_of(name="type"),
+ name="TestDraft4LegacyTypeCheck",
+ Validator=create(
+ meta_schema=Draft4Validator.META_SCHEMA,
+ validators=Draft4Validator.VALIDATORS,
+ default_types=_DEPRECATED_DEFAULT_TYPES,
+ ),
+ )
diff --git a/jsonschema/tests/test_types.py b/jsonschema/tests/test_types.py
new file mode 100644
index 0000000..82071ca
--- /dev/null
+++ b/jsonschema/tests/test_types.py
@@ -0,0 +1,192 @@
+"""
+Tests for the `TypeChecker`-based type interface.
+
+The actual correctness of the type checking is handled in
+`test_jsonschema_test_suite`; these tests check that TypeChecker
+functions correctly at a more granular level.
+"""
+from collections import namedtuple
+from unittest import TestCase
+
+from jsonschema import ValidationError, _validators
+from jsonschema._types import TypeChecker
+from jsonschema.exceptions import UndefinedTypeCheck
+from jsonschema.validators import Draft4Validator, extend
+
+
+def equals_2(checker, instance):
+ return instance == 2
+
+
+def is_namedtuple(instance):
+ return isinstance(instance, tuple) and getattr(instance, "_fields", None)
+
+
+def is_object_or_named_tuple(checker, instance):
+ if Draft4Validator.TYPE_CHECKER.is_type(instance, "object"):
+ return True
+ return is_namedtuple(instance)
+
+
+def coerce_named_tuple(fn):
+ def coerced(validator, value, instance, schema):
+ if is_namedtuple(instance):
+ instance = instance._asdict()
+ return fn(validator, value, instance, schema)
+ return coerced
+
+
+required = coerce_named_tuple(_validators.required)
+properties = coerce_named_tuple(_validators.properties)
+
+
+class TestTypeChecker(TestCase):
+ def test_is_type(self):
+ checker = TypeChecker({"two": equals_2})
+ self.assertEqual(
+ (
+ checker.is_type(instance=2, type="two"),
+ checker.is_type(instance="bar", type="two"),
+ ),
+ (True, False),
+ )
+
+ def test_is_unknown_type(self):
+ with self.assertRaises(UndefinedTypeCheck) as context:
+ TypeChecker().is_type(4, "foobar")
+ self.assertIn("foobar", str(context.exception))
+
+ def test_checks_can_be_added_at_init(self):
+ checker = TypeChecker({"two": equals_2})
+ self.assertEqual(checker, TypeChecker().redefine("two", equals_2))
+
+ def test_redefine_existing_type(self):
+ self.assertEqual(
+ TypeChecker().redefine("two", object()).redefine("two", equals_2),
+ TypeChecker().redefine("two", equals_2),
+ )
+
+ def test_remove(self):
+ self.assertEqual(
+ TypeChecker({"two": equals_2}).remove("two"),
+ TypeChecker(),
+ )
+
+ def test_remove_unknown_type(self):
+ with self.assertRaises(UndefinedTypeCheck) as context:
+ TypeChecker().remove("foobar")
+ self.assertIn("foobar", str(context.exception))
+
+ def test_redefine_many(self):
+ self.assertEqual(
+ TypeChecker().redefine_many({"foo": int, "bar": str}),
+ TypeChecker().redefine("foo", int).redefine("bar", str),
+ )
+
+ def test_remove_multiple(self):
+ self.assertEqual(
+ TypeChecker({"foo": int, "bar": str}).remove("foo", "bar"),
+ TypeChecker(),
+ )
+
+ def test_type_check_can_raise_key_error(self):
+ """
+ Make sure no one writes:
+
+ try:
+ self._type_checkers[type](...)
+ except KeyError:
+
+ ignoring the fact that the function itself can raise that.
+ """
+
+ error = KeyError("Stuff")
+
+ def raises_keyerror(checker, instance):
+ raise error
+
+ with self.assertRaises(KeyError) as context:
+ TypeChecker({"foo": raises_keyerror}).is_type(4, "foo")
+
+ self.assertIs(context.exception, error)
+
+
+class TestCustomTypes(TestCase):
+ def test_simple_type_can_be_extended(self):
+ def int_or_str_int(checker, instance):
+ if not isinstance(instance, (int, str)):
+ return False
+ try:
+ int(instance)
+ except ValueError:
+ return False
+ return True
+
+ CustomValidator = extend(
+ Draft4Validator,
+ type_checker=Draft4Validator.TYPE_CHECKER.redefine(
+ "integer", int_or_str_int,
+ ),
+ )
+ validator = CustomValidator({"type": "integer"})
+
+ validator.validate(4)
+ validator.validate("4")
+
+ with self.assertRaises(ValidationError):
+ validator.validate(4.4)
+
+ def test_object_can_be_extended(self):
+ schema = {"type": "object"}
+
+ Point = namedtuple("Point", ["x", "y"])
+
+ type_checker = Draft4Validator.TYPE_CHECKER.redefine(
+ u"object", is_object_or_named_tuple,
+ )
+
+ CustomValidator = extend(Draft4Validator, type_checker=type_checker)
+ validator = CustomValidator(schema)
+
+ validator.validate(Point(x=4, y=5))
+
+ def test_object_extensions_require_custom_validators(self):
+ schema = {"type": "object", "required": ["x"]}
+
+ type_checker = Draft4Validator.TYPE_CHECKER.redefine(
+ u"object", is_object_or_named_tuple,
+ )
+
+ CustomValidator = extend(Draft4Validator, type_checker=type_checker)
+ validator = CustomValidator(schema)
+
+ Point = namedtuple("Point", ["x", "y"])
+ # Cannot handle required
+ with self.assertRaises(ValidationError):
+ validator.validate(Point(x=4, y=5))
+
+ def test_object_extensions_can_handle_custom_validators(self):
+ schema = {
+ "type": "object",
+ "required": ["x"],
+ "properties": {"x": {"type": "integer"}},
+ }
+
+ type_checker = Draft4Validator.TYPE_CHECKER.redefine(
+ u"object", is_object_or_named_tuple,
+ )
+
+ CustomValidator = extend(
+ Draft4Validator,
+ type_checker=type_checker,
+ validators={"required": required, "properties": properties},
+ )
+
+ validator = CustomValidator(schema)
+
+ Point = namedtuple("Point", ["x", "y"])
+ # Can now process required and properties
+ validator.validate(Point(x=4, y=5))
+
+ with self.assertRaises(ValidationError):
+ validator.validate(Point(x="not an integer", y=5))
diff --git a/jsonschema/tests/test_validators.py b/jsonschema/tests/test_validators.py
new file mode 100644
index 0000000..fc6a355
--- /dev/null
+++ b/jsonschema/tests/test_validators.py
@@ -0,0 +1,1798 @@
+from collections import deque
+from contextlib import contextmanager
+from decimal import Decimal
+from io import BytesIO
+from unittest import TestCase
+from urllib.request import pathname2url
+import json
+import os
+import sys
+import tempfile
+import unittest
+
+from twisted.trial.unittest import SynchronousTestCase
+import attr
+
+from jsonschema import FormatChecker, TypeChecker, exceptions, validators
+from jsonschema.tests._helpers import bug
+
+
+def startswith(validator, startswith, instance, schema):
+ if not instance.startswith(startswith):
+ yield exceptions.ValidationError(u"Whoops!")
+
+
+class TestCreateAndExtend(SynchronousTestCase):
+ def setUp(self):
+ self.addCleanup(
+ self.assertEqual,
+ validators.meta_schemas,
+ dict(validators.meta_schemas),
+ )
+
+ self.meta_schema = {u"$id": "some://meta/schema"}
+ self.validators = {u"startswith": startswith}
+ self.type_checker = TypeChecker()
+ self.Validator = validators.create(
+ meta_schema=self.meta_schema,
+ validators=self.validators,
+ type_checker=self.type_checker,
+ )
+
+ def test_attrs(self):
+ self.assertEqual(
+ (
+ self.Validator.VALIDATORS,
+ self.Validator.META_SCHEMA,
+ self.Validator.TYPE_CHECKER,
+ ), (
+ self.validators,
+ self.meta_schema,
+ self.type_checker,
+ ),
+ )
+
+ def test_init(self):
+ schema = {u"startswith": u"foo"}
+ self.assertEqual(self.Validator(schema).schema, schema)
+
+ def test_iter_errors(self):
+ schema = {u"startswith": u"hel"}
+ iter_errors = self.Validator(schema).iter_errors
+
+ errors = list(iter_errors(u"hello"))
+ self.assertEqual(errors, [])
+
+ expected_error = exceptions.ValidationError(
+ u"Whoops!",
+ instance=u"goodbye",
+ schema=schema,
+ validator=u"startswith",
+ validator_value=u"hel",
+ schema_path=deque([u"startswith"]),
+ )
+
+ errors = list(iter_errors(u"goodbye"))
+ self.assertEqual(len(errors), 1)
+ self.assertEqual(errors[0]._contents(), expected_error._contents())
+
+ def test_if_a_version_is_provided_it_is_registered(self):
+ Validator = validators.create(
+ meta_schema={u"$id": "something"},
+ version="my version",
+ )
+ self.addCleanup(validators.meta_schemas.pop, "something")
+ self.assertEqual(Validator.__name__, "MyVersionValidator")
+
+ def test_if_a_version_is_not_provided_it_is_not_registered(self):
+ original = dict(validators.meta_schemas)
+ validators.create(meta_schema={u"id": "id"})
+ self.assertEqual(validators.meta_schemas, original)
+
+ def test_validates_registers_meta_schema_id(self):
+ meta_schema_key = "meta schema id"
+ my_meta_schema = {u"id": meta_schema_key}
+
+ validators.create(
+ meta_schema=my_meta_schema,
+ version="my version",
+ id_of=lambda s: s.get("id", ""),
+ )
+ self.addCleanup(validators.meta_schemas.pop, meta_schema_key)
+
+ self.assertIn(meta_schema_key, validators.meta_schemas)
+
+ def test_validates_registers_meta_schema_draft6_id(self):
+ meta_schema_key = "meta schema $id"
+ my_meta_schema = {u"$id": meta_schema_key}
+
+ validators.create(
+ meta_schema=my_meta_schema,
+ version="my version",
+ )
+ self.addCleanup(validators.meta_schemas.pop, meta_schema_key)
+
+ self.assertIn(meta_schema_key, validators.meta_schemas)
+
+ def test_create_default_types(self):
+ Validator = validators.create(meta_schema={}, validators=())
+ self.assertTrue(
+ all(
+ Validator({}).is_type(instance=instance, type=type)
+ for type, instance in [
+ (u"array", []),
+ (u"boolean", True),
+ (u"integer", 12),
+ (u"null", None),
+ (u"number", 12.0),
+ (u"object", {}),
+ (u"string", u"foo"),
+ ]
+ ),
+ )
+
+ def test_extend(self):
+ original = dict(self.Validator.VALIDATORS)
+ new = object()
+
+ Extended = validators.extend(
+ self.Validator,
+ validators={u"new": new},
+ )
+ self.assertEqual(
+ (
+ Extended.VALIDATORS,
+ Extended.META_SCHEMA,
+ Extended.TYPE_CHECKER,
+ self.Validator.VALIDATORS,
+ ), (
+ dict(original, new=new),
+ self.Validator.META_SCHEMA,
+ self.Validator.TYPE_CHECKER,
+ original,
+ ),
+ )
+
+ def test_extend_idof(self):
+ """
+ Extending a validator preserves its notion of schema IDs.
+ """
+ def id_of(schema):
+ return schema.get(u"__test__", self.Validator.ID_OF(schema))
+ correct_id = "the://correct/id/"
+ meta_schema = {
+ u"$id": "the://wrong/id/",
+ u"__test__": correct_id,
+ }
+ Original = validators.create(
+ meta_schema=meta_schema,
+ validators=self.validators,
+ type_checker=self.type_checker,
+ id_of=id_of,
+ )
+ self.assertEqual(Original.ID_OF(Original.META_SCHEMA), correct_id)
+
+ Derived = validators.extend(Original)
+ self.assertEqual(Derived.ID_OF(Derived.META_SCHEMA), correct_id)
+
+
+class TestLegacyTypeChecking(SynchronousTestCase):
+ def test_create_default_types(self):
+ Validator = validators.create(meta_schema={}, validators=())
+ self.assertEqual(
+ set(Validator.DEFAULT_TYPES), {
+ u"array",
+ u"boolean",
+ u"integer",
+ u"null",
+ u"number",
+ u"object", u"string",
+ },
+ )
+ self.flushWarnings()
+
+ def test_extend(self):
+ Validator = validators.create(meta_schema={}, validators=())
+ original = dict(Validator.VALIDATORS)
+ new = object()
+
+ Extended = validators.extend(
+ Validator,
+ validators={u"new": new},
+ )
+ self.assertEqual(
+ (
+ Extended.VALIDATORS,
+ Extended.META_SCHEMA,
+ Extended.TYPE_CHECKER,
+ Validator.VALIDATORS,
+
+ Extended.DEFAULT_TYPES,
+ Extended({}).DEFAULT_TYPES,
+ self.flushWarnings()[0]["message"],
+ ), (
+ dict(original, new=new),
+ Validator.META_SCHEMA,
+ Validator.TYPE_CHECKER,
+ original,
+
+ Validator.DEFAULT_TYPES,
+ Validator.DEFAULT_TYPES,
+ self.flushWarnings()[0]["message"],
+ ),
+ )
+
+ def test_types_redefines_the_validators_type_checker(self):
+ schema = {"type": "string"}
+ self.assertFalse(validators.Draft7Validator(schema).is_valid(12))
+
+ validator = validators.Draft7Validator(
+ schema,
+ types={"string": (str, int)},
+ )
+ self.assertTrue(validator.is_valid(12))
+ self.flushWarnings()
+
+ def test_providing_default_types_warns(self):
+ self.assertWarns(
+ category=DeprecationWarning,
+ message=(
+ "The default_types argument is deprecated. "
+ "Use the type_checker argument instead."
+ ),
+ # https://tm.tl/9363 :'(
+ filename=sys.modules[self.assertWarns.__module__].__file__,
+
+ f=validators.create,
+ meta_schema={},
+ validators={},
+ default_types={"foo": object},
+ )
+
+ def test_cannot_ask_for_default_types_with_non_default_type_checker(self):
+ """
+ We raise an error when you ask a validator with non-default
+ type checker for its DEFAULT_TYPES.
+
+ The type checker argument is new, so no one but this library
+ itself should be trying to use it, and doing so while then
+ asking for DEFAULT_TYPES makes no sense (not to mention is
+ deprecated), since type checkers are not strictly about Python
+ type.
+ """
+ Validator = validators.create(
+ meta_schema={},
+ validators={},
+ type_checker=TypeChecker(),
+ )
+ with self.assertRaises(validators._DontDoThat) as e:
+ Validator.DEFAULT_TYPES
+
+ self.assertIn(
+ "DEFAULT_TYPES cannot be used on Validators using TypeCheckers",
+ str(e.exception),
+ )
+ with self.assertRaises(validators._DontDoThat):
+ Validator({}).DEFAULT_TYPES
+
+ self.assertFalse(self.flushWarnings())
+
+ def test_providing_explicit_type_checker_does_not_warn(self):
+ Validator = validators.create(
+ meta_schema={},
+ validators={},
+ type_checker=TypeChecker(),
+ )
+ self.assertFalse(self.flushWarnings())
+
+ Validator({})
+ self.assertFalse(self.flushWarnings())
+
+ def test_providing_neither_does_not_warn(self):
+ Validator = validators.create(meta_schema={}, validators={})
+ self.assertFalse(self.flushWarnings())
+
+ Validator({})
+ self.assertFalse(self.flushWarnings())
+
+ def test_providing_default_types_with_type_checker_errors(self):
+ with self.assertRaises(TypeError) as e:
+ validators.create(
+ meta_schema={},
+ validators={},
+ default_types={"foo": object},
+ type_checker=TypeChecker(),
+ )
+
+ self.assertIn(
+ "Do not specify default_types when providing a type checker",
+ str(e.exception),
+ )
+ self.assertFalse(self.flushWarnings())
+
+ def test_extending_a_legacy_validator_with_a_type_checker_errors(self):
+ Validator = validators.create(
+ meta_schema={},
+ validators={},
+ default_types={u"array": list}
+ )
+ with self.assertRaises(TypeError) as e:
+ validators.extend(
+ Validator,
+ validators={},
+ type_checker=TypeChecker(),
+ )
+
+ self.assertIn(
+ (
+ "Cannot extend a validator created with default_types "
+ "with a type_checker. Update the validator to use a "
+ "type_checker when created."
+ ),
+ str(e.exception),
+ )
+ self.flushWarnings()
+
+ def test_extending_a_legacy_validator_does_not_rewarn(self):
+ Validator = validators.create(meta_schema={}, default_types={})
+ self.assertTrue(self.flushWarnings())
+
+ validators.extend(Validator)
+ self.assertFalse(self.flushWarnings())
+
+ def test_accessing_default_types_warns(self):
+ Validator = validators.create(meta_schema={}, validators={})
+ self.assertFalse(self.flushWarnings())
+
+ self.assertWarns(
+ DeprecationWarning,
+ (
+ "The DEFAULT_TYPES attribute is deprecated. "
+ "See the type checker attached to this validator instead."
+ ),
+ # https://tm.tl/9363 :'(
+ sys.modules[self.assertWarns.__module__].__file__,
+
+ getattr,
+ Validator,
+ "DEFAULT_TYPES",
+ )
+
+ def test_accessing_default_types_on_the_instance_warns(self):
+ Validator = validators.create(meta_schema={}, validators={})
+ self.assertFalse(self.flushWarnings())
+
+ self.assertWarns(
+ DeprecationWarning,
+ (
+ "The DEFAULT_TYPES attribute is deprecated. "
+ "See the type checker attached to this validator instead."
+ ),
+ # https://tm.tl/9363 :'(
+ sys.modules[self.assertWarns.__module__].__file__,
+
+ getattr,
+ Validator({}),
+ "DEFAULT_TYPES",
+ )
+
+ def test_providing_types_to_init_warns(self):
+ Validator = validators.create(meta_schema={}, validators={})
+ self.assertFalse(self.flushWarnings())
+
+ self.assertWarns(
+ category=DeprecationWarning,
+ message=(
+ "The types argument is deprecated. "
+ "Provide a type_checker to jsonschema.validators.extend "
+ "instead."
+ ),
+ # https://tm.tl/9363 :'(
+ filename=sys.modules[self.assertWarns.__module__].__file__,
+
+ f=Validator,
+ schema={},
+ types={"bar": object},
+ )
+
+
+class TestIterErrors(TestCase):
+ def setUp(self):
+ self.validator = validators.Draft3Validator({})
+
+ def test_iter_errors(self):
+ instance = [1, 2]
+ schema = {
+ u"disallow": u"array",
+ u"enum": [["a", "b", "c"], ["d", "e", "f"]],
+ u"minItems": 3,
+ }
+
+ got = (e.message for e in self.validator.iter_errors(instance, schema))
+ expected = [
+ "%r is disallowed for [1, 2]" % (schema["disallow"],),
+ "[1, 2] is too short",
+ "[1, 2] is not one of %r" % (schema["enum"],),
+ ]
+ self.assertEqual(sorted(got), sorted(expected))
+
+ def test_iter_errors_multiple_failures_one_validator(self):
+ instance = {"foo": 2, "bar": [1], "baz": 15, "quux": "spam"}
+ schema = {
+ u"properties": {
+ "foo": {u"type": "string"},
+ "bar": {u"minItems": 2},
+ "baz": {u"maximum": 10, u"enum": [2, 4, 6, 8]},
+ },
+ }
+
+ errors = list(self.validator.iter_errors(instance, schema))
+ self.assertEqual(len(errors), 4)
+
+
+class TestValidationErrorMessages(TestCase):
+ def message_for(self, instance, schema, *args, **kwargs):
+ kwargs.setdefault("cls", validators.Draft3Validator)
+ with self.assertRaises(exceptions.ValidationError) as e:
+ validators.validate(instance, schema, *args, **kwargs)
+ return e.exception.message
+
+ def test_single_type_failure(self):
+ message = self.message_for(instance=1, schema={u"type": u"string"})
+ self.assertEqual(message, "1 is not of type %r" % u"string")
+
+ def test_single_type_list_failure(self):
+ message = self.message_for(instance=1, schema={u"type": [u"string"]})
+ self.assertEqual(message, "1 is not of type %r" % u"string")
+
+ def test_multiple_type_failure(self):
+ types = u"string", u"object"
+ message = self.message_for(instance=1, schema={u"type": list(types)})
+ self.assertEqual(message, "1 is not of type %r, %r" % types)
+
+ def test_object_without_title_type_failure(self):
+ type = {u"type": [{u"minimum": 3}]}
+ message = self.message_for(instance=1, schema={u"type": [type]})
+ self.assertEqual(message, "1 is less than the minimum of 3")
+
+ def test_object_with_named_type_failure(self):
+ schema = {u"type": [{u"name": "Foo", u"minimum": 3}]}
+ message = self.message_for(instance=1, schema=schema)
+ self.assertEqual(message, "1 is less than the minimum of 3")
+
+ def test_minimum(self):
+ message = self.message_for(instance=1, schema={"minimum": 2})
+ self.assertEqual(message, "1 is less than the minimum of 2")
+
+ def test_maximum(self):
+ message = self.message_for(instance=1, schema={"maximum": 0})
+ self.assertEqual(message, "1 is greater than the maximum of 0")
+
+ def test_dependencies_single_element(self):
+ depend, on = "bar", "foo"
+ schema = {u"dependencies": {depend: on}}
+ message = self.message_for(
+ instance={"bar": 2},
+ schema=schema,
+ cls=validators.Draft3Validator,
+ )
+ self.assertEqual(message, "%r is a dependency of %r" % (on, depend))
+
+ def test_dependencies_list_draft3(self):
+ depend, on = "bar", "foo"
+ schema = {u"dependencies": {depend: [on]}}
+ message = self.message_for(
+ instance={"bar": 2},
+ schema=schema,
+ cls=validators.Draft3Validator,
+ )
+ self.assertEqual(message, "%r is a dependency of %r" % (on, depend))
+
+ def test_dependencies_list_draft7(self):
+ depend, on = "bar", "foo"
+ schema = {u"dependencies": {depend: [on]}}
+ message = self.message_for(
+ instance={"bar": 2},
+ schema=schema,
+ cls=validators.Draft7Validator,
+ )
+ self.assertEqual(message, "%r is a dependency of %r" % (on, depend))
+
+ def test_additionalItems_single_failure(self):
+ message = self.message_for(
+ instance=[2],
+ schema={u"items": [], u"additionalItems": False},
+ )
+ self.assertIn("(2 was unexpected)", message)
+
+ def test_additionalItems_multiple_failures(self):
+ message = self.message_for(
+ instance=[1, 2, 3],
+ schema={u"items": [], u"additionalItems": False}
+ )
+ self.assertIn("(1, 2, 3 were unexpected)", message)
+
+ def test_additionalProperties_single_failure(self):
+ additional = "foo"
+ schema = {u"additionalProperties": False}
+ message = self.message_for(instance={additional: 2}, schema=schema)
+ self.assertIn("(%r was unexpected)" % (additional,), message)
+
+ def test_additionalProperties_multiple_failures(self):
+ schema = {u"additionalProperties": False}
+ message = self.message_for(
+ instance=dict.fromkeys(["foo", "bar"]),
+ schema=schema,
+ )
+
+ self.assertIn(repr("foo"), message)
+ self.assertIn(repr("bar"), message)
+ self.assertIn("were unexpected)", message)
+
+ def test_const(self):
+ schema = {u"const": 12}
+ message = self.message_for(
+ instance={"foo": "bar"},
+ schema=schema,
+ cls=validators.Draft6Validator,
+ )
+ self.assertIn("12 was expected", message)
+
+ def test_contains(self):
+ schema = {u"contains": {u"const": 12}}
+ message = self.message_for(
+ instance=[2, {}, []],
+ schema=schema,
+ cls=validators.Draft6Validator,
+ )
+ self.assertIn(
+ "None of [2, {}, []] are valid under the given schema",
+ message,
+ )
+
+ def test_invalid_format_default_message(self):
+ checker = FormatChecker(formats=())
+ checker.checks(u"thing")(lambda value: False)
+
+ schema = {u"format": u"thing"}
+ message = self.message_for(
+ instance="bla",
+ schema=schema,
+ format_checker=checker,
+ )
+
+ self.assertIn(repr("bla"), message)
+ self.assertIn(repr("thing"), message)
+ self.assertIn("is not a", message)
+
+ def test_additionalProperties_false_patternProperties(self):
+ schema = {u"type": u"object",
+ u"additionalProperties": False,
+ u"patternProperties": {
+ u"^abc$": {u"type": u"string"},
+ u"^def$": {u"type": u"string"},
+ }}
+ message = self.message_for(
+ instance={u"zebra": 123},
+ schema=schema,
+ cls=validators.Draft4Validator,
+ )
+ self.assertEqual(
+ message,
+ "{} does not match any of the regexes: {}, {}".format(
+ repr(u"zebra"), repr(u"^abc$"), repr(u"^def$"),
+ ),
+ )
+ message = self.message_for(
+ instance={u"zebra": 123, u"fish": 456},
+ schema=schema,
+ cls=validators.Draft4Validator,
+ )
+ self.assertEqual(
+ message,
+ "{}, {} do not match any of the regexes: {}, {}".format(
+ repr(u"fish"), repr(u"zebra"), repr(u"^abc$"), repr(u"^def$")
+ ),
+ )
+
+ def test_False_schema(self):
+ message = self.message_for(
+ instance="something",
+ schema=False,
+ cls=validators.Draft7Validator,
+ )
+ self.assertIn("False schema does not allow 'something'", message)
+
+
+class TestValidationErrorDetails(TestCase):
+ # TODO: These really need unit tests for each individual validator, rather
+ # than just these higher level tests.
+ def test_anyOf(self):
+ instance = 5
+ schema = {
+ "anyOf": [
+ {"minimum": 20},
+ {"type": "string"},
+ ],
+ }
+
+ validator = validators.Draft4Validator(schema)
+ errors = list(validator.iter_errors(instance))
+ self.assertEqual(len(errors), 1)
+ e = errors[0]
+
+ self.assertEqual(e.validator, "anyOf")
+ self.assertEqual(e.validator_value, schema["anyOf"])
+ self.assertEqual(e.instance, instance)
+ self.assertEqual(e.schema, schema)
+ self.assertIsNone(e.parent)
+
+ self.assertEqual(e.path, deque([]))
+ self.assertEqual(e.relative_path, deque([]))
+ self.assertEqual(e.absolute_path, deque([]))
+ self.assertEqual(e.json_path, '$')
+
+ self.assertEqual(e.schema_path, deque(["anyOf"]))
+ self.assertEqual(e.relative_schema_path, deque(["anyOf"]))
+ self.assertEqual(e.absolute_schema_path, deque(["anyOf"]))
+
+ self.assertEqual(len(e.context), 2)
+
+ e1, e2 = sorted_errors(e.context)
+
+ self.assertEqual(e1.validator, "minimum")
+ self.assertEqual(e1.validator_value, schema["anyOf"][0]["minimum"])
+ self.assertEqual(e1.instance, instance)
+ self.assertEqual(e1.schema, schema["anyOf"][0])
+ self.assertIs(e1.parent, e)
+
+ self.assertEqual(e1.path, deque([]))
+ self.assertEqual(e1.absolute_path, deque([]))
+ self.assertEqual(e1.relative_path, deque([]))
+ self.assertEqual(e1.json_path, '$')
+
+ self.assertEqual(e1.schema_path, deque([0, "minimum"]))
+ self.assertEqual(e1.relative_schema_path, deque([0, "minimum"]))
+ self.assertEqual(
+ e1.absolute_schema_path, deque(["anyOf", 0, "minimum"]),
+ )
+
+ self.assertFalse(e1.context)
+
+ self.assertEqual(e2.validator, "type")
+ self.assertEqual(e2.validator_value, schema["anyOf"][1]["type"])
+ self.assertEqual(e2.instance, instance)
+ self.assertEqual(e2.schema, schema["anyOf"][1])
+ self.assertIs(e2.parent, e)
+
+ self.assertEqual(e2.path, deque([]))
+ self.assertEqual(e2.relative_path, deque([]))
+ self.assertEqual(e2.absolute_path, deque([]))
+ self.assertEqual(e2.json_path, '$')
+
+ self.assertEqual(e2.schema_path, deque([1, "type"]))
+ self.assertEqual(e2.relative_schema_path, deque([1, "type"]))
+ self.assertEqual(e2.absolute_schema_path, deque(["anyOf", 1, "type"]))
+
+ self.assertEqual(len(e2.context), 0)
+
+ def test_type(self):
+ instance = {"foo": 1}
+ schema = {
+ "type": [
+ {"type": "integer"},
+ {
+ "type": "object",
+ "properties": {"foo": {"enum": [2]}},
+ },
+ ],
+ }
+
+ validator = validators.Draft3Validator(schema)
+ errors = list(validator.iter_errors(instance))
+ self.assertEqual(len(errors), 1)
+ e = errors[0]
+
+ self.assertEqual(e.validator, "type")
+ self.assertEqual(e.validator_value, schema["type"])
+ self.assertEqual(e.instance, instance)
+ self.assertEqual(e.schema, schema)
+ self.assertIsNone(e.parent)
+
+ self.assertEqual(e.path, deque([]))
+ self.assertEqual(e.relative_path, deque([]))
+ self.assertEqual(e.absolute_path, deque([]))
+ self.assertEqual(e.json_path, '$')
+
+ self.assertEqual(e.schema_path, deque(["type"]))
+ self.assertEqual(e.relative_schema_path, deque(["type"]))
+ self.assertEqual(e.absolute_schema_path, deque(["type"]))
+
+ self.assertEqual(len(e.context), 2)
+
+ e1, e2 = sorted_errors(e.context)
+
+ self.assertEqual(e1.validator, "type")
+ self.assertEqual(e1.validator_value, schema["type"][0]["type"])
+ self.assertEqual(e1.instance, instance)
+ self.assertEqual(e1.schema, schema["type"][0])
+ self.assertIs(e1.parent, e)
+
+ self.assertEqual(e1.path, deque([]))
+ self.assertEqual(e1.relative_path, deque([]))
+ self.assertEqual(e1.absolute_path, deque([]))
+ self.assertEqual(e1.json_path, '$')
+
+ self.assertEqual(e1.schema_path, deque([0, "type"]))
+ self.assertEqual(e1.relative_schema_path, deque([0, "type"]))
+ self.assertEqual(e1.absolute_schema_path, deque(["type", 0, "type"]))
+
+ self.assertFalse(e1.context)
+
+ self.assertEqual(e2.validator, "enum")
+ self.assertEqual(e2.validator_value, [2])
+ self.assertEqual(e2.instance, 1)
+ self.assertEqual(e2.schema, {u"enum": [2]})
+ self.assertIs(e2.parent, e)
+
+ self.assertEqual(e2.path, deque(["foo"]))
+ self.assertEqual(e2.relative_path, deque(["foo"]))
+ self.assertEqual(e2.absolute_path, deque(["foo"]))
+ self.assertEqual(e2.json_path, '$.foo')
+
+ self.assertEqual(
+ e2.schema_path, deque([1, "properties", "foo", "enum"]),
+ )
+ self.assertEqual(
+ e2.relative_schema_path, deque([1, "properties", "foo", "enum"]),
+ )
+ self.assertEqual(
+ e2.absolute_schema_path,
+ deque(["type", 1, "properties", "foo", "enum"]),
+ )
+
+ self.assertFalse(e2.context)
+
+ def test_single_nesting(self):
+ instance = {"foo": 2, "bar": [1], "baz": 15, "quux": "spam"}
+ schema = {
+ "properties": {
+ "foo": {"type": "string"},
+ "bar": {"minItems": 2},
+ "baz": {"maximum": 10, "enum": [2, 4, 6, 8]},
+ },
+ }
+
+ validator = validators.Draft3Validator(schema)
+ errors = validator.iter_errors(instance)
+ e1, e2, e3, e4 = sorted_errors(errors)
+
+ self.assertEqual(e1.path, deque(["bar"]))
+ self.assertEqual(e2.path, deque(["baz"]))
+ self.assertEqual(e3.path, deque(["baz"]))
+ self.assertEqual(e4.path, deque(["foo"]))
+
+ self.assertEqual(e1.relative_path, deque(["bar"]))
+ self.assertEqual(e2.relative_path, deque(["baz"]))
+ self.assertEqual(e3.relative_path, deque(["baz"]))
+ self.assertEqual(e4.relative_path, deque(["foo"]))
+
+ self.assertEqual(e1.absolute_path, deque(["bar"]))
+ self.assertEqual(e2.absolute_path, deque(["baz"]))
+ self.assertEqual(e3.absolute_path, deque(["baz"]))
+ self.assertEqual(e4.absolute_path, deque(["foo"]))
+
+ self.assertEqual(e1.json_path, '$.bar')
+ self.assertEqual(e2.json_path, '$.baz')
+ self.assertEqual(e3.json_path, '$.baz')
+ self.assertEqual(e4.json_path, '$.foo')
+
+ self.assertEqual(e1.validator, "minItems")
+ self.assertEqual(e2.validator, "enum")
+ self.assertEqual(e3.validator, "maximum")
+ self.assertEqual(e4.validator, "type")
+
+ def test_multiple_nesting(self):
+ instance = [1, {"foo": 2, "bar": {"baz": [1]}}, "quux"]
+ schema = {
+ "type": "string",
+ "items": {
+ "type": ["string", "object"],
+ "properties": {
+ "foo": {"enum": [1, 3]},
+ "bar": {
+ "type": "array",
+ "properties": {
+ "bar": {"required": True},
+ "baz": {"minItems": 2},
+ },
+ },
+ },
+ },
+ }
+
+ validator = validators.Draft3Validator(schema)
+ errors = validator.iter_errors(instance)
+ e1, e2, e3, e4, e5, e6 = sorted_errors(errors)
+
+ self.assertEqual(e1.path, deque([]))
+ self.assertEqual(e2.path, deque([0]))
+ self.assertEqual(e3.path, deque([1, "bar"]))
+ self.assertEqual(e4.path, deque([1, "bar", "bar"]))
+ self.assertEqual(e5.path, deque([1, "bar", "baz"]))
+ self.assertEqual(e6.path, deque([1, "foo"]))
+
+ self.assertEqual(e1.json_path, '$')
+ self.assertEqual(e2.json_path, '$[0]')
+ self.assertEqual(e3.json_path, '$[1].bar')
+ self.assertEqual(e4.json_path, '$[1].bar.bar')
+ self.assertEqual(e5.json_path, '$[1].bar.baz')
+ self.assertEqual(e6.json_path, '$[1].foo')
+
+ self.assertEqual(e1.schema_path, deque(["type"]))
+ self.assertEqual(e2.schema_path, deque(["items", "type"]))
+ self.assertEqual(
+ list(e3.schema_path), ["items", "properties", "bar", "type"],
+ )
+ self.assertEqual(
+ list(e4.schema_path),
+ ["items", "properties", "bar", "properties", "bar", "required"],
+ )
+ self.assertEqual(
+ list(e5.schema_path),
+ ["items", "properties", "bar", "properties", "baz", "minItems"]
+ )
+ self.assertEqual(
+ list(e6.schema_path), ["items", "properties", "foo", "enum"],
+ )
+
+ self.assertEqual(e1.validator, "type")
+ self.assertEqual(e2.validator, "type")
+ self.assertEqual(e3.validator, "type")
+ self.assertEqual(e4.validator, "required")
+ self.assertEqual(e5.validator, "minItems")
+ self.assertEqual(e6.validator, "enum")
+
+ def test_recursive(self):
+ schema = {
+ "definitions": {
+ "node": {
+ "anyOf": [{
+ "type": "object",
+ "required": ["name", "children"],
+ "properties": {
+ "name": {
+ "type": "string",
+ },
+ "children": {
+ "type": "object",
+ "patternProperties": {
+ "^.*$": {
+ "$ref": "#/definitions/node",
+ },
+ },
+ },
+ },
+ }],
+ },
+ },
+ "type": "object",
+ "required": ["root"],
+ "properties": {"root": {"$ref": "#/definitions/node"}},
+ }
+
+ instance = {
+ "root": {
+ "name": "root",
+ "children": {
+ "a": {
+ "name": "a",
+ "children": {
+ "ab": {
+ "name": "ab",
+ # missing "children"
+ },
+ },
+ },
+ },
+ },
+ }
+ validator = validators.Draft4Validator(schema)
+
+ e, = validator.iter_errors(instance)
+ self.assertEqual(e.absolute_path, deque(["root"]))
+ self.assertEqual(
+ e.absolute_schema_path, deque(["properties", "root", "anyOf"]),
+ )
+ self.assertEqual(e.json_path, '$.root')
+
+ e1, = e.context
+ self.assertEqual(e1.absolute_path, deque(["root", "children", "a"]))
+ self.assertEqual(
+ e1.absolute_schema_path, deque(
+ [
+ "properties",
+ "root",
+ "anyOf",
+ 0,
+ "properties",
+ "children",
+ "patternProperties",
+ "^.*$",
+ "anyOf",
+ ],
+ ),
+ )
+ self.assertEqual(e1.json_path, '$.root.children.a')
+
+ e2, = e1.context
+ self.assertEqual(
+ e2.absolute_path, deque(
+ ["root", "children", "a", "children", "ab"],
+ ),
+ )
+ self.assertEqual(
+ e2.absolute_schema_path, deque(
+ [
+ "properties",
+ "root",
+ "anyOf",
+ 0,
+ "properties",
+ "children",
+ "patternProperties",
+ "^.*$",
+ "anyOf",
+ 0,
+ "properties",
+ "children",
+ "patternProperties",
+ "^.*$",
+ "anyOf",
+ ],
+ ),
+ )
+ self.assertEqual(e2.json_path, '$.root.children.a.children.ab')
+
+ def test_additionalProperties(self):
+ instance = {"bar": "bar", "foo": 2}
+ schema = {"additionalProperties": {"type": "integer", "minimum": 5}}
+
+ validator = validators.Draft3Validator(schema)
+ errors = validator.iter_errors(instance)
+ e1, e2 = sorted_errors(errors)
+
+ self.assertEqual(e1.path, deque(["bar"]))
+ self.assertEqual(e2.path, deque(["foo"]))
+
+ self.assertEqual(e1.json_path, '$.bar')
+ self.assertEqual(e2.json_path, '$.foo')
+
+ self.assertEqual(e1.validator, "type")
+ self.assertEqual(e2.validator, "minimum")
+
+ def test_patternProperties(self):
+ instance = {"bar": 1, "foo": 2}
+ schema = {
+ "patternProperties": {
+ "bar": {"type": "string"},
+ "foo": {"minimum": 5},
+ },
+ }
+
+ validator = validators.Draft3Validator(schema)
+ errors = validator.iter_errors(instance)
+ e1, e2 = sorted_errors(errors)
+
+ self.assertEqual(e1.path, deque(["bar"]))
+ self.assertEqual(e2.path, deque(["foo"]))
+
+ self.assertEqual(e1.json_path, '$.bar')
+ self.assertEqual(e2.json_path, '$.foo')
+
+ self.assertEqual(e1.validator, "type")
+ self.assertEqual(e2.validator, "minimum")
+
+ def test_additionalItems(self):
+ instance = ["foo", 1]
+ schema = {
+ "items": [],
+ "additionalItems": {"type": "integer", "minimum": 5},
+ }
+
+ validator = validators.Draft3Validator(schema)
+ errors = validator.iter_errors(instance)
+ e1, e2 = sorted_errors(errors)
+
+ self.assertEqual(e1.path, deque([0]))
+ self.assertEqual(e2.path, deque([1]))
+
+ self.assertEqual(e1.json_path, '$[0]')
+ self.assertEqual(e2.json_path, '$[1]')
+
+ self.assertEqual(e1.validator, "type")
+ self.assertEqual(e2.validator, "minimum")
+
+ def test_additionalItems_with_items(self):
+ instance = ["foo", "bar", 1]
+ schema = {
+ "items": [{}],
+ "additionalItems": {"type": "integer", "minimum": 5},
+ }
+
+ validator = validators.Draft3Validator(schema)
+ errors = validator.iter_errors(instance)
+ e1, e2 = sorted_errors(errors)
+
+ self.assertEqual(e1.path, deque([1]))
+ self.assertEqual(e2.path, deque([2]))
+
+ self.assertEqual(e1.json_path, '$[1]')
+ self.assertEqual(e2.json_path, '$[2]')
+
+ self.assertEqual(e1.validator, "type")
+ self.assertEqual(e2.validator, "minimum")
+
+ def test_propertyNames(self):
+ instance = {"foo": 12}
+ schema = {"propertyNames": {"not": {"const": "foo"}}}
+
+ validator = validators.Draft7Validator(schema)
+ error, = validator.iter_errors(instance)
+
+ self.assertEqual(error.validator, "not")
+ self.assertEqual(
+ error.message,
+ "%r is not allowed for %r" % ({"const": "foo"}, "foo"),
+ )
+ self.assertEqual(error.path, deque([]))
+ self.assertEqual(error.json_path, '$')
+ self.assertEqual(error.schema_path, deque(["propertyNames", "not"]))
+
+ def test_if_then(self):
+ schema = {
+ "if": {"const": 12},
+ "then": {"const": 13},
+ }
+
+ validator = validators.Draft7Validator(schema)
+ error, = validator.iter_errors(12)
+
+ self.assertEqual(error.validator, "const")
+ self.assertEqual(error.message, "13 was expected")
+ self.assertEqual(error.path, deque([]))
+ self.assertEqual(error.json_path, '$')
+ self.assertEqual(error.schema_path, deque(["then", "const"]))
+
+ def test_if_else(self):
+ schema = {
+ "if": {"const": 12},
+ "else": {"const": 13},
+ }
+
+ validator = validators.Draft7Validator(schema)
+ error, = validator.iter_errors(15)
+
+ self.assertEqual(error.validator, "const")
+ self.assertEqual(error.message, "13 was expected")
+ self.assertEqual(error.path, deque([]))
+ self.assertEqual(error.json_path, '$')
+ self.assertEqual(error.schema_path, deque(["else", "const"]))
+
+ def test_boolean_schema_False(self):
+ validator = validators.Draft7Validator(False)
+ error, = validator.iter_errors(12)
+
+ self.assertEqual(
+ (
+ error.message,
+ error.validator,
+ error.validator_value,
+ error.instance,
+ error.schema,
+ error.schema_path,
+ error.json_path,
+ ),
+ (
+ "False schema does not allow 12",
+ None,
+ None,
+ 12,
+ False,
+ deque([]),
+ '$',
+ ),
+ )
+
+ def test_ref(self):
+ ref, schema = "someRef", {"additionalProperties": {"type": "integer"}}
+ validator = validators.Draft7Validator(
+ {"$ref": ref},
+ resolver=validators.RefResolver("", {}, store={ref: schema}),
+ )
+ error, = validator.iter_errors({"foo": "notAnInteger"})
+
+ self.assertEqual(
+ (
+ error.message,
+ error.validator,
+ error.validator_value,
+ error.instance,
+ error.absolute_path,
+ error.schema,
+ error.schema_path,
+ error.json_path,
+ ),
+ (
+ "'notAnInteger' is not of type 'integer'",
+ "type",
+ "integer",
+ "notAnInteger",
+ deque(["foo"]),
+ {"type": "integer"},
+ deque(["additionalProperties", "type"]),
+ '$.foo',
+ ),
+ )
+
+
+class MetaSchemaTestsMixin(object):
+ # TODO: These all belong upstream
+ def test_invalid_properties(self):
+ with self.assertRaises(exceptions.SchemaError):
+ self.Validator.check_schema({"properties": {"test": object()}})
+
+ def test_minItems_invalid_string(self):
+ with self.assertRaises(exceptions.SchemaError):
+ # needs to be an integer
+ self.Validator.check_schema({"minItems": "1"})
+
+ def test_enum_allows_empty_arrays(self):
+ """
+ Technically, all the spec says is they SHOULD have elements, not MUST.
+
+ See https://github.com/Julian/jsonschema/issues/529.
+ """
+ self.Validator.check_schema({"enum": []})
+
+ def test_enum_allows_non_unique_items(self):
+ """
+ Technically, all the spec says is they SHOULD be unique, not MUST.
+
+ See https://github.com/Julian/jsonschema/issues/529.
+ """
+ self.Validator.check_schema({"enum": [12, 12]})
+
+
+class ValidatorTestMixin(MetaSchemaTestsMixin, object):
+ def test_valid_instances_are_valid(self):
+ schema, instance = self.valid
+ self.assertTrue(self.Validator(schema).is_valid(instance))
+
+ def test_invalid_instances_are_not_valid(self):
+ schema, instance = self.invalid
+ self.assertFalse(self.Validator(schema).is_valid(instance))
+
+ def test_non_existent_properties_are_ignored(self):
+ self.Validator({object(): object()}).validate(instance=object())
+
+ def test_it_creates_a_ref_resolver_if_not_provided(self):
+ self.assertIsInstance(
+ self.Validator({}).resolver,
+ validators.RefResolver,
+ )
+
+ def test_it_delegates_to_a_ref_resolver(self):
+ ref, schema = "someCoolRef", {"type": "integer"}
+ resolver = validators.RefResolver("", {}, store={ref: schema})
+ validator = self.Validator({"$ref": ref}, resolver=resolver)
+
+ with self.assertRaises(exceptions.ValidationError):
+ validator.validate(None)
+
+ def test_it_delegates_to_a_legacy_ref_resolver(self):
+ """
+ Legacy RefResolvers support only the context manager form of
+ resolution.
+ """
+
+ class LegacyRefResolver(object):
+ @contextmanager
+ def resolving(this, ref):
+ self.assertEqual(ref, "the ref")
+ yield {"type": "integer"}
+
+ resolver = LegacyRefResolver()
+ schema = {"$ref": "the ref"}
+
+ with self.assertRaises(exceptions.ValidationError):
+ self.Validator(schema, resolver=resolver).validate(None)
+
+ def test_is_type_is_true_for_valid_type(self):
+ self.assertTrue(self.Validator({}).is_type("foo", "string"))
+
+ def test_is_type_is_false_for_invalid_type(self):
+ self.assertFalse(self.Validator({}).is_type("foo", "array"))
+
+ def test_is_type_evades_bool_inheriting_from_int(self):
+ self.assertFalse(self.Validator({}).is_type(True, "integer"))
+ self.assertFalse(self.Validator({}).is_type(True, "number"))
+
+ def test_patterns_can_be_native_strings(self):
+ """
+ See https://github.com/Julian/jsonschema/issues/611.
+ """
+ self.Validator({"pattern": "foo"}).validate("foo")
+
+ def test_it_can_validate_with_decimals(self):
+ schema = {"items": {"type": "number"}}
+ Validator = validators.extend(
+ self.Validator,
+ type_checker=self.Validator.TYPE_CHECKER.redefine(
+ "number",
+ lambda checker, thing: isinstance(
+ thing, (int, float, Decimal),
+ ) and not isinstance(thing, bool),
+ )
+ )
+
+ validator = Validator(schema)
+ validator.validate([1, 1.1, Decimal(1) / Decimal(8)])
+
+ invalid = ["foo", {}, [], True, None]
+ self.assertEqual(
+ [error.instance for error in validator.iter_errors(invalid)],
+ invalid,
+ )
+
+ def test_it_returns_true_for_formats_it_does_not_know_about(self):
+ validator = self.Validator(
+ {"format": "carrot"}, format_checker=FormatChecker(),
+ )
+ validator.validate("bugs")
+
+ def test_it_does_not_validate_formats_by_default(self):
+ validator = self.Validator({})
+ self.assertIsNone(validator.format_checker)
+
+ def test_it_validates_formats_if_a_checker_is_provided(self):
+ checker = FormatChecker()
+ bad = ValueError("Bad!")
+
+ @checker.checks("foo", raises=ValueError)
+ def check(value):
+ if value == "good":
+ return True
+ elif value == "bad":
+ raise bad
+ else: # pragma: no cover
+ self.fail("What is {}? [Baby Don't Hurt Me]".format(value))
+
+ validator = self.Validator(
+ {"format": "foo"}, format_checker=checker,
+ )
+
+ validator.validate("good")
+ with self.assertRaises(exceptions.ValidationError) as cm:
+ validator.validate("bad")
+
+ # Make sure original cause is attached
+ self.assertIs(cm.exception.cause, bad)
+
+ def test_non_string_custom_type(self):
+ non_string_type = object()
+ schema = {"type": [non_string_type]}
+ Crazy = validators.extend(
+ self.Validator,
+ type_checker=self.Validator.TYPE_CHECKER.redefine(
+ non_string_type,
+ lambda checker, thing: isinstance(thing, int),
+ )
+ )
+ Crazy(schema).validate(15)
+
+ def test_it_properly_formats_tuples_in_errors(self):
+ """
+ A tuple instance properly formats validation errors for uniqueItems.
+
+ See https://github.com/Julian/jsonschema/pull/224
+ """
+ TupleValidator = validators.extend(
+ self.Validator,
+ type_checker=self.Validator.TYPE_CHECKER.redefine(
+ "array",
+ lambda checker, thing: isinstance(thing, tuple),
+ )
+ )
+ with self.assertRaises(exceptions.ValidationError) as e:
+ TupleValidator({"uniqueItems": True}).validate((1, 1))
+ self.assertIn("(1, 1) has non-unique elements", str(e.exception))
+
+
+class AntiDraft6LeakMixin(object):
+ """
+ Make sure functionality from draft 6 doesn't leak backwards in time.
+ """
+
+ def test_True_is_not_a_schema(self):
+ with self.assertRaises(exceptions.SchemaError) as e:
+ self.Validator.check_schema(True)
+ self.assertIn("True is not of type", str(e.exception))
+
+ def test_False_is_not_a_schema(self):
+ with self.assertRaises(exceptions.SchemaError) as e:
+ self.Validator.check_schema(False)
+ self.assertIn("False is not of type", str(e.exception))
+
+ @unittest.skip(bug(523))
+ def test_True_is_not_a_schema_even_if_you_forget_to_check(self):
+ resolver = validators.RefResolver("", {})
+ with self.assertRaises(Exception) as e:
+ self.Validator(True, resolver=resolver).validate(12)
+ self.assertNotIsInstance(e.exception, exceptions.ValidationError)
+
+ @unittest.skip(bug(523))
+ def test_False_is_not_a_schema_even_if_you_forget_to_check(self):
+ resolver = validators.RefResolver("", {})
+ with self.assertRaises(Exception) as e:
+ self.Validator(False, resolver=resolver).validate(12)
+ self.assertNotIsInstance(e.exception, exceptions.ValidationError)
+
+
+class TestDraft3Validator(AntiDraft6LeakMixin, ValidatorTestMixin, TestCase):
+ Validator = validators.Draft3Validator
+ valid = {}, {}
+ invalid = {"type": "integer"}, "foo"
+
+ def test_any_type_is_valid_for_type_any(self):
+ validator = self.Validator({"type": "any"})
+ validator.validate(object())
+
+ def test_any_type_is_redefinable(self):
+ """
+ Sigh, because why not.
+ """
+ Crazy = validators.extend(
+ self.Validator,
+ type_checker=self.Validator.TYPE_CHECKER.redefine(
+ "any", lambda checker, thing: isinstance(thing, int),
+ )
+ )
+ validator = Crazy({"type": "any"})
+ validator.validate(12)
+ with self.assertRaises(exceptions.ValidationError):
+ validator.validate("foo")
+
+ def test_is_type_is_true_for_any_type(self):
+ self.assertTrue(self.Validator({}).is_valid(object(), {"type": "any"}))
+
+ def test_is_type_does_not_evade_bool_if_it_is_being_tested(self):
+ self.assertTrue(self.Validator({}).is_type(True, "boolean"))
+ self.assertTrue(self.Validator({}).is_valid(True, {"type": "any"}))
+
+
+class TestDraft4Validator(AntiDraft6LeakMixin, ValidatorTestMixin, TestCase):
+ Validator = validators.Draft4Validator
+ valid = {}, {}
+ invalid = {"type": "integer"}, "foo"
+
+
+class TestDraft6Validator(ValidatorTestMixin, TestCase):
+ Validator = validators.Draft6Validator
+ valid = {}, {}
+ invalid = {"type": "integer"}, "foo"
+
+
+class TestDraft7Validator(ValidatorTestMixin, TestCase):
+ Validator = validators.Draft7Validator
+ valid = {}, {}
+ invalid = {"type": "integer"}, "foo"
+
+
+class TestValidatorFor(SynchronousTestCase):
+ def test_draft_3(self):
+ schema = {"$schema": "http://json-schema.org/draft-03/schema"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft3Validator,
+ )
+
+ schema = {"$schema": "http://json-schema.org/draft-03/schema#"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft3Validator,
+ )
+
+ def test_draft_4(self):
+ schema = {"$schema": "http://json-schema.org/draft-04/schema"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft4Validator,
+ )
+
+ schema = {"$schema": "http://json-schema.org/draft-04/schema#"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft4Validator,
+ )
+
+ def test_draft_6(self):
+ schema = {"$schema": "http://json-schema.org/draft-06/schema"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft6Validator,
+ )
+
+ schema = {"$schema": "http://json-schema.org/draft-06/schema#"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft6Validator,
+ )
+
+ def test_draft_7(self):
+ schema = {"$schema": "http://json-schema.org/draft-07/schema"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft7Validator,
+ )
+
+ schema = {"$schema": "http://json-schema.org/draft-07/schema#"}
+ self.assertIs(
+ validators.validator_for(schema),
+ validators.Draft7Validator,
+ )
+
+ def test_True(self):
+ self.assertIs(
+ validators.validator_for(True),
+ validators._LATEST_VERSION,
+ )
+
+ def test_False(self):
+ self.assertIs(
+ validators.validator_for(False),
+ validators._LATEST_VERSION,
+ )
+
+ def test_custom_validator(self):
+ Validator = validators.create(
+ meta_schema={"id": "meta schema id"},
+ version="12",
+ id_of=lambda s: s.get("id", ""),
+ )
+ schema = {"$schema": "meta schema id"}
+ self.assertIs(
+ validators.validator_for(schema),
+ Validator,
+ )
+
+ def test_custom_validator_draft6(self):
+ Validator = validators.create(
+ meta_schema={"$id": "meta schema $id"},
+ version="13",
+ )
+ schema = {"$schema": "meta schema $id"}
+ self.assertIs(
+ validators.validator_for(schema),
+ Validator,
+ )
+
+ def test_validator_for_jsonschema_default(self):
+ self.assertIs(validators.validator_for({}), validators._LATEST_VERSION)
+
+ def test_validator_for_custom_default(self):
+ self.assertIs(validators.validator_for({}, default=None), None)
+
+ def test_warns_if_meta_schema_specified_was_not_found(self):
+ self.assertWarns(
+ category=DeprecationWarning,
+ message=(
+ "The metaschema specified by $schema was not found. "
+ "Using the latest draft to validate, but this will raise "
+ "an error in the future."
+ ),
+ # https://tm.tl/9363 :'(
+ filename=sys.modules[self.assertWarns.__module__].__file__,
+
+ f=validators.validator_for,
+ schema={u"$schema": "unknownSchema"},
+ default={},
+ )
+
+ def test_does_not_warn_if_meta_schema_is_unspecified(self):
+ validators.validator_for(schema={}, default={}),
+ self.assertFalse(self.flushWarnings())
+
+
+class TestValidate(SynchronousTestCase):
+ def assertUses(self, schema, Validator):
+ result = []
+ self.patch(Validator, "check_schema", result.append)
+ validators.validate({}, schema)
+ self.assertEqual(result, [schema])
+
+ def test_draft3_validator_is_chosen(self):
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-03/schema#"},
+ Validator=validators.Draft3Validator,
+ )
+ # Make sure it works without the empty fragment
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-03/schema"},
+ Validator=validators.Draft3Validator,
+ )
+
+ def test_draft4_validator_is_chosen(self):
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-04/schema#"},
+ Validator=validators.Draft4Validator,
+ )
+ # Make sure it works without the empty fragment
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-04/schema"},
+ Validator=validators.Draft4Validator,
+ )
+
+ def test_draft6_validator_is_chosen(self):
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-06/schema#"},
+ Validator=validators.Draft6Validator,
+ )
+ # Make sure it works without the empty fragment
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-06/schema"},
+ Validator=validators.Draft6Validator,
+ )
+
+ def test_draft7_validator_is_chosen(self):
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-07/schema#"},
+ Validator=validators.Draft7Validator,
+ )
+ # Make sure it works without the empty fragment
+ self.assertUses(
+ schema={"$schema": "http://json-schema.org/draft-07/schema"},
+ Validator=validators.Draft7Validator,
+ )
+
+ def test_draft7_validator_is_the_default(self):
+ self.assertUses(schema={}, Validator=validators.Draft7Validator)
+
+ def test_validation_error_message(self):
+ with self.assertRaises(exceptions.ValidationError) as e:
+ validators.validate(12, {"type": "string"})
+ self.assertRegexpMatches(
+ str(e.exception),
+ "(?s)Failed validating u?'.*' in schema.*On instance",
+ )
+
+ def test_schema_error_message(self):
+ with self.assertRaises(exceptions.SchemaError) as e:
+ validators.validate(12, {"type": 12})
+ self.assertRegexpMatches(
+ str(e.exception),
+ "(?s)Failed validating u?'.*' in metaschema.*On schema",
+ )
+
+ def test_it_uses_best_match(self):
+ # This is a schema that best_match will recurse into
+ schema = {"oneOf": [{"type": "string"}, {"type": "array"}]}
+ with self.assertRaises(exceptions.ValidationError) as e:
+ validators.validate(12, schema)
+ self.assertIn("12 is not of type", str(e.exception))
+
+
+class TestRefResolver(SynchronousTestCase):
+
+ base_uri = ""
+ stored_uri = "foo://stored"
+ stored_schema = {"stored": "schema"}
+
+ def setUp(self):
+ self.referrer = {}
+ self.store = {self.stored_uri: self.stored_schema}
+ self.resolver = validators.RefResolver(
+ self.base_uri, self.referrer, self.store,
+ )
+
+ def test_it_does_not_retrieve_schema_urls_from_the_network(self):
+ ref = validators.Draft3Validator.META_SCHEMA["id"]
+ self.patch(
+ self.resolver,
+ "resolve_remote",
+ lambda *args, **kwargs: self.fail("Should not have been called!"),
+ )
+ with self.resolver.resolving(ref) as resolved:
+ pass
+ self.assertEqual(resolved, validators.Draft3Validator.META_SCHEMA)
+
+ def test_it_resolves_local_refs(self):
+ ref = "#/properties/foo"
+ self.referrer["properties"] = {"foo": object()}
+ with self.resolver.resolving(ref) as resolved:
+ self.assertEqual(resolved, self.referrer["properties"]["foo"])
+
+ def test_it_resolves_local_refs_with_id(self):
+ schema = {"id": "http://bar/schema#", "a": {"foo": "bar"}}
+ resolver = validators.RefResolver.from_schema(
+ schema,
+ id_of=lambda schema: schema.get(u"id", u""),
+ )
+ with resolver.resolving("#/a") as resolved:
+ self.assertEqual(resolved, schema["a"])
+ with resolver.resolving("http://bar/schema#/a") as resolved:
+ self.assertEqual(resolved, schema["a"])
+
+ def test_it_retrieves_stored_refs(self):
+ with self.resolver.resolving(self.stored_uri) as resolved:
+ self.assertIs(resolved, self.stored_schema)
+
+ self.resolver.store["cached_ref"] = {"foo": 12}
+ with self.resolver.resolving("cached_ref#/foo") as resolved:
+ self.assertEqual(resolved, 12)
+
+ def test_it_retrieves_unstored_refs_via_requests(self):
+ ref = "http://bar#baz"
+ schema = {"baz": 12}
+
+ if "requests" in sys.modules:
+ self.addCleanup(
+ sys.modules.__setitem__, "requests", sys.modules["requests"],
+ )
+ sys.modules["requests"] = ReallyFakeRequests({"http://bar": schema})
+
+ with self.resolver.resolving(ref) as resolved:
+ self.assertEqual(resolved, 12)
+
+ def test_it_retrieves_unstored_refs_via_urlopen(self):
+ ref = "http://bar#baz"
+ schema = {"baz": 12}
+
+ if "requests" in sys.modules:
+ self.addCleanup(
+ sys.modules.__setitem__, "requests", sys.modules["requests"],
+ )
+ sys.modules["requests"] = None
+
+ @contextmanager
+ def fake_urlopen(url):
+ self.assertEqual(url, "http://bar")
+ yield BytesIO(json.dumps(schema).encode("utf8"))
+
+ self.addCleanup(setattr, validators, "urlopen", validators.urlopen)
+ validators.urlopen = fake_urlopen
+
+ with self.resolver.resolving(ref) as resolved:
+ pass
+ self.assertEqual(resolved, 12)
+
+ def test_it_retrieves_local_refs_via_urlopen(self):
+ with tempfile.NamedTemporaryFile(delete=False, mode="wt") as tempf:
+ self.addCleanup(os.remove, tempf.name)
+ json.dump({"foo": "bar"}, tempf)
+
+ ref = "file://{}#foo".format(pathname2url(tempf.name))
+ with self.resolver.resolving(ref) as resolved:
+ self.assertEqual(resolved, "bar")
+
+ def test_it_can_construct_a_base_uri_from_a_schema(self):
+ schema = {"id": "foo"}
+ resolver = validators.RefResolver.from_schema(
+ schema,
+ id_of=lambda schema: schema.get(u"id", u""),
+ )
+ self.assertEqual(resolver.base_uri, "foo")
+ self.assertEqual(resolver.resolution_scope, "foo")
+ with resolver.resolving("") as resolved:
+ self.assertEqual(resolved, schema)
+ with resolver.resolving("#") as resolved:
+ self.assertEqual(resolved, schema)
+ with resolver.resolving("foo") as resolved:
+ self.assertEqual(resolved, schema)
+ with resolver.resolving("foo#") as resolved:
+ self.assertEqual(resolved, schema)
+
+ def test_it_can_construct_a_base_uri_from_a_schema_without_id(self):
+ schema = {}
+ resolver = validators.RefResolver.from_schema(schema)
+ self.assertEqual(resolver.base_uri, "")
+ self.assertEqual(resolver.resolution_scope, "")
+ with resolver.resolving("") as resolved:
+ self.assertEqual(resolved, schema)
+ with resolver.resolving("#") as resolved:
+ self.assertEqual(resolved, schema)
+
+ def test_custom_uri_scheme_handlers(self):
+ def handler(url):
+ self.assertEqual(url, ref)
+ return schema
+
+ schema = {"foo": "bar"}
+ ref = "foo://bar"
+ resolver = validators.RefResolver("", {}, handlers={"foo": handler})
+ with resolver.resolving(ref) as resolved:
+ self.assertEqual(resolved, schema)
+
+ def test_cache_remote_on(self):
+ response = [object()]
+
+ def handler(url):
+ try:
+ return response.pop()
+ except IndexError: # pragma: no cover
+ self.fail("Response must not have been cached!")
+
+ ref = "foo://bar"
+ resolver = validators.RefResolver(
+ "", {}, cache_remote=True, handlers={"foo": handler},
+ )
+ with resolver.resolving(ref):
+ pass
+ with resolver.resolving(ref):
+ pass
+
+ def test_cache_remote_off(self):
+ response = [object()]
+
+ def handler(url):
+ try:
+ return response.pop()
+ except IndexError: # pragma: no cover
+ self.fail("Handler called twice!")
+
+ ref = "foo://bar"
+ resolver = validators.RefResolver(
+ "", {}, cache_remote=False, handlers={"foo": handler},
+ )
+ with resolver.resolving(ref):
+ pass
+
+ def test_if_you_give_it_junk_you_get_a_resolution_error(self):
+ error = ValueError("Oh no! What's this?")
+
+ def handler(url):
+ raise error
+
+ ref = "foo://bar"
+ resolver = validators.RefResolver("", {}, handlers={"foo": handler})
+ with self.assertRaises(exceptions.RefResolutionError) as err:
+ with resolver.resolving(ref):
+ self.fail("Shouldn't get this far!") # pragma: no cover
+ self.assertEqual(err.exception, exceptions.RefResolutionError(error))
+
+ def test_helpful_error_message_on_failed_pop_scope(self):
+ resolver = validators.RefResolver("", {})
+ resolver.pop_scope()
+ with self.assertRaises(exceptions.RefResolutionError) as exc:
+ resolver.pop_scope()
+ self.assertIn("Failed to pop the scope", str(exc.exception))
+
+
+def sorted_errors(errors):
+ def key(error):
+ return (
+ [str(e) for e in error.path],
+ [str(e) for e in error.schema_path],
+ )
+ return sorted(errors, key=key)
+
+
+@attr.s
+class ReallyFakeRequests(object):
+
+ _responses = attr.ib()
+
+ def get(self, url):
+ response = self._responses.get(url)
+ if url is None: # pragma: no cover
+ raise ValueError("Unknown URL: " + repr(url))
+ return _ReallyFakeJSONResponse(json.dumps(response))
+
+
+@attr.s
+class _ReallyFakeJSONResponse(object):
+
+ _response = attr.ib()
+
+ def json(self):
+ return json.loads(self._response)
diff --git a/jsonschema/validators.py b/jsonschema/validators.py
new file mode 100644
index 0000000..d6e0a0c
--- /dev/null
+++ b/jsonschema/validators.py
@@ -0,0 +1,957 @@
+"""
+Creation and extension of validators, with implementations for existing drafts.
+"""
+from collections.abc import Sequence
+from functools import lru_cache
+from urllib.parse import unquote, urldefrag, urljoin, urlsplit
+from urllib.request import urlopen
+from warnings import warn
+import contextlib
+import json
+import numbers
+
+from jsonschema import (
+ _legacy_validators,
+ _types,
+ _utils,
+ _validators,
+ exceptions,
+)
+# Sigh. https://gitlab.com/pycqa/flake8/issues/280
+# https://github.com/pyga/ebb-lint/issues/7
+# Imported for backwards compatibility.
+from jsonschema.exceptions import ErrorTree
+
+ErrorTree
+
+
+class _DontDoThat(Exception):
+ """
+ Raised when a Validators with non-default type checker is misused.
+
+ Asking one for DEFAULT_TYPES doesn't make sense, since type checkers
+ exist for the unrepresentable cases where DEFAULT_TYPES can't
+ represent the type relationship.
+ """
+
+ def __str__(self):
+ return "DEFAULT_TYPES cannot be used on Validators using TypeCheckers"
+
+
+validators = {}
+meta_schemas = _utils.URIDict()
+
+
+def _generate_legacy_type_checks(types=()):
+ """
+ Generate newer-style type checks out of JSON-type-name-to-type mappings.
+
+ Arguments:
+
+ types (dict):
+
+ A mapping of type names to their Python types
+
+ Returns:
+
+ A dictionary of definitions to pass to `TypeChecker`
+ """
+ types = dict(types)
+
+ def gen_type_check(pytypes):
+ pytypes = _utils.flatten(pytypes)
+
+ def type_check(checker, instance):
+ if isinstance(instance, bool):
+ if bool not in pytypes:
+ return False
+ return isinstance(instance, pytypes)
+
+ return type_check
+
+ definitions = {}
+ for typename, pytypes in types.items():
+ definitions[typename] = gen_type_check(pytypes)
+
+ return definitions
+
+
+_DEPRECATED_DEFAULT_TYPES = {
+ u"array": list,
+ u"boolean": bool,
+ u"integer": int,
+ u"null": type(None),
+ u"number": numbers.Number,
+ u"object": dict,
+ u"string": str,
+}
+_TYPE_CHECKER_FOR_DEPRECATED_DEFAULT_TYPES = _types.TypeChecker(
+ type_checkers=_generate_legacy_type_checks(_DEPRECATED_DEFAULT_TYPES),
+)
+
+
+def validates(version):
+ """
+ Register the decorated validator for a ``version`` of the specification.
+
+ Registered validators and their meta schemas will be considered when
+ parsing ``$schema`` properties' URIs.
+
+ Arguments:
+
+ version (str):
+
+ An identifier to use as the version's name
+
+ Returns:
+
+ collections.abc.Callable:
+
+ a class decorator to decorate the validator with the version
+ """
+
+ def _validates(cls):
+ validators[version] = cls
+ meta_schema_id = cls.ID_OF(cls.META_SCHEMA)
+ if meta_schema_id:
+ meta_schemas[meta_schema_id] = cls
+ return cls
+ return _validates
+
+
+def _DEFAULT_TYPES(self):
+ if self._CREATED_WITH_DEFAULT_TYPES is None:
+ raise _DontDoThat()
+
+ warn(
+ (
+ "The DEFAULT_TYPES attribute is deprecated. "
+ "See the type checker attached to this validator instead."
+ ),
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self._DEFAULT_TYPES
+
+
+class _DefaultTypesDeprecatingMetaClass(type):
+ DEFAULT_TYPES = property(_DEFAULT_TYPES)
+
+
+def _id_of(schema):
+ if schema is True or schema is False:
+ return u""
+ return schema.get(u"$id", u"")
+
+
+def create(
+ meta_schema,
+ validators=(),
+ version=None,
+ default_types=None,
+ type_checker=None,
+ id_of=_id_of,
+):
+ """
+ Create a new validator class.
+
+ Arguments:
+
+ meta_schema (collections.abc.Mapping):
+
+ the meta schema for the new validator class
+
+ validators (collections.abc.Mapping):
+
+ a mapping from names to callables, where each callable will
+ validate the schema property with the given name.
+
+ Each callable should take 4 arguments:
+
+ 1. a validator instance,
+ 2. the value of the property being validated within the
+ instance
+ 3. the instance
+ 4. the schema
+
+ version (str):
+
+ an identifier for the version that this validator class will
+ validate. If provided, the returned validator class will
+ have its ``__name__`` set to include the version, and also
+ will have `jsonschema.validators.validates` automatically
+ called for the given version.
+
+ type_checker (jsonschema.TypeChecker):
+
+ a type checker, used when applying the :validator:`type` validator.
+
+ If unprovided, a `jsonschema.TypeChecker` will be created
+ with a set of default types typical of JSON Schema drafts.
+
+ default_types (collections.abc.Mapping):
+
+ .. deprecated:: 3.0.0
+
+ Please use the type_checker argument instead.
+
+ If set, it provides mappings of JSON types to Python types
+ that will be converted to functions and redefined in this
+ object's `jsonschema.TypeChecker`.
+
+ id_of (collections.abc.Callable):
+
+ A function that given a schema, returns its ID.
+
+ Returns:
+
+ a new `jsonschema.IValidator` class
+ """
+
+ if default_types is not None:
+ if type_checker is not None:
+ raise TypeError(
+ "Do not specify default_types when providing a type checker.",
+ )
+ _created_with_default_types = True
+ warn(
+ (
+ "The default_types argument is deprecated. "
+ "Use the type_checker argument instead."
+ ),
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ type_checker = _types.TypeChecker(
+ type_checkers=_generate_legacy_type_checks(default_types),
+ )
+ else:
+ default_types = _DEPRECATED_DEFAULT_TYPES
+ if type_checker is None:
+ _created_with_default_types = False
+ type_checker = _TYPE_CHECKER_FOR_DEPRECATED_DEFAULT_TYPES
+ elif type_checker is _TYPE_CHECKER_FOR_DEPRECATED_DEFAULT_TYPES:
+ _created_with_default_types = False
+ else:
+ _created_with_default_types = None
+
+ class Validator(metaclass=_DefaultTypesDeprecatingMetaClass):
+
+ VALIDATORS = dict(validators)
+ META_SCHEMA = dict(meta_schema)
+ TYPE_CHECKER = type_checker
+ ID_OF = staticmethod(id_of)
+
+ DEFAULT_TYPES = property(_DEFAULT_TYPES)
+ _DEFAULT_TYPES = dict(default_types)
+ _CREATED_WITH_DEFAULT_TYPES = _created_with_default_types
+
+ def __init__(
+ self,
+ schema,
+ types=(),
+ resolver=None,
+ format_checker=None,
+ ):
+ if types:
+ warn(
+ (
+ "The types argument is deprecated. Provide "
+ "a type_checker to jsonschema.validators.extend "
+ "instead."
+ ),
+ DeprecationWarning,
+ stacklevel=2,
+ )
+
+ self.TYPE_CHECKER = self.TYPE_CHECKER.redefine_many(
+ _generate_legacy_type_checks(types),
+ )
+
+ if resolver is None:
+ resolver = RefResolver.from_schema(schema, id_of=id_of)
+
+ self.resolver = resolver
+ self.format_checker = format_checker
+ self.schema = schema
+
+ @classmethod
+ def check_schema(cls, schema):
+ for error in cls(cls.META_SCHEMA).iter_errors(schema):
+ raise exceptions.SchemaError.create_from(error)
+
+ def iter_errors(self, instance, _schema=None):
+ if _schema is None:
+ _schema = self.schema
+
+ if _schema is True:
+ return
+ elif _schema is False:
+ yield exceptions.ValidationError(
+ "False schema does not allow %r" % (instance,),
+ validator=None,
+ validator_value=None,
+ instance=instance,
+ schema=_schema,
+ )
+ return
+
+ scope = id_of(_schema)
+ if scope:
+ self.resolver.push_scope(scope)
+ try:
+ ref = _schema.get(u"$ref")
+ if ref is not None:
+ validators = [(u"$ref", ref)]
+ else:
+ validators = _schema.items()
+
+ for k, v in validators:
+ validator = self.VALIDATORS.get(k)
+ if validator is None:
+ continue
+
+ errors = validator(self, v, instance, _schema) or ()
+ for error in errors:
+ # set details if not already set by the called fn
+ error._set(
+ validator=k,
+ validator_value=v,
+ instance=instance,
+ schema=_schema,
+ )
+ if k not in {u"if", u"$ref"}:
+ error.schema_path.appendleft(k)
+ yield error
+ finally:
+ if scope:
+ self.resolver.pop_scope()
+
+ def descend(self, instance, schema, path=None, schema_path=None):
+ for error in self.iter_errors(instance, schema):
+ if path is not None:
+ error.path.appendleft(path)
+ if schema_path is not None:
+ error.schema_path.appendleft(schema_path)
+ yield error
+
+ def validate(self, *args, **kwargs):
+ for error in self.iter_errors(*args, **kwargs):
+ raise error
+
+ def is_type(self, instance, type):
+ try:
+ return self.TYPE_CHECKER.is_type(instance, type)
+ except exceptions.UndefinedTypeCheck:
+ raise exceptions.UnknownType(type, instance, self.schema)
+
+ def is_valid(self, instance, _schema=None):
+ error = next(self.iter_errors(instance, _schema), None)
+ return error is None
+
+ if version is not None:
+ Validator = validates(version)(Validator)
+ Validator.__name__ = version.title().replace(" ", "") + "Validator"
+
+ return Validator
+
+
+def extend(validator, validators=(), version=None, type_checker=None):
+ """
+ Create a new validator class by extending an existing one.
+
+ Arguments:
+
+ validator (jsonschema.IValidator):
+
+ an existing validator class
+
+ validators (collections.abc.Mapping):
+
+ a mapping of new validator callables to extend with, whose
+ structure is as in `create`.
+
+ .. note::
+
+ Any validator callables with the same name as an
+ existing one will (silently) replace the old validator
+ callable entirely, effectively overriding any validation
+ done in the "parent" validator class.
+
+ If you wish to instead extend the behavior of a parent's
+ validator callable, delegate and call it directly in
+ the new validator function by retrieving it using
+ ``OldValidator.VALIDATORS["validator_name"]``.
+
+ version (str):
+
+ a version for the new validator class
+
+ type_checker (jsonschema.TypeChecker):
+
+ a type checker, used when applying the :validator:`type` validator.
+
+ If unprovided, the type checker of the extended
+ `jsonschema.IValidator` will be carried along.`
+
+ Returns:
+
+ a new `jsonschema.IValidator` class extending the one provided
+
+ .. note:: Meta Schemas
+
+ The new validator class will have its parent's meta schema.
+
+ If you wish to change or extend the meta schema in the new
+ validator class, modify ``META_SCHEMA`` directly on the returned
+ class. Note that no implicit copying is done, so a copy should
+ likely be made before modifying it, in order to not affect the
+ old validator.
+ """
+
+ all_validators = dict(validator.VALIDATORS)
+ all_validators.update(validators)
+
+ if type_checker is None:
+ type_checker = validator.TYPE_CHECKER
+ elif validator._CREATED_WITH_DEFAULT_TYPES:
+ raise TypeError(
+ "Cannot extend a validator created with default_types "
+ "with a type_checker. Update the validator to use a "
+ "type_checker when created."
+ )
+ return create(
+ meta_schema=validator.META_SCHEMA,
+ validators=all_validators,
+ version=version,
+ type_checker=type_checker,
+ id_of=validator.ID_OF,
+ )
+
+
+Draft3Validator = create(
+ meta_schema=_utils.load_schema("draft3"),
+ validators={
+ u"$ref": _validators.ref,
+ u"additionalItems": _validators.additionalItems,
+ u"additionalProperties": _validators.additionalProperties,
+ u"dependencies": _legacy_validators.dependencies_draft3,
+ u"disallow": _legacy_validators.disallow_draft3,
+ u"divisibleBy": _validators.multipleOf,
+ u"enum": _validators.enum,
+ u"extends": _legacy_validators.extends_draft3,
+ u"format": _validators.format,
+ u"items": _legacy_validators.items_draft3_draft4,
+ u"maxItems": _validators.maxItems,
+ u"maxLength": _validators.maxLength,
+ u"maximum": _legacy_validators.maximum_draft3_draft4,
+ u"minItems": _validators.minItems,
+ u"minLength": _validators.minLength,
+ u"minimum": _legacy_validators.minimum_draft3_draft4,
+ u"pattern": _validators.pattern,
+ u"patternProperties": _validators.patternProperties,
+ u"properties": _legacy_validators.properties_draft3,
+ u"type": _legacy_validators.type_draft3,
+ u"uniqueItems": _validators.uniqueItems,
+ },
+ type_checker=_types.draft3_type_checker,
+ version="draft3",
+ id_of=lambda schema: schema.get(u"id", ""),
+)
+
+Draft4Validator = create(
+ meta_schema=_utils.load_schema("draft4"),
+ validators={
+ u"$ref": _validators.ref,
+ u"additionalItems": _validators.additionalItems,
+ u"additionalProperties": _validators.additionalProperties,
+ u"allOf": _validators.allOf,
+ u"anyOf": _validators.anyOf,
+ u"dependencies": _validators.dependencies,
+ u"enum": _validators.enum,
+ u"format": _validators.format,
+ u"items": _legacy_validators.items_draft3_draft4,
+ u"maxItems": _validators.maxItems,
+ u"maxLength": _validators.maxLength,
+ u"maxProperties": _validators.maxProperties,
+ u"maximum": _legacy_validators.maximum_draft3_draft4,
+ u"minItems": _validators.minItems,
+ u"minLength": _validators.minLength,
+ u"minProperties": _validators.minProperties,
+ u"minimum": _legacy_validators.minimum_draft3_draft4,
+ u"multipleOf": _validators.multipleOf,
+ u"not": _validators.not_,
+ u"oneOf": _validators.oneOf,
+ u"pattern": _validators.pattern,
+ u"patternProperties": _validators.patternProperties,
+ u"properties": _validators.properties,
+ u"required": _validators.required,
+ u"type": _validators.type,
+ u"uniqueItems": _validators.uniqueItems,
+ },
+ type_checker=_types.draft4_type_checker,
+ version="draft4",
+ id_of=lambda schema: schema.get(u"id", ""),
+)
+
+Draft6Validator = create(
+ meta_schema=_utils.load_schema("draft6"),
+ validators={
+ u"$ref": _validators.ref,
+ u"additionalItems": _validators.additionalItems,
+ u"additionalProperties": _validators.additionalProperties,
+ u"allOf": _validators.allOf,
+ u"anyOf": _validators.anyOf,
+ u"const": _validators.const,
+ u"contains": _validators.contains,
+ u"dependencies": _validators.dependencies,
+ u"enum": _validators.enum,
+ u"exclusiveMaximum": _validators.exclusiveMaximum,
+ u"exclusiveMinimum": _validators.exclusiveMinimum,
+ u"format": _validators.format,
+ u"items": _validators.items,
+ u"maxItems": _validators.maxItems,
+ u"maxLength": _validators.maxLength,
+ u"maxProperties": _validators.maxProperties,
+ u"maximum": _validators.maximum,
+ u"minItems": _validators.minItems,
+ u"minLength": _validators.minLength,
+ u"minProperties": _validators.minProperties,
+ u"minimum": _validators.minimum,
+ u"multipleOf": _validators.multipleOf,
+ u"not": _validators.not_,
+ u"oneOf": _validators.oneOf,
+ u"pattern": _validators.pattern,
+ u"patternProperties": _validators.patternProperties,
+ u"properties": _validators.properties,
+ u"propertyNames": _validators.propertyNames,
+ u"required": _validators.required,
+ u"type": _validators.type,
+ u"uniqueItems": _validators.uniqueItems,
+ },
+ type_checker=_types.draft6_type_checker,
+ version="draft6",
+)
+
+Draft7Validator = create(
+ meta_schema=_utils.load_schema("draft7"),
+ validators={
+ u"$ref": _validators.ref,
+ u"additionalItems": _validators.additionalItems,
+ u"additionalProperties": _validators.additionalProperties,
+ u"allOf": _validators.allOf,
+ u"anyOf": _validators.anyOf,
+ u"const": _validators.const,
+ u"contains": _validators.contains,
+ u"dependencies": _validators.dependencies,
+ u"enum": _validators.enum,
+ u"exclusiveMaximum": _validators.exclusiveMaximum,
+ u"exclusiveMinimum": _validators.exclusiveMinimum,
+ u"format": _validators.format,
+ u"if": _validators.if_,
+ u"items": _validators.items,
+ u"maxItems": _validators.maxItems,
+ u"maxLength": _validators.maxLength,
+ u"maxProperties": _validators.maxProperties,
+ u"maximum": _validators.maximum,
+ u"minItems": _validators.minItems,
+ u"minLength": _validators.minLength,
+ u"minProperties": _validators.minProperties,
+ u"minimum": _validators.minimum,
+ u"multipleOf": _validators.multipleOf,
+ u"oneOf": _validators.oneOf,
+ u"not": _validators.not_,
+ u"pattern": _validators.pattern,
+ u"patternProperties": _validators.patternProperties,
+ u"properties": _validators.properties,
+ u"propertyNames": _validators.propertyNames,
+ u"required": _validators.required,
+ u"type": _validators.type,
+ u"uniqueItems": _validators.uniqueItems,
+ },
+ type_checker=_types.draft7_type_checker,
+ version="draft7",
+)
+
+_LATEST_VERSION = Draft7Validator
+
+
+class RefResolver(object):
+ """
+ Resolve JSON References.
+
+ Arguments:
+
+ base_uri (str):
+
+ The URI of the referring document
+
+ referrer:
+
+ The actual referring document
+
+ store (dict):
+
+ A mapping from URIs to documents to cache
+
+ cache_remote (bool):
+
+ Whether remote refs should be cached after first resolution
+
+ handlers (dict):
+
+ A mapping from URI schemes to functions that should be used
+ to retrieve them
+
+ urljoin_cache (:func:`functools.lru_cache`):
+
+ A cache that will be used for caching the results of joining
+ the resolution scope to subscopes.
+
+ remote_cache (:func:`functools.lru_cache`):
+
+ A cache that will be used for caching the results of
+ resolved remote URLs.
+
+ Attributes:
+
+ cache_remote (bool):
+
+ Whether remote refs should be cached after first resolution
+ """
+
+ def __init__(
+ self,
+ base_uri,
+ referrer,
+ store=(),
+ cache_remote=True,
+ handlers=(),
+ urljoin_cache=None,
+ remote_cache=None,
+ ):
+ if urljoin_cache is None:
+ urljoin_cache = lru_cache(1024)(urljoin)
+ if remote_cache is None:
+ remote_cache = lru_cache(1024)(self.resolve_from_url)
+
+ self.referrer = referrer
+ self.cache_remote = cache_remote
+ self.handlers = dict(handlers)
+
+ self._scopes_stack = [base_uri]
+ self.store = _utils.URIDict(
+ (id, validator.META_SCHEMA)
+ for id, validator in meta_schemas.items()
+ )
+ self.store.update(store)
+ self.store[base_uri] = referrer
+
+ self._urljoin_cache = urljoin_cache
+ self._remote_cache = remote_cache
+
+ @classmethod
+ def from_schema(cls, schema, id_of=_id_of, *args, **kwargs):
+ """
+ Construct a resolver from a JSON schema object.
+
+ Arguments:
+
+ schema:
+
+ the referring schema
+
+ Returns:
+
+ `RefResolver`
+ """
+
+ return cls(base_uri=id_of(schema), referrer=schema, *args, **kwargs)
+
+ def push_scope(self, scope):
+ """
+ Enter a given sub-scope.
+
+ Treats further dereferences as being performed underneath the
+ given scope.
+ """
+ self._scopes_stack.append(
+ self._urljoin_cache(self.resolution_scope, scope),
+ )
+
+ def pop_scope(self):
+ """
+ Exit the most recent entered scope.
+
+ Treats further dereferences as being performed underneath the
+ original scope.
+
+ Don't call this method more times than `push_scope` has been
+ called.
+ """
+ try:
+ self._scopes_stack.pop()
+ except IndexError:
+ raise exceptions.RefResolutionError(
+ "Failed to pop the scope from an empty stack. "
+ "`pop_scope()` should only be called once for every "
+ "`push_scope()`"
+ )
+
+ @property
+ def resolution_scope(self):
+ """
+ Retrieve the current resolution scope.
+ """
+ return self._scopes_stack[-1]
+
+ @property
+ def base_uri(self):
+ """
+ Retrieve the current base URI, not including any fragment.
+ """
+ uri, _ = urldefrag(self.resolution_scope)
+ return uri
+
+ @contextlib.contextmanager
+ def in_scope(self, scope):
+ """
+ Temporarily enter the given scope for the duration of the context.
+ """
+ self.push_scope(scope)
+ try:
+ yield
+ finally:
+ self.pop_scope()
+
+ @contextlib.contextmanager
+ def resolving(self, ref):
+ """
+ Resolve the given ``ref`` and enter its resolution scope.
+
+ Exits the scope on exit of this context manager.
+
+ Arguments:
+
+ ref (str):
+
+ The reference to resolve
+ """
+
+ url, resolved = self.resolve(ref)
+ self.push_scope(url)
+ try:
+ yield resolved
+ finally:
+ self.pop_scope()
+
+ def resolve(self, ref):
+ """
+ Resolve the given reference.
+ """
+ url = self._urljoin_cache(self.resolution_scope, ref)
+ return url, self._remote_cache(url)
+
+ def resolve_from_url(self, url):
+ """
+ Resolve the given remote URL.
+ """
+ url, fragment = urldefrag(url)
+ try:
+ document = self.store[url]
+ except KeyError:
+ try:
+ document = self.resolve_remote(url)
+ except Exception as exc:
+ raise exceptions.RefResolutionError(exc)
+
+ return self.resolve_fragment(document, fragment)
+
+ def resolve_fragment(self, document, fragment):
+ """
+ Resolve a ``fragment`` within the referenced ``document``.
+
+ Arguments:
+
+ document:
+
+ The referent document
+
+ fragment (str):
+
+ a URI fragment to resolve within it
+ """
+
+ fragment = fragment.lstrip(u"/")
+ parts = unquote(fragment).split(u"/") if fragment else []
+
+ for part in parts:
+ part = part.replace(u"~1", u"/").replace(u"~0", u"~")
+
+ if isinstance(document, Sequence):
+ # Array indexes should be turned into integers
+ try:
+ part = int(part)
+ except ValueError:
+ pass
+ try:
+ document = document[part]
+ except (TypeError, LookupError):
+ raise exceptions.RefResolutionError(
+ "Unresolvable JSON pointer: %r" % fragment
+ )
+
+ return document
+
+ def resolve_remote(self, uri):
+ """
+ Resolve a remote ``uri``.
+
+ If called directly, does not check the store first, but after
+ retrieving the document at the specified URI it will be saved in
+ the store if :attr:`cache_remote` is True.
+
+ .. note::
+
+ If the requests_ library is present, ``jsonschema`` will use it to
+ request the remote ``uri``, so that the correct encoding is
+ detected and used.
+
+ If it isn't, or if the scheme of the ``uri`` is not ``http`` or
+ ``https``, UTF-8 is assumed.
+
+ Arguments:
+
+ uri (str):
+
+ The URI to resolve
+
+ Returns:
+
+ The retrieved document
+
+ .. _requests: https://pypi.org/project/requests/
+ """
+ try:
+ import requests
+ except ImportError:
+ requests = None
+
+ scheme = urlsplit(uri).scheme
+
+ if scheme in self.handlers:
+ result = self.handlers[scheme](uri)
+ elif scheme in [u"http", u"https"] and requests:
+ # Requests has support for detecting the correct encoding of
+ # json over http
+ result = requests.get(uri).json()
+ else:
+ # Otherwise, pass off to urllib and assume utf-8
+ with urlopen(uri) as url:
+ result = json.loads(url.read().decode("utf-8"))
+
+ if self.cache_remote:
+ self.store[uri] = result
+ return result
+
+
+def validate(instance, schema, cls=None, *args, **kwargs):
+ """
+ Validate an instance under the given schema.
+
+ >>> validate([2, 3, 4], {"maxItems": 2})
+ Traceback (most recent call last):
+ ...
+ ValidationError: [2, 3, 4] is too long
+
+ :func:`validate` will first verify that the provided schema is
+ itself valid, since not doing so can lead to less obvious error
+ messages and fail in less obvious or consistent ways.
+
+ If you know you have a valid schema already, especially if you
+ intend to validate multiple instances with the same schema, you
+ likely would prefer using the `IValidator.validate` method directly
+ on a specific validator (e.g. ``Draft7Validator.validate``).
+
+
+ Arguments:
+
+ instance:
+
+ The instance to validate
+
+ schema:
+
+ The schema to validate with
+
+ cls (IValidator):
+
+ The class that will be used to validate the instance.
+
+ If the ``cls`` argument is not provided, two things will happen
+ in accordance with the specification. First, if the schema has a
+ :validator:`$schema` property containing a known meta-schema [#]_
+ then the proper validator will be used. The specification recommends
+ that all schemas contain :validator:`$schema` properties for this
+ reason. If no :validator:`$schema` property is found, the default
+ validator class is the latest released draft.
+
+ Any other provided positional and keyword arguments will be passed
+ on when instantiating the ``cls``.
+
+ Raises:
+
+ `jsonschema.exceptions.ValidationError` if the instance
+ is invalid
+
+ `jsonschema.exceptions.SchemaError` if the schema itself
+ is invalid
+
+ .. rubric:: Footnotes
+ .. [#] known by a validator registered with
+ `jsonschema.validators.validates`
+ """
+ if cls is None:
+ cls = validator_for(schema)
+
+ cls.check_schema(schema)
+ validator = cls(schema, *args, **kwargs)
+ error = exceptions.best_match(validator.iter_errors(instance))
+ if error is not None:
+ raise error
+
+
+def validator_for(schema, default=_LATEST_VERSION):
+ """
+ Retrieve the validator class appropriate for validating the given schema.
+
+ Uses the :validator:`$schema` property that should be present in the
+ given schema to look up the appropriate validator class.
+
+ Arguments:
+
+ schema (collections.abc.Mapping or bool):
+
+ the schema to look at
+
+ default:
+
+ the default to return if the appropriate validator class
+ cannot be determined.
+
+ If unprovided, the default is to return the latest supported
+ draft.
+ """
+ if schema is True or schema is False or u"$schema" not in schema:
+ return default
+ if schema[u"$schema"] not in meta_schemas:
+ warn(
+ (
+ "The metaschema specified by $schema was not found. "
+ "Using the latest draft to validate, but this will raise "
+ "an error in the future."
+ ),
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return meta_schemas.get(schema[u"$schema"], _LATEST_VERSION)
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..477d5e6
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,16 @@
+[build-system]
+requires = [
+ # The minimum setuptools version is specific to the PEP 517 backend,
+ # and may be stricter than the version required in `setup.py`
+ "setuptools>=40.6.0",
+ "setuptools_scm[toml]>=3.4",
+ "wheel",
+]
+build-backend = "setuptools.build_meta"
+
+[tool.isort]
+from_first = true
+include_trailing_comma = true
+multi_line_output = 3
+
+[tool.setuptools_scm]
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..6464c6e
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,74 @@
+[metadata]
+name = jsonschema
+url = https://github.com/Julian/jsonschema
+project_urls =
+ Docs = https://python-jsonschema.readthedocs.io/en/latest/
+description = An implementation of JSON Schema validation for Python
+long_description = file: README.rst
+long_description_content_type = text/x-rst
+author = Julian Berman
+author_email = Julian@GrayVines.com
+license = MIT
+classifiers =
+ Development Status :: 5 - Production/Stable
+ Intended Audience :: Developers
+ License :: OSI Approved :: MIT License
+ Operating System :: OS Independent
+ Programming Language :: Python
+ Programming Language :: Python :: 3.6
+ Programming Language :: Python :: 3.7
+ Programming Language :: Python :: 3.8
+ Programming Language :: Python :: Implementation :: CPython
+ Programming Language :: Python :: Implementation :: PyPy
+python_requires = ">=3.6"
+
+[options]
+packages = find:
+install_requires =
+ attrs>=17.4.0
+ functools32;python_version<'3'
+ importlib_metadata;python_version<'3.8'
+ pyrsistent>=0.14.0
+
+[options.extras_require]
+format =
+ fqdn
+ idna
+ jsonpointer>1.13
+ rfc3987
+ strict-rfc3339
+ webcolors
+format_nongpl =
+ fqdn
+ idna
+ jsonpointer>1.13
+ webcolors
+ rfc3986-validator>0.1.0
+ rfc3339-validator
+
+[options.entry_points]
+console_scripts =
+ jsonschema = jsonschema.cli:main
+
+[options.package_data]
+jsonschema = schemas/*.json
+
+[bdist_wheel]
+universal = 1
+
+[flake8]
+builtins = unicode
+exclude =
+ jsonschema/__init__.py
+ jsonschema/_reflect.py
+
+[pydocstyle]
+match = (?!(test_|_|compat|cli)).*\.py # see PyCQA/pydocstyle#323
+add-select =
+ D410, # Trailing whitespace plz
+add-ignore =
+ D107, # Hah, no
+ D200, # 1-line docstrings don't need to be on one line
+ D202, # One line is fine.
+ D412, # Trailing whitespace plz
+ D413, # No trailing whitespace plz
diff --git a/tox.ini b/tox.ini
index 72fd562..26a535f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,9 +1,116 @@
[tox]
-minversion = 1.6
-envlist = sanity
+envlist =
+ py{36,37,38,py3}-{noextra,format,format_nongpl}-{build,tests},
+ readme
+ safety
+ secrets
+ style
+ docs-{html,doctest,linkcheck,spelling,style}
skipsdist = True
-[testenv:sanity]
-# used just for validating the structure of the test case files themselves
-deps = jsonschema>=3.2.0
-commands = {envpython} bin/jsonschema_suite check
+[testenv]
+changedir = {envtmpdir}
+passenv = CODECOV* CI
+setenv =
+ JSON_SCHEMA_TEST_SUITE = {toxinidir}/json
+
+ coverage,codecov: MAYBE_COVERAGE = coverage run -m
+ coverage,codecov: COVERAGE_RCFILE={toxinidir}/.coveragerc
+ coverage,codecov: COVERAGE_DEBUG_FILE={envtmpdir}/coverage-debug
+ coverage,codecov: COVERAGE_FILE={envtmpdir}/coverage-data
+whitelist_externals =
+ mkdir
+commands =
+ noextra: {envpython} -m pip install {toxinidir}
+ format: {envpython} -m pip install '{toxinidir}[format]'
+ format_nongpl: {envpython} -m pip install '{toxinidir}[format_nongpl]'
+
+ tests,coverage,codecov: {envpython} -m {env:MAYBE_COVERAGE:} twisted.trial {posargs:jsonschema}
+ tests: {envpython} -m doctest {toxinidir}/README.rst
+
+ coverage: {envpython} -m coverage report --show-missing
+ coverage: {envpython} -m coverage html --directory={envtmpdir}/htmlcov
+ codecov: {envpython} -m coverage xml -o {envtmpdir}/coverage.xml
+ codecov: codecov --required --disable gcov --file {envtmpdir}/coverage.xml
+
+ perf: mkdir {envtmpdir}/benchmarks/
+ perf: {envpython} {toxinidir}/jsonschema/benchmarks/issue232.py --inherit-environ JSON_SCHEMA_TEST_SUITE --output {envtmpdir}/benchmarks/issue232.json
+ perf: {envpython} {toxinidir}/jsonschema/benchmarks/json_schema_test_suite.py --inherit-environ JSON_SCHEMA_TEST_SUITE --output {envtmpdir}/benchmarks/json_schema_test_suite.json
+
+ build: {envpython} -m pep517.check {toxinidir}
+deps =
+ build: pep517
+
+ perf: pyperf
+
+ tests,coverage,codecov: twisted
+
+ coverage,codecov: coverage
+ codecov: codecov
+
+[testenv:bandit]
+deps = bandit
+commands = {envbindir}/bandit --recursive {toxinidir}/jsonschema
+
+[testenv:readme]
+deps =
+ docutils
+ pep517
+ twine
+commands =
+ {envpython} -m pep517.build --out-dir {envtmpdir}/dist {toxinidir}
+ {envpython} -m twine check {envtmpdir}/dist/*
+ {envbindir}/rst2html5.py --halt=warning {toxinidir}/CHANGELOG.rst /dev/null
+
+[testenv:safety]
+deps = safety
+commands =
+ {envpython} -m pip install '{toxinidir}[format]'
+ {envpython} -m safety check
+
+[testenv:secrets]
+deps = detect-secrets
+commands = {envbindir}/detect-secrets scan {toxinidir}
+
+[testenv:style]
+basepython = pypy3
+deps =
+ ebb-lint>=0.19.1.0
+commands =
+ {envpython} -m flake8 {posargs} {toxinidir}/jsonschema {toxinidir}/docs
+
+[testenv:docs-html]
+basepython = pypy3
+commands = {envpython} -m sphinx -b html {toxinidir}/docs/ {envtmpdir}/build {posargs:-a -n -q -T -W}
+deps =
+ -r{toxinidir}/docs/requirements.txt
+ {toxinidir}
+
+[testenv:docs-doctest]
+basepython = pypy3
+commands = {envpython} -m sphinx -b doctest {toxinidir}/docs/ {envtmpdir}/build {posargs:-a -n -q -T -W}
+deps =
+ -r{toxinidir}/docs/requirements.txt
+ {toxinidir}
+
+[testenv:docs-linkcheck]
+basepython = pypy3
+commands = {envpython} -m sphinx -b linkcheck {toxinidir}/docs/ {envtmpdir}/build {posargs:-a -n -q -T -W}
+deps =
+ -r{toxinidir}/docs/requirements.txt
+ {toxinidir}
+
+[testenv:docs-spelling]
+basepython = pypy3
+commands = {envpython} -m sphinx -b spelling {toxinidir}/docs/ {envtmpdir}/build {posargs:-a -n -T -W}
+deps =
+ -r{toxinidir}/docs/requirements.txt
+ {toxinidir}
+
+[testenv:docs-style]
+basepython = pypy3
+commands = doc8 {posargs} {toxinidir}/docs
+deps =
+ doc8
+ pygments
+ pygments-github-lexers