Skip to content

Commit 440e306

Browse files
committed
Feat: raise granular exception types for lock errors
1 parent 0d47d65 commit 440e306

File tree

6 files changed

+74
-29
lines changed

6 files changed

+74
-29
lines changed

Diff for: CHANGES

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
* Raise granular exceptions when lock operations fail
12
* Update `ResponseT` type hint
23
* Allow to control the minimum SSL version
34
* Add an optional lock_name attribute to LockError.

Diff for: redis/asyncio/lock.py

+12-7
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
from types import SimpleNamespace
55
from typing import TYPE_CHECKING, Awaitable, Optional, Union
66

7-
from redis.exceptions import LockError, LockNotOwnedError
7+
from redis.exceptions import (
8+
IndefiniteLockError,
9+
LockAquireError,
10+
LockNotLockedError,
11+
LockNotOwnedError,
12+
)
813

914
if TYPE_CHECKING:
1015
from redis.asyncio import Redis, RedisCluster
@@ -159,7 +164,7 @@ def register_scripts(self):
159164
async def __aenter__(self):
160165
if await self.acquire():
161166
return self
162-
raise LockError("Unable to acquire lock within the time specified")
167+
raise LockAquireError("Unable to acquire lock within the time specified")
163168

164169
async def __aexit__(self, exc_type, exc_value, traceback):
165170
await self.release()
@@ -249,7 +254,7 @@ def release(self) -> Awaitable[None]:
249254
"""Releases the already acquired lock"""
250255
expected_token = self.local.token
251256
if expected_token is None:
252-
raise LockError("Cannot release an unlocked lock")
257+
raise LockNotLockedError("Cannot release an unlocked lock")
253258
self.local.token = None
254259
return self.do_release(expected_token)
255260

@@ -275,9 +280,9 @@ def extend(
275280
`additional_time`.
276281
"""
277282
if self.local.token is None:
278-
raise LockError("Cannot extend an unlocked lock")
283+
raise LockNotLockedError("Cannot extend an unlocked lock")
279284
if self.timeout is None:
280-
raise LockError("Cannot extend a lock with no timeout")
285+
raise IndefiniteLockError("Cannot extend a lock with no timeout")
281286
return self.do_extend(additional_time, replace_ttl)
282287

283288
async def do_extend(self, additional_time, replace_ttl) -> bool:
@@ -297,9 +302,9 @@ def reacquire(self) -> Awaitable[bool]:
297302
Resets a TTL of an already acquired lock back to a timeout value.
298303
"""
299304
if self.local.token is None:
300-
raise LockError("Cannot reacquire an unlocked lock")
305+
raise LockNotLockedError("Cannot reacquire an unlocked lock")
301306
if self.timeout is None:
302-
raise LockError("Cannot reacquire a lock with no timeout")
307+
raise IndefiniteLockError("Cannot reacquire a lock with no timeout")
303308
return self.do_reacquire()
304309

305310
async def do_reacquire(self) -> bool:

Diff for: redis/exceptions.py

+15
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,21 @@ def __init__(self, message=None, lock_name=None):
8787
self.lock_name = lock_name
8888

8989

90+
class LockAquireError(LockError):
91+
"Error acquring a lock in a given time"
92+
...
93+
94+
95+
class IndefiniteLockError(LockError):
96+
"Error whilst trying to adjust lifetime of a lock that is indefinite"
97+
...
98+
99+
100+
class LockNotLockedError(LockError):
101+
"Error whilst trying to perform an operation on an unlocked lock"
102+
...
103+
104+
90105
class LockNotOwnedError(LockError):
91106
"Error trying to extend or release a lock that is (no longer) owned"
92107
pass

Diff for: redis/lock.py

+20-7
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
from types import SimpleNamespace, TracebackType
55
from typing import Optional, Type
66

7-
from redis.exceptions import LockError, LockNotOwnedError
7+
from redis.exceptions import (
8+
IndefiniteLockError,
9+
LockAquireError,
10+
LockNotLockedError,
11+
LockNotOwnedError,
12+
)
813
from redis.typing import Number
914

1015

@@ -157,7 +162,7 @@ def register_scripts(self) -> None:
157162
def __enter__(self) -> "Lock":
158163
if self.acquire():
159164
return self
160-
raise LockError(
165+
raise LockAquireError(
161166
"Unable to acquire lock within the time specified",
162167
lock_name=self.name,
163168
)
@@ -251,7 +256,9 @@ def release(self) -> None:
251256
"""
252257
expected_token = self.local.token
253258
if expected_token is None:
254-
raise LockError("Cannot release an unlocked lock", lock_name=self.name)
259+
raise LockNotLockedError(
260+
"Cannot release an unlocked lock", lock_name=self.name
261+
)
255262
self.local.token = None
256263
self.do_release(expected_token)
257264

@@ -276,9 +283,13 @@ def extend(self, additional_time: int, replace_ttl: bool = False) -> bool:
276283
`additional_time`.
277284
"""
278285
if self.local.token is None:
279-
raise LockError("Cannot extend an unlocked lock", lock_name=self.name)
286+
raise LockNotLockedError(
287+
"Cannot extend an unlocked lock", lock_name=self.name
288+
)
280289
if self.timeout is None:
281-
raise LockError("Cannot extend a lock with no timeout", lock_name=self.name)
290+
raise IndefiniteLockError(
291+
"Cannot extend a lock with no timeout", lock_name=self.name
292+
)
282293
return self.do_extend(additional_time, replace_ttl)
283294

284295
def do_extend(self, additional_time: int, replace_ttl: bool) -> bool:
@@ -301,9 +312,11 @@ def reacquire(self) -> bool:
301312
Resets a TTL of an already acquired lock back to a timeout value.
302313
"""
303314
if self.local.token is None:
304-
raise LockError("Cannot reacquire an unlocked lock", lock_name=self.name)
315+
raise LockNotLockedError(
316+
"Cannot reacquire an unlocked lock", lock_name=self.name
317+
)
305318
if self.timeout is None:
306-
raise LockError(
319+
raise IndefiniteLockError(
307320
"Cannot reacquire a lock with no timeout",
308321
lock_name=self.name,
309322
)

Diff for: tests/test_asyncio/test_lock.py

+12-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
import pytest
44
import pytest_asyncio
55
from redis.asyncio.lock import Lock
6-
from redis.exceptions import LockError, LockNotOwnedError
6+
from redis.exceptions import (
7+
IndefiniteLockError,
8+
LockAquireError,
9+
LockNotLockedError,
10+
LockNotOwnedError,
11+
)
712

813

914
class TestLock:
@@ -125,7 +130,7 @@ async def test_context_manager(self, r):
125130

126131
async def test_context_manager_raises_when_locked_not_acquired(self, r):
127132
await r.set("foo", "bar")
128-
with pytest.raises(LockError):
133+
with pytest.raises(LockAquireError):
129134
async with self.get_lock(r, "foo", blocking_timeout=0.1):
130135
pass
131136

@@ -144,7 +149,7 @@ async def test_high_sleep_small_blocking_timeout(self, r):
144149

145150
async def test_releasing_unlocked_lock_raises_error(self, r):
146151
lock = self.get_lock(r, "foo")
147-
with pytest.raises(LockError):
152+
with pytest.raises(LockNotLockedError):
148153
await lock.release()
149154

150155
async def test_releasing_lock_no_longer_owned_raises_error(self, r):
@@ -183,13 +188,13 @@ async def test_extend_lock_float(self, r):
183188

184189
async def test_extending_unlocked_lock_raises_error(self, r):
185190
lock = self.get_lock(r, "foo", timeout=10)
186-
with pytest.raises(LockError):
191+
with pytest.raises(LockNotLockedError):
187192
await lock.extend(10)
188193

189194
async def test_extending_lock_with_no_timeout_raises_error(self, r):
190195
lock = self.get_lock(r, "foo")
191196
assert await lock.acquire(blocking=False)
192-
with pytest.raises(LockError):
197+
with pytest.raises(IndefiniteLockError):
193198
await lock.extend(10)
194199
await lock.release()
195200

@@ -211,13 +216,13 @@ async def test_reacquire_lock(self, r):
211216

212217
async def test_reacquiring_unlocked_lock_raises_error(self, r):
213218
lock = self.get_lock(r, "foo", timeout=10)
214-
with pytest.raises(LockError):
219+
with pytest.raises(LockNotLockedError):
215220
await lock.reacquire()
216221

217222
async def test_reacquiring_lock_with_no_timeout_raises_error(self, r):
218223
lock = self.get_lock(r, "foo")
219224
assert await lock.acquire(blocking=False)
220-
with pytest.raises(LockError):
225+
with pytest.raises(IndefiniteLockError):
221226
await lock.reacquire()
222227
await lock.release()
223228

Diff for: tests/test_lock.py

+14-8
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22

33
import pytest
44
from redis.client import Redis
5-
from redis.exceptions import LockError, LockNotOwnedError
5+
from redis.exceptions import (
6+
IndefiniteLockError,
7+
LockAquireError,
8+
LockError,
9+
LockNotLockedError,
10+
LockNotOwnedError,
11+
)
612
from redis.lock import Lock
713

814
from .conftest import _get_client
@@ -129,7 +135,7 @@ def test_context_manager_blocking_timeout(self, r):
129135

130136
def test_context_manager_raises_when_locked_not_acquired(self, r):
131137
r.set("foo", "bar")
132-
with pytest.raises(LockError):
138+
with pytest.raises(LockAquireError):
133139
with self.get_lock(r, "foo", blocking_timeout=0.1):
134140
pass
135141

@@ -148,7 +154,7 @@ def test_high_sleep_small_blocking_timeout(self, r):
148154

149155
def test_releasing_unlocked_lock_raises_error(self, r):
150156
lock = self.get_lock(r, "foo")
151-
with pytest.raises(LockError):
157+
with pytest.raises(LockNotLockedError):
152158
lock.release()
153159

154160
def test_releasing_lock_no_longer_owned_raises_error(self, r):
@@ -187,13 +193,13 @@ def test_extend_lock_float(self, r):
187193

188194
def test_extending_unlocked_lock_raises_error(self, r):
189195
lock = self.get_lock(r, "foo", timeout=10)
190-
with pytest.raises(LockError):
196+
with pytest.raises(LockNotLockedError):
191197
lock.extend(10)
192198

193199
def test_extending_lock_with_no_timeout_raises_error(self, r):
194200
lock = self.get_lock(r, "foo")
195201
assert lock.acquire(blocking=False)
196-
with pytest.raises(LockError):
202+
with pytest.raises(IndefiniteLockError):
197203
lock.extend(10)
198204
lock.release()
199205

@@ -215,13 +221,13 @@ def test_reacquire_lock(self, r):
215221

216222
def test_reacquiring_unlocked_lock_raises_error(self, r):
217223
lock = self.get_lock(r, "foo", timeout=10)
218-
with pytest.raises(LockError):
224+
with pytest.raises(LockNotLockedError):
219225
lock.reacquire()
220226

221227
def test_reacquiring_lock_with_no_timeout_raises_error(self, r):
222228
lock = self.get_lock(r, "foo")
223229
assert lock.acquire(blocking=False)
224-
with pytest.raises(LockError):
230+
with pytest.raises(IndefiniteLockError):
225231
lock.reacquire()
226232
lock.release()
227233

@@ -234,7 +240,7 @@ def test_reacquiring_lock_no_longer_owned_raises_error(self, r):
234240

235241
def test_context_manager_reacquiring_lock_with_no_timeout_raises_error(self, r):
236242
with self.get_lock(r, "foo", timeout=None, blocking=False) as lock:
237-
with pytest.raises(LockError):
243+
with pytest.raises(IndefiniteLockError):
238244
lock.reacquire()
239245

240246
def test_context_manager_reacquiring_lock_no_longer_owned_raises_error(self, r):

0 commit comments

Comments
 (0)