Skip to content

Commit

Permalink
Merge branch 'add-mypy-config'
Browse files Browse the repository at this point in the history
* add-mypy-config:
  Bump doc requirements.
  Omit 'if TYPE_CHECKING' blocks from coverage
  Cleanup internal type annotation variables
  Fix sphinx nitpick error arising from annotations
  Fix nitpick error on type annotation
  Use future import for type annotations
  Parenthesize dict-tuple to pacify pypy3.7 parser
  Fix typing_extensions import handling for mypy
  Setup mypy in `tox -e typing` and get it to pass
  • Loading branch information
Julian committed Jan 6, 2022
2 parents fc0990a + bc4f2d5 commit eed6d8b
Show file tree
Hide file tree
Showing 14 changed files with 111 additions and 33 deletions.
4 changes: 4 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@
branch = True
source = jsonschema
omit = */jsonschema/_reflect.py,*/jsonschema/__main__.py,*/jsonschema/benchmarks/*,*/jsonschema/tests/fuzz_validate.py

[report]
exclude_lines =
if TYPE_CHECKING:
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ jobs:
toxenv: secrets
- name: 3.9
toxenv: style
- name: 3.9
toxenv: typing
exclude:
- os: windows-latest
python-version:
Expand Down
16 changes: 8 additions & 8 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@
#
alabaster==0.7.12
# via sphinx
attrs==21.2.0
attrs==21.4.0
# via jsonschema
babel==2.9.1
# via sphinx
beautifulsoup4==4.10.0
# via furo
certifi==2021.10.8
# via requests
charset-normalizer==2.0.9
charset-normalizer==2.0.10
# via requests
docutils==0.17.1
# via sphinx
furo==2021.11.23
furo==2022.1.2
# via -r docs/requirements.in
idna==3.3
# via requests
Expand All @@ -28,15 +28,15 @@ jinja2==3.0.3
# via sphinx
file:.#egg=jsonschema
# via -r docs/requirements.in
lxml==4.6.5
lxml==4.7.1
# via -r docs/requirements.in
markupsafe==2.0.1
# via jinja2
packaging==21.3
# via sphinx
pyenchant==3.2.2
# via sphinxcontrib-spelling
pygments==2.10.0
pygments==2.11.2
# via
# furo
# sphinx
Expand All @@ -46,13 +46,13 @@ pyrsistent==0.18.0
# via jsonschema
pytz==2021.3
# via babel
requests==2.26.0
requests==2.27.1
# via sphinx
snowballstemmer==2.2.0
# via sphinx
soupsieve==2.3.1
# via beautifulsoup4
sphinx==4.3.1
sphinx==4.3.2
# via
# -r docs/requirements.in
# furo
Expand All @@ -69,7 +69,7 @@ sphinxcontrib-qthelp==1.0.3
# via sphinx
sphinxcontrib-serializinghtml==1.1.5
# via sphinx
sphinxcontrib-spelling==7.3.0
sphinxcontrib-spelling==7.3.2
# via -r docs/requirements.in
urllib3==1.26.7
# via requests
Expand Down
11 changes: 10 additions & 1 deletion jsonschema/_format.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from __future__ import annotations

from contextlib import suppress
from uuid import UUID
import datetime
import ipaddress
import re
import typing

from jsonschema.exceptions import FormatError

Expand Down Expand Up @@ -30,7 +33,13 @@ class FormatChecker(object):
limit which formats will be used during validation.
"""

checkers = {}
checkers: dict[
str,
tuple[
typing.Callable[[typing.Any], bool],
Exception | tuple[Exception, ...],
],
] = {}

def __init__(self, formats=None):
if formats is None:
Expand Down
30 changes: 29 additions & 1 deletion jsonschema/_types.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,33 @@
from __future__ import annotations

import numbers
import typing

from pyrsistent import pmap
import attr

from jsonschema.exceptions import UndefinedTypeCheck


# unfortunately, the type of pmap is generic, and if used as the attr.ib
# converter, the generic type is presented to mypy, which then fails to match
# the concrete type of a type checker mapping
# this "do nothing" wrapper presents the correct information to mypy
def _typed_pmap_converter(
init_val: typing.Mapping[
str,
typing.Callable[["TypeChecker", typing.Any], bool],
],
) -> typing.Mapping[str, typing.Callable[["TypeChecker", typing.Any], bool]]:
return typing.cast(
typing.Mapping[
str,
typing.Callable[["TypeChecker", typing.Any], bool],
],
pmap(init_val),
)


def is_array(checker, instance):
return isinstance(instance, list)

Expand Down Expand Up @@ -60,7 +82,13 @@ class TypeChecker(object):
The initial mapping of types to their checking functions.
"""
_type_checkers = attr.ib(default=pmap(), converter=pmap)

_type_checkers: typing.Mapping[
str, typing.Callable[["TypeChecker", typing.Any], bool],
] = attr.ib(
default=pmap(),
converter=_typed_pmap_converter,
)

def is_type(self, instance, type):
"""
Expand Down
2 changes: 1 addition & 1 deletion jsonschema/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
if sys.version_info >= (3, 9): # pragma: no cover
from importlib import resources
else: # pragma: no cover
import importlib_resources as resources
import importlib_resources as resources # type: ignore


class URIDict(MutableMapping):
Expand Down
2 changes: 1 addition & 1 deletion jsonschema/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
try:
from importlib import metadata
except ImportError:
import importlib_metadata as metadata
import importlib_metadata as metadata # type: ignore

import attr

Expand Down
6 changes: 4 additions & 2 deletions jsonschema/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""
Validation errors, and some surrounding helpers.
"""
from __future__ import annotations

from collections import defaultdict, deque
from pprint import pformat
from textwrap import dedent, indent
Expand All @@ -10,8 +12,8 @@

from jsonschema import _utils

WEAK_MATCHES = frozenset(["anyOf", "oneOf"])
STRONG_MATCHES = frozenset()
WEAK_MATCHES: frozenset[str] = frozenset(["anyOf", "oneOf"])
STRONG_MATCHES: frozenset[str] = frozenset()

_unset = _utils.Unset()

Expand Down
35 changes: 25 additions & 10 deletions jsonschema/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,30 @@
# for reference material on Protocols, see
# https://www.python.org/dev/peps/pep-0544/

from typing import Any, ClassVar, Iterator, Optional, Union
from __future__ import annotations

try:
from typing import TYPE_CHECKING, Any, ClassVar, Iterator
import sys

# doing these imports with `try ... except ImportError` doesn't pass mypy
# checking because mypy sees `typing._SpecialForm` and
# `typing_extensions._SpecialForm` as incompatible
#
# see:
# https://mypy.readthedocs.io/en/stable/runtime_troubles.html#using-new-additions-to-the-typing-module
# https://github.com/python/mypy/issues/4427
if sys.version_info >= (3, 8):
from typing import Protocol, runtime_checkable
except ImportError:
else:
from typing_extensions import Protocol, runtime_checkable

from jsonschema._format import FormatChecker
from jsonschema._types import TypeChecker
# in order for Sphinx to resolve references accurately from type annotations,
# it needs to see names like `jsonschema.TypeChecker`
# therefore, only import at type-checking time (to avoid circular references),
# but use `jsonschema` for any types which will otherwise not be resolvable
if TYPE_CHECKING:
import jsonschema

from jsonschema.exceptions import ValidationError
from jsonschema.validators import RefResolver

Expand Down Expand Up @@ -62,16 +77,16 @@ class Validator(Protocol):

#: A `jsonschema.TypeChecker` that will be used when validating
#: :validator:`type` properties in JSON schemas.
TYPE_CHECKER: ClassVar[TypeChecker]
TYPE_CHECKER: ClassVar[jsonschema.TypeChecker]

#: The schema that was passed in when initializing the object.
schema: Union[dict, bool]
schema: dict | bool

def __init__(
self,
schema: Union[dict, bool],
resolver: Optional[RefResolver] = None,
format_checker: Optional[FormatChecker] = None,
schema: dict | bool,
resolver: RefResolver | None = None,
format_checker: jsonschema.FormatChecker | None = None,
) -> None:
...

Expand Down
2 changes: 1 addition & 1 deletion jsonschema/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
try: # pragma: no cover
from importlib import metadata
except ImportError: # pragma: no cover
import importlib_metadata as metadata
import importlib_metadata as metadata # type: ignore

from pyrsistent import m

Expand Down
14 changes: 8 additions & 6 deletions jsonschema/tests/test_validators.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from collections import deque, namedtuple
from contextlib import contextmanager
from decimal import Decimal
Expand Down Expand Up @@ -1662,7 +1664,7 @@ def test_False_is_not_a_schema_even_if_you_forget_to_check(self):

class TestDraft3Validator(AntiDraft6LeakMixin, ValidatorTestMixin, TestCase):
Validator = validators.Draft3Validator
valid = {}, {}
valid: tuple[dict, dict] = ({}, {})
invalid = {"type": "integer"}, "foo"

def test_any_type_is_valid_for_type_any(self):
Expand Down Expand Up @@ -1694,31 +1696,31 @@ def test_is_type_does_not_evade_bool_if_it_is_being_tested(self):

class TestDraft4Validator(AntiDraft6LeakMixin, ValidatorTestMixin, TestCase):
Validator = validators.Draft4Validator
valid = {}, {}
valid: tuple[dict, dict] = ({}, {})
invalid = {"type": "integer"}, "foo"


class TestDraft6Validator(ValidatorTestMixin, TestCase):
Validator = validators.Draft6Validator
valid = {}, {}
valid: tuple[dict, dict] = ({}, {})
invalid = {"type": "integer"}, "foo"


class TestDraft7Validator(ValidatorTestMixin, TestCase):
Validator = validators.Draft7Validator
valid = {}, {}
valid: tuple[dict, dict] = ({}, {})
invalid = {"type": "integer"}, "foo"


class TestDraft201909Validator(ValidatorTestMixin, TestCase):
Validator = validators.Draft201909Validator
valid = {}, {}
valid: tuple[dict, dict] = ({}, {})
invalid = {"type": "integer"}, "foo"


class TestDraft202012Validator(ValidatorTestMixin, TestCase):
Validator = validators.Draft202012Validator
valid = {}, {}
valid: tuple[dict, dict] = ({}, {})
invalid = {"type": "integer"}, "foo"


Expand Down
7 changes: 5 additions & 2 deletions jsonschema/validators.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""
Creation and extension of validators, with implementations for existing drafts.
"""
from __future__ import annotations

from collections import deque
from collections.abc import Sequence
from functools import lru_cache
Expand All @@ -10,6 +12,7 @@
import contextlib
import json
import reprlib
import typing
import warnings

import attr
Expand All @@ -22,9 +25,9 @@
exceptions,
)

_VALIDATORS = {}
_VALIDATORS: dict[str, typing.Any] = {}
_META_SCHEMAS = _utils.URIDict()
_VOCABULARIES = []
_VOCABULARIES: list[tuple[str, typing.Any]] = []


def __getattr__(name):
Expand Down
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ ignore =
B306, # See https://github.com/PyCQA/flake8-bugbear/issues/131
W503, # (flake8 default) old PEP8 boolean operator line breaks
[mypy]
ignore_missing_imports = true
[pydocstyle]
match = (?!(test_|_|compat|cli)).*\.py # see PyCQA/pydocstyle#323
add-select =
Expand Down
10 changes: 10 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ envlist =
safety
secrets
style
typing
docs-{html,doctest,linkcheck,spelling,style}
skipsdist = True

Expand Down Expand Up @@ -89,6 +90,15 @@ deps =
commands =
{envpython} -m flake8 {posargs} {toxinidir}/jsonschema {toxinidir}/docs

[testenv:typing]
skip_install = true
deps =
mypy
pyrsistent
types-attrs
types-requests
commands = {envpython} -m mypy --config {toxinidir}/setup.cfg {posargs} {toxinidir}/jsonschema

[testenv:docs-dirhtml]
commands = {envpython} -m sphinx -b dirhtml {toxinidir}/docs/ {envtmpdir}/build {posargs:-a -n -q -T -W}
deps =
Expand Down

0 comments on commit eed6d8b

Please sign in to comment.