From ae2e1bd44d94baf93b9be58b042af51f2e72071e Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Mon, 18 Dec 2023 09:20:15 -0700 Subject: [PATCH 1/2] skip `ssl` import if not available `utils.py` has support for detecting whether the `ssl` module is available, and we can use this to omit SSL-specific funcionality while still providing other features (e.g. unencrypted connections). Prior to this patch, the `connection.py` modules both triggered an `ImportError` due to unconditional imports of the `ssl` module. Now, we check `utils.SSL_AVAILABLE` prior to attempting the import and only raise an error later if (and only if) the application requests an encrypted connection. This helps support platforms such as `wasm32-wasi` where the `ssl` module is not built by default. Signed-off-by: Joel Dice --- redis/asyncio/connection.py | 19 +++++++++++++++++-- redis/connection.py | 7 ++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/redis/asyncio/connection.py b/redis/asyncio/connection.py index bbd438fc0b..d71a0dfaf3 100644 --- a/redis/asyncio/connection.py +++ b/redis/asyncio/connection.py @@ -3,7 +3,6 @@ import enum import inspect import socket -import ssl import sys import warnings import weakref @@ -25,6 +24,16 @@ ) from urllib.parse import ParseResult, parse_qs, unquote, urlparse +from ..utils import SSL_AVAILABLE + +if SSL_AVAILABLE: + import ssl + from ssl import SSLContext + +else: + ssl = None + SSLContext = None + # the functionality is available in 3.11.x but has a major issue before # 3.11.3. See https://github.com/redis/redis-py/issues/2633 if sys.version_info >= (3, 11, 3): @@ -740,6 +749,9 @@ def __init__( ssl_check_hostname: bool = False, **kwargs, ): + if not SSL_AVAILABLE: + raise RedisError("Python wasn't built with SSL support") + self.ssl_context: RedisSSLContext = RedisSSLContext( keyfile=ssl_keyfile, certfile=ssl_certfile, @@ -800,6 +812,9 @@ def __init__( ca_data: Optional[str] = None, check_hostname: bool = False, ): + if not SSL_AVAILABLE: + raise RedisError("Python wasn't built with SSL support") + self.keyfile = keyfile self.certfile = certfile if cert_reqs is None: @@ -820,7 +835,7 @@ def __init__( self.check_hostname = check_hostname self.context: Optional[ssl.SSLContext] = None - def get(self) -> ssl.SSLContext: + def get(self) -> SSLContext: if not self.context: context = ssl.create_default_context() context.check_hostname = self.check_hostname diff --git a/redis/connection.py b/redis/connection.py index c201224e35..2fde18d8b9 100644 --- a/redis/connection.py +++ b/redis/connection.py @@ -1,7 +1,6 @@ import copy import os import socket -import ssl import sys import threading import weakref @@ -35,6 +34,12 @@ str_if_bytes, ) +if SSL_AVAILABLE: + import ssl + +else: + ssl = None + if HIREDIS_AVAILABLE: import hiredis From fb7f2b2082e0ca2a45f28ddd052630f78e8937da Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Tue, 30 Jul 2024 15:45:40 -0600 Subject: [PATCH 2/2] make ssl module optional Signed-off-by: Joel Dice --- redis/asyncio/client.py | 11 +++++++++-- redis/asyncio/cluster.py | 11 +++++++++-- redis/asyncio/connection.py | 9 +++++---- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/redis/asyncio/client.py b/redis/asyncio/client.py index 3e2912bfca..c817e5a108 100644 --- a/redis/asyncio/client.py +++ b/redis/asyncio/client.py @@ -2,7 +2,6 @@ import copy import inspect import re -import ssl import warnings from typing import ( TYPE_CHECKING, @@ -71,6 +70,7 @@ from redis.typing import ChannelT, EncodableT, KeyT from redis.utils import ( HIREDIS_AVAILABLE, + SSL_AVAILABLE, _set_info_logger, deprecated_function, get_lib_version, @@ -78,6 +78,13 @@ str_if_bytes, ) +if SSL_AVAILABLE: + import ssl + from ssl import TLSVersion +else: + ssl = None + TLSVersion = None + PubSubHandler = Callable[[Dict[str, str]], Awaitable[None]] _KeyT = TypeVar("_KeyT", bound=KeyT) _ArgT = TypeVar("_ArgT", KeyT, EncodableT) @@ -227,7 +234,7 @@ def __init__( ssl_ca_certs: Optional[str] = None, ssl_ca_data: Optional[str] = None, ssl_check_hostname: bool = False, - ssl_min_version: Optional[ssl.TLSVersion] = None, + ssl_min_version: Optional[TLSVersion] = None, max_connections: Optional[int] = None, single_connection_client: bool = False, health_check_interval: int = 0, diff --git a/redis/asyncio/cluster.py b/redis/asyncio/cluster.py index 4fb2fc4647..6190cab726 100644 --- a/redis/asyncio/cluster.py +++ b/redis/asyncio/cluster.py @@ -2,7 +2,6 @@ import collections import random import socket -import ssl import warnings from typing import ( Any, @@ -70,6 +69,7 @@ ) from redis.typing import AnyKeyT, EncodableT, KeyT from redis.utils import ( + SSL_AVAILABLE, deprecated_function, dict_merge, get_lib_version, @@ -77,6 +77,13 @@ str_if_bytes, ) +if SSL_AVAILABLE: + import ssl + from ssl import TLSVersion +else: + ssl = None + TLSVersion = None + TargetNodesT = TypeVar( "TargetNodesT", str, "ClusterNode", List["ClusterNode"], Dict[Any, "ClusterNode"] ) @@ -272,7 +279,7 @@ def __init__( ssl_certfile: Optional[str] = None, ssl_check_hostname: bool = False, ssl_keyfile: Optional[str] = None, - ssl_min_version: Optional[ssl.TLSVersion] = None, + ssl_min_version: Optional[TLSVersion] = None, protocol: Optional[int] = 2, address_remap: Optional[Callable[[str, int], Tuple[str, int]]] = None, cache_enabled: bool = False, diff --git a/redis/asyncio/connection.py b/redis/asyncio/connection.py index 13a70458cc..93139a3125 100644 --- a/redis/asyncio/connection.py +++ b/redis/asyncio/connection.py @@ -30,11 +30,12 @@ if SSL_AVAILABLE: import ssl - from ssl import SSLContext + from ssl import SSLContext, TLSVersion else: ssl = None SSLContext = None + TLSVersion = None # the functionality is available in 3.11.x but has a major issue before # 3.11.3. See https://github.com/redis/redis-py/issues/2633 @@ -832,7 +833,7 @@ def __init__( ssl_ca_certs: Optional[str] = None, ssl_ca_data: Optional[str] = None, ssl_check_hostname: bool = False, - ssl_min_version: Optional[ssl.TLSVersion] = None, + ssl_min_version: Optional[TLSVersion] = None, **kwargs, ): if not SSL_AVAILABLE: @@ -903,7 +904,7 @@ def __init__( ca_certs: Optional[str] = None, ca_data: Optional[str] = None, check_hostname: bool = False, - min_version: Optional[ssl.TLSVersion] = None, + min_version: Optional[TLSVersion] = None, ): if not SSL_AVAILABLE: raise RedisError("Python wasn't built with SSL support") @@ -927,7 +928,7 @@ def __init__( self.ca_data = ca_data self.check_hostname = check_hostname self.min_version = min_version - self.context: Optional[ssl.SSLContext] = None + self.context: Optional[SSLContext] = None def get(self) -> SSLContext: if not self.context: