Skip to content

Commit fcc882b

Browse files
joachimBurketJoachim Burketterencehonles
authored
feat: Allow cache OPTIONS to specify USERNAME (#657)
--------- Co-authored-by: Joachim Burket <joachim.burket@hopitalvs.ch> Co-authored-by: Terence D. Honles <terence@honles.com>
1 parent 85c5c3d commit fcc882b

5 files changed

Lines changed: 123 additions & 42 deletions

File tree

README.rst

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -106,56 +106,50 @@ There are several ways to specify a database number:
106106
- If using the ``redis://`` scheme, the path argument of the URL, e.g.
107107
``redis://localhost/0``
108108

109-
When using `Redis' ACLs <https://redis.io/topics/acl>`_, you will need to add the
110-
username to the URL (and provide the password with the Cache ``OPTIONS``).
109+
When using `Redis' ACLs <https://redis.io/topics/acl>`_, you will need to add
110+
the username and the password in the connection string or in ``OPTIONS`` with
111+
the keys ``USERNAME`` and ``PASSWORD``. *NOTE: Values in the connection string
112+
have precedence!*
113+
111114
The login for the user ``django`` would look like this:
112115

113116
.. code-block:: python
114117
115118
CACHES = {
116119
"default": {
117120
"BACKEND": "django_redis.cache.RedisCache",
118-
"LOCATION": "redis://django@localhost:6379/0",
119-
"OPTIONS": {
120-
"CLIENT_CLASS": "django_redis.client.DefaultClient",
121-
"PASSWORD": "mysecret"
122-
}
121+
"LOCATION": "redis://django:mysecret@localhost:6379/0",
123122
}
124123
}
125124
126-
An alternative would be write both username and password into the URL:
125+
Instead you may specify both of these values in ``OPTIONS``:
127126

128127
.. code-block:: python
129128
130129
CACHES = {
131130
"default": {
132131
"BACKEND": "django_redis.cache.RedisCache",
133-
"LOCATION": "redis://django:mysecret@localhost:6379/0",
132+
"LOCATION": "redis://localhost:6379/0",
134133
"OPTIONS": {
135-
"CLIENT_CLASS": "django_redis.client.DefaultClient",
134+
"USERNAME": "django",
135+
"PASSWORD": "mysecret",
136136
}
137137
}
138138
}
139139
140-
In some circumstances the password you should use to connect Redis
141-
is not URL-safe, in this case you can escape it or just use the
142-
convenience option in ``OPTIONS`` dict:
140+
And, finally you may mix the two as follows (be sure not to include a password,
141+
even if blank in the connection string):
143142

144143
.. code-block:: python
145144
146145
CACHES = {
147146
"default": {
148147
"BACKEND": "django_redis.cache.RedisCache",
149-
"LOCATION": "redis://127.0.0.1:6379/1",
150-
"OPTIONS": {
151-
"CLIENT_CLASS": "django_redis.client.DefaultClient",
152-
"PASSWORD": "mysecret"
153-
}
148+
"LOCATION": "redis://django@localhost:6379/0",
149+
"OPTIONS": {"PASSWORD": "mysecret"}
154150
}
155151
}
156152
157-
Take care, that this option does not overwrites the password in the uri, so if
158-
you have set the password in the uri, this settings will be ignored.
159153
160154
Configure as session backend
161155
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -708,8 +702,11 @@ In order to enable this functionality you should add the following:
708702
# Sentinels which are passed directly to redis Sentinel.
709703
"SENTINELS": SENTINELS,
710704
711-
# kwargs for redis Sentinel (optional).
712-
"SENTINEL_KWARGS": {},
705+
# kwargs for redis Sentinel (optional). Example with auth on sentinels
706+
"SENTINEL_KWARGS": {
707+
"username": "sentinel-user",
708+
"password": "sentinel-pass",
709+
},
713710
714711
# You can still override the connection pool (optional).
715712
"CONNECTION_POOL_CLASS": "redis.sentinel.SentinelConnectionPool",

changelog.d/657.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added the ability to pass redis username in the cache options

django_redis/pool.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ def make_connection_params(self, url: str) -> dict[str, Any]:
9595
"parser_class": self.get_parser_cls(),
9696
}
9797

98+
username = self.options.get("USERNAME", None)
99+
if username:
100+
kwargs["username"] = username
101+
98102
password = self.options.get("PASSWORD", None)
99103
if password:
100104
kwargs["password"] = password

tests/test_connection_params.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
5+
from django_redis import pool
6+
7+
8+
@pytest.mark.parametrize(
9+
"connection_string",
10+
[
11+
"unix://tmp/foo.bar?db=1",
12+
"redis://localhost/2",
13+
"redis://redis-master/0?is_master=0",
14+
"redis://redis-master/2?is_master=False",
15+
"rediss://localhost:3333?db=2",
16+
],
17+
)
18+
def test_connection_strings(connection_string: str):
19+
res = pool.ConnectionFactory({}).make_connection_params(connection_string)
20+
assert res["url"] == connection_string
21+
22+
23+
def test_connection_options():
24+
params = pool.ConnectionFactory(
25+
{
26+
"USERNAME": "django",
27+
"PASSWORD": "mysecret",
28+
"SOCKET_TIMEOUT": 5,
29+
},
30+
).make_connection_params("")
31+
assert (
32+
params.items()
33+
>= {
34+
"username": "django",
35+
"password": "mysecret",
36+
"socket_timeout": 5,
37+
}.items()
38+
)
39+
40+
41+
@pytest.mark.parametrize(
42+
"config, expected",
43+
[
44+
# username and password tests
45+
(
46+
{
47+
"LOCATION": "redis://django:mysecret@localhost:6379/0",
48+
},
49+
{
50+
"username": "django",
51+
"password": "mysecret",
52+
"host": "localhost",
53+
"port": 6379,
54+
},
55+
),
56+
(
57+
{
58+
"LOCATION": "redis://localhost:6379/0",
59+
"OPTIONS": {
60+
"USERNAME": "django",
61+
"PASSWORD": "mysecret",
62+
},
63+
},
64+
{
65+
"username": "django",
66+
"password": "mysecret",
67+
},
68+
),
69+
(
70+
{
71+
"LOCATION": "redis://django@localhost:6379/0",
72+
"OPTIONS": {"PASSWORD": "mysecret"},
73+
},
74+
{
75+
"username": "django",
76+
"password": "mysecret",
77+
},
78+
),
79+
# url has precedence!
80+
(
81+
{
82+
"LOCATION": "redis://django:old-password@localhost:6379/0",
83+
"OPTIONS": {"PASSWORD": "mysecret"},
84+
},
85+
{
86+
"username": "django",
87+
"password": "old-password",
88+
},
89+
),
90+
],
91+
)
92+
def test_connection_connection_kwargs(config, expected):
93+
factory = pool.ConnectionFactory(config.get("OPTIONS", {}))
94+
assert (
95+
factory.get_connection_pool(
96+
factory.make_connection_params(config["LOCATION"]),
97+
).connection_kwargs.items()
98+
>= expected.items()
99+
)

tests/test_connection_string.py

Lines changed: 0 additions & 20 deletions
This file was deleted.

0 commit comments

Comments
 (0)