diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 7eeaac17..a226290b 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,5 +9,6 @@ python: - method: pip path: . sphinx: + configuration: docs/conf.py builder: html fail_on_warning: true diff --git a/CHANGES.rst b/CHANGES.rst index fc8ab27d..e101832c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,11 @@ +Unreleased +---------- + +Added +~~~~~~~ +- Redis backend now identifies itself to Redis servers using the ``lib_name`` and ``lib_version`` parameters. The format follows redis-py conventions: ``lib_name`` is set to ``redis-py(flask-session_v)`` and ``lib_version`` is set to the redis-py version. + + 0.8.0 - 2024-03-26 ------------------ diff --git a/src/flask_session/_utils.py b/src/flask_session/_utils.py index cd7f6855..373fec3d 100644 --- a/src/flask_session/_utils.py +++ b/src/flask_session/_utils.py @@ -65,3 +65,72 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: return wrapper return decorator + + +def get_flask_session_version() -> str: + """Get the flask-session version string. + + Returns: + flask-session version in the format 'major.minor.patch' + or 'unknown' if not available. + """ + try: + from importlib.metadata import version + + return version("flask-session") + except Exception: + # importlib.metadata not available or package metadata not found + # Fallback to __version__ from __init__.py + try: + from flask_session import __version__ + + return __version__ + except (ImportError, AttributeError): + return "unknown" + + +def get_redis_py_version() -> str: + """Get the redis-py version string. + + Returns: + redis-py version in the format 'major.minor.patch' + or 'unknown' if not available. + """ + try: + from importlib.metadata import version + + return version("redis") + except Exception: + return "unknown" + + +def add_redis_version_info(kwargs): + """Add version identification for redis-py client. + + This function adds library identification to Redis connection kwargs, + allowing Redis operators to see which library is using the connection. + + Follows the format: lib_name='redis-py(flask-session_v0.8.0)' + and lib_version=''. + + Only sets lib_name and lib_version if not already provided by user, + ensuring user-provided values are never overridden. + + Args: + kwargs: Dictionary of keyword arguments to pass to Redis client. + Will be modified in-place to add 'lib_name' and 'lib_version' + if they are not already present. + + Example: + >>> kwargs = {} + >>> add_redis_version_info(kwargs) + >>> kwargs['lib_name'] + 'redis-py(flask-session_v0.8.0)' + >>> kwargs['lib_version'] + '5.0.8' + """ + if "lib_name" not in kwargs: + flask_session_ver = get_flask_session_version() + redis_py_ver = get_redis_py_version() + kwargs["lib_name"] = f"redis-py(flask-session_v{flask_session_ver})" + kwargs["lib_version"] = redis_py_ver diff --git a/src/flask_session/redis/redis.py b/src/flask_session/redis/redis.py index abed80df..27e20ec5 100644 --- a/src/flask_session/redis/redis.py +++ b/src/flask_session/redis/redis.py @@ -5,7 +5,7 @@ from flask import Flask from redis import Redis -from .._utils import total_seconds +from .._utils import add_redis_version_info, total_seconds from ..base import ServerSideSession, ServerSideSessionInterface from ..defaults import Defaults @@ -53,7 +53,10 @@ def __init__( RuntimeWarning, stacklevel=1, ) - client = Redis() + # Add version identification for redis-py client + kwargs = {} + add_redis_version_info(kwargs) + client = Redis(**kwargs) self.client = client super().__init__( app, key_prefix, use_signer, permanent, sid_length, serialization_format diff --git a/tests/test_redis.py b/tests/test_redis.py index 7c59964b..de7fdb7d 100644 --- a/tests/test_redis.py +++ b/tests/test_redis.py @@ -2,9 +2,11 @@ from contextlib import contextmanager import flask -from flask_session.redis import RedisSession from redis import Redis +from flask_session._utils import get_flask_session_version, get_redis_py_version +from flask_session.redis import RedisSession + class TestRedisSession: """This requires package: redis""" @@ -40,3 +42,38 @@ def test_redis_default(self, app_utils): json.loads(byte_string.decode("utf-8")) if byte_string else {} ) assert stored_session.get("value") == "44" + + def test_redis_adds_version_info(self, app_utils): + """Test that RedisSessionInterface adds Flask-Session version info when creating default client.""" + with self.setup_redis(): + # Don't provide a Redis client, let it create one with version info + app = app_utils.create_app({"SESSION_TYPE": "redis"}) + + with app.test_request_context(): + # Access the session interface to trigger client creation + session_interface = app.session_interface + + # Check that lib_name and lib_version were set in connection pool + conn_kwargs = session_interface.client.connection_pool.connection_kwargs + flask_session_ver = get_flask_session_version() + expected_lib_name = f"redis-py(flask-session_v{flask_session_ver})" + assert conn_kwargs.get("lib_name") == expected_lib_name + assert conn_kwargs.get("lib_version") == get_redis_py_version() + + def test_redis_respects_custom_version_info(self, app_utils): + """Test that custom lib_name and lib_version are not overridden.""" + with self.setup_redis(): + # Create a Redis client with custom version info + custom_client = Redis(lib_name="MyCustomApp", lib_version="1.2.3") + + app = app_utils.create_app( + {"SESSION_TYPE": "redis", "SESSION_REDIS": custom_client} + ) + + with app.test_request_context(): + session_interface = app.session_interface + + # Check that custom values were preserved + conn_kwargs = session_interface.client.connection_pool.connection_kwargs + assert conn_kwargs.get("lib_name") == "MyCustomApp" + assert conn_kwargs.get("lib_version") == "1.2.3"