11from __future__ import annotations
22
3+ from asyncio import Condition as AsyncCondition
34from datetime import timedelta
45from functools import partial
56from threading import Condition
1011
1112from py_cashier .logger import logger
1213
13- from ._abc import BaseLock , BaseStorage , Result , TValue
14+ from ._abc import BaseAsyncLock , BaseAsyncStorage , BaseLock , BaseStorage , Result , TValue
1415
1516if TYPE_CHECKING :
1617 from types import TracebackType
@@ -39,6 +40,27 @@ def unregister_lock(self, key: str) -> None:
3940 self ._condition .notify_all ()
4041
4142
43+ class AsyncLockStorage :
44+ def __init__ (self ) -> None :
45+ self ._locks : set [str ] = set ()
46+ self ._condition = AsyncCondition ()
47+
48+ async def register_lock (self , key : str ) -> None :
49+ async with self ._condition :
50+ while key in self ._locks :
51+ logger .debug ("Key '%s' is in use, waiting for release." , key )
52+ await self ._condition .wait ()
53+ logger .debug ("Registering lock for key '%s'." , key )
54+ self ._locks .add (key )
55+ self ._condition .notify_all ()
56+
57+ async def unregister_lock (self , key : str ) -> None :
58+ async with self ._condition :
59+ self ._locks .discard (key )
60+ logger .debug ("Unregistering lock for key '%s'." , key )
61+ self ._condition .notify_all ()
62+
63+
4264class SimpleLock (BaseLock ):
4365 def __init__ (self , lock_storage : LockStorage , key : str ) -> None :
4466 self ._lock_storage = lock_storage
@@ -58,9 +80,15 @@ def __exit__(
5880 ) -> None :
5981 self ._lock_storage .unregister_lock (self ._key )
6082
61- # Async context manager methods are useless here, as the lock logic is synchronous.
83+
84+ class SimpleAsyncLock (BaseAsyncLock ):
85+ def __init__ (self , lock_storage : AsyncLockStorage , key : str ) -> None :
86+ self ._lock_storage = lock_storage
87+ self ._key = key
88+
6289 @override
6390 async def __aenter__ (self ) -> Self :
91+ await self ._lock_storage .register_lock (self ._key )
6492 return self
6593
6694 @override
@@ -70,7 +98,7 @@ async def __aexit__(
7098 exc_val : BaseException | None ,
7199 exc_tb : TracebackType | None ,
72100 ) -> None :
73- self ._lock_storage .unregister_lock (self ._key )
101+ await self ._lock_storage .unregister_lock (self ._key )
74102
75103
76104class TTLMapStorage (BaseStorage [TValue , SimpleLock ]):
@@ -101,10 +129,27 @@ def get(self, key: str) -> Result[TValue] | None:
101129 def set (self , key : str , value : TValue ) -> None :
102130 self ._storage [key ] = value
103131
132+
133+ class TTLMapAsyncStorage (BaseAsyncStorage [TValue , SimpleAsyncLock ]):
134+ def __init__ (
135+ self ,
136+ max_size : int | None = 1024 ,
137+ ttl : timedelta | None = timedelta (minutes = 1 ),
138+ ) -> None :
139+ self ._lock_storage = AsyncLockStorage ()
140+ self ._storage : TTLMap [str , TValue ] = TTLMap (max_size = max_size , ttl = ttl )
141+
142+ @override
143+ def lock (self , key : str ) -> SimpleAsyncLock :
144+ return SimpleAsyncLock (self ._lock_storage , key )
145+
104146 @override
105147 async def aget (self , key : str ) -> Result [TValue ] | None :
106- return self .get (key )
148+ try :
149+ return Result (self ._storage [key ])
150+ except KeyError :
151+ return None
107152
108153 @override
109154 async def aset (self , key : str , value : TValue ) -> None :
110- return self .set ( key , value )
155+ self ._storage [ key ] = value
0 commit comments