limits 3.10.1__py3-none-any.whl → 3.12.0__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/_version.py +3 -3
- limits/aio/storage/base.py +1 -1
- limits/aio/storage/mongodb.py +14 -4
- limits/storage/__init__.py +2 -1
- limits/storage/mongodb.py +67 -20
- limits/typing.py +10 -1
- {limits-3.10.1.dist-info → limits-3.12.0.dist-info}/METADATA +3 -2
- {limits-3.10.1.dist-info → limits-3.12.0.dist-info}/RECORD +11 -11
- {limits-3.10.1.dist-info → limits-3.12.0.dist-info}/LICENSE.txt +0 -0
- {limits-3.10.1.dist-info → limits-3.12.0.dist-info}/WHEEL +0 -0
- {limits-3.10.1.dist-info → limits-3.12.0.dist-info}/top_level.txt +0 -0
limits/_version.py
CHANGED
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2024-
|
|
11
|
+
"date": "2024-05-12T10:01:06-0700",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "3.
|
|
14
|
+
"full-revisionid": "ff28751a2326de0ad6a978e316397534acf29b81",
|
|
15
|
+
"version": "3.12.0"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
limits/aio/storage/base.py
CHANGED
|
@@ -27,7 +27,7 @@ def _wrap_errors(
|
|
|
27
27
|
fn: Callable[P, Awaitable[R]],
|
|
28
28
|
) -> Callable[P, Awaitable[R]]:
|
|
29
29
|
@functools.wraps(fn)
|
|
30
|
-
async def inner(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
30
|
+
async def inner(*args: P.args, **kwargs: P.kwargs) -> R: # type: ignore[misc]
|
|
31
31
|
try:
|
|
32
32
|
return await fn(*args, **kwargs)
|
|
33
33
|
except storage.base_exceptions as exc:
|
limits/aio/storage/mongodb.py
CHANGED
|
@@ -116,7 +116,11 @@ class MongoDBStorage(Storage, MovingWindowSupport):
|
|
|
116
116
|
:param key: the key to get the expiry for
|
|
117
117
|
"""
|
|
118
118
|
counter = await self.database.counters.find_one({"_id": key})
|
|
119
|
-
expiry =
|
|
119
|
+
expiry = (
|
|
120
|
+
counter["expireAt"]
|
|
121
|
+
if counter
|
|
122
|
+
else datetime.datetime.now(datetime.timezone.utc)
|
|
123
|
+
)
|
|
120
124
|
|
|
121
125
|
return calendar.timegm(expiry.timetuple())
|
|
122
126
|
|
|
@@ -125,7 +129,10 @@ class MongoDBStorage(Storage, MovingWindowSupport):
|
|
|
125
129
|
:param key: the key to get the counter value for
|
|
126
130
|
"""
|
|
127
131
|
counter = await self.database.counters.find_one(
|
|
128
|
-
{
|
|
132
|
+
{
|
|
133
|
+
"_id": key,
|
|
134
|
+
"expireAt": {"$gte": datetime.datetime.now(datetime.timezone.utc)},
|
|
135
|
+
},
|
|
129
136
|
projection=["count"],
|
|
130
137
|
)
|
|
131
138
|
|
|
@@ -145,7 +152,9 @@ class MongoDBStorage(Storage, MovingWindowSupport):
|
|
|
145
152
|
"""
|
|
146
153
|
await self.create_indices()
|
|
147
154
|
|
|
148
|
-
expiration = datetime.datetime.
|
|
155
|
+
expiration = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
|
|
156
|
+
seconds=expiry
|
|
157
|
+
)
|
|
149
158
|
|
|
150
159
|
response = await self.database.counters.find_one_and_update(
|
|
151
160
|
{"_id": key},
|
|
@@ -252,7 +261,8 @@ class MongoDBStorage(Storage, MovingWindowSupport):
|
|
|
252
261
|
|
|
253
262
|
updates["$set"] = {
|
|
254
263
|
"expireAt": (
|
|
255
|
-
datetime.datetime.
|
|
264
|
+
datetime.datetime.now(datetime.timezone.utc)
|
|
265
|
+
+ datetime.timedelta(seconds=expiry)
|
|
256
266
|
)
|
|
257
267
|
}
|
|
258
268
|
updates["$push"]["entries"]["$each"] = [timestamp] * amount
|
limits/storage/__init__.py
CHANGED
|
@@ -13,7 +13,7 @@ from .base import MovingWindowSupport, Storage
|
|
|
13
13
|
from .etcd import EtcdStorage
|
|
14
14
|
from .memcached import MemcachedStorage
|
|
15
15
|
from .memory import MemoryStorage
|
|
16
|
-
from .mongodb import MongoDBStorage
|
|
16
|
+
from .mongodb import MongoDBStorage, MongoDBStorageBase
|
|
17
17
|
from .redis import RedisStorage
|
|
18
18
|
from .redis_cluster import RedisClusterStorage
|
|
19
19
|
from .redis_sentinel import RedisSentinelStorage
|
|
@@ -68,6 +68,7 @@ __all__ = [
|
|
|
68
68
|
"Storage",
|
|
69
69
|
"MovingWindowSupport",
|
|
70
70
|
"EtcdStorage",
|
|
71
|
+
"MongoDBStorageBase",
|
|
71
72
|
"MemoryStorage",
|
|
72
73
|
"MongoDBStorage",
|
|
73
74
|
"RedisStorage",
|
limits/storage/mongodb.py
CHANGED
|
@@ -3,29 +3,33 @@ from __future__ import annotations
|
|
|
3
3
|
import calendar
|
|
4
4
|
import datetime
|
|
5
5
|
import time
|
|
6
|
-
from
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from typing import Any, cast
|
|
7
8
|
|
|
8
9
|
from deprecated.sphinx import versionadded
|
|
9
10
|
|
|
10
|
-
from limits.typing import
|
|
11
|
+
from limits.typing import (
|
|
12
|
+
Dict,
|
|
13
|
+
MongoClient,
|
|
14
|
+
MongoCollection,
|
|
15
|
+
MongoDatabase,
|
|
16
|
+
Optional,
|
|
17
|
+
Tuple,
|
|
18
|
+
Type,
|
|
19
|
+
Union,
|
|
20
|
+
)
|
|
11
21
|
|
|
12
22
|
from ..util import get_dependency
|
|
13
23
|
from .base import MovingWindowSupport, Storage
|
|
14
24
|
|
|
15
|
-
if TYPE_CHECKING:
|
|
16
|
-
import pymongo
|
|
17
25
|
|
|
18
|
-
|
|
19
|
-
@versionadded(version="2.1")
|
|
20
|
-
class MongoDBStorage(Storage, MovingWindowSupport):
|
|
26
|
+
class MongoDBStorageBase(Storage, MovingWindowSupport, ABC):
|
|
21
27
|
"""
|
|
22
28
|
Rate limit storage with MongoDB as backend.
|
|
23
29
|
|
|
24
30
|
Depends on :pypi:`pymongo`.
|
|
25
31
|
"""
|
|
26
32
|
|
|
27
|
-
STORAGE_SCHEME = ["mongodb", "mongodb+srv"]
|
|
28
|
-
|
|
29
33
|
DEPENDENCIES = ["pymongo"]
|
|
30
34
|
|
|
31
35
|
def __init__(
|
|
@@ -48,16 +52,39 @@ class MongoDBStorage(Storage, MovingWindowSupport):
|
|
|
48
52
|
"""
|
|
49
53
|
|
|
50
54
|
super().__init__(uri, wrap_exceptions=wrap_exceptions, **options)
|
|
51
|
-
|
|
55
|
+
self._database_name = database_name
|
|
52
56
|
self.lib = self.dependencies["pymongo"].module
|
|
53
57
|
self.lib_errors, _ = get_dependency("pymongo.errors")
|
|
58
|
+
self._storage_uri = uri
|
|
59
|
+
self._storage_options = options
|
|
60
|
+
self._storage: Optional[MongoClient] = None
|
|
54
61
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
@property
|
|
63
|
+
def storage(self) -> MongoClient:
|
|
64
|
+
if self._storage is None:
|
|
65
|
+
self._storage = self._init_mongo_client(
|
|
66
|
+
self._storage_uri, **self._storage_options
|
|
67
|
+
)
|
|
68
|
+
self.__initialize_database()
|
|
69
|
+
return self._storage
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def _database(self) -> MongoDatabase:
|
|
73
|
+
return self.storage[self._database_name]
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def counters(self) -> MongoCollection:
|
|
77
|
+
return self._database["counters"]
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def windows(self) -> MongoCollection:
|
|
81
|
+
return self._database["windows"]
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
def _init_mongo_client(
|
|
85
|
+
self, uri: Optional[str], **options: Union[int, str, bool]
|
|
86
|
+
) -> MongoClient:
|
|
87
|
+
raise NotImplementedError()
|
|
61
88
|
|
|
62
89
|
@property
|
|
63
90
|
def base_exceptions(
|
|
@@ -91,7 +118,11 @@ class MongoDBStorage(Storage, MovingWindowSupport):
|
|
|
91
118
|
:param key: the key to get the expiry for
|
|
92
119
|
"""
|
|
93
120
|
counter = self.counters.find_one({"_id": key})
|
|
94
|
-
expiry =
|
|
121
|
+
expiry = (
|
|
122
|
+
counter["expireAt"]
|
|
123
|
+
if counter
|
|
124
|
+
else datetime.datetime.now(datetime.timezone.utc)
|
|
125
|
+
)
|
|
95
126
|
|
|
96
127
|
return calendar.timegm(expiry.timetuple())
|
|
97
128
|
|
|
@@ -100,7 +131,10 @@ class MongoDBStorage(Storage, MovingWindowSupport):
|
|
|
100
131
|
:param key: the key to get the counter value for
|
|
101
132
|
"""
|
|
102
133
|
counter = self.counters.find_one(
|
|
103
|
-
{
|
|
134
|
+
{
|
|
135
|
+
"_id": key,
|
|
136
|
+
"expireAt": {"$gte": datetime.datetime.now(datetime.timezone.utc)},
|
|
137
|
+
},
|
|
104
138
|
projection=["count"],
|
|
105
139
|
)
|
|
106
140
|
|
|
@@ -116,7 +150,9 @@ class MongoDBStorage(Storage, MovingWindowSupport):
|
|
|
116
150
|
:param expiry: amount in seconds for the key to expire in
|
|
117
151
|
:param amount: the number to increment by
|
|
118
152
|
"""
|
|
119
|
-
expiration = datetime.datetime.
|
|
153
|
+
expiration = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(
|
|
154
|
+
seconds=expiry
|
|
155
|
+
)
|
|
120
156
|
|
|
121
157
|
return int(
|
|
122
158
|
self.counters.find_one_and_update(
|
|
@@ -220,7 +256,8 @@ class MongoDBStorage(Storage, MovingWindowSupport):
|
|
|
220
256
|
|
|
221
257
|
updates["$set"] = {
|
|
222
258
|
"expireAt": (
|
|
223
|
-
datetime.datetime.
|
|
259
|
+
datetime.datetime.now(datetime.timezone.utc)
|
|
260
|
+
+ datetime.timedelta(seconds=expiry)
|
|
224
261
|
)
|
|
225
262
|
}
|
|
226
263
|
updates["$push"]["entries"]["$each"] = [timestamp] * amount
|
|
@@ -238,3 +275,13 @@ class MongoDBStorage(Storage, MovingWindowSupport):
|
|
|
238
275
|
return True
|
|
239
276
|
except self.lib.errors.DuplicateKeyError:
|
|
240
277
|
return False
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@versionadded(version="2.1")
|
|
281
|
+
class MongoDBStorage(MongoDBStorageBase):
|
|
282
|
+
STORAGE_SCHEME = ["mongodb", "mongodb+srv"]
|
|
283
|
+
|
|
284
|
+
def _init_mongo_client(
|
|
285
|
+
self, uri: Optional[str], **options: Union[int, str, bool]
|
|
286
|
+
) -> MongoClient:
|
|
287
|
+
return cast(MongoClient, self.lib.MongoClient(uri, **options))
|
limits/typing.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import (
|
|
2
2
|
TYPE_CHECKING,
|
|
3
|
+
Any,
|
|
3
4
|
Awaitable,
|
|
4
5
|
Callable,
|
|
5
6
|
Dict,
|
|
@@ -12,7 +13,7 @@ from typing import (
|
|
|
12
13
|
Union,
|
|
13
14
|
)
|
|
14
15
|
|
|
15
|
-
from typing_extensions import ClassVar, Counter, ParamSpec, Protocol
|
|
16
|
+
from typing_extensions import ClassVar, Counter, ParamSpec, Protocol, TypeAlias
|
|
16
17
|
|
|
17
18
|
Serializable = Union[int, str, float]
|
|
18
19
|
|
|
@@ -24,6 +25,7 @@ P = ParamSpec("P")
|
|
|
24
25
|
if TYPE_CHECKING:
|
|
25
26
|
import coredis
|
|
26
27
|
import coredis.commands.script
|
|
28
|
+
import pymongo
|
|
27
29
|
import redis
|
|
28
30
|
|
|
29
31
|
|
|
@@ -107,6 +109,10 @@ class ScriptP(Protocol[R_co]):
|
|
|
107
109
|
def __call__(self, keys: List[Serializable], args: List[Serializable]) -> R_co: ...
|
|
108
110
|
|
|
109
111
|
|
|
112
|
+
MongoClient: TypeAlias = "pymongo.MongoClient[Dict[str, Any]]" # type:ignore[misc]
|
|
113
|
+
MongoDatabase: TypeAlias = "pymongo.database.Database[Dict[str, Any]]" # type:ignore[misc]
|
|
114
|
+
MongoCollection: TypeAlias = "pymongo.collection.Collection[Dict[str, Any]]" # type:ignore[misc]
|
|
115
|
+
|
|
110
116
|
__all__ = [
|
|
111
117
|
"AsyncRedisClient",
|
|
112
118
|
"Awaitable",
|
|
@@ -118,6 +124,9 @@ __all__ = [
|
|
|
118
124
|
"ItemP",
|
|
119
125
|
"List",
|
|
120
126
|
"MemcachedClientP",
|
|
127
|
+
"MongoClient",
|
|
128
|
+
"MongoCollection",
|
|
129
|
+
"MongoDatabase",
|
|
121
130
|
"NamedTuple",
|
|
122
131
|
"Optional",
|
|
123
132
|
"P",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: limits
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.12.0
|
|
4
4
|
Summary: Rate limiting utilities
|
|
5
5
|
Home-page: https://limits.readthedocs.org
|
|
6
6
|
Author: Ali-Akber Saifee
|
|
@@ -18,8 +18,9 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.9
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.10
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
22
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
22
|
-
Requires-Python: >=3.
|
|
23
|
+
Requires-Python: >=3.8
|
|
23
24
|
License-File: LICENSE.txt
|
|
24
25
|
Requires-Dist: deprecated >=1.2
|
|
25
26
|
Requires-Dist: importlib-resources >=1.3
|
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
limits/__init__.py,sha256=j_yVhgN9pdz8o5rQjVwdJTBSq8F-CTzof9kkiYgjRbw,728
|
|
2
|
-
limits/_version.py,sha256=
|
|
2
|
+
limits/_version.py,sha256=hEL57l9rkTEoBd9jvOB4z-OkCjpEs9jAM_ocoojdL6k,498
|
|
3
3
|
limits/errors.py,sha256=xCKGOVJiD-g8FlsQQb17AW2pTUvalYSuizPpvEVoYJE,626
|
|
4
4
|
limits/limits.py,sha256=ZsXESq2e1ji7c2ZKjSkIAasCjiLdjVLPUa9oah_I8U4,4943
|
|
5
5
|
limits/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
limits/strategies.py,sha256=7pr2V34KdOEfxnYOf882Cl2qKY-KK6HwKjdYo_IsD4c,6690
|
|
7
|
-
limits/typing.py,sha256=
|
|
7
|
+
limits/typing.py,sha256=nwJLek44QIg3869AbOSvPwotfp6JR7vEHz_UgZBiqgg,3287
|
|
8
8
|
limits/util.py,sha256=xMRR5bKksYcnzY0H0xORDGvRFF5btiBognY2sSd38NE,5743
|
|
9
9
|
limits/version.py,sha256=YwkF3dtq1KGzvmL3iVGctA8NNtGlK_0arrzZkZGVjUs,47
|
|
10
10
|
limits/aio/__init__.py,sha256=IOetunwQy1c5GefzitK8lewbTzHGiE-kmE9NlqSdr3U,82
|
|
11
11
|
limits/aio/strategies.py,sha256=REaQ-lqgqkN5wrFZ26AZ3sCHO8oZBL_mWhI6nMRaBz8,6485
|
|
12
12
|
limits/aio/storage/__init__.py,sha256=CbtuSlVl1jPyN_vsEI_ApWblDblVaL46xcZ2M_oM0V8,595
|
|
13
|
-
limits/aio/storage/base.py,sha256=
|
|
13
|
+
limits/aio/storage/base.py,sha256=xdYpBBonyMjxE9iT-2oZjm6x29aDU6Xd09MeBYbZcMo,4817
|
|
14
14
|
limits/aio/storage/etcd.py,sha256=Rjb_EYKFRr4F2Z6zvAPP9vQOyXJQHaju3VjxxUs75_c,4791
|
|
15
15
|
limits/aio/storage/memcached.py,sha256=6aTlACfCtchdcZqoisnei0MOlCH7yLV9A1yCjOE5f9g,4802
|
|
16
16
|
limits/aio/storage/memory.py,sha256=DlmWluqUwBUWQIQ6XZi-mPrb15vfzBhA4iAKhBELDnE,5856
|
|
17
|
-
limits/aio/storage/mongodb.py,sha256=
|
|
17
|
+
limits/aio/storage/mongodb.py,sha256=sFQQK-JuInGEXC5Upjk_9NBe9hoNhgxQYimdRraXxQ8,9392
|
|
18
18
|
limits/aio/storage/redis.py,sha256=jkqtdIwTpfXTXwgTWTA1Jlc3Lpc-vnu4XRy6CIptiZA,15651
|
|
19
19
|
limits/resources/redis/lua_scripts/acquire_moving_window.lua,sha256=5CFJX7D6T6RG5SFr6eVZ6zepmI1EkGWmKeVEO4QNrWo,483
|
|
20
20
|
limits/resources/redis/lua_scripts/clear_keys.lua,sha256=zU0cVfLGmapRQF9x9u0GclapM_IB2pJLszNzVQ1QRK4,184
|
|
21
21
|
limits/resources/redis/lua_scripts/incr_expire.lua,sha256=Uq9NcrrcDI-F87TDAJexoSJn2SDgeXIUEYozCp9S3oA,195
|
|
22
22
|
limits/resources/redis/lua_scripts/moving_window.lua,sha256=iAInenlVd_fFDi15APpRWbOuPUz_G3nFnVAqb7wOedA,398
|
|
23
|
-
limits/storage/__init__.py,sha256=
|
|
23
|
+
limits/storage/__init__.py,sha256=XAW1jVDMLFkPr_Tl1SXpg_p4Y3nhEatTSYq1MlnYJcA,2564
|
|
24
24
|
limits/storage/base.py,sha256=fDdYLa-RrnjhBTO1hE5aTTM8q8n3M5HD-65KyWWXBtg,4627
|
|
25
25
|
limits/storage/etcd.py,sha256=wkC_mj4Tsf2nwUKByMiHiGzA40N3mDepEwdLmvH8wmw,4484
|
|
26
26
|
limits/storage/memcached.py,sha256=bMzfZgYa_EWcZAjSZLcygpk3hpeOAErBpRE8dVwyXQs,6640
|
|
27
27
|
limits/storage/memory.py,sha256=R16E-Ccnmn1-LlolkFf-kB1-QHh8eiwFFLYVv0PuFD0,5561
|
|
28
|
-
limits/storage/mongodb.py,sha256=
|
|
28
|
+
limits/storage/mongodb.py,sha256=QWd_SW--P86SMPDrDrBQS-2xKbWY3cw9x71KsI506zY,9213
|
|
29
29
|
limits/storage/redis.py,sha256=3zJ1gDMDepT_pGN9d2aAN7Pea7tMBI49VK60IHv-Ooc,8452
|
|
30
30
|
limits/storage/redis_cluster.py,sha256=KwhWV0v3_TliRyS3OU15IlpeC8gRQr29U4FkcME01fo,5380
|
|
31
31
|
limits/storage/redis_sentinel.py,sha256=7PVB0hBl0I_enhN_h9QSJTE7zGuYtjkebotTqxm2iZo,3875
|
|
32
32
|
limits/storage/registry.py,sha256=xcBcxuu6srqmoS4WqDpkCXnRLB19ctH98v21P8S9kS8,708
|
|
33
|
-
limits-3.
|
|
34
|
-
limits-3.
|
|
35
|
-
limits-3.
|
|
36
|
-
limits-3.
|
|
37
|
-
limits-3.
|
|
33
|
+
limits-3.12.0.dist-info/LICENSE.txt,sha256=T6i7kq7F5gIPfcno9FCxU5Hcwm22Bjq0uHZV3ElcjsQ,1061
|
|
34
|
+
limits-3.12.0.dist-info/METADATA,sha256=Evty_7zeDjM5XTCfNpdERwhzmfMnOU36NdkHiQE841s,7170
|
|
35
|
+
limits-3.12.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
36
|
+
limits-3.12.0.dist-info/top_level.txt,sha256=C7g5ahldPoU2s6iWTaJayUrbGmPK1d6e9t5Nn0vQ2jM,7
|
|
37
|
+
limits-3.12.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|