limits 4.0.1__py3-none-any.whl → 4.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- limits/__init__.py +3 -1
- limits/_version.py +4 -4
- limits/aio/__init__.py +2 -0
- limits/aio/storage/__init__.py +4 -1
- limits/aio/storage/base.py +70 -24
- limits/aio/storage/etcd.py +8 -2
- limits/aio/storage/memcached.py +159 -33
- limits/aio/storage/memory.py +100 -13
- limits/aio/storage/mongodb.py +217 -9
- limits/aio/storage/redis/__init__.py +341 -0
- limits/aio/storage/redis/bridge.py +121 -0
- limits/aio/storage/redis/coredis.py +209 -0
- limits/aio/storage/redis/redispy.py +257 -0
- limits/aio/strategies.py +124 -1
- limits/errors.py +2 -0
- limits/limits.py +10 -11
- limits/resources/redis/lua_scripts/acquire_sliding_window.lua +45 -0
- limits/resources/redis/lua_scripts/sliding_window.lua +17 -0
- limits/storage/__init__.py +6 -3
- limits/storage/base.py +92 -24
- limits/storage/etcd.py +8 -2
- limits/storage/memcached.py +143 -34
- limits/storage/memory.py +99 -12
- limits/storage/mongodb.py +204 -11
- limits/storage/redis.py +159 -138
- limits/storage/redis_cluster.py +5 -3
- limits/storage/redis_sentinel.py +14 -35
- limits/storage/registry.py +3 -3
- limits/strategies.py +121 -5
- limits/typing.py +55 -19
- limits/util.py +29 -18
- limits-4.2.dist-info/METADATA +268 -0
- limits-4.2.dist-info/RECORD +42 -0
- limits/aio/storage/redis.py +0 -470
- limits-4.0.1.dist-info/METADATA +0 -192
- limits-4.0.1.dist-info/RECORD +0 -37
- {limits-4.0.1.dist-info → limits-4.2.dist-info}/LICENSE.txt +0 -0
- {limits-4.0.1.dist-info → limits-4.2.dist-info}/WHEEL +0 -0
- {limits-4.0.1.dist-info → limits-4.2.dist-info}/top_level.txt +0 -0
limits/aio/storage/memory.py
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import asyncio
|
|
2
4
|
import time
|
|
3
|
-
from collections import Counter
|
|
5
|
+
from collections import Counter, defaultdict
|
|
6
|
+
from math import floor
|
|
4
7
|
|
|
5
8
|
from deprecated.sphinx import versionadded
|
|
6
9
|
|
|
7
10
|
import limits.typing
|
|
8
|
-
from limits.aio.storage.base import
|
|
9
|
-
|
|
11
|
+
from limits.aio.storage.base import (
|
|
12
|
+
MovingWindowSupport,
|
|
13
|
+
SlidingWindowCounterSupport,
|
|
14
|
+
Storage,
|
|
15
|
+
)
|
|
16
|
+
from limits.storage.base import TimestampedSlidingWindow
|
|
17
|
+
from limits.typing import Optional, Type, Union
|
|
10
18
|
|
|
11
19
|
|
|
12
20
|
class LockableEntry(asyncio.Lock):
|
|
@@ -17,7 +25,9 @@ class LockableEntry(asyncio.Lock):
|
|
|
17
25
|
|
|
18
26
|
|
|
19
27
|
@versionadded(version="2.1")
|
|
20
|
-
class MemoryStorage(
|
|
28
|
+
class MemoryStorage(
|
|
29
|
+
Storage, MovingWindowSupport, SlidingWindowCounterSupport, TimestampedSlidingWindow
|
|
30
|
+
):
|
|
21
31
|
"""
|
|
22
32
|
rate limit storage using :class:`collections.Counter`
|
|
23
33
|
as an in memory storage for fixed and elastic window strategies,
|
|
@@ -34,15 +44,16 @@ class MemoryStorage(Storage, MovingWindowSupport):
|
|
|
34
44
|
self, uri: Optional[str] = None, wrap_exceptions: bool = False, **_: str
|
|
35
45
|
) -> None:
|
|
36
46
|
self.storage: limits.typing.Counter[str] = Counter()
|
|
37
|
-
self.
|
|
38
|
-
self.
|
|
47
|
+
self.locks: defaultdict[str, asyncio.Lock] = defaultdict(asyncio.Lock)
|
|
48
|
+
self.expirations: dict[str, float] = {}
|
|
49
|
+
self.events: dict[str, list[LockableEntry]] = {}
|
|
39
50
|
self.timer: Optional[asyncio.Task[None]] = None
|
|
40
51
|
super().__init__(uri, wrap_exceptions=wrap_exceptions, **_)
|
|
41
52
|
|
|
42
53
|
@property
|
|
43
54
|
def base_exceptions(
|
|
44
55
|
self,
|
|
45
|
-
) -> Union[Type[Exception],
|
|
56
|
+
) -> Union[Type[Exception], tuple[Type[Exception], ...]]: # pragma: no cover
|
|
46
57
|
return ValueError
|
|
47
58
|
|
|
48
59
|
async def __expire_events(self) -> None:
|
|
@@ -56,13 +67,14 @@ class MemoryStorage(Storage, MovingWindowSupport):
|
|
|
56
67
|
if self.expirations[key] <= time.time():
|
|
57
68
|
self.storage.pop(key, None)
|
|
58
69
|
self.expirations.pop(key, None)
|
|
70
|
+
self.locks.pop(key, None)
|
|
59
71
|
|
|
60
72
|
async def __schedule_expiry(self) -> None:
|
|
61
73
|
if not self.timer or self.timer.done():
|
|
62
74
|
self.timer = asyncio.create_task(self.__expire_events())
|
|
63
75
|
|
|
64
76
|
async def incr(
|
|
65
|
-
self, key: str, expiry:
|
|
77
|
+
self, key: str, expiry: float, elastic_expiry: bool = False, amount: int = 1
|
|
66
78
|
) -> int:
|
|
67
79
|
"""
|
|
68
80
|
increments the counter for a given rate limit key
|
|
@@ -75,10 +87,24 @@ class MemoryStorage(Storage, MovingWindowSupport):
|
|
|
75
87
|
"""
|
|
76
88
|
await self.get(key)
|
|
77
89
|
await self.__schedule_expiry()
|
|
78
|
-
self.
|
|
90
|
+
async with self.locks[key]:
|
|
91
|
+
self.storage[key] += amount
|
|
92
|
+
|
|
93
|
+
if elastic_expiry or self.storage[key] == amount:
|
|
94
|
+
self.expirations[key] = time.time() + expiry
|
|
95
|
+
|
|
96
|
+
return self.storage.get(key, amount)
|
|
97
|
+
|
|
98
|
+
async def decr(self, key: str, amount: int = 1) -> int:
|
|
99
|
+
"""
|
|
100
|
+
decrements the counter for a given rate limit key. 0 is the minimum allowed value.
|
|
79
101
|
|
|
80
|
-
|
|
81
|
-
|
|
102
|
+
:param amount: the number to increment by
|
|
103
|
+
"""
|
|
104
|
+
await self.get(key)
|
|
105
|
+
await self.__schedule_expiry()
|
|
106
|
+
async with self.locks[key]:
|
|
107
|
+
self.storage[key] = max(self.storage[key] - amount, 0)
|
|
82
108
|
|
|
83
109
|
return self.storage.get(key, amount)
|
|
84
110
|
|
|
@@ -86,10 +112,10 @@ class MemoryStorage(Storage, MovingWindowSupport):
|
|
|
86
112
|
"""
|
|
87
113
|
:param key: the key to get the counter value for
|
|
88
114
|
"""
|
|
89
|
-
|
|
90
115
|
if self.expirations.get(key, 0) <= time.time():
|
|
91
116
|
self.storage.pop(key, None)
|
|
92
117
|
self.expirations.pop(key, None)
|
|
118
|
+
self.locks.pop(key, None)
|
|
93
119
|
|
|
94
120
|
return self.storage.get(key, 0)
|
|
95
121
|
|
|
@@ -100,6 +126,7 @@ class MemoryStorage(Storage, MovingWindowSupport):
|
|
|
100
126
|
self.storage.pop(key, None)
|
|
101
127
|
self.expirations.pop(key, None)
|
|
102
128
|
self.events.pop(key, None)
|
|
129
|
+
self.locks.pop(key, None)
|
|
103
130
|
|
|
104
131
|
async def acquire_entry(
|
|
105
132
|
self, key: str, limit: int, expiry: int, amount: int = 1
|
|
@@ -153,7 +180,7 @@ class MemoryStorage(Storage, MovingWindowSupport):
|
|
|
153
180
|
# FIXME: arg limit is not used
|
|
154
181
|
async def get_moving_window(
|
|
155
182
|
self, key: str, limit: int, expiry: int
|
|
156
|
-
) ->
|
|
183
|
+
) -> tuple[float, int]:
|
|
157
184
|
"""
|
|
158
185
|
returns the starting point and the number of entries in the moving
|
|
159
186
|
window
|
|
@@ -171,6 +198,65 @@ class MemoryStorage(Storage, MovingWindowSupport):
|
|
|
171
198
|
|
|
172
199
|
return timestamp, acquired
|
|
173
200
|
|
|
201
|
+
async def acquire_sliding_window_entry(
|
|
202
|
+
self,
|
|
203
|
+
key: str,
|
|
204
|
+
limit: int,
|
|
205
|
+
expiry: int,
|
|
206
|
+
amount: int = 1,
|
|
207
|
+
) -> bool:
|
|
208
|
+
if amount > limit:
|
|
209
|
+
return False
|
|
210
|
+
now = time.time()
|
|
211
|
+
previous_key, current_key = self.sliding_window_keys(key, expiry, now)
|
|
212
|
+
(
|
|
213
|
+
previous_count,
|
|
214
|
+
previous_ttl,
|
|
215
|
+
current_count,
|
|
216
|
+
_,
|
|
217
|
+
) = await self._get_sliding_window_info(previous_key, current_key, expiry, now)
|
|
218
|
+
weighted_count = previous_count * previous_ttl / expiry + current_count
|
|
219
|
+
if floor(weighted_count) + amount > limit:
|
|
220
|
+
return False
|
|
221
|
+
else:
|
|
222
|
+
# Hit, increase the current counter.
|
|
223
|
+
# If the counter doesn't exist yet, set twice the theorical expiry.
|
|
224
|
+
current_count = await self.incr(current_key, 2 * expiry, amount=amount)
|
|
225
|
+
weighted_count = previous_count * previous_ttl / expiry + current_count
|
|
226
|
+
if floor(weighted_count) > limit:
|
|
227
|
+
# Another hit won the race condition: revert the incrementation and refuse this hit
|
|
228
|
+
# Limitation: during high concurrency at the end of the window,
|
|
229
|
+
# the counter is shifted and cannot be decremented, so less requests than expected are allowed.
|
|
230
|
+
await self.decr(current_key, amount)
|
|
231
|
+
# print("Concurrent call, reverting the counter increase")
|
|
232
|
+
return False
|
|
233
|
+
return True
|
|
234
|
+
|
|
235
|
+
async def get_sliding_window(
|
|
236
|
+
self, key: str, expiry: int
|
|
237
|
+
) -> tuple[int, float, int, float]:
|
|
238
|
+
now = time.time()
|
|
239
|
+
previous_key, current_key = self.sliding_window_keys(key, expiry, now)
|
|
240
|
+
return await self._get_sliding_window_info(
|
|
241
|
+
previous_key, current_key, expiry, now
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
async def _get_sliding_window_info(
|
|
245
|
+
self,
|
|
246
|
+
previous_key: str,
|
|
247
|
+
current_key: str,
|
|
248
|
+
expiry: int,
|
|
249
|
+
now: float,
|
|
250
|
+
) -> tuple[int, float, int, float]:
|
|
251
|
+
previous_count = await self.get(previous_key)
|
|
252
|
+
current_count = await self.get(current_key)
|
|
253
|
+
if previous_count == 0:
|
|
254
|
+
previous_ttl = float(0)
|
|
255
|
+
else:
|
|
256
|
+
previous_ttl = (1 - (((now - expiry) / expiry) % 1)) * expiry
|
|
257
|
+
current_ttl = (1 - ((now / expiry) % 1)) * expiry + expiry
|
|
258
|
+
return previous_count, previous_ttl, current_count, current_ttl
|
|
259
|
+
|
|
174
260
|
async def check(self) -> bool:
|
|
175
261
|
"""
|
|
176
262
|
check if storage is healthy
|
|
@@ -183,5 +269,6 @@ class MemoryStorage(Storage, MovingWindowSupport):
|
|
|
183
269
|
self.storage.clear()
|
|
184
270
|
self.expirations.clear()
|
|
185
271
|
self.events.clear()
|
|
272
|
+
self.locks.clear()
|
|
186
273
|
|
|
187
274
|
return num_items
|
limits/aio/storage/mongodb.py
CHANGED
|
@@ -3,12 +3,22 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import datetime
|
|
5
5
|
import time
|
|
6
|
-
from typing import cast
|
|
7
6
|
|
|
8
7
|
from deprecated.sphinx import versionadded, versionchanged
|
|
9
8
|
|
|
10
|
-
from limits.aio.storage.base import
|
|
11
|
-
|
|
9
|
+
from limits.aio.storage.base import (
|
|
10
|
+
MovingWindowSupport,
|
|
11
|
+
SlidingWindowCounterSupport,
|
|
12
|
+
Storage,
|
|
13
|
+
)
|
|
14
|
+
from limits.typing import (
|
|
15
|
+
Optional,
|
|
16
|
+
ParamSpec,
|
|
17
|
+
Type,
|
|
18
|
+
TypeVar,
|
|
19
|
+
Union,
|
|
20
|
+
cast,
|
|
21
|
+
)
|
|
12
22
|
from limits.util import get_dependency
|
|
13
23
|
|
|
14
24
|
P = ParamSpec("P")
|
|
@@ -20,7 +30,7 @@ R = TypeVar("R")
|
|
|
20
30
|
version="3.14.0",
|
|
21
31
|
reason="Added option to select custom collection names for windows & counters",
|
|
22
32
|
)
|
|
23
|
-
class MongoDBStorage(Storage, MovingWindowSupport):
|
|
33
|
+
class MongoDBStorage(Storage, MovingWindowSupport, SlidingWindowCounterSupport):
|
|
24
34
|
"""
|
|
25
35
|
Rate limit storage with MongoDB as backend.
|
|
26
36
|
|
|
@@ -50,7 +60,8 @@ class MongoDBStorage(Storage, MovingWindowSupport):
|
|
|
50
60
|
collections.
|
|
51
61
|
:param counter_collection_name: The collection name to use for individual counters
|
|
52
62
|
used in fixed window strategies
|
|
53
|
-
:param window_collection_name: The collection name to use for moving window
|
|
63
|
+
:param window_collection_name: The collection name to use for sliding & moving window
|
|
64
|
+
storage
|
|
54
65
|
:param wrap_exceptions: Whether to wrap storage exceptions in
|
|
55
66
|
:exc:`limits.errors.StorageError` before raising it.
|
|
56
67
|
:param options: all remaining keyword arguments are passed
|
|
@@ -83,7 +94,7 @@ class MongoDBStorage(Storage, MovingWindowSupport):
|
|
|
83
94
|
@property
|
|
84
95
|
def base_exceptions(
|
|
85
96
|
self,
|
|
86
|
-
) -> Union[Type[Exception],
|
|
97
|
+
) -> Union[Type[Exception], tuple[Type[Exception], ...]]: # pragma: no cover
|
|
87
98
|
return self.lib_errors.PyMongoError # type: ignore
|
|
88
99
|
|
|
89
100
|
@property
|
|
@@ -224,7 +235,7 @@ class MongoDBStorage(Storage, MovingWindowSupport):
|
|
|
224
235
|
|
|
225
236
|
async def get_moving_window(
|
|
226
237
|
self, key: str, limit: int, expiry: int
|
|
227
|
-
) ->
|
|
238
|
+
) -> tuple[float, int]:
|
|
228
239
|
"""
|
|
229
240
|
returns the starting point and the number of entries in the moving
|
|
230
241
|
window
|
|
@@ -281,9 +292,9 @@ class MongoDBStorage(Storage, MovingWindowSupport):
|
|
|
281
292
|
|
|
282
293
|
timestamp = time.time()
|
|
283
294
|
try:
|
|
284
|
-
updates:
|
|
295
|
+
updates: dict[
|
|
285
296
|
str,
|
|
286
|
-
|
|
297
|
+
dict[str, Union[datetime.datetime, dict[str, Union[list[float], int]]]],
|
|
287
298
|
] = {
|
|
288
299
|
"$push": {
|
|
289
300
|
"entries": {
|
|
@@ -312,3 +323,200 @@ class MongoDBStorage(Storage, MovingWindowSupport):
|
|
|
312
323
|
return True
|
|
313
324
|
except self.proxy_dependency.module.errors.DuplicateKeyError:
|
|
314
325
|
return False
|
|
326
|
+
|
|
327
|
+
async def acquire_sliding_window_entry(
|
|
328
|
+
self, key: str, limit: int, expiry: int, amount: int = 1
|
|
329
|
+
) -> bool:
|
|
330
|
+
await self.create_indices()
|
|
331
|
+
expiry_ms = expiry * 1000
|
|
332
|
+
result = await self.database[
|
|
333
|
+
self.__collection_mapping["windows"]
|
|
334
|
+
].find_one_and_update(
|
|
335
|
+
{"_id": key},
|
|
336
|
+
[
|
|
337
|
+
{
|
|
338
|
+
"$set": {
|
|
339
|
+
"previousCount": {
|
|
340
|
+
"$cond": {
|
|
341
|
+
"if": {
|
|
342
|
+
"$lte": [
|
|
343
|
+
{"$subtract": ["$expiresAt", "$$NOW"]},
|
|
344
|
+
expiry_ms,
|
|
345
|
+
]
|
|
346
|
+
},
|
|
347
|
+
"then": {"$ifNull": ["$currentCount", 0]},
|
|
348
|
+
"else": {"$ifNull": ["$previousCount", 0]},
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
"$set": {
|
|
355
|
+
"currentCount": {
|
|
356
|
+
"$cond": {
|
|
357
|
+
"if": {
|
|
358
|
+
"$lte": [
|
|
359
|
+
{"$subtract": ["$expiresAt", "$$NOW"]},
|
|
360
|
+
expiry_ms,
|
|
361
|
+
]
|
|
362
|
+
},
|
|
363
|
+
"then": 0,
|
|
364
|
+
"else": {"$ifNull": ["$currentCount", 0]},
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
"expiresAt": {
|
|
368
|
+
"$cond": {
|
|
369
|
+
"if": {
|
|
370
|
+
"$lte": [
|
|
371
|
+
{"$subtract": ["$expiresAt", "$$NOW"]},
|
|
372
|
+
expiry_ms,
|
|
373
|
+
]
|
|
374
|
+
},
|
|
375
|
+
"then": {
|
|
376
|
+
"$cond": {
|
|
377
|
+
"if": {"$gt": ["$expiresAt", 0]},
|
|
378
|
+
"then": {"$add": ["$expiresAt", expiry_ms]},
|
|
379
|
+
"else": {"$add": ["$$NOW", 2 * expiry_ms]},
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
"else": "$expiresAt",
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
"$set": {
|
|
389
|
+
"curWeightedCount": {
|
|
390
|
+
"$floor": {
|
|
391
|
+
"$add": [
|
|
392
|
+
{
|
|
393
|
+
"$multiply": [
|
|
394
|
+
"$previousCount",
|
|
395
|
+
{
|
|
396
|
+
"$divide": [
|
|
397
|
+
{
|
|
398
|
+
"$max": [
|
|
399
|
+
0,
|
|
400
|
+
{
|
|
401
|
+
"$subtract": [
|
|
402
|
+
"$expiresAt",
|
|
403
|
+
{
|
|
404
|
+
"$add": [
|
|
405
|
+
"$$NOW",
|
|
406
|
+
expiry_ms,
|
|
407
|
+
]
|
|
408
|
+
},
|
|
409
|
+
]
|
|
410
|
+
},
|
|
411
|
+
]
|
|
412
|
+
},
|
|
413
|
+
expiry_ms,
|
|
414
|
+
]
|
|
415
|
+
},
|
|
416
|
+
]
|
|
417
|
+
},
|
|
418
|
+
"$currentCount",
|
|
419
|
+
]
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
"$set": {
|
|
426
|
+
"currentCount": {
|
|
427
|
+
"$cond": {
|
|
428
|
+
"if": {
|
|
429
|
+
"$lte": [
|
|
430
|
+
{"$add": ["$curWeightedCount", amount]},
|
|
431
|
+
limit,
|
|
432
|
+
]
|
|
433
|
+
},
|
|
434
|
+
"then": {"$add": ["$currentCount", amount]},
|
|
435
|
+
"else": "$currentCount",
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
"$set": {
|
|
442
|
+
"_acquired": {
|
|
443
|
+
"$lte": [{"$add": ["$curWeightedCount", amount]}, limit]
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
{"$unset": ["curWeightedCount"]},
|
|
448
|
+
],
|
|
449
|
+
return_document=self.proxy_dependency.module.ReturnDocument.AFTER,
|
|
450
|
+
upsert=True,
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
return cast(bool, result["_acquired"])
|
|
454
|
+
|
|
455
|
+
async def get_sliding_window(
|
|
456
|
+
self, key: str, expiry: int
|
|
457
|
+
) -> tuple[int, float, int, float]:
|
|
458
|
+
expiry_ms = expiry * 1000
|
|
459
|
+
if result := await self.database[
|
|
460
|
+
self.__collection_mapping["windows"]
|
|
461
|
+
].find_one_and_update(
|
|
462
|
+
{"_id": key},
|
|
463
|
+
[
|
|
464
|
+
{
|
|
465
|
+
"$set": {
|
|
466
|
+
"previousCount": {
|
|
467
|
+
"$cond": {
|
|
468
|
+
"if": {
|
|
469
|
+
"$lte": [
|
|
470
|
+
{"$subtract": ["$expiresAt", "$$NOW"]},
|
|
471
|
+
expiry_ms,
|
|
472
|
+
]
|
|
473
|
+
},
|
|
474
|
+
"then": {"$ifNull": ["$currentCount", 0]},
|
|
475
|
+
"else": {"$ifNull": ["$previousCount", 0]},
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
"currentCount": {
|
|
479
|
+
"$cond": {
|
|
480
|
+
"if": {
|
|
481
|
+
"$lte": [
|
|
482
|
+
{"$subtract": ["$expiresAt", "$$NOW"]},
|
|
483
|
+
expiry_ms,
|
|
484
|
+
]
|
|
485
|
+
},
|
|
486
|
+
"then": 0,
|
|
487
|
+
"else": {"$ifNull": ["$currentCount", 0]},
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
"expiresAt": {
|
|
491
|
+
"$cond": {
|
|
492
|
+
"if": {
|
|
493
|
+
"$lte": [
|
|
494
|
+
{"$subtract": ["$expiresAt", "$$NOW"]},
|
|
495
|
+
expiry_ms,
|
|
496
|
+
]
|
|
497
|
+
},
|
|
498
|
+
"then": {"$add": ["$expiresAt", expiry_ms]},
|
|
499
|
+
"else": "$expiresAt",
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
],
|
|
505
|
+
return_document=self.proxy_dependency.module.ReturnDocument.AFTER,
|
|
506
|
+
projection=["currentCount", "previousCount", "expiresAt"],
|
|
507
|
+
):
|
|
508
|
+
expires_at = (
|
|
509
|
+
(result["expiresAt"].replace(tzinfo=datetime.timezone.utc).timestamp())
|
|
510
|
+
if result.get("expiresAt")
|
|
511
|
+
else time.time()
|
|
512
|
+
)
|
|
513
|
+
current_ttl = max(0, expires_at - time.time())
|
|
514
|
+
prev_ttl = max(0, current_ttl - expiry if result["previousCount"] else 0)
|
|
515
|
+
|
|
516
|
+
return (
|
|
517
|
+
result["previousCount"],
|
|
518
|
+
prev_ttl,
|
|
519
|
+
result["currentCount"],
|
|
520
|
+
current_ttl,
|
|
521
|
+
)
|
|
522
|
+
return 0, 0.0, 0, 0.0
|