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 CHANGED
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2024-03-17T11:35:51-0700",
11
+ "date": "2024-05-12T10:01:06-0700",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "cf7f1e273700987e38e2af734719253785a303b0",
15
- "version": "3.10.1"
14
+ "full-revisionid": "ff28751a2326de0ad6a978e316397534acf29b81",
15
+ "version": "3.12.0"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -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:
@@ -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 = counter["expireAt"] if counter else datetime.datetime.utcnow()
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
- {"_id": key, "expireAt": {"$gte": datetime.datetime.utcnow()}},
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.utcnow() + datetime.timedelta(seconds=expiry)
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.utcnow() + datetime.timedelta(seconds=expiry)
264
+ datetime.datetime.now(datetime.timezone.utc)
265
+ + datetime.timedelta(seconds=expiry)
256
266
  )
257
267
  }
258
268
  updates["$push"]["entries"]["$each"] = [timestamp] * amount
@@ -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 typing import TYPE_CHECKING, Any
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 Dict, Optional, Tuple, Type, Union
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
- self.storage: "pymongo.MongoClient" = self.lib.MongoClient( # type: ignore[type-arg]
56
- uri, **options
57
- )
58
- self.counters = self.storage.get_database(database_name).counters
59
- self.windows = self.storage.get_database(database_name).windows
60
- self.__initialize_database()
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 = counter["expireAt"] if counter else datetime.datetime.utcnow()
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
- {"_id": key, "expireAt": {"$gte": datetime.datetime.utcnow()}},
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.utcnow() + datetime.timedelta(seconds=expiry)
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.utcnow() + datetime.timedelta(seconds=expiry)
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.10.1
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.7
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=PvRq5kmAeA-fueZ3szG1C22nkUmb9GSWeeUdlNI0Q0Q,498
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=nZjoMCwQBoRKiyJJvpM0pY3ZA8UgL3A5HFN8rwJrmi8,2910
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=dSM39OguwdLdBIHaz98eWljHzxUfgcatweNSeo9d8OQ,4795
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=5Xm1oUbMQ9UjenE0s6o6Cdea35ntqWhEDUv6HEx79kQ,9183
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=8i1-SoTEV_XGAMYDepcLra7do-Tx4rUPbPrUQVVJgTw,2518
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=U68bKPbEjkZJXwgyqI329Hph6ZBvPpMs54YKW15fZ6k,8079
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.10.1.dist-info/LICENSE.txt,sha256=T6i7kq7F5gIPfcno9FCxU5Hcwm22Bjq0uHZV3ElcjsQ,1061
34
- limits-3.10.1.dist-info/METADATA,sha256=diqcU_qYhkekhZHIOLAcShrWhOqX4I2PM3c_qyrzzgM,7119
35
- limits-3.10.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
36
- limits-3.10.1.dist-info/top_level.txt,sha256=C7g5ahldPoU2s6iWTaJayUrbGmPK1d6e9t5Nn0vQ2jM,7
37
- limits-3.10.1.dist-info/RECORD,,
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,,