From 34a7c07cd5a776419199ce01ef8c92d3cc0f7398 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 17 Aug 2023 22:01:53 +0300 Subject: [PATCH 1/4] Add support for Python 3.12 --- .github/workflows/main.yml | 3 ++- setup.cfg | 1 + tox.ini | 7 ++++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1d3a1c47..532d4a28 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: platform: ["ubuntu-latest", "windows-latest"] - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "pypy-3.8", "pypy-3.9"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "pypy3.8", "pypy3.9"] steps: - uses: "actions/checkout@v3" @@ -26,6 +26,7 @@ jobs: with: python-version: "${{ matrix.python-version }}" + allow-prereleases: true - name: "Install dependencies" run: | diff --git a/setup.cfg b/setup.cfg index 44ccf0b3..9c4634f9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,6 +28,7 @@ classifiers = Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 Topic :: Utilities [options] diff --git a/tox.ini b/tox.ini index 07ace117..37e6b7f4 100644 --- a/tox.ini +++ b/tox.ini @@ -13,15 +13,16 @@ python = 3.9: py39 3.10: py310 3.11: py311 - pypy-3.8: pypy3 - pypy-3.9: pypy3 + 3.12: py312 + pypy3.8: pypy3 + pypy3.9: pypy3 [tox] envlist = lint typing - py{37,38,39,310,311,py3}-{crypto,nocrypto} + py{37,38,39,310,311,312,py3}-{crypto,nocrypto} docs pypi-description coverage-report From be50ac161d7dc0b2d2177c83c118d1418817bdc3 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 17 Aug 2023 22:11:11 +0300 Subject: [PATCH 2/4] Only attempt deploy for upstream --- .github/workflows/pypi-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pypi-package.yml b/.github/workflows/pypi-package.yml index 5d6c6fb6..0e6210aa 100644 --- a/.github/workflows/pypi-package.yml +++ b/.github/workflows/pypi-package.yml @@ -34,7 +34,7 @@ jobs: release-test-pypi: name: Publish in-dev package to test.pypi.org environment: release-test-pypi - if: github.event_name == 'push' && github.ref == 'refs/heads/master' + if: github.repository_owner == 'jpadilla' && github.event_name == 'push' && github.ref == 'refs/heads/master' runs-on: ubuntu-latest needs: build-package @@ -54,7 +54,7 @@ jobs: release-pypi: name: Publish released package to pypi.org environment: release-pypi - if: github.event.action == 'published' + if: github.repository_owner == 'jpadilla' && github.event.action == 'published' runs-on: ubuntu-latest needs: build-package From 5a9b599b48196a77cc227ac8ac4e3091690c8535 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 17 Aug 2023 22:20:04 +0300 Subject: [PATCH 3/4] Drop support for EOL Python 3.7 --- .github/workflows/main.yml | 4 ++-- .pre-commit-config.yaml | 4 ++-- jwt/algorithms.py | 18 ++++++++++-------- setup.cfg | 5 +---- tox.ini | 7 +++---- 5 files changed, 18 insertions(+), 20 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 532d4a28..6ec8fd61 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,12 +13,12 @@ jobs: name: "Python ${{ matrix.python-version }} on ${{ matrix.platform }}" runs-on: "${{ matrix.platform }}" env: - USING_COVERAGE: '3.7,3.8' + USING_COVERAGE: '3.8' strategy: matrix: platform: ["ubuntu-latest", "windows-latest"] - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "pypy3.8", "pypy3.9"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "pypy3.8", "pypy3.9"] steps: - uses: "actions/checkout@v3" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 799c149d..fb1c89c3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,13 +3,13 @@ repos: rev: 23.7.0 hooks: - id: black - args: ["--target-version=py37"] + args: ["--target-version=py38"] - repo: https://github.com/asottile/blacken-docs rev: 1.15.0 hooks: - id: blacken-docs - args: ["--target-version=py37"] + args: ["--target-version=py38"] - repo: https://github.com/PyCQA/flake8 rev: 6.1.0 diff --git a/jwt/algorithms.py b/jwt/algorithms.py index ed187152..56c28cb4 100644 --- a/jwt/algorithms.py +++ b/jwt/algorithms.py @@ -3,9 +3,17 @@ import hashlib import hmac import json -import sys from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, ClassVar, NoReturn, Union, cast, overload +from typing import ( + TYPE_CHECKING, + Any, + ClassVar, + Literal, + NoReturn, + Union, + cast, + overload, +) from .exceptions import InvalidKeyError from .types import HashlibHash, JWKDict @@ -21,12 +29,6 @@ to_base64url_uint, ) -if sys.version_info >= (3, 8): - from typing import Literal -else: - from typing_extensions import Literal - - try: from cryptography.exceptions import InvalidSignature from cryptography.hazmat.backends import default_backend diff --git a/setup.cfg b/setup.cfg index 9c4634f9..e5f01712 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,7 +23,6 @@ classifiers = Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 @@ -34,10 +33,8 @@ classifiers = [options] zip_safe = false include_package_data = true -python_requires = >=3.7 +python_requires = >=3.8 packages = find: -install_requires = - typing_extensions; python_version<="3.7" [options.package_data] * = py.typed diff --git a/tox.ini b/tox.ini index 37e6b7f4..84940242 100644 --- a/tox.ini +++ b/tox.ini @@ -8,11 +8,10 @@ filterwarnings = [gh-actions] python = - 3.7: py37, docs 3.8: py38, typing 3.9: py39 3.10: py310 - 3.11: py311 + 3.11: py311, docs 3.12: py312 pypy3.8: pypy3 pypy3.9: pypy3 @@ -22,7 +21,7 @@ python = envlist = lint typing - py{37,38,39,310,311,312,py3}-{crypto,nocrypto} + py{38,39,310,311,312,py3}-{crypto,nocrypto} docs pypi-description coverage-report @@ -41,7 +40,7 @@ commands = {envpython} -b -m coverage run -m pytest {posargs} [testenv:docs] -basepython = python3.7 +basepython = python3.11 extras = docs commands = sphinx-build -n -T -W -b html -d {envtmpdir}/doctrees docs docs/_build/html From 1b4c4c3cef067c87482505aaba65436acc8a08d0 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Thu, 17 Aug 2023 22:24:25 +0300 Subject: [PATCH 4/4] Upgrade syntax with pyupgrade --py38-plus --- jwt/algorithms.py | 27 +++++++-------------------- jwt/api_jwk.py | 10 +++++----- tests/test_api_jwt.py | 4 +--- 3 files changed, 13 insertions(+), 28 deletions(-) diff --git a/jwt/algorithms.py b/jwt/algorithms.py index 56c28cb4..ece86b4d 100644 --- a/jwt/algorithms.py +++ b/jwt/algorithms.py @@ -4,16 +4,7 @@ import hmac import json from abc import ABC, abstractmethod -from typing import ( - TYPE_CHECKING, - Any, - ClassVar, - Literal, - NoReturn, - Union, - cast, - overload, -) +from typing import TYPE_CHECKING, Any, ClassVar, Literal, NoReturn, cast, overload from .exceptions import InvalidKeyError from .types import HashlibHash, JWKDict @@ -207,7 +198,7 @@ def to_jwk(key_obj, as_dict: Literal[False] = False) -> str: @staticmethod @abstractmethod - def to_jwk(key_obj, as_dict: bool = False) -> Union[JWKDict, str]: + def to_jwk(key_obj, as_dict: bool = False) -> JWKDict | str: """ Serializes a given key into a JWK """ @@ -285,7 +276,7 @@ def to_jwk(key_obj: str | bytes, as_dict: Literal[False] = False) -> str: ... # pragma: no cover @staticmethod - def to_jwk(key_obj: str | bytes, as_dict: bool = False) -> Union[JWKDict, str]: + def to_jwk(key_obj: str | bytes, as_dict: bool = False) -> JWKDict | str: jwk = { "k": base64url_encode(force_bytes(key_obj)).decode(), "kty": "oct", @@ -365,9 +356,7 @@ def to_jwk(key_obj: AllowedRSAKeys, as_dict: Literal[False] = False) -> str: ... # pragma: no cover @staticmethod - def to_jwk( - key_obj: AllowedRSAKeys, as_dict: bool = False - ) -> Union[JWKDict, str]: + def to_jwk(key_obj: AllowedRSAKeys, as_dict: bool = False) -> JWKDict | str: obj: dict[str, Any] | None = None if hasattr(key_obj, "private_numbers"): @@ -535,7 +524,7 @@ def sign(self, msg: bytes, key: EllipticCurvePrivateKey) -> bytes: return der_to_raw_signature(der_sig, key.curve) - def verify(self, msg: bytes, key: "AllowedECKeys", sig: bytes) -> bool: + def verify(self, msg: bytes, key: AllowedECKeys, sig: bytes) -> bool: try: der_sig = raw_to_der_signature(sig, key.curve) except ValueError: @@ -563,9 +552,7 @@ def to_jwk(key_obj: AllowedECKeys, as_dict: Literal[False] = False) -> str: ... # pragma: no cover @staticmethod - def to_jwk( - key_obj: AllowedECKeys, as_dict: bool = False - ) -> Union[JWKDict, str]: + def to_jwk(key_obj: AllowedECKeys, as_dict: bool = False) -> JWKDict | str: if isinstance(key_obj, EllipticCurvePrivateKey): public_numbers = key_obj.public_key().public_numbers() elif isinstance(key_obj, EllipticCurvePublicKey): @@ -782,7 +769,7 @@ def to_jwk(key: AllowedOKPKeys, as_dict: Literal[False] = False) -> str: ... # pragma: no cover @staticmethod - def to_jwk(key: AllowedOKPKeys, as_dict: bool = False) -> Union[JWKDict, str]: + def to_jwk(key: AllowedOKPKeys, as_dict: bool = False) -> JWKDict | str: if isinstance(key, (Ed25519PublicKey, Ed448PublicKey)): x = key.public_bytes( encoding=Encoding.Raw, diff --git a/jwt/api_jwk.py b/jwt/api_jwk.py index 456c7f4d..af745783 100644 --- a/jwt/api_jwk.py +++ b/jwt/api_jwk.py @@ -60,11 +60,11 @@ def __init__(self, jwk_data: JWKDict, algorithm: str | None = None) -> None: self.key = self.Algorithm.from_jwk(self._jwk_data) @staticmethod - def from_dict(obj: JWKDict, algorithm: str | None = None) -> "PyJWK": + def from_dict(obj: JWKDict, algorithm: str | None = None) -> PyJWK: return PyJWK(obj, algorithm) @staticmethod - def from_json(data: str, algorithm: None = None) -> "PyJWK": + def from_json(data: str, algorithm: None = None) -> PyJWK: obj = json.loads(data) return PyJWK.from_dict(obj, algorithm) @@ -104,16 +104,16 @@ def __init__(self, keys: list[JWKDict]) -> None: ) @staticmethod - def from_dict(obj: dict[str, Any]) -> "PyJWKSet": + def from_dict(obj: dict[str, Any]) -> PyJWKSet: keys = obj.get("keys", []) return PyJWKSet(keys) @staticmethod - def from_json(data: str) -> "PyJWKSet": + def from_json(data: str) -> PyJWKSet: obj = json.loads(data) return PyJWKSet.from_dict(obj) - def __getitem__(self, kid: str) -> "PyJWK": + def __getitem__(self, kid: str) -> PyJWK: for key in self.keys: if key.key_id == kid: return key diff --git a/tests/test_api_jwt.py b/tests/test_api_jwt.py index 82b92994..b51170b3 100644 --- a/tests/test_api_jwt.py +++ b/tests/test_api_jwt.py @@ -437,9 +437,7 @@ def test_raise_exception_audience_as_bytes(self, jwt): payload = {"some": "payload", "aud": ["urn:me", "urn:someone-else"]} token = jwt.encode(payload, "secret") with pytest.raises(InvalidAudienceError): - jwt.decode( - token, "secret", audience="urn:me".encode(), algorithms=["HS256"] - ) + jwt.decode(token, "secret", audience=b"urn:me", algorithms=["HS256"]) def test_raise_exception_invalid_audience_in_array(self, jwt): payload = {