From f827be366cc2560266a412697b5194ee4782b510 Mon Sep 17 00:00:00 2001 From: Julian Maurin Date: Wed, 3 Aug 2022 06:28:10 +0200 Subject: Mypy as pre-commit check + api_jws typing (#787) * feat(mypy): from tox to pre-commit * fix(mypy): apply mypy fixes * feat(api_jws): typing Co-authored-by: JulianMaurin --- .pre-commit-config.yaml | 5 ++++ jwt/api_jws.py | 61 +++++++++++++++++++++++++------------------------ jwt/help.py | 13 ++++++----- jwt/utils.py | 4 ++-- pyproject.toml | 8 +++++-- setup.cfg | 7 ------ tox.ini | 6 ----- 7 files changed, 51 insertions(+), 53 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d23a169..00b112e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,3 +34,8 @@ repos: hooks: - id: check-manifest args: [--no-build-isolation] + + - repo: https://github.com/pre-commit/mirrors-mypy + rev: "v0.971" + hooks: + - id: mypy diff --git a/jwt/api_jws.py b/jwt/api_jws.py index 90206c9..ab8490f 100644 --- a/jwt/api_jws.py +++ b/jwt/api_jws.py @@ -1,8 +1,9 @@ +from __future__ import annotations + import binascii import json import warnings -from collections.abc import Mapping -from typing import Any, Dict, List, Optional, Type +from typing import Any, Type from .algorithms import ( Algorithm, @@ -23,7 +24,7 @@ from .warnings import RemovedInPyjwt3Warning class PyJWS: header_typ = "JWT" - def __init__(self, algorithms=None, options=None): + def __init__(self, algorithms=None, options=None) -> None: self._algorithms = get_default_algorithms() self._valid_algs = ( set(algorithms) if algorithms is not None else set(self._algorithms) @@ -39,10 +40,10 @@ class PyJWS: self.options = {**self._get_default_options(), **options} @staticmethod - def _get_default_options(): + def _get_default_options() -> dict[str, bool]: return {"verify_signature": True} - def register_algorithm(self, alg_id, alg_obj): + def register_algorithm(self, alg_id: str, alg_obj: Algorithm) -> None: """ Registers a new Algorithm for use when creating and verifying tokens. """ @@ -55,7 +56,7 @@ class PyJWS: self._algorithms[alg_id] = alg_obj self._valid_algs.add(alg_id) - def unregister_algorithm(self, alg_id): + def unregister_algorithm(self, alg_id: str) -> None: """ Unregisters an Algorithm for use when creating and verifying tokens Throws KeyError if algorithm is not registered. @@ -69,7 +70,7 @@ class PyJWS: del self._algorithms[alg_id] self._valid_algs.remove(alg_id) - def get_algorithms(self): + def get_algorithms(self) -> list[str]: """ Returns a list of supported values for the 'alg' parameter. """ @@ -96,9 +97,9 @@ class PyJWS: self, payload: bytes, key: str, - algorithm: Optional[str] = "HS256", - headers: Optional[Dict[str, Any]] = None, - json_encoder: Optional[Type[json.JSONEncoder]] = None, + algorithm: str | None = "HS256", + headers: dict[str, Any] | None = None, + json_encoder: Type[json.JSONEncoder] | None = None, is_payload_detached: bool = False, ) -> str: segments = [] @@ -117,7 +118,7 @@ class PyJWS: is_payload_detached = True # Header - header = {"typ": self.header_typ, "alg": algorithm_} # type: Dict[str, Any] + header: dict[str, Any] = {"typ": self.header_typ, "alg": algorithm_} if headers: self._validate_headers(headers) @@ -165,11 +166,11 @@ class PyJWS: self, jwt: str, key: str = "", - algorithms: Optional[List[str]] = None, - options: Optional[Dict[str, Any]] = None, - detached_payload: Optional[bytes] = None, + algorithms: list[str] | None = None, + options: dict[str, Any] | None = None, + detached_payload: bytes | None = None, **kwargs, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: if kwargs: warnings.warn( "passing additional kwargs to decode_complete() is deprecated " @@ -210,9 +211,9 @@ class PyJWS: self, jwt: str, key: str = "", - algorithms: Optional[List[str]] = None, - options: Optional[Dict[str, Any]] = None, - detached_payload: Optional[bytes] = None, + algorithms: list[str] | None = None, + options: dict[str, Any] | None = None, + detached_payload: bytes | None = None, **kwargs, ) -> str: if kwargs: @@ -227,7 +228,7 @@ class PyJWS: ) return decoded["payload"] - def get_unverified_header(self, jwt): + def get_unverified_header(self, jwt: str | bytes) -> dict: """Returns back the JWT header parameters as a dict() Note: The signature is not verified so the header parameters @@ -238,7 +239,7 @@ class PyJWS: return headers - def _load(self, jwt): + def _load(self, jwt: str | bytes) -> tuple[bytes, bytes, dict, bytes]: if isinstance(jwt, str): jwt = jwt.encode("utf-8") @@ -261,7 +262,7 @@ class PyJWS: except ValueError as e: raise DecodeError(f"Invalid header string: {e}") from e - if not isinstance(header, Mapping): + if not isinstance(header, dict): raise DecodeError("Invalid header string: must be a json object") try: @@ -278,16 +279,16 @@ class PyJWS: def _verify_signature( self, - signing_input, - header, - signature, - key="", - algorithms=None, - ): + signing_input: bytes, + header: dict, + signature: bytes, + key: str = "", + algorithms: list[str] | None = None, + ) -> None: alg = header.get("alg") - if algorithms is not None and alg not in algorithms: + if not alg or (algorithms is not None and alg not in algorithms): raise InvalidAlgorithmError("The specified alg value is not allowed") try: @@ -299,11 +300,11 @@ class PyJWS: if not alg_obj.verify(signing_input, key, signature): raise InvalidSignatureError("Signature verification failed") - def _validate_headers(self, headers): + def _validate_headers(self, headers: dict[str, Any]) -> None: if "kid" in headers: self._validate_kid(headers["kid"]) - def _validate_kid(self, kid): + def _validate_kid(self, kid: str) -> None: if not isinstance(kid, str): raise InvalidTokenError("Key ID header parameter must be a string") diff --git a/jwt/help.py b/jwt/help.py index 767c323..0c02eb9 100644 --- a/jwt/help.py +++ b/jwt/help.py @@ -8,7 +8,7 @@ from . import __version__ as pyjwt_version try: import cryptography except ModuleNotFoundError: - cryptography = None # type: ignore + cryptography = None def info() -> Dict[str, Dict[str, str]]: @@ -29,14 +29,15 @@ def info() -> Dict[str, Dict[str, str]]: if implementation == "CPython": implementation_version = platform.python_version() elif implementation == "PyPy": + pypy_version_info = getattr(sys, "pypy_version_info") implementation_version = ( - f"{sys.pypy_version_info.major}." # type: ignore[attr-defined] - f"{sys.pypy_version_info.minor}." - f"{sys.pypy_version_info.micro}" + f"{pypy_version_info.major}." + f"{pypy_version_info.minor}." + f"{pypy_version_info.micro}" ) - if sys.pypy_version_info.releaselevel != "final": # type: ignore[attr-defined] + if pypy_version_info.releaselevel != "final": implementation_version = "".join( - [implementation_version, sys.pypy_version_info.releaselevel] # type: ignore[attr-defined] + [implementation_version, pypy_version_info.releaselevel] ) else: implementation_version = "Unknown" diff --git a/jwt/utils.py b/jwt/utils.py index b8ad5fa..16cae06 100644 --- a/jwt/utils.py +++ b/jwt/utils.py @@ -1,7 +1,7 @@ import base64 import binascii import re -from typing import Any, Union +from typing import Union try: from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurve @@ -10,7 +10,7 @@ try: encode_dss_signature, ) except ModuleNotFoundError: - EllipticCurve = Any # type: ignore + EllipticCurve = None def force_bytes(value: Union[str, bytes]) -> bytes: diff --git a/pyproject.toml b/pyproject.toml index 8c870eb..f406505 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,6 @@ requires = ["setuptools"] build-backend = "setuptools.build_meta" - [tool.coverage.run] parallel = true branch = true @@ -14,8 +13,13 @@ source = ["jwt", ".tox/*/site-packages"] [tool.coverage.report] show_missing = true - [tool.isort] profile = "black" atomic = true combine_as_imports = true + +[tool.mypy] +python_version = 3.7 +ignore_missing_imports = true +warn_unused_ignores = true +no_implicit_optional = true diff --git a/setup.cfg b/setup.cfg index 9fc9169..434e22c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -57,7 +57,6 @@ dev = types-cryptography>=3.3.21 pytest>=6.0.0,<7.0.0 coverage[toml]==5.0.4 - mypy pre-commit [options.packages.find] @@ -67,9 +66,3 @@ exclude = [flake8] extend-ignore = E203, E501 - -[mypy] -python_version = 3.7 -ignore_missing_imports = true -warn_unused_ignores = true -no_implicit_optional = true diff --git a/tox.ini b/tox.ini index 3e8513a..07ace11 100644 --- a/tox.ini +++ b/tox.ini @@ -48,12 +48,6 @@ commands = python -m doctest README.rst -[testenv:typing] -basepython = python3.8 -extras = dev -commands = mypy jwt - - [testenv:lint] basepython = python3.8 extras = dev -- cgit v1.2.1