Repository: jpadilla/pyjwt
Pull request #744 (2022-03-30 07:37:34)

Replace various string interpolations with f-strings
Just a bit of housekeeping. Follows up on #536.


Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 307b888..890329d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
 repos:
   - repo: https://github.com/psf/black
-    rev: 22.1.0
+    rev: 22.3.0
     hooks:
       - id: black
         args: ["--target-version=py36"]
@@ -23,14 +23,14 @@ repos:
       - id: isort
 
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.1.0
+    rev: v4.2.0
     hooks:
       - id: trailing-whitespace
       - id: end-of-file-fixer
       - id: debug-statements
 
   - repo: https://github.com/mgedmin/check-manifest
-    rev: "0.47"
+    rev: "0.48"
     hooks:
       - id: check-manifest
         args: [--no-build-isolation]
diff --git a/jwt/__init__.py b/jwt/__init__.py
index 33b69d7..e692a84 100644
--- a/jwt/__init__.py
+++ b/jwt/__init__.py
@@ -31,7 +31,7 @@ __title__ = "PyJWT"
 __description__ = "JSON Web Token implementation in Python"
 __url__ = "https://pyjwt.readthedocs.io"
 __uri__ = __url__
-__doc__ = __description__ + " <" + __uri__ + ">"
+__doc__ = f"{__description__} <{__uri__}>"
 
 __author__ = "José Padilla"
 __email__ = "hello@jpadilla.com"
diff --git a/jwt/api_jwk.py b/jwt/api_jwk.py
index f58bae2..31250d5 100644
--- a/jwt/api_jwk.py
+++ b/jwt/api_jwk.py
@@ -11,7 +11,7 @@ class PyJWK:
 
         kty = self._jwk_data.get("kty", None)
         if not kty:
-            raise InvalidKeyError("kty is not found: %s" % self._jwk_data)
+            raise InvalidKeyError(f"kty is not found: {self._jwk_data}")
 
         if not algorithm and isinstance(self._jwk_data, dict):
             algorithm = self._jwk_data.get("alg", None)
@@ -29,25 +29,25 @@ class PyJWK:
                 elif crv == "secp256k1":
                     algorithm = "ES256K"
                 else:
-                    raise InvalidKeyError("Unsupported crv: %s" % crv)
+                    raise InvalidKeyError(f"Unsupported crv: {crv}")
             elif kty == "RSA":
                 algorithm = "RS256"
             elif kty == "oct":
                 algorithm = "HS256"
             elif kty == "OKP":
                 if not crv:
-                    raise InvalidKeyError("crv is not found: %s" % self._jwk_data)
+                    raise InvalidKeyError(f"crv is not found: {self._jwk_data}")
                 if crv == "Ed25519":
                     algorithm = "EdDSA"
                 else:
-                    raise InvalidKeyError("Unsupported crv: %s" % crv)
+                    raise InvalidKeyError(f"Unsupported crv: {crv}")
             else:
-                raise InvalidKeyError("Unsupported kty: %s" % kty)
+                raise InvalidKeyError(f"Unsupported kty: {kty}")
 
         self.Algorithm = self._algorithms.get(algorithm)
 
         if not self.Algorithm:
-            raise PyJWKError("Unable to find a algorithm for key: %s" % self._jwk_data)
+            raise PyJWKError(f"Unable to find a algorithm for key: {self._jwk_data}")
 
         self.key = self.Algorithm.from_jwk(self._jwk_data)
 
@@ -100,4 +100,4 @@ class PyJWKSet:
         for key in self.keys:
             if key.key_id == kid:
                 return key
-        raise KeyError("keyset has no key for kid: %s" % kid)
+        raise KeyError(f"keyset has no key for kid: {kid}")
diff --git a/jwt/api_jws.py b/jwt/api_jws.py
index f32de8f..cbf4f6f 100644
--- a/jwt/api_jws.py
+++ b/jwt/api_jws.py
@@ -136,8 +136,7 @@ class PyJWS:
         except KeyError as e:
             if not has_crypto and algorithm in requires_cryptography:
                 raise NotImplementedError(
-                    "Algorithm '%s' could not be found. Do you have cryptography "
-                    "installed?" % algorithm
+                    f"Algorithm '{algorithm}' could not be found. Do you have cryptography installed?"
                 ) from e
             raise NotImplementedError("Algorithm not supported") from e
 
@@ -231,7 +230,7 @@ class PyJWS:
         try:
             header = json.loads(header_data)
         except ValueError as e:
-            raise DecodeError("Invalid header string: %s" % e) from e
+            raise DecodeError(f"Invalid header string: {e}") from e
 
         if not isinstance(header, Mapping):
             raise DecodeError("Invalid header string: must be a json object")
diff --git a/jwt/api_jwt.py b/jwt/api_jwt.py
index c9d34a5..7d2177b 100644
--- a/jwt/api_jwt.py
+++ b/jwt/api_jwt.py
@@ -1,4 +1,5 @@
 import json
+import warnings
 from calendar import timegm
 from collections.abc import Iterable, Mapping
 from datetime import datetime, timedelta, timezone
@@ -70,10 +71,19 @@ class PyJWT:
         options: Optional[Dict] = None,
         **kwargs,
     ) -> Dict[str, Any]:
-        if options is None:
-            options = {"verify_signature": True}
-        else:
-            options.setdefault("verify_signature", True)
+        options = dict(options or {})  # shallow-copy or initialize an empty dict
+        options.setdefault("verify_signature", True)
+
+        # If the user has set the legacy `verify` argument, and it doesn't match
+        # what the relevant `options` entry for the argument is, inform the user
+        # that they're likely making a mistake.
+        if "verify" in kwargs and kwargs["verify"] != options["verify_signature"]:
+            warnings.warn(
+                "The `verify` argument to `decode` does nothing in PyJWT 2.0 and newer. "
+                "The equivalent is setting `verify_signature` to False in the `options` dictionary. "
+                "This invocation has a mismatch between the kwarg and the option entry.",
+                category=DeprecationWarning,
+            )
 
         if not options["verify_signature"]:
             options.setdefault("verify_exp", False)
@@ -98,7 +108,7 @@ class PyJWT:
         try:
             payload = json.loads(decoded["payload"])
         except ValueError as e:
-            raise DecodeError("Invalid payload string: %s" % e)
+            raise DecodeError(f"Invalid payload string: {e}")
         if not isinstance(payload, dict):
             raise DecodeError("Invalid payload string: must be a json object")
 
diff --git a/jwt/exceptions.py b/jwt/exceptions.py
index 308899a..ee201ad 100644
--- a/jwt/exceptions.py
+++ b/jwt/exceptions.py
@@ -51,7 +51,7 @@ class MissingRequiredClaimError(InvalidTokenError):
         self.claim = claim
 
     def __str__(self):
-        return 'Token is missing the "%s" claim' % self.claim
+        return f'Token is missing the "{self.claim}" claim'
 
 
 class PyJWKError(PyJWTError):
diff --git a/jwt/help.py b/jwt/help.py
index d8f2302..d5c3ebb 100644
--- a/jwt/help.py
+++ b/jwt/help.py
@@ -28,10 +28,10 @@ def info():
     if implementation == "CPython":
         implementation_version = platform.python_version()
     elif implementation == "PyPy":
-        implementation_version = "{}.{}.{}".format(
-            sys.pypy_version_info.major,
-            sys.pypy_version_info.minor,
-            sys.pypy_version_info.micro,
+        implementation_version = (
+            f"{sys.pypy_version_info.major}."
+            f"{sys.pypy_version_info.minor}."
+            f"{sys.pypy_version_info.micro}"
         )
         if sys.pypy_version_info.releaselevel != "final":
             implementation_version = "".join(


Test patch:
diff --git a/tests/test_algorithms.py b/tests/test_algorithms.py
index f4ab75b..fca930c 100644
--- a/tests/test_algorithms.py
+++ b/tests/test_algorithms.py
@@ -226,16 +226,14 @@ class TestAlgorithms:
         for curve in ("P-256", "P-384", "P-521", "secp256k1"):
             with pytest.raises(InvalidKeyError):
                 algo.from_jwk(
-                    '{{"kty": "EC", "crv": "{}", "x": "dGVzdA==", '
-                    '"y": "dGVzdA=="}}'.format(curve)
+                    f'{{"kty": "EC", "crv": "{curve}", "x": "dGVzdA==", "y": "dGVzdA=="}}'
                 )
 
         # EC private key length invalid
         for (curve, point) in valid_points.items():
             with pytest.raises(InvalidKeyError):
                 algo.from_jwk(
-                    '{{"kty": "EC", "crv": "{}", "x": "{}", "y": "{}", '
-                    '"d": "dGVzdA=="}}'.format(curve, point["x"], point["y"])
+                    f'{{"kty": "EC", "crv": "{curve}", "x": "{point["x"]}", "y": "{point["y"]}", "d": "dGVzdA=="}}'
                 )
 
     @crypto_required
diff --git a/tests/test_api_jwt.py b/tests/test_api_jwt.py
index fa3167a..84e41e0 100644
--- a/tests/test_api_jwt.py
+++ b/tests/test_api_jwt.py
@@ -658,3 +658,27 @@ class TestJWT:
         jwt_message = jwt.encode(payload, secret)
 
         jwt.decode(jwt_message, secret, options={"verify_signature": False})
+
+    def test_decode_legacy_verify_warning(self, jwt, payload):
+        secret = "secret"
+        jwt_message = jwt.encode(payload, secret)
+
+        with pytest.deprecated_call():
+            # The implicit default for options.verify_signature is True,
+            # but the user sets verify to False.
+            jwt.decode(jwt_message, secret, verify=False, algorithms=["HS256"])
+
+        with pytest.deprecated_call():
+            # The user explicitly sets verify=True,
+            # but contradicts it in verify_signature.
+            jwt.decode(
+                jwt_message, secret, verify=True, options={"verify_signature": False}
+            )
+
+    def test_decode_no_options_mutation(self, jwt, payload):
+        options = {"verify_signature": True}
+        orig_options = options.copy()
+        secret = "secret"
+        jwt_message = jwt.encode(payload, secret)
+        jwt.decode(jwt_message, secret, options=options, algorithms=["HS256"])
+        assert options == orig_options