Repository: django/django

Issue:
# Fixed #36879 -- Identified Django client in Redis client metadata.

#### Trac ticket number
<!-- Replace XXXXX with the corresponding Trac ticket number. -->
<!-- Or delete the line and write "N/A" if this is a trivial PR. -->

ticket-36879

#### Branch description
Redis documentation recommends that clients identify themselves via connection metadata to help operators monitor, debug, and reason about production systems (for example using CLIENT SETINFO / CLIENT INFO).

This patch allows Django to set its own driver info for redis-py connections, which will be a different `lib-name`:

 `redis-py` -> `redis-py(django_v{django_version})`.

This is achieved by subclassing the redis-py `Connection` class to include Django's custom `lib_name` or `driver_info`.

#### AI Assistance Disclosure (REQUIRED)
<!-- Please select exactly ONE of the following: -->
- [x] **No AI tools were used** in preparing this PR.
- [ ] **If AI tools were used**, I have disclosed which ones, and fully reviewed and verified their output.

#### Checklist
- [x] This PR follows the [contribution guidelines](https://docs.djangoproject.com/en/stable/internals/contributing/writing-code/submitting-patches/).
- [x] This PR **does not** disclose a security vulnerability (see [vulnerability reporting](https://docs.djangoproject.com/en/stable/internals/security/)).
- [x] This PR targets the `main` branch. <!-- Backports will be evaluated and done by mergers, when necessary. -->
- [x] The commit message is written in past tense, mentions the ticket number, and ends with a period.
- [x] I have checked the "Has patch" ticket flag in the Trac system.
- [x] I have added or updated relevant tests.
- [ ] I have added or updated relevant docs, including release notes if applicable.
- [ ] I have attached screenshots in both light and dark modes for any UI changes.


Implementation patch:
diff --git a/django/core/cache/backends/redis.py b/django/core/cache/backends/redis.py
index bbf070a37573..727ea51f84d6 100644
--- a/django/core/cache/backends/redis.py
+++ b/django/core/cache/backends/redis.py
@@ -4,6 +4,7 @@
 import random
 import re
 
+import django
 from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache
 from django.utils.functional import cached_property
 from django.utils.module_loading import import_string
@@ -59,7 +60,19 @@ def __init__(
             parser_class = import_string(parser_class)
         parser_class = parser_class or self._lib.connection.DefaultParser
 
-        self._pool_options = {"parser_class": parser_class, **options}
+        version = django.get_version()
+        if hasattr(self._lib, "DriverInfo"):
+            driver_info = self._lib.DriverInfo().add_upstream_driver("django", version)
+            driver_info_options = {"driver_info": driver_info}
+        else:
+            # DriverInfo is not available in this redis-py version.
+            driver_info_options = {"lib_name": f"redis-py(django_v{version})"}
+
+        self._pool_options = {
+            "parser_class": parser_class,
+            **driver_info_options,
+            **options,
+        }
 
     def _get_connection_pool_index(self, write):
         # Write to the first server. Read from other servers if there are more,


Test patch:
diff --git a/tests/cache/tests.py b/tests/cache/tests.py
index db5df2070124..b36e3a6a0602 100644
--- a/tests/cache/tests.py
+++ b/tests/cache/tests.py
@@ -15,6 +15,7 @@
 from pathlib import Path
 from unittest import mock, skipIf
 
+import django
 from django.conf import settings
 from django.core import management, signals
 from django.core.cache import (
@@ -1986,6 +1987,24 @@ def test_redis_pool_options(self):
         self.assertEqual(pool.connection_kwargs["socket_timeout"], 0.1)
         self.assertIs(pool.connection_kwargs["retry_on_timeout"], True)
 
+    def test_client_driver_info(self):
+        client_info = cache._cache.get_client().client_info()
+        if {"lib-name", "lib-ver"}.issubset(client_info):
+            version = django.get_version()
+            if hasattr(self.lib, "DriverInfo"):
+                info = self._lib.DriverInfo().add_upstream_driver("django", version)
+                correct_lib_name = info.formatted_name
+            else:
+                correct_lib_name = f"redis-py(django_v{version})"
+            # Relax the assertion to allow date variance in editable installs.
+            truncated_lib_name = correct_lib_name.rsplit(".dev", maxsplit=1)[0]
+            self.assertIn(truncated_lib_name, client_info["lib-name"])
+            self.assertEqual(client_info["lib-ver"], self.lib.__version__)
+        else:
+            # Redis versions below 7.2 lack CLIENT SETINFO.
+            self.assertNotIn("lib-ver", client_info)
+            self.assertNotIn("lib-name", client_info)
+
 
 class FileBasedCachePathLibTests(FileBasedCacheTests):
     def mkdtemp(self):