-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path_ttl_map.py
More file actions
155 lines (122 loc) · 4.59 KB
/
Copy path_ttl_map.py
File metadata and controls
155 lines (122 loc) · 4.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
from __future__ import annotations
from asyncio import Condition as AsyncCondition
from datetime import timedelta
from functools import partial
from threading import Condition
from typing import TYPE_CHECKING, Callable
from ttlru_map import TTLMap
from typing_extensions import override
from py_cashier.logger import logger
from ._abc import BaseAsyncLock, BaseAsyncStorage, BaseLock, BaseStorage, Result, TValue
if TYPE_CHECKING:
from types import TracebackType
from typing_extensions import Self
class LockStorage:
def __init__(self) -> None:
self._locks: set[str] = set()
self._condition = Condition()
def register_lock(self, key: str) -> None:
with self._condition:
while key in self._locks:
logger.debug("Key '%s' is in use, waiting for release.", key)
self._condition.wait()
logger.debug("Registering lock for key '%s'.", key)
self._locks.add(key)
self._condition.notify_all()
def unregister_lock(self, key: str) -> None:
with self._condition:
self._locks.discard(key)
logger.debug("Unregistering lock for key '%s'.", key)
self._condition.notify_all()
class AsyncLockStorage:
def __init__(self) -> None:
self._locks: set[str] = set()
self._condition = AsyncCondition()
async def register_lock(self, key: str) -> None:
async with self._condition:
while key in self._locks:
logger.debug("Key '%s' is in use, waiting for release.", key)
await self._condition.wait()
logger.debug("Registering lock for key '%s'.", key)
self._locks.add(key)
self._condition.notify_all()
async def unregister_lock(self, key: str) -> None:
async with self._condition:
self._locks.discard(key)
logger.debug("Unregistering lock for key '%s'.", key)
self._condition.notify_all()
class SimpleLock(BaseLock):
def __init__(self, lock_storage: LockStorage, key: str) -> None:
self._lock_storage = lock_storage
self._key = key
@override
def __enter__(self) -> Self:
self._lock_storage.register_lock(self._key)
return self
@override
def __exit__(
self,
exc_type: type[BaseException] | None,
exc_val: BaseException | None,
exc_tb: TracebackType | None,
) -> None:
self._lock_storage.unregister_lock(self._key)
class SimpleAsyncLock(BaseAsyncLock):
def __init__(self, lock_storage: AsyncLockStorage, key: str) -> None:
self._lock_storage = lock_storage
self._key = key
@override
async def __aenter__(self) -> Self:
await self._lock_storage.register_lock(self._key)
return self
@override
async def __aexit__(
self,
exc_type: type[BaseException] | None,
exc_val: BaseException | None,
exc_tb: TracebackType | None,
) -> None:
await self._lock_storage.unregister_lock(self._key)
class TTLMapStorage(BaseStorage[TValue, SimpleLock]):
def __init__(
self,
max_size: int | None = 1024,
ttl: timedelta | None = timedelta(minutes=1),
) -> None:
self._lock_storage = LockStorage()
self._storage: TTLMap[str, TValue] = TTLMap(max_size=max_size, ttl=ttl)
@classmethod
def build(cls, max_size: int | None = 1024) -> Callable[[timedelta | None], Self]:
return partial(cls, max_size=max_size)
@override
def lock(self, key: str) -> SimpleLock:
return SimpleLock(self._lock_storage, key)
@override
def get(self, key: str) -> Result[TValue] | None:
try:
return Result(self._storage[key])
except KeyError:
return None
@override
def set(self, key: str, value: TValue) -> None:
self._storage[key] = value
class TTLMapAsyncStorage(BaseAsyncStorage[TValue, SimpleAsyncLock]):
def __init__(
self,
max_size: int | None = 1024,
ttl: timedelta | None = timedelta(minutes=1),
) -> None:
self._lock_storage = AsyncLockStorage()
self._storage: TTLMap[str, TValue] = TTLMap(max_size=max_size, ttl=ttl)
@override
def lock(self, key: str) -> SimpleAsyncLock:
return SimpleAsyncLock(self._lock_storage, key)
@override
async def aget(self, key: str) -> Result[TValue] | None:
try:
return Result(self._storage[key])
except KeyError:
return None
@override
async def aset(self, key: str, value: TValue) -> None:
self._storage[key] = value