From b97a73a1b63d990cca3e32a9797e762d682e31f1 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 20 Feb 2025 13:04:02 +0200 Subject: [PATCH] Make PyJWT an optional dependency --- .github/actions/run-tests/action.yml | 2 +- CONTRIBUTING.md | 14 +++++++------- pyproject.toml | 4 +++- redis/auth/token.py | 7 ++++++- tests/conftest.py | 2 +- tests/test_asyncio/conftest.py | 2 +- tests/test_auth/test_token.py | 3 ++- 7 files changed, 21 insertions(+), 13 deletions(-) diff --git a/.github/actions/run-tests/action.yml b/.github/actions/run-tests/action.yml index d822e1d499..aa958a9236 100644 --- a/.github/actions/run-tests/action.yml +++ b/.github/actions/run-tests/action.yml @@ -38,7 +38,7 @@ runs: echo "::group::Installing dependencies" pip install -r dev_requirements.txt pip uninstall -y redis # uninstall Redis package installed via redis-entraid - pip install -e . # install the working copy + pip install -e .[jwt] # install the working copy if [ "${{inputs.parser-backend}}" == "hiredis" ]; then pip install "hiredis${{inputs.hiredis-version}}" echo "PARSER_BACKEND=$(echo "${{inputs.parser-backend}}_${{inputs.hiredis-version}}" | sed 's/[^a-zA-Z0-9]/_/g')" >> $GITHUB_ENV diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 79983e4cb5..eb333f644f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,13 +32,13 @@ Here's how to get started with your code contribution: 1. Create your own fork of redis-py 2. Do the changes in your fork -3. - *Create a virtualenv and install the development dependencies from the dev_requirements.txt file:* - - a. python -m venv .venv - b. source .venv/bin/activate - c. pip install -r dev_requirements.txt - c. pip install -e . +3. Create a virtualenv and install the development dependencies from the dev_requirements.txt file: + ``` + python -m venv .venv + source .venv/bin/activate + pip install -r dev_requirements.txt + pip install -e .[jwt] + ``` 4. If you need a development environment, run `invoke devenv`. Note: this relies on docker-compose to build environments, and assumes that you have a version supporting [docker profiles](https://docs.docker.com/compose/profiles/). 5. While developing, make sure the tests pass by running `invoke tests` diff --git a/pyproject.toml b/pyproject.toml index 7becde948e..0ec38ed3b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,6 @@ classifiers = [ ] dependencies = [ 'async-timeout>=4.0.3; python_full_version<"3.11.3"', - "PyJWT~=2.9.0", ] [project.optional-dependencies] @@ -49,6 +48,9 @@ ocsp = [ "pyopenssl==20.0.1", "requests>=2.31.0", ] +jwt = [ + "PyJWT~=2.9.0", +] [project.urls] Changes = "https://github.com/redis/redis-py/releases" diff --git a/redis/auth/token.py b/redis/auth/token.py index 876e95c4fa..1c5246469b 100644 --- a/redis/auth/token.py +++ b/redis/auth/token.py @@ -1,7 +1,6 @@ from abc import ABC, abstractmethod from datetime import datetime, timezone -import jwt from redis.auth.err import InvalidTokenSchemaErr @@ -81,6 +80,12 @@ class JWToken(TokenInterface): REQUIRED_FIELDS = {"exp"} def __init__(self, token: str): + try: + import jwt + except ImportError as ie: + raise ImportError( + f"The PyJWT library is required for {self.__class__.__name__}.", + ) from ie self._value = token self._decoded = jwt.decode( self._value, diff --git a/tests/conftest.py b/tests/conftest.py index fc732c0d72..8795c9f022 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,7 +10,6 @@ from unittest.mock import Mock from urllib.parse import urlparse -import jwt import pytest import redis from packaging.version import Version @@ -615,6 +614,7 @@ def cache_key(request) -> CacheKey: def mock_identity_provider() -> IdentityProviderInterface: + jwt = pytest.importorskip("jwt") mock_provider = Mock(spec=IdentityProviderInterface) token = {"exp": datetime.now(timezone.utc).timestamp() + 3600, "oid": "username"} encoded = jwt.encode(token, "secret", algorithm="HS256") diff --git a/tests/test_asyncio/conftest.py b/tests/test_asyncio/conftest.py index fb6c51140e..99ad155d0a 100644 --- a/tests/test_asyncio/conftest.py +++ b/tests/test_asyncio/conftest.py @@ -5,7 +5,6 @@ from enum import Enum from typing import Union -import jwt import pytest import pytest_asyncio import redis.asyncio as redis @@ -247,6 +246,7 @@ async def mock_cluster_resp_slaves(create_redis, **kwargs): def mock_identity_provider() -> IdentityProviderInterface: + jwt = pytest.importorskip("jwt") mock_provider = Mock(spec=IdentityProviderInterface) token = {"exp": datetime.now(timezone.utc).timestamp() + 3600, "oid": "username"} encoded = jwt.encode(token, "secret", algorithm="HS256") diff --git a/tests/test_auth/test_token.py b/tests/test_auth/test_token.py index 978cc2ca8c..2d72e08895 100644 --- a/tests/test_auth/test_token.py +++ b/tests/test_auth/test_token.py @@ -1,6 +1,5 @@ from datetime import datetime, timezone -import jwt import pytest from redis.auth.err import InvalidTokenSchemaErr from redis.auth.token import JWToken, SimpleToken @@ -39,6 +38,8 @@ def test_simple_token(self): assert token.get_expires_at_ms() == -1 def test_jwt_token(self): + jwt = pytest.importorskip("jwt") + token = { "exp": datetime.now(timezone.utc).timestamp() + 100, "iat": datetime.now(timezone.utc).timestamp(),