Skip to content

Commit 8d82e16

Browse files
committed
docs: add types to recipe.lock
1 parent 3f81fa5 commit 8d82e16

File tree

4 files changed

+81
-60
lines changed

4 files changed

+81
-60
lines changed

kazoo/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -917,7 +917,7 @@ def create(
917917
sequence=False,
918918
makepath=False,
919919
include_data=False,
920-
):
920+
) -> str:
921921
"""Create a node with the given value as its data. Optionally
922922
set an ACL on the node.
923923

kazoo/protocol/states.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
"""Kazoo State and Event objects"""
22
from collections import namedtuple
3+
from enum import Enum
34

45

5-
class KazooState(object):
6+
class KazooState(str, Enum):
67
"""High level connection state values
78

89
States inspired by Netflix Curator.

kazoo/recipe/lock.py

Lines changed: 76 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -15,40 +15,37 @@
1515

1616
"""
1717
import re
18-
from time import monotonic as now
1918
import uuid
19+
from time import monotonic as now
20+
from typing import TYPE_CHECKING, Any, ContextManager, List, Optional, Sequence, Type, Union, cast
21+
22+
from kazoo.exceptions import (CancelledError, KazooException, LockTimeout,
23+
NoNodeError)
24+
from kazoo.protocol.states import KazooState, WatchedEvent
25+
from kazoo.retry import ForceRetryError, KazooRetry, RetryFailedError
2026

21-
from kazoo.exceptions import (
22-
CancelledError,
23-
KazooException,
24-
LockTimeout,
25-
NoNodeError,
26-
)
27-
from kazoo.protocol.states import KazooState
28-
from kazoo.retry import (
29-
ForceRetryError,
30-
KazooRetry,
31-
RetryFailedError,
32-
)
27+
if TYPE_CHECKING:
28+
from kazoo.client import KazooClient
29+
from typing_extensions import Literal
3330

3431

3532
class _Watch(object):
36-
def __init__(self, duration=None):
33+
def __init__(self, duration: Optional[float] = None):
3734
self.duration = duration
38-
self.started_at = None
35+
self.started_at: Optional[float] = None
3936

40-
def start(self):
37+
def start(self) -> None:
4138
self.started_at = now()
4239

43-
def leftover(self):
40+
def leftover(self) -> Optional[float]:
4441
if self.duration is None:
4542
return None
4643
else:
47-
elapsed = now() - self.started_at
44+
elapsed = now() - cast(float, self.started_at)
4845
return max(0, self.duration - elapsed)
4946

5047

51-
class Lock(object):
48+
class Lock(ContextManager[None]):
5249
"""Kazoo Lock
5350

5451
Example usage with a :class:`~kazoo.client.KazooClient` instance:
@@ -77,7 +74,13 @@ class Lock(object):
7774
# sequence number. Involved in read/write locks.
7875
_EXCLUDE_NAMES = ["__lock__"]
7976

80-
def __init__(self, client, path, identifier=None, extra_lock_patterns=()):
77+
def __init__(
78+
self,
79+
client: KazooClient,
80+
path: str,
81+
identifier: Optional[str] = None,
82+
extra_lock_patterns: Sequence[str] = (),
83+
):
8184
"""Create a Kazoo lock.
8285

8386
:param client: A :class:`~kazoo.client.KazooClient` instance.
@@ -109,7 +112,7 @@ def __init__(self, client, path, identifier=None, extra_lock_patterns=()):
109112
# some data is written to the node. this can be queried via
110113
# contenders() to see who is contending for the lock
111114
self.data = str(identifier or "").encode("utf-8")
112-
self.node = None
115+
self.node: Optional[str] = None
113116

114117
self.wake_event = client.handler.event_object()
115118

@@ -125,20 +128,25 @@ def __init__(self, client, path, identifier=None, extra_lock_patterns=()):
125128
self.assured_path = False
126129
self.cancelled = False
127130
self._retry = KazooRetry(
128-
max_tries=None, sleep_func=client.handler.sleep_func
131+
max_tries=-1, sleep_func=client.handler.sleep_func
129132
)
130133
self._acquire_method_lock = client.handler.lock_object()
131134

132-
def _ensure_path(self):
135+
def _ensure_path(self) -> None:
133136
self.client.ensure_path(self.path)
134137
self.assured_path = True
135138

136-
def cancel(self):
139+
def cancel(self) -> None:
137140
"""Cancel a pending lock acquire."""
138141
self.cancelled = True
139142
self.wake_event.set()
140143

141-
def acquire(self, blocking=True, timeout=None, ephemeral=True):
144+
def acquire(
145+
self,
146+
blocking: bool = True,
147+
timeout: Optional[float] = None,
148+
ephemeral: bool = True,
149+
) -> bool:
142150
"""
143151
Acquire the lock. By defaults blocks and waits forever.
144152

@@ -204,11 +212,13 @@ def acquire(self, blocking=True, timeout=None, ephemeral=True):
204212
finally:
205213
self._acquire_method_lock.release()
206214

207-
def _watch_session(self, state):
215+
def _watch_session(self, state: KazooState) -> bool:
208216
self.wake_event.set()
209217
return True
210218

211-
def _inner_acquire(self, blocking, timeout, ephemeral=True):
219+
def _inner_acquire(
220+
self, blocking: bool, timeout: Optional[float], ephemeral: bool=True
221+
) -> bool:
212222

213223
# wait until it's our chance to get it..
214224
if self.is_acquired:
@@ -266,10 +276,10 @@ def _inner_acquire(self, blocking, timeout, ephemeral=True):
266276
finally:
267277
self.client.remove_listener(self._watch_session)
268278

269-
def _watch_predecessor(self, event):
279+
def _watch_predecessor(self, event: WatchedEvent) -> None:
270280
self.wake_event.set()
271281

272-
def _get_predecessor(self, node):
282+
def _get_predecessor(self, node: str) -> Optional[str]:
273283
"""returns `node`'s predecessor or None
274284

275285
Note: This handle the case where the current lock is not a contender
@@ -278,7 +288,7 @@ def _get_predecessor(self, node):
278288
"""
279289
node_sequence = node[len(self.prefix) :]
280290
children = self.client.get_children(self.path)
281-
found_self = False
291+
found_self: Union[Literal[False],re.Match[bytes]] = False
282292
# Filter out the contenders using the computed regex
283293
contender_matches = []
284294
for child in children:
@@ -293,7 +303,7 @@ def _get_predecessor(self, node):
293303
if child == node:
294304
# Remember the node's match object so we can short circuit
295305
# below.
296-
found_self = match
306+
found_self = cast(re.Match[bytes], match)
297307

298308
if found_self is False: # pragma: nocover
299309
# somehow we aren't in the childrens -- probably we are
@@ -309,42 +319,42 @@ def _get_predecessor(self, node):
309319
sorted_matches = sorted(contender_matches, key=lambda m: m.groups())
310320
return sorted_matches[-1].string
311321

312-
def _find_node(self):
322+
def _find_node(self) -> Optional[str]:
313323
children = self.client.get_children(self.path)
314324
for child in children:
315325
if child.startswith(self.prefix):
316326
return child
317327
return None
318328

319-
def _delete_node(self, node):
329+
def _delete_node(self, node: str) -> None:
320330
self.client.delete(self.path + "/" + node)
321331

322-
def _best_effort_cleanup(self):
332+
def _best_effort_cleanup(self) -> None:
323333
try:
324334
node = self.node or self._find_node()
325335
if node:
326336
self._delete_node(node)
327337
except KazooException: # pragma: nocover
328338
pass
329339

330-
def release(self):
340+
def release(self) -> bool:
331341
"""Release the lock immediately."""
332342
return self.client.retry(self._inner_release)
333343

334-
def _inner_release(self):
344+
def _inner_release(self) -> bool:
335345
if not self.is_acquired:
336346
return False
337347

338348
try:
339-
self._delete_node(self.node)
349+
self._delete_node(cast(str, self.node))
340350
except NoNodeError: # pragma: nocover
341351
pass
342352

343353
self.is_acquired = False
344354
self.node = None
345355
return True
346356

347-
def contenders(self):
357+
def contenders(self) -> List[str]:
348358
"""Return an ordered list of the current contenders for the
349359
lock.
350360

@@ -380,7 +390,7 @@ def contenders(self):
380390
for match in sorted(contender_matches, key=lambda m: m.groups())
381391
]
382392
# Retrieve all the contender nodes data (preserving order).
383-
contenders = []
393+
contenders: List[str] = []
384394
for node in contender_nodes:
385395
try:
386396
data, stat = self.client.get(self.path + "/" + node)
@@ -391,10 +401,10 @@ def contenders(self):
391401

392402
return contenders
393403

394-
def __enter__(self):
404+
def __enter__(self) -> None:
395405
self.acquire()
396406

397-
def __exit__(self, exc_type, exc_value, traceback):
407+
def __exit__(self, exc_type: Optional[Type[BaseException]], exc_value: Optional[BaseException], traceback: Any) -> None:
398408
self.release()
399409

400410

@@ -458,7 +468,7 @@ class ReadLock(Lock):
458468
_EXCLUDE_NAMES = ["__lock__"]
459469

460470

461-
class Semaphore(object):
471+
class Semaphore(ContextManager[None]):
462472
"""A Zookeeper-based Semaphore
463473

464474
This synchronization primitive operates in the same manner as the
@@ -493,7 +503,13 @@ class Semaphore(object):
493503

494504
"""
495505

496-
def __init__(self, client, path, identifier=None, max_leases=1):
506+
def __init__(
507+
self,
508+
client: KazooClient,
509+
path: str,
510+
identifier: Optional[str] = None,
511+
max_leases: int = 1,
512+
):
497513
"""Create a Kazoo Lock
498514

499515
:param client: A :class:`~kazoo.client.KazooClient` instance.
@@ -529,7 +545,7 @@ def __init__(self, client, path, identifier=None, max_leases=1):
529545
self.cancelled = False
530546
self._session_expired = False
531547

532-
def _ensure_path(self):
548+
def _ensure_path(self) -> None:
533549
result = self.client.ensure_path(self.path)
534550
self.assured_path = True
535551
if result is True:
@@ -550,12 +566,14 @@ def _ensure_path(self):
550566
else:
551567
self.client.set(self.path, str(self.max_leases).encode("utf-8"))
552568

553-
def cancel(self):
569+
def cancel(self) -> None:
554570
"""Cancel a pending semaphore acquire."""
555571
self.cancelled = True
556572
self.wake_event.set()
557573

558-
def acquire(self, blocking=True, timeout=None):
574+
def acquire(
575+
self, blocking: bool = True, timeout: Optional[float] = None
576+
) -> bool:
559577
"""Acquire the semaphore. By defaults blocks and waits forever.
560578

561579
:param blocking: Block until semaphore is obtained or
@@ -593,7 +611,7 @@ def acquire(self, blocking=True, timeout=None):
593611

594612
return self.is_acquired
595613

596-
def _inner_acquire(self, blocking, timeout=None):
614+
def _inner_acquire(self, blocking: bool, timeout: Optional[float] = None) -> bool:
597615
"""Inner loop that runs from the top anytime a command hits a
598616
retryable Zookeeper exception."""
599617
self._session_expired = False
@@ -634,10 +652,10 @@ def _inner_acquire(self, blocking, timeout=None):
634652
finally:
635653
lock.release()
636654

637-
def _watch_lease_change(self, event):
655+
def _watch_lease_change(self, event: WatchedEvent) -> None:
638656
self.wake_event.set()
639657

640-
def _get_lease(self, data=None):
658+
def _get_lease(self) -> bool:
641659
# Make sure the session is still valid
642660
if self._session_expired:
643661
raise ForceRetryError("Retry on session loss at top")
@@ -666,25 +684,26 @@ def _get_lease(self, data=None):
666684
# Return current state
667685
return self.is_acquired
668686

669-
def _watch_session(self, state):
687+
def _watch_session(self, state: KazooState) -> Optional[Literal[True]]:
670688
if state == KazooState.LOST:
671689
self._session_expired = True
672690
self.wake_event.set()
673691

674692
# Return true to de-register
675693
return True
694+
return None
676695

677-
def _best_effort_cleanup(self):
696+
def _best_effort_cleanup(self) -> None:
678697
try:
679698
self.client.delete(self.create_path)
680699
except KazooException: # pragma: nocover
681700
pass
682701

683-
def release(self):
702+
def release(self) -> bool:
684703
"""Release the lease immediately."""
685704
return self.client.retry(self._inner_release)
686705

687-
def _inner_release(self):
706+
def _inner_release(self) -> bool:
688707
if not self.is_acquired:
689708
return False
690709
try:
@@ -694,7 +713,7 @@ def _inner_release(self):
694713
self.is_acquired = False
695714
return True
696715

697-
def lease_holders(self):
716+
def lease_holders(self) -> List[str]:
698717
"""Return an unordered list of the current lease holders.
699718

700719
.. note::
@@ -708,7 +727,7 @@ def lease_holders(self):
708727

709728
children = self.client.get_children(self.path)
710729

711-
lease_holders = []
730+
lease_holders: List[str] = []
712731
for child in children:
713732
try:
714733
data, stat = self.client.get(self.path + "/" + child)
@@ -717,8 +736,8 @@ def lease_holders(self):
717736
pass
718737
return lease_holders
719738

720-
def __enter__(self):
739+
def __enter__(self) -> None:
721740
self.acquire()
722741

723-
def __exit__(self, exc_type, exc_value, traceback):
742+
def __exit__(self, exc_type: Optional[Type[BaseException]], exc_value: Optional[BaseException], traceback: Any) -> None:
724743
self.release()

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ follow_imports = "silent"
2929
ignore_missing_imports = true
3030
disable_error_code = ["no-untyped-call", "no-untyped-def"]
3131
files = [
32-
"kazoo/recipe/election.py"
32+
"kazoo/recipe/election.py",
33+
"kazoo/recipe/lock.py"
3334
]
3435

3536
[[tool.mypy.overrides]]

0 commit comments

Comments
 (0)