Skip to content

Commit 0e45615

Browse files
jvanascozzzeek
authored andcommitted
Custom lock prefix
Added a new :paramref:`.RedisBackend.lock_prefix` parameter to the :class:`.RedisBackend` and :class:`.ValkeyBackend` backends to allow customization of the lock key prefix. The prefix must be between 2 and 10 characters long and may contain any alphanumeric character and the symbols ``_-.:`` . This allows for improved visual clarity when inspecting backend keys, particularly when key names may be ambiguous with the default ``_lock`` prefix. Pull request courtesy Jonathan Vanasco. Extends Redis/Valkey with a "lock_prefix" argument, which allows users to override the default "_lock{0}" template. I proposed this 10 years ago, and have been using it in a custom backend. I keep forgetting to turn this into a PR. The rationale for this PR: The current design uses a template of `"_lock{0}" % key` which can be visually confusing to scan during manual audits: _lockkey1, _lockkey2, etc. This PR allows for `"_lok.{0}" % key`, which is easier to read: _lok.key1, _lok.key2, etc. A regex is used to validate the lock_prefix, restricting it to 2-10 chars `[A-Z0-9_-:.]` A test for correct usage is provided. Because the test system uses setupclass, I could not think of a good way to implement bad config args. Fixes: #76 Closes: #277 Pull-request: #277 Pull-request-sha: dda8fff Change-Id: I22a6776168c78030e4472591a0c1e12a45b67cfb
1 parent f65a1d0 commit 0e45615

5 files changed

Lines changed: 84 additions & 2 deletions

File tree

docs/build/unreleased/76.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.. change::
2+
:tags: feature, redis
3+
:tickets: 76
4+
5+
Added a new :paramref:`.RedisBackend.lock_prefix` parameter to the
6+
:class:`.RedisBackend` and :class:`.ValkeyBackend` backends to allow
7+
customization of the lock key prefix. The prefix must be between 2 and 10
8+
characters long and may contain any alphanumeric character and the symbols
9+
``_-.:`` . This allows for improved visual clarity when inspecting backend
10+
keys, particularly when key names may be ambiguous with the default
11+
``_lock`` prefix. Pull request courtesy Jonathan Vanasco.

dogpile/cache/backends/redis.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
77
"""
88

9+
import re
910
import typing
1011
import warnings
1112

@@ -21,6 +22,9 @@
2122
__all__ = ("RedisBackend", "RedisSentinelBackend", "RedisClusterBackend")
2223

2324

25+
RE_VALID_PREFIX = re.compile(r"^[\w\-\.\:]{2,10}$")
26+
27+
2428
class RedisBackend(BytesBackend):
2529
r"""A `Redis <http://redis.io/>`__ backend, using the
2630
`redis-py <http://pypi.python.org/pypi/redis/>`__ driver.
@@ -87,6 +91,12 @@ class RedisBackend(BytesBackend):
8791
8892
.. versionadded:: 1.4.1
8993
94+
:param lock_prefix: string. This prefix is used for generating the name of
95+
locks. If customized, the prefix must be between 2 and 10 characters long,
96+
and may contain any alphanumeric character and the symbols "_-.:".
97+
98+
.. versionadded:: 1.5.0
99+
90100
:param socket_timeout: float, seconds for socket timeout.
91101
Default is None (no timeout).
92102
@@ -132,6 +142,8 @@ class RedisBackend(BytesBackend):
132142
.. versionadded:: 1.1.6
133143
"""
134144

145+
lock_template: str = "_lock{0}"
146+
135147
def __init__(self, arguments):
136148
arguments = arguments.copy()
137149
self._imports()
@@ -174,6 +186,16 @@ def __init__(self, arguments):
174186
self.redis_expiration_time = arguments.pop("redis_expiration_time", 0)
175187
self.connection_pool = arguments.pop("connection_pool", None)
176188

189+
lock_prefix = arguments.pop("lock_prefix", None)
190+
if lock_prefix:
191+
if (not isinstance(lock_prefix, str)) or (
192+
not RE_VALID_PREFIX.match(lock_prefix)
193+
):
194+
raise ValueError(
195+
"Invalid `lock_prefix` submitted: `%s`." % lock_prefix
196+
)
197+
self.lock_template = "%s{0}" % lock_prefix
198+
177199
self._create_client()
178200

179201
def _imports(self):
@@ -225,7 +247,7 @@ def get_mutex(self, key):
225247
if self.distributed_lock:
226248
return _RedisLockWrapper(
227249
self.writer_client.lock(
228-
"_lock{0}".format(key),
250+
self.lock_template.format(key),
229251
timeout=self.lock_timeout,
230252
sleep=self.lock_sleep,
231253
thread_local=self.thread_local_lock,

dogpile/cache/backends/valkey.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
77
"""
88

9+
import re
910
import typing
1011
import warnings
1112

@@ -21,6 +22,9 @@
2122
__all__ = ("ValkeyBackend", "ValkeySentinelBackend", "ValkeyClusterBackend")
2223

2324

25+
RE_VALID_PREFIX = re.compile(r"^[\w\-\.\:]{2,10}$")
26+
27+
2428
class ValkeyBackend(BytesBackend):
2529
r"""A `Valkey <http://valkey.io/>`__ backend, using the
2630
`valkey-py <http://pypi.python.org/pypi/valkey/>`__ driver.
@@ -88,6 +92,12 @@ class ValkeyBackend(BytesBackend):
8892
8993
.. versionadded:: 1.4.1
9094
95+
:param lock_prefix: string. This prefix is used for generating the name of
96+
locks. If customized, the prefix must be between 2 and 10 characters long,
97+
and may contain any alphanumeric character and the symbols "_-.:".
98+
99+
.. versionadded:: 1.5.0
100+
91101
:param socket_timeout: float, seconds for socket timeout.
92102
Default is None (no timeout).
93103
@@ -125,6 +135,8 @@ class ValkeyBackend(BytesBackend):
125135
``charset``, etc.
126136
"""
127137

138+
lock_template: str = "_lock{0}"
139+
128140
def __init__(self, arguments):
129141
arguments = arguments.copy()
130142
self._imports()
@@ -168,6 +180,15 @@ def __init__(self, arguments):
168180
"valkey_expiration_time", 0
169181
)
170182
self.connection_pool = arguments.pop("connection_pool", None)
183+
184+
lock_prefix = arguments.pop("lock_prefix", None)
185+
if lock_prefix:
186+
if (not isinstance(lock_prefix, str)) or (
187+
not RE_VALID_PREFIX.match(lock_prefix)
188+
):
189+
raise ValueError("Invalid `lock_prefix` submitted.")
190+
self.lock_template = "%s{0}" % lock_prefix
191+
171192
self._create_client()
172193

173194
def _imports(self):
@@ -219,7 +240,7 @@ def get_mutex(self, key):
219240
if self.distributed_lock:
220241
return _ValkeyLockWrapper(
221242
self.writer_client.lock(
222-
"_lock{0}".format(key),
243+
self.lock_template.format(key),
223244
timeout=self.lock_timeout,
224245
sleep=self.lock_sleep,
225246
thread_local=self.thread_local_lock,

tests/cache/test_redis_backend.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,19 @@ class RedisDistributedMutexTest(_TestRedisConn, _GenericMutexTestSuite):
6464
}
6565

6666

67+
class RedisDistributedMutexPrefixTest(_TestRedisConn, _GenericMutexTestSuite):
68+
backend = "dogpile.cache.redis"
69+
config_args = {
70+
"arguments": {
71+
"host": REDIS_HOST,
72+
"port": REDIS_PORT,
73+
"db": 0,
74+
"distributed_lock": True,
75+
"lock_prefix": "_lok.",
76+
}
77+
}
78+
79+
6780
class RedisAsyncCreationTest(_TestRedisConn, _GenericBackendFixture):
6881
backend = "dogpile.cache.redis"
6982
config_args = {

tests/cache/test_valkey_backend.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,21 @@ class ValkeyDistributedMutexTest(_TestValkeyConn, _GenericMutexTestSuite):
6464
}
6565

6666

67+
class ValkeyDistributedMutexPrefixTest(
68+
_TestValkeyConn, _GenericMutexTestSuite
69+
):
70+
backend = "dogpile.cache.valkey"
71+
config_args = {
72+
"arguments": {
73+
"host": VALKEY_HOST,
74+
"port": VALKEY_PORT,
75+
"db": 0,
76+
"distributed_lock": True,
77+
"lock_prefix": "_lok.",
78+
}
79+
}
80+
81+
6782
class ValkeyAsyncCreationTest(_TestValkeyConn, _GenericBackendFixture):
6883
backend = "dogpile.cache.valkey"
6984
config_args = {

0 commit comments

Comments
 (0)