diff options
author | wilson chen <willson.chenwx@gmail.com> | 2020-08-29 10:13:32 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-08-29 10:13:32 +0800 |
commit | ba914b00e2c150fc722b86e0b53cf0aa82652de7 (patch) | |
tree | 743f3c3b3c0a777acfab5775f6923c6931e57294 | |
parent | 03868b3316ba2f17fbd091df91bcb06bd89a8552 (diff) | |
parent | 5fc5b1450a0cdb903ce226105b18169a954dec55 (diff) | |
download | jsonschema-ba914b00e2c150fc722b86e0b53cf0aa82652de7.tar.gz |
Merge branch 'master' into fix_issue669
57 files changed, 900 insertions, 361 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e0cc77f..d89d6fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,13 +18,17 @@ jobs: os: [macos-latest, ubuntu-latest, windows-latest] python-version: - name: pypy3 - toxenv: pypy3-build + toxenv: pypy3-noextra-build - name: pypy3 - toxenv: pypy3-tests + toxenv: pypy3-noextra-tests - name: pypy3 - toxenv: pypy3-tests_nongpl + toxenv: pypy3-format-build - name: pypy3 - toxenv: demo + toxenv: pypy3-format-tests + - name: pypy3 + toxenv: pypy3-format_nongpl-build + - name: pypy3 + toxenv: pypy3-format_nongpl-tests - name: pypy3 toxenv: readme - name: pypy3 @@ -44,36 +48,66 @@ jobs: - name: pypy3 toxenv: docs-style - name: 3.6 - toxenv: py36-build + 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-tests + toxenv: py36-format_nongpl-build - name: 3.6 - toxenv: py36-tests_nongpl + toxenv: py36-format_nongpl-tests - name: 3.7 - toxenv: py37-build + toxenv: py37-noextra-build - name: 3.7 - toxenv: py37-tests + toxenv: py37-noextra-tests - name: 3.7 - toxenv: py37-tests_nongpl + 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-build + toxenv: py38-format-build - name: 3.8 - toxenv: py38-tests + toxenv: py38-format-tests - name: 3.8 - toxenv: py38-tests_nongpl + toxenv: py38-format_nongpl-build + - name: 3.8 + toxenv: py38-format_nongpl-tests exclude: - os: windows-latest python-version: name: pypy3 - toxenv: pypy3-build + 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-tests + toxenv: pypy3-format-tests - os: windows-latest python-version: name: pypy3 - toxenv: pypy3-tests_nongpl + toxenv: pypy3-format_nongpl-tests - os: windows-latest python-version: name: pypy3 @@ -89,27 +123,51 @@ jobs: - os: windows-latest python-version: name: 3.6 - toxenv: py36-tests + toxenv: py36-noextra-build - os: windows-latest python-version: name: 3.6 - toxenv: py36-tests_nongpl + toxenv: py36-format-build - os: windows-latest python-version: name: 3.6 - toxenv: py36-build + 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-build + toxenv: py37-format_nongpl-tests - os: windows-latest python-version: name: 3.8 - toxenv: py38-build + toxenv: py38-noextra-tests - os: windows-latest python-version: - name: pypy3 - toxenv: demo + 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 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..a1dc799 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,27 @@ +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.3.2 + hooks: + - id: isort +- repo: https://github.com/myint/docformatter + rev: v1.3.1 + hooks: + - id: docformatter + args: + - --in-place + - --pre-summary-newline + - --make-summary-multi-line diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8f0a270..7c1a67e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -43,7 +43,7 @@ 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) +* Fixed an issue with ``ErrorTree``'s handling of multiple errors (#288) v2.5.0 ------ @@ -175,7 +175,7 @@ v0.4 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 + * ``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. diff --git a/DEMO.ipynb b/DEMO.ipynb deleted file mode 100644 index f008b79..0000000 --- a/DEMO.ipynb +++ /dev/null @@ -1,167 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# jsonschema\n", - "---" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`jsonschema` is an implementation of [JSON Schema](https://json-schema.org) for Python (supporting 2.7+ including Python 3)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Usage" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from jsonschema import validate" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# A sample schema, like what we'd get from json.load()\n", - "schema = {\n", - " \"type\" : \"object\",\n", - " \"properties\" : {\n", - " \"price\" : {\"type\" : \"number\"},\n", - " \"name\" : {\"type\" : \"string\"},\n", - " },\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# If no exception is raised by validate(), the instance is valid.\n", - "validate(instance={\"name\" : \"Eggs\", \"price\" : 34.99}, schema=schema)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "ename": "ValidationError", - "evalue": "'Invalid' is not of type 'number'\n\nFailed validating 'type' in schema['properties']['price']:\n {'type': 'number'}\n\nOn instance['price']:\n 'Invalid'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mValidationError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m<ipython-input-5-e1e543273d1f>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m validate(\n\u001b[1;32m 2\u001b[0m \u001b[0minstance\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m{\u001b[0m\u001b[0;34m\"name\"\u001b[0m \u001b[0;34m:\u001b[0m \u001b[0;34m\"Eggs\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"price\"\u001b[0m \u001b[0;34m:\u001b[0m \u001b[0;34m\"Invalid\"\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mschema\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mschema\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4\u001b[0m )\n", - "\u001b[0;32m~/Development/jsonschema/jsonschema/validators.py\u001b[0m in \u001b[0;36mvalidate\u001b[0;34m(instance, schema, cls, *args, **kwargs)\u001b[0m\n\u001b[1;32m 899\u001b[0m \u001b[0merror\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mexceptions\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbest_match\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mvalidator\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miter_errors\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minstance\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 900\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0merror\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 901\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0merror\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 902\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 903\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mValidationError\u001b[0m: 'Invalid' is not of type 'number'\n\nFailed validating 'type' in schema['properties']['price']:\n {'type': 'number'}\n\nOn instance['price']:\n 'Invalid'" - ] - } - ], - "source": [ - "validate(\n", - " instance={\"name\" : \"Eggs\", \"price\" : \"Invalid\"},\n", - " schema=schema,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It can also be used from console:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!echo '{\"name\" : \"Eggs\", \"price\" : 34.99}' > /tmp/sample.json" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!echo '{\"type\" : \"object\", \"properties\" : {\"price\" : {\"type\" : \"number\"}, \"name\" : {\"type\" : \"string\"}}}' > /tmp/sample.schema" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!jsonschema -i /tmp/sample.json /tmp/sample.schema" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Do your own experiments here..." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Try `jsonschema` youself by adding your code below and running your own experiments 👇" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import jsonschema\n", - "\n", - "# your code here\n", - "jsonschema." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.4" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} @@ -80,24 +80,6 @@ Installation $ pip install jsonschema -Demo ----- - -Try ``jsonschema`` interactively in this online demo: - -.. image:: https://user-images.githubusercontent.com/1155573/56745335-8b158a00-6750-11e9-8776-83fa675939c4.png - :target: https://notebooks.ai/demo/gh/Julian/jsonschema - :alt: Open Live Demo - - -Online demo Notebook will look similar to this: - - -.. image:: https://user-images.githubusercontent.com/1155573/56820861-5c1c1880-6823-11e9-802a-ce01c5ec574f.gif - :alt: Open Live Demo - :width: 480 px - - Release Notes ------------- @@ -125,7 +107,7 @@ Benchmarks ---------- ``jsonschema``'s benchmarks make use of `pyperf -<https://pyperf.readthedocs.io>`_. Running them can be done via:: +<https://pyperf.readthedocs.io>`_. Running them can be done via:: $ tox -e perf diff --git a/demo.yml b/demo.yml deleted file mode 100644 index a2d7e46..0000000 --- a/demo.yml +++ /dev/null @@ -1,2 +0,0 @@ -requirements: - - jsonschema==3.0.1 diff --git a/docs/conf.py b/docs/conf.py index 565b00a..fbf6f30 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,7 +9,6 @@ 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. diff --git a/docs/jsonschema_role.py b/docs/jsonschema_role.py index a63ff72..8a4feaa 100644 --- a/docs/jsonschema_role.py +++ b/docs/jsonschema_role.py @@ -1,17 +1,13 @@ from datetime import datetime -from docutils import nodes import errno import os +import urllib.request -try: - import urllib2 as urllib -except ImportError: - import urllib.request as urllib - -import certifi -import jsonschema +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" @@ -68,8 +64,8 @@ def fetch_or_load(spec_path): if error.errno != errno.ENOENT: raise - request = urllib.Request(VALIDATION_SPEC, headers=headers) - response = urllib.urlopen(request, cafile=certifi.where()) + 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: diff --git a/docs/validate.rst b/docs/validate.rst index 3489f20..dc46017 100644 --- a/docs/validate.rst +++ b/docs/validate.rst @@ -277,15 +277,15 @@ validation can be enabled by hooking in a format-checking object into an .. doctest:: - >>> validate("localhost", {"format" : "hostname"}) + >>> validate("127.0.0.1", {"format" : "ipv4"}) >>> validate( ... instance="-12", - ... schema={"format" : "hostname"}, + ... schema={"format" : "ipv4"}, ... format_checker=draft7_format_checker, ... ) Traceback (most recent call last): ... - ValidationError: "-12" is not a "hostname" + ValidationError: "-12" is not a "ipv4" .. autoclass:: FormatChecker :members: diff --git a/json/bin/jsonschema_suite b/json/bin/jsonschema_suite index 9ccd8e8..628bc5d 100755 --- a/json/bin/jsonschema_suite +++ b/json/bin/jsonschema_suite @@ -50,7 +50,9 @@ REMOTES = { u"refToInteger": {u"$ref": u"#/$defs/integer"}, } }, - "folder/folderInteger.json": {u"type": u"integer"} + "baseUriChange/folderInteger.json": {u"type": u"integer"}, + "baseUriChangeFolder/folderInteger.json": {u"type": u"integer"}, + "baseUriChangeFolderInSubschema/folderInteger.json": {u"type": u"integer"}, } REMOTES_DIR = os.path.join(ROOT_DIR, "remotes") diff --git a/json/remotes/folder/folderInteger.json b/json/remotes/baseUriChange/folderInteger.json index 8b50ea3..8b50ea3 100644 --- a/json/remotes/folder/folderInteger.json +++ b/json/remotes/baseUriChange/folderInteger.json diff --git a/json/remotes/baseUriChangeFolder/folderInteger.json b/json/remotes/baseUriChangeFolder/folderInteger.json new file mode 100644 index 0000000..8b50ea3 --- /dev/null +++ b/json/remotes/baseUriChangeFolder/folderInteger.json @@ -0,0 +1,3 @@ +{ + "type": "integer" +} diff --git a/json/remotes/baseUriChangeFolderInSubschema/folderInteger.json b/json/remotes/baseUriChangeFolderInSubschema/folderInteger.json new file mode 100644 index 0000000..8b50ea3 --- /dev/null +++ b/json/remotes/baseUriChangeFolderInSubschema/folderInteger.json @@ -0,0 +1,3 @@ +{ + "type": "integer" +} diff --git a/json/tests/draft2019-09/const.json b/json/tests/draft2019-09/const.json index c53d04d..1c2cafc 100644 --- a/json/tests/draft2019-09/const.json +++ b/json/tests/draft2019-09/const.json @@ -126,6 +126,90 @@ ] }, { + "description": "const with [false] does not match [0]", + "schema": {"const": [false]}, + "tests": [ + { + "description": "[false] is valid", + "data": [false], + "valid": true + }, + { + "description": "[0] is invalid", + "data": [0], + "valid": false + }, + { + "description": "[0.0] is invalid", + "data": [0.0], + "valid": false + } + ] + }, + { + "description": "const with [true] does not match [1]", + "schema": {"const": [true]}, + "tests": [ + { + "description": "[true] is valid", + "data": [true], + "valid": true + }, + { + "description": "[1] is invalid", + "data": [1], + "valid": false + }, + { + "description": "[1.0] is invalid", + "data": [1.0], + "valid": false + } + ] + }, + { + "description": "const with {\"a\": false} does not match {\"a\": 0}", + "schema": {"const": {"a": false}}, + "tests": [ + { + "description": "{\"a\": false} is valid", + "data": {"a": false}, + "valid": true + }, + { + "description": "{\"a\": 0} is invalid", + "data": {"a": 0}, + "valid": false + }, + { + "description": "{\"a\": 0.0} is invalid", + "data": {"a": 0.0}, + "valid": false + } + ] + }, + { + "description": "const with {\"a\": true} does not match {\"a\": 1}", + "schema": {"const": {"a": true}}, + "tests": [ + { + "description": "{\"a\": true} is valid", + "data": {"a": true}, + "valid": true + }, + { + "description": "{\"a\": 1} is invalid", + "data": {"a": 1}, + "valid": false + }, + { + "description": "{\"a\": 1.0} is invalid", + "data": {"a": 1.0}, + "valid": false + } + ] + }, + { "description": "const with 0 does not match other zero-like types", "schema": {"const": 0}, "tests": [ diff --git a/json/tests/draft2019-09/optional/format/date-time.json b/json/tests/draft2019-09/optional/format/date-time.json index dfccee6..900fcb7 100644 --- a/json/tests/draft2019-09/optional/format/date-time.json +++ b/json/tests/draft2019-09/optional/format/date-time.json @@ -47,6 +47,16 @@ "description": "only RFC3339 not all of ISO 8601 are valid", "data": "2013-350T01:01:01", "valid": false + }, + { + "description": "invalid non-padded month dates", + "data": "1963-6-19T08:30:06.283185Z", + "valid": false + }, + { + "description": "invalid non-padded day dates", + "data": "1963-06-1T08:30:06.283185Z", + "valid": false } ] } diff --git a/json/tests/draft2019-09/optional/format/date.json b/json/tests/draft2019-09/optional/format/date.json index cd23baa..453b51d 100644 --- a/json/tests/draft2019-09/optional/format/date.json +++ b/json/tests/draft2019-09/optional/format/date.json @@ -17,6 +17,16 @@ "description": "only RFC3339 not all of ISO 8601 are valid", "data": "2013-350", "valid": false + }, + { + "description": "invalidates non-padded month dates", + "data": "1998-1-20", + "valid": false + }, + { + "description": "invalidates non-padded day dates", + "data": "1998-01-1", + "valid": false } ] } diff --git a/json/tests/draft2019-09/optional/format/ipv6.json b/json/tests/draft2019-09/optional/format/ipv6.json index 9b0881e..2a08cb4 100644 --- a/json/tests/draft2019-09/optional/format/ipv6.json +++ b/json/tests/draft2019-09/optional/format/ipv6.json @@ -112,6 +112,41 @@ "description": "ipv4 segment must have 4 octets", "data": "1:2:3:4:1.2.3", "valid": false + }, + { + "description": "leading whitespace is invalid", + "data": " ::1", + "valid": false + }, + { + "description": "trailing whitespace is invalid", + "data": "::1 ", + "valid": false + }, + { + "description": "netmask is not a part of ipv6 address", + "data": "fe80::/64", + "valid": false + }, + { + "description": "zone id is not a part of ipv6 address", + "data": "fe80::a%eth1", + "valid": false + }, + { + "description": "a long valid ipv6", + "data": "1000:1000:1000:1000:1000:1000:255.255.255.255", + "valid": true + }, + { + "description": "a long invalid ipv6, below length limit, first", + "data": "100:100:100:100:100:100:255.255.255.255.255", + "valid": false + }, + { + "description": "a long invalid ipv6, below length limit, second", + "data": "100:100:100:100:100:100:100:255.255.255.255", + "valid": false } ] } diff --git a/json/tests/draft2019-09/optional/format/uri.json b/json/tests/draft2019-09/optional/format/uri.json index 25cc40c..4306a68 100644 --- a/json/tests/draft2019-09/optional/format/uri.json +++ b/json/tests/draft2019-09/optional/format/uri.json @@ -97,6 +97,11 @@ "description": "an invalid URI with spaces and missing scheme", "data": ":// should fail", "valid": false + }, + { + "description": "an invalid URI with comma in scheme", + "data": "bar,baz:foo", + "valid": false } ] } diff --git a/json/tests/draft2019-09/refRemote.json b/json/tests/draft2019-09/refRemote.json index 515263d..b9c6a28 100644 --- a/json/tests/draft2019-09/refRemote.json +++ b/json/tests/draft2019-09/refRemote.json @@ -54,7 +54,7 @@ "schema": { "$id": "http://localhost:1234/", "items": { - "$id": "folder/", + "$id": "baseUriChange/", "items": {"$ref": "folderInteger.json"} } }, @@ -76,10 +76,10 @@ "schema": { "$id": "http://localhost:1234/scope_change_defs1.json", "type" : "object", - "properties": {"list": {"$ref": "folder/"}}, + "properties": {"list": {"$ref": "baseUriChangeFolder/"}}, "$defs": { "baz": { - "$id": "folder/", + "$id": "baseUriChangeFolder/", "type": "array", "items": {"$ref": "folderInteger.json"} } @@ -103,10 +103,10 @@ "schema": { "$id": "http://localhost:1234/scope_change_defs2.json", "type" : "object", - "properties": {"list": {"$ref": "folder/#/$defs/bar"}}, + "properties": {"list": {"$ref": "baseUriChangeFolderInSubschema/#/$defs/bar"}}, "$defs": { "baz": { - "$id": "folder/", + "$id": "baseUriChangeFolderInSubschema/", "$defs": { "bar": { "type": "array", diff --git a/json/tests/draft2019-09/uniqueItems.json b/json/tests/draft2019-09/uniqueItems.json index fb1ddb5..4846c77 100644 --- a/json/tests/draft2019-09/uniqueItems.json +++ b/json/tests/draft2019-09/uniqueItems.json @@ -113,6 +113,16 @@ "description": "objects are non-unique despite key order", "data": [{"a": 1, "b": 2}, {"b": 2, "a": 1}], "valid": false + }, + { + "description": "{\"a\": false} and {\"a\": 0} are unique", + "data": [{"a": false}, {"a": 0}], + "valid": true + }, + { + "description": "{\"a\": true} and {\"a\": 1} are unique", + "data": [{"a": true}, {"a": 1}], + "valid": true } ] }, diff --git a/json/tests/draft3/optional/format/date-time.json b/json/tests/draft3/optional/format/date-time.json index 90279ba..58261fa 100644 --- a/json/tests/draft3/optional/format/date-time.json +++ b/json/tests/draft3/optional/format/date-time.json @@ -22,6 +22,16 @@ "description": "only RFC3339 not all of ISO 8601 are valid", "data": "2013-350T01:01:01", "valid": false + }, + { + "description": "invalid non-padded month dates", + "data": "1963-6-19T08:30:06.283185Z", + "valid": false + }, + { + "description": "invalid non-padded day dates", + "data": "1963-06-1T08:30:06.283185Z", + "valid": false } ] } diff --git a/json/tests/draft3/optional/format/date.json b/json/tests/draft3/optional/format/date.json index bd83042..4842b48 100644 --- a/json/tests/draft3/optional/format/date.json +++ b/json/tests/draft3/optional/format/date.json @@ -12,6 +12,21 @@ "description": "an invalid date string", "data": "06/19/1963", "valid": false + }, + { + "description": "only RFC3339 not all of ISO 8601 are valid", + "data": "2013-350", + "valid": false + }, + { + "description": "invalidates non-padded month dates", + "data": "1998-1-20", + "valid": false + }, + { + "description": "invalidates non-padded day dates", + "data": "1998-01-1", + "valid": false } ] } diff --git a/json/tests/draft3/refRemote.json b/json/tests/draft3/refRemote.json index 4ca8047..de0cb43 100644 --- a/json/tests/draft3/refRemote.json +++ b/json/tests/draft3/refRemote.json @@ -54,7 +54,7 @@ "schema": { "id": "http://localhost:1234/", "items": { - "id": "folder/", + "id": "baseUriChange/", "items": {"$ref": "folderInteger.json"} } }, diff --git a/json/tests/draft3/uniqueItems.json b/json/tests/draft3/uniqueItems.json index 8f8dbdd..fd4b849 100644 --- a/json/tests/draft3/uniqueItems.json +++ b/json/tests/draft3/uniqueItems.json @@ -93,6 +93,16 @@ "description": "non-unique heterogeneous types are invalid", "data": [{}, [1], true, null, {}, 1], "valid": false + }, + { + "description": "{\"a\": false} and {\"a\": 0} are unique", + "data": [{"a": false}, {"a": 0}], + "valid": true + }, + { + "description": "{\"a\": true} and {\"a\": 1} are unique", + "data": [{"a": true}, {"a": 1}], + "valid": true } ] }, diff --git a/json/tests/draft4/optional/format/date-time.json b/json/tests/draft4/optional/format/date-time.json index dfccee6..900fcb7 100644 --- a/json/tests/draft4/optional/format/date-time.json +++ b/json/tests/draft4/optional/format/date-time.json @@ -47,6 +47,16 @@ "description": "only RFC3339 not all of ISO 8601 are valid", "data": "2013-350T01:01:01", "valid": false + }, + { + "description": "invalid non-padded month dates", + "data": "1963-6-19T08:30:06.283185Z", + "valid": false + }, + { + "description": "invalid non-padded day dates", + "data": "1963-06-1T08:30:06.283185Z", + "valid": false } ] } diff --git a/json/tests/draft4/optional/format/ipv6.json b/json/tests/draft4/optional/format/ipv6.json index 9b0881e..2a08cb4 100644 --- a/json/tests/draft4/optional/format/ipv6.json +++ b/json/tests/draft4/optional/format/ipv6.json @@ -112,6 +112,41 @@ "description": "ipv4 segment must have 4 octets", "data": "1:2:3:4:1.2.3", "valid": false + }, + { + "description": "leading whitespace is invalid", + "data": " ::1", + "valid": false + }, + { + "description": "trailing whitespace is invalid", + "data": "::1 ", + "valid": false + }, + { + "description": "netmask is not a part of ipv6 address", + "data": "fe80::/64", + "valid": false + }, + { + "description": "zone id is not a part of ipv6 address", + "data": "fe80::a%eth1", + "valid": false + }, + { + "description": "a long valid ipv6", + "data": "1000:1000:1000:1000:1000:1000:255.255.255.255", + "valid": true + }, + { + "description": "a long invalid ipv6, below length limit, first", + "data": "100:100:100:100:100:100:255.255.255.255.255", + "valid": false + }, + { + "description": "a long invalid ipv6, below length limit, second", + "data": "100:100:100:100:100:100:100:255.255.255.255", + "valid": false } ] } diff --git a/json/tests/draft4/optional/format/uri.json b/json/tests/draft4/optional/format/uri.json index 25cc40c..4306a68 100644 --- a/json/tests/draft4/optional/format/uri.json +++ b/json/tests/draft4/optional/format/uri.json @@ -97,6 +97,11 @@ "description": "an invalid URI with spaces and missing scheme", "data": ":// should fail", "valid": false + }, + { + "description": "an invalid URI with comma in scheme", + "data": "bar,baz:foo", + "valid": false } ] } diff --git a/json/tests/draft4/refRemote.json b/json/tests/draft4/refRemote.json index 8611fad..ce5e99a 100644 --- a/json/tests/draft4/refRemote.json +++ b/json/tests/draft4/refRemote.json @@ -54,7 +54,7 @@ "schema": { "id": "http://localhost:1234/", "items": { - "id": "folder/", + "id": "baseUriChange/", "items": {"$ref": "folderInteger.json"} } }, @@ -81,7 +81,7 @@ }, "definitions": { "baz": { - "id": "folder/", + "id": "baseUriChangeFolder/", "type": "array", "items": {"$ref": "folderInteger.json"} } @@ -110,7 +110,7 @@ }, "definitions": { "baz": { - "id": "folder/", + "id": "baseUriChangeFolderInSubschema/", "definitions": { "bar": { "type": "array", diff --git a/json/tests/draft4/uniqueItems.json b/json/tests/draft4/uniqueItems.json index fb1ddb5..4846c77 100644 --- a/json/tests/draft4/uniqueItems.json +++ b/json/tests/draft4/uniqueItems.json @@ -113,6 +113,16 @@ "description": "objects are non-unique despite key order", "data": [{"a": 1, "b": 2}, {"b": 2, "a": 1}], "valid": false + }, + { + "description": "{\"a\": false} and {\"a\": 0} are unique", + "data": [{"a": false}, {"a": 0}], + "valid": true + }, + { + "description": "{\"a\": true} and {\"a\": 1} are unique", + "data": [{"a": true}, {"a": 1}], + "valid": true } ] }, diff --git a/json/tests/draft6/const.json b/json/tests/draft6/const.json index c53d04d..1c2cafc 100644 --- a/json/tests/draft6/const.json +++ b/json/tests/draft6/const.json @@ -126,6 +126,90 @@ ] }, { + "description": "const with [false] does not match [0]", + "schema": {"const": [false]}, + "tests": [ + { + "description": "[false] is valid", + "data": [false], + "valid": true + }, + { + "description": "[0] is invalid", + "data": [0], + "valid": false + }, + { + "description": "[0.0] is invalid", + "data": [0.0], + "valid": false + } + ] + }, + { + "description": "const with [true] does not match [1]", + "schema": {"const": [true]}, + "tests": [ + { + "description": "[true] is valid", + "data": [true], + "valid": true + }, + { + "description": "[1] is invalid", + "data": [1], + "valid": false + }, + { + "description": "[1.0] is invalid", + "data": [1.0], + "valid": false + } + ] + }, + { + "description": "const with {\"a\": false} does not match {\"a\": 0}", + "schema": {"const": {"a": false}}, + "tests": [ + { + "description": "{\"a\": false} is valid", + "data": {"a": false}, + "valid": true + }, + { + "description": "{\"a\": 0} is invalid", + "data": {"a": 0}, + "valid": false + }, + { + "description": "{\"a\": 0.0} is invalid", + "data": {"a": 0.0}, + "valid": false + } + ] + }, + { + "description": "const with {\"a\": true} does not match {\"a\": 1}", + "schema": {"const": {"a": true}}, + "tests": [ + { + "description": "{\"a\": true} is valid", + "data": {"a": true}, + "valid": true + }, + { + "description": "{\"a\": 1} is invalid", + "data": {"a": 1}, + "valid": false + }, + { + "description": "{\"a\": 1.0} is invalid", + "data": {"a": 1.0}, + "valid": false + } + ] + }, + { "description": "const with 0 does not match other zero-like types", "schema": {"const": 0}, "tests": [ diff --git a/json/tests/draft6/optional/format/date-time.json b/json/tests/draft6/optional/format/date-time.json index fc949e9..a6320a9 100644 --- a/json/tests/draft6/optional/format/date-time.json +++ b/json/tests/draft6/optional/format/date-time.json @@ -37,7 +37,7 @@ "description": "an invalid closing Z after time-zone offset", "data": "1963-06-19T08:30:06.28123+01:00Z", "valid": false - }, + }, { "description": "an invalid date-time string", "data": "06/19/1963 08:30:06 PST", @@ -52,6 +52,16 @@ "description": "only RFC3339 not all of ISO 8601 are valid", "data": "2013-350T01:01:01", "valid": false + }, + { + "description": "invalid non-padded month dates", + "data": "1963-6-19T08:30:06.283185Z", + "valid": false + }, + { + "description": "invalid non-padded day dates", + "data": "1963-06-1T08:30:06.283185Z", + "valid": false } ] } diff --git a/json/tests/draft6/optional/format/ipv6.json b/json/tests/draft6/optional/format/ipv6.json index 9b0881e..2a08cb4 100644 --- a/json/tests/draft6/optional/format/ipv6.json +++ b/json/tests/draft6/optional/format/ipv6.json @@ -112,6 +112,41 @@ "description": "ipv4 segment must have 4 octets", "data": "1:2:3:4:1.2.3", "valid": false + }, + { + "description": "leading whitespace is invalid", + "data": " ::1", + "valid": false + }, + { + "description": "trailing whitespace is invalid", + "data": "::1 ", + "valid": false + }, + { + "description": "netmask is not a part of ipv6 address", + "data": "fe80::/64", + "valid": false + }, + { + "description": "zone id is not a part of ipv6 address", + "data": "fe80::a%eth1", + "valid": false + }, + { + "description": "a long valid ipv6", + "data": "1000:1000:1000:1000:1000:1000:255.255.255.255", + "valid": true + }, + { + "description": "a long invalid ipv6, below length limit, first", + "data": "100:100:100:100:100:100:255.255.255.255.255", + "valid": false + }, + { + "description": "a long invalid ipv6, below length limit, second", + "data": "100:100:100:100:100:100:100:255.255.255.255", + "valid": false } ] } diff --git a/json/tests/draft6/optional/format/uri.json b/json/tests/draft6/optional/format/uri.json index 25cc40c..4306a68 100644 --- a/json/tests/draft6/optional/format/uri.json +++ b/json/tests/draft6/optional/format/uri.json @@ -97,6 +97,11 @@ "description": "an invalid URI with spaces and missing scheme", "data": ":// should fail", "valid": false + }, + { + "description": "an invalid URI with comma in scheme", + "data": "bar,baz:foo", + "valid": false } ] } diff --git a/json/tests/draft6/refRemote.json b/json/tests/draft6/refRemote.json index 819d326..74a7862 100644 --- a/json/tests/draft6/refRemote.json +++ b/json/tests/draft6/refRemote.json @@ -54,7 +54,7 @@ "schema": { "$id": "http://localhost:1234/", "items": { - "$id": "folder/", + "$id": "baseUriChange/", "items": {"$ref": "folderInteger.json"} } }, @@ -81,7 +81,7 @@ }, "definitions": { "baz": { - "$id": "folder/", + "$id": "baseUriChangeFolder/", "type": "array", "items": {"$ref": "folderInteger.json"} } @@ -110,7 +110,7 @@ }, "definitions": { "baz": { - "$id": "folder/", + "$id": "baseUriChangeFolderInSubschema/", "definitions": { "bar": { "type": "array", diff --git a/json/tests/draft6/uniqueItems.json b/json/tests/draft6/uniqueItems.json index fb1ddb5..4846c77 100644 --- a/json/tests/draft6/uniqueItems.json +++ b/json/tests/draft6/uniqueItems.json @@ -113,6 +113,16 @@ "description": "objects are non-unique despite key order", "data": [{"a": 1, "b": 2}, {"b": 2, "a": 1}], "valid": false + }, + { + "description": "{\"a\": false} and {\"a\": 0} are unique", + "data": [{"a": false}, {"a": 0}], + "valid": true + }, + { + "description": "{\"a\": true} and {\"a\": 1} are unique", + "data": [{"a": true}, {"a": 1}], + "valid": true } ] }, diff --git a/json/tests/draft7/const.json b/json/tests/draft7/const.json index c53d04d..1c2cafc 100644 --- a/json/tests/draft7/const.json +++ b/json/tests/draft7/const.json @@ -126,6 +126,90 @@ ] }, { + "description": "const with [false] does not match [0]", + "schema": {"const": [false]}, + "tests": [ + { + "description": "[false] is valid", + "data": [false], + "valid": true + }, + { + "description": "[0] is invalid", + "data": [0], + "valid": false + }, + { + "description": "[0.0] is invalid", + "data": [0.0], + "valid": false + } + ] + }, + { + "description": "const with [true] does not match [1]", + "schema": {"const": [true]}, + "tests": [ + { + "description": "[true] is valid", + "data": [true], + "valid": true + }, + { + "description": "[1] is invalid", + "data": [1], + "valid": false + }, + { + "description": "[1.0] is invalid", + "data": [1.0], + "valid": false + } + ] + }, + { + "description": "const with {\"a\": false} does not match {\"a\": 0}", + "schema": {"const": {"a": false}}, + "tests": [ + { + "description": "{\"a\": false} is valid", + "data": {"a": false}, + "valid": true + }, + { + "description": "{\"a\": 0} is invalid", + "data": {"a": 0}, + "valid": false + }, + { + "description": "{\"a\": 0.0} is invalid", + "data": {"a": 0.0}, + "valid": false + } + ] + }, + { + "description": "const with {\"a\": true} does not match {\"a\": 1}", + "schema": {"const": {"a": true}}, + "tests": [ + { + "description": "{\"a\": true} is valid", + "data": {"a": true}, + "valid": true + }, + { + "description": "{\"a\": 1} is invalid", + "data": {"a": 1}, + "valid": false + }, + { + "description": "{\"a\": 1.0} is invalid", + "data": {"a": 1.0}, + "valid": false + } + ] + }, + { "description": "const with 0 does not match other zero-like types", "schema": {"const": 0}, "tests": [ diff --git a/json/tests/draft7/optional/format/date-time.json b/json/tests/draft7/optional/format/date-time.json index dfccee6..900fcb7 100644 --- a/json/tests/draft7/optional/format/date-time.json +++ b/json/tests/draft7/optional/format/date-time.json @@ -47,6 +47,16 @@ "description": "only RFC3339 not all of ISO 8601 are valid", "data": "2013-350T01:01:01", "valid": false + }, + { + "description": "invalid non-padded month dates", + "data": "1963-6-19T08:30:06.283185Z", + "valid": false + }, + { + "description": "invalid non-padded day dates", + "data": "1963-06-1T08:30:06.283185Z", + "valid": false } ] } diff --git a/json/tests/draft7/optional/format/date.json b/json/tests/draft7/optional/format/date.json index cd23baa..453b51d 100644 --- a/json/tests/draft7/optional/format/date.json +++ b/json/tests/draft7/optional/format/date.json @@ -17,6 +17,16 @@ "description": "only RFC3339 not all of ISO 8601 are valid", "data": "2013-350", "valid": false + }, + { + "description": "invalidates non-padded month dates", + "data": "1998-1-20", + "valid": false + }, + { + "description": "invalidates non-padded day dates", + "data": "1998-01-1", + "valid": false } ] } diff --git a/json/tests/draft7/optional/format/ipv6.json b/json/tests/draft7/optional/format/ipv6.json index 9b0881e..2a08cb4 100644 --- a/json/tests/draft7/optional/format/ipv6.json +++ b/json/tests/draft7/optional/format/ipv6.json @@ -112,6 +112,41 @@ "description": "ipv4 segment must have 4 octets", "data": "1:2:3:4:1.2.3", "valid": false + }, + { + "description": "leading whitespace is invalid", + "data": " ::1", + "valid": false + }, + { + "description": "trailing whitespace is invalid", + "data": "::1 ", + "valid": false + }, + { + "description": "netmask is not a part of ipv6 address", + "data": "fe80::/64", + "valid": false + }, + { + "description": "zone id is not a part of ipv6 address", + "data": "fe80::a%eth1", + "valid": false + }, + { + "description": "a long valid ipv6", + "data": "1000:1000:1000:1000:1000:1000:255.255.255.255", + "valid": true + }, + { + "description": "a long invalid ipv6, below length limit, first", + "data": "100:100:100:100:100:100:255.255.255.255.255", + "valid": false + }, + { + "description": "a long invalid ipv6, below length limit, second", + "data": "100:100:100:100:100:100:100:255.255.255.255", + "valid": false } ] } diff --git a/json/tests/draft7/optional/format/uri.json b/json/tests/draft7/optional/format/uri.json index 25cc40c..4306a68 100644 --- a/json/tests/draft7/optional/format/uri.json +++ b/json/tests/draft7/optional/format/uri.json @@ -97,6 +97,11 @@ "description": "an invalid URI with spaces and missing scheme", "data": ":// should fail", "valid": false + }, + { + "description": "an invalid URI with comma in scheme", + "data": "bar,baz:foo", + "valid": false } ] } diff --git a/json/tests/draft7/refRemote.json b/json/tests/draft7/refRemote.json index 819d326..74a7862 100644 --- a/json/tests/draft7/refRemote.json +++ b/json/tests/draft7/refRemote.json @@ -54,7 +54,7 @@ "schema": { "$id": "http://localhost:1234/", "items": { - "$id": "folder/", + "$id": "baseUriChange/", "items": {"$ref": "folderInteger.json"} } }, @@ -81,7 +81,7 @@ }, "definitions": { "baz": { - "$id": "folder/", + "$id": "baseUriChangeFolder/", "type": "array", "items": {"$ref": "folderInteger.json"} } @@ -110,7 +110,7 @@ }, "definitions": { "baz": { - "$id": "folder/", + "$id": "baseUriChangeFolderInSubschema/", "definitions": { "bar": { "type": "array", diff --git a/json/tests/draft7/uniqueItems.json b/json/tests/draft7/uniqueItems.json index fb1ddb5..4846c77 100644 --- a/json/tests/draft7/uniqueItems.json +++ b/json/tests/draft7/uniqueItems.json @@ -113,6 +113,16 @@ "description": "objects are non-unique despite key order", "data": [{"a": 1, "b": 2}, {"b": 2, "a": 1}], "valid": false + }, + { + "description": "{\"a\": false} and {\"a\": 0} are unique", + "data": [{"a": false}, {"a": 0}], + "valid": true + }, + { + "description": "{\"a\": true} and {\"a\": 1} are unique", + "data": [{"a": true}, {"a": 1}], + "valid": true } ] }, diff --git a/jsonschema/__init__.py b/jsonschema/__init__.py index 6b630cd..619a7ea 100644 --- a/jsonschema/__init__.py +++ b/jsonschema/__init__.py @@ -8,9 +8,6 @@ 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.exceptions import ( - ErrorTree, FormatError, RefResolutionError, SchemaError, ValidationError -) from jsonschema._format import ( FormatChecker, draft3_format_checker, @@ -19,6 +16,13 @@ from jsonschema._format import ( draft7_format_checker, ) from jsonschema._types import TypeChecker +from jsonschema.exceptions import ( + ErrorTree, + FormatError, + RefResolutionError, + SchemaError, + ValidationError, +) from jsonschema.validators import ( Draft3Validator, Draft4Validator, @@ -27,6 +31,7 @@ from jsonschema.validators import ( RefResolver, validate, ) + try: from importlib import metadata except ImportError: # for Python<3.8 diff --git a/jsonschema/__main__.py b/jsonschema/__main__.py index 82c29fd..fdc21e2 100644 --- a/jsonschema/__main__.py +++ b/jsonschema/__main__.py @@ -1,2 +1,3 @@ from jsonschema.cli import main + main() diff --git a/jsonschema/_format.py b/jsonschema/_format.py index 3c1e6fe..ce47864 100644 --- a/jsonschema/_format.py +++ b/jsonschema/_format.py @@ -1,7 +1,6 @@ import datetime +import ipaddress import re -import socket -import struct from jsonschema.exceptions import FormatError @@ -183,51 +182,41 @@ def is_email(instance): return "@" in instance -_ipv4_re = re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$") - - @_checks_drafts( - draft3="ip-address", draft4="ipv4", draft6="ipv4", draft7="ipv4", + draft3="ip-address", + draft4="ipv4", + draft6="ipv4", + draft7="ipv4", + raises=ipaddress.AddressValueError, ) def is_ipv4(instance): if not isinstance(instance, str): return True - if not _ipv4_re.match(instance): - return False - return all(0 <= int(component) <= 255 for component in instance.split(".")) + 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) -if hasattr(socket, "inet_pton"): - # FIXME: Really this only should raise struct.error, but see the sadness - # that is https://twistedmatrix.com/trac/ticket/9409 +try: + from fqdn import FQDN +except ImportError: + pass +else: @_checks_drafts( - name="ipv6", raises=(socket.error, struct.error, ValueError), + draft3="host-name", + draft4="hostname", + draft6="hostname", + draft7="hostname", ) - def is_ipv6(instance): + def is_host_name(instance): if not isinstance(instance, str): return True - return socket.inet_pton(socket.AF_INET6, instance) - - -_host_name_re = re.compile(r"^[A-Za-z0-9][A-Za-z0-9\.\-]{1,255}$") - - -@_checks_drafts( - draft3="host-name", - draft4="hostname", - draft6="hostname", - draft7="hostname", -) -def is_host_name(instance): - if not isinstance(instance, str): - return True - if not _host_name_re.match(instance): - return False - components = instance.split(".") - for component in components: - if len(component) > 63: - return False - return True + return FQDN(instance).is_valid try: @@ -330,11 +319,18 @@ def is_regex(instance): 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 datetime.datetime.strptime(instance, "%Y-%m-%d") + return _is_date(instance) @_checks_drafts(draft3="time", raises=ValueError) diff --git a/jsonschema/benchmarks/issue232.py b/jsonschema/benchmarks/issue232.py index b1b05b2..e08dff9 100644 --- a/jsonschema/benchmarks/issue232.py +++ b/jsonschema/benchmarks/issue232.py @@ -12,7 +12,6 @@ from pyrsistent import m from jsonschema.tests._suite import Version import jsonschema - issue232 = Version( path=Path(__file__).parent / "issue232", remotes=m(), diff --git a/jsonschema/benchmarks/json_schema_test_suite.py b/jsonschema/benchmarks/json_schema_test_suite.py index 5add505..4126fdc 100644 --- a/jsonschema/benchmarks/json_schema_test_suite.py +++ b/jsonschema/benchmarks/json_schema_test_suite.py @@ -9,6 +9,5 @@ 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 index 649273b..2ae93c2 100644 --- a/jsonschema/cli.py +++ b/jsonschema/cli.py @@ -200,8 +200,6 @@ parser.add_argument( def parse_args(args): arguments = vars(parser.parse_args(args=args or ["--help"])) - if arguments["validator"] is None: - arguments["validator"] = validator_for(arguments["schema"]) if arguments["output"] != "plain" and arguments["error_format"]: raise parser.error( "--error-format can only be used with --output plain" @@ -238,6 +236,9 @@ def run(arguments, stdout=sys.stdout, stderr=sys.stderr, stdin=sys.stdin): except _CannotLoadFile: return 1 + if arguments["validator"] is None: + arguments["validator"] = validator_for(schema) + try: arguments["validator"].check_schema(schema) except SchemaError as error: diff --git a/jsonschema/exceptions.py b/jsonschema/exceptions.py index 2c2ce4d..46ffced 100644 --- a/jsonschema/exceptions.py +++ b/jsonschema/exceptions.py @@ -10,7 +10,6 @@ import attr from jsonschema import _utils - WEAK_MATCHES = frozenset(["anyOf", "oneOf"]) STRONG_MATCHES = frozenset() diff --git a/jsonschema/tests/_helpers.py b/jsonschema/tests/_helpers.py index 761b404..51fff4f 100644 --- a/jsonschema/tests/_helpers.py +++ b/jsonschema/tests/_helpers.py @@ -1,5 +1,5 @@ -from io import StringIO from contextlib import contextmanager +from io import StringIO import sys diff --git a/jsonschema/tests/test_cli.py b/jsonschema/tests/test_cli.py index 8d2fdf7..08e92c4 100644 --- a/jsonschema/tests/test_cli.py +++ b/jsonschema/tests/test_cli.py @@ -7,9 +7,8 @@ import json import os import subprocess import sys -import tempfile -from jsonschema import Draft4Validator, cli, __version__ +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 @@ -676,7 +675,7 @@ class TestCLI(TestCase): stderr="", ) - def test_successful_validation__of_just_the_schema_pretty_output(self): + 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"], @@ -684,35 +683,54 @@ class TestCLI(TestCase): stderr="", ) - def test_successful_validation_with_specifying_base_uri(self): - try: - schema_file = tempfile.NamedTemporaryFile( - mode='w+', - prefix='schema', - suffix='.json', - dir='..', - delete=False - ) - self.addCleanup(os.remove, schema_file.name) - schema = """ - {"type": "object", "properties": {"KEY1": - {"$ref": %s%s#definitions/schemas"}}, - "definitions": {"schemas": {"type": "string"}}} - """ % ("\"", os.path.basename(schema_file.name)) - schema_file.write(schema) - finally: - schema_file.close() + 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=schema, some_instance='{"KEY1": "1"}'), - argv=["-i", "some_instance", "--base-uri", "..", "some_schema"], + files=dict(some_schema='{"const": "check"}', some_instance='"a"'), + argv=["-i", "some_instance", "some_schema"], + exit_code=1, stdout="", - stderr="", + 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_real_validator(self): + 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='{"minimum": 30}', some_instance="37"), + files=dict(some_schema=schema, some_instance=instance), argv=["-i", "some_instance", "some_schema"], stdout="", stderr="", @@ -744,15 +762,6 @@ class TestParser(TestCase): ) self.assertIs(arguments["validator"], Draft4Validator) - def test_latest_validator_is_the_default(self): - arguments = cli.parse_args( - [ - "--instance", "mem://some/instance", - "mem://some/schema", - ] - ) - self.assertIs(arguments["validator"], _LATEST_VERSION) - def test_unknown_output(self): # Avoid the help message on stdout with captured_output() as (stdout, stderr): @@ -809,4 +818,4 @@ class TestCLIIntegration(TestCase): [sys.executable, "-m", "jsonschema", "--help"], stderr=subprocess.STDOUT, ) - self.assertEqual(output, output_for_help) + self.assertEqual(output, output_for_help)
\ No newline at end of file diff --git a/jsonschema/tests/test_format.py b/jsonschema/tests/test_format.py index 254985f..6dba484 100644 --- a/jsonschema/tests/test_format.py +++ b/jsonschema/tests/test_format.py @@ -4,10 +4,9 @@ Tests for the parts of jsonschema related to the :validator:`format` property. from unittest import TestCase -from jsonschema import FormatError, ValidationError, FormatChecker +from jsonschema import FormatChecker, FormatError, ValidationError from jsonschema.validators import Draft4Validator - BOOM = ValueError("Boom!") BANG = ZeroDivisionError("Bang!") diff --git a/jsonschema/tests/test_jsonschema_test_suite.py b/jsonschema/tests/test_jsonschema_test_suite.py index e561f3e..0f9698a 100644 --- a/jsonschema/tests/test_jsonschema_test_suite.py +++ b/jsonschema/tests/test_jsonschema_test_suite.py @@ -23,7 +23,6 @@ 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") @@ -88,6 +87,24 @@ else: 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(), @@ -98,6 +115,7 @@ TestDraft3 = DRAFT3.to_unittest_testcase( 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( @@ -106,11 +124,6 @@ TestDraft3 = DRAFT3.to_unittest_testcase( description="case-insensitive T and Z", )(test) or skip( - message=bug(), - subject="host-name", - description="ends with hyphen", - )(test) - or skip( message=bug(686), subject="uniqueItems", description="[0] and [false] are unique", @@ -130,6 +143,16 @@ TestDraft3 = DRAFT3.to_unittest_testcase( 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) ), ) @@ -144,6 +167,7 @@ TestDraft4 = DRAFT4.to_unittest_testcase( 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( @@ -181,11 +205,6 @@ TestDraft4 = DRAFT4.to_unittest_testcase( description="case-insensitive T and Z", )(test) or skip( - message=bug(), - subject="hostname", - description="ends with hyphen", - )(test) - or skip( message=bug(686), subject="uniqueItems", description="[0] and [false] are unique", @@ -205,6 +224,16 @@ TestDraft4 = DRAFT4.to_unittest_testcase( 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) ), ) @@ -218,6 +247,7 @@ TestDraft6 = DRAFT6.to_unittest_testcase( 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( @@ -255,11 +285,6 @@ TestDraft6 = DRAFT6.to_unittest_testcase( description="case-insensitive T and Z", )(test) or skip( - message=bug(), - subject="hostname", - description="ends with hyphen", - )(test) - or skip( message=bug(686), subject="uniqueItems", description="[0] and [false] are unique", @@ -279,6 +304,36 @@ TestDraft6 = DRAFT6.to_unittest_testcase( 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) ), ) @@ -293,6 +348,7 @@ TestDraft7 = DRAFT7.to_unittest_testcase( 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( @@ -330,11 +386,6 @@ TestDraft7 = DRAFT7.to_unittest_testcase( description="case-insensitive T and Z", )(test) or skip( - message=bug(), - subject="hostname", - description="ends with hyphen", - )(test) - or skip( message=bug(593), subject="content", valid=False, @@ -376,6 +427,36 @@ TestDraft7 = DRAFT7.to_unittest_testcase( 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) ), ) diff --git a/jsonschema/validators.py b/jsonschema/validators.py index 2ea23ac..d6e0a0c 100644 --- a/jsonschema/validators.py +++ b/jsonschema/validators.py @@ -17,11 +17,11 @@ from jsonschema import ( _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 diff --git a/pyproject.toml b/pyproject.toml index 9cdcbf7..477d5e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,4 +8,9 @@ requires = [ ] build-backend = "setuptools.build_meta" +[tool.isort] +from_first = true +include_trailing_comma = true +multi_line_output = 3 + [tool.setuptools_scm] @@ -32,12 +32,14 @@ install_requires = [options.extras_require] format = + fqdn idna jsonpointer>1.13 rfc3987 strict-rfc3339 webcolors format_nongpl = + fqdn idna jsonpointer>1.13 webcolors @@ -1,7 +1,6 @@ [tox] envlist = - py{36,37,38,py3}-{build,tests,tests_nongpl}, - demo + py{36,37,38,py3}-{noextra,format,format_nongpl}-{build,tests}, readme safety secrets @@ -16,10 +15,11 @@ setenv = whitelist_externals = mkdir commands = - perf,tests: {envpython} -m pip install '{toxinidir}[format]' - tests_nongpl: {envpython} -m pip install '{toxinidir}[format_nongpl]' + noextra: {envpython} -m pip install {toxinidir} + format: {envpython} -m pip install '{toxinidir}[format]' + format_nongpl: {envpython} -m pip install '{toxinidir}[format_nongpl]' - tests,tests_nongpl: {envpython} -m twisted.trial {posargs:jsonschema} + tests: {envpython} -m twisted.trial {posargs:jsonschema} tests: {envpython} -m doctest {toxinidir}/README.rst perf: mkdir {envtmpdir}/benchmarks/ @@ -33,7 +33,7 @@ deps = perf: pyperf - tests,tests_nongpl,coverage,codecov: twisted + tests,coverage,codecov: twisted coverage,codecov: coverage codecov: codecov @@ -42,11 +42,6 @@ deps = deps = bandit commands = {envbindir}/bandit --recursive {toxinidir}/jsonschema -[testenv:demo] -deps = jupyter -commands = - {envbindir}/jupyter nbconvert --output-dir {envtmpdir} {toxinidir}/DEMO.ipynb - [testenv:readme] deps = docutils |