Skip to content

Commit a8554fd

Browse files
committed
✨ feat: add ratelimit support
1 parent eeb58d7 commit a8554fd

File tree

4 files changed

+77
-3
lines changed

4 files changed

+77
-3
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,19 @@ Example:
104104
gw = Gateway(max_retries=5, retry_backoff_factor=0.5, retry_backoff_jitter=1.0, retry_backoff_max=120)
105105
````
106106

107+
## Rate Limit Options
108+
109+
the following options can be provided when initializing the Gateway to customize the rate limit behavior of the SDK.
110+
111+
These options are:
112+
- limiter_max_requests (integer, default 5)
113+
- limiter_window (integer, dafault 1)
114+
115+
Example:
116+
```python
117+
gw = Gateway(limiter_max_requests=20, limiter_window=5)
118+
````
119+
107120
# Example
108121

109122
A simple example that prints all your Virtual Machine and Volume IDs.

osc_sdk_python/call.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@
99

1010

1111
class Call(object):
12-
def __init__(self, logger=None, **kwargs):
12+
def __init__(self, logger=None, limiter=None, **kwargs):
1313
self.version = kwargs.pop("version", "latest")
1414
self.host = kwargs.pop("host", None)
1515
self.ssl = kwargs.pop("_ssl", True)
1616
self.user_agent = kwargs.pop("user_agent", DEFAULT_USER_AGENT)
1717
self.logger = logger
18+
self.limiter = limiter
1819
self.update_credentials(
1920
access_key=kwargs.pop("access_key", None),
2021
secret_key=kwargs.pop("secret_key", None),
@@ -29,6 +30,10 @@ def __init__(self, logger=None, **kwargs):
2930
retry_backoff_jitter=kwargs.pop("retry_backoff_jitter", None),
3031
retry_backoff_max=kwargs.pop("retry_backoff_max", None)
3132
)
33+
self.update_limiter(
34+
limiter_max_requests=kwargs.pop("limiter_max_requests", None),
35+
limiter_window=kwargs.pop("limiter_window", None)
36+
)
3237

3338
def update_credentials(
3439
self,
@@ -59,6 +64,18 @@ def update_credentials(
5964
"retry_backoff_max": retry_backoff_max,
6065
}
6166

67+
def update_limiter(
68+
self,
69+
limiter_window=None,
70+
limiter_max_requests=None,
71+
):
72+
if limiter_window is not None:
73+
self.limiter.window = limiter_window
74+
75+
if limiter_max_requests is not None:
76+
self.limiter.max_requests = limiter_max_requests
77+
78+
6279
def api(self, action, **data):
6380
try:
6481
credentials = Credentials(**self.credentials)
@@ -78,6 +95,9 @@ def api(self, action, **data):
7895
else:
7996
endpoint = "{}{}".format(endpoint, uri)
8097

98+
if self.limiter is not None:
99+
self.limiter.acquire()
100+
81101
requester = Requester(
82102
Authentication(credentials, host, user_agent=self.user_agent),
83103
endpoint,
@@ -86,7 +106,7 @@ def api(self, action, **data):
86106
credentials.retry_backoff_jitter,
87107
credentials.retry_backoff_max,
88108
)
89-
if self.logger != None:
109+
if self.logger is not None:
90110
self.logger.do_log(
91111
"uri: " + uri + "\npayload:\n" + json.dumps(data, indent=2)
92112
)

osc_sdk_python/limiter.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from datetime import datetime, timezone, timedelta
2+
import time
3+
4+
5+
class RateLimiter:
6+
def __init__(self, window: int, max_requests: int):
7+
self.window = window
8+
self.max_requests = max_requests
9+
self.requests = []
10+
11+
def acquire(self):
12+
now = datetime.now(timezone.utc)
13+
14+
self.clean_old_requests(now)
15+
16+
if len(self.requests) >= self.max_requests:
17+
oldest = self.requests[0]
18+
wait_time = self.window - (now - oldest)
19+
time.sleep(wait_time.total_seconds())
20+
21+
now = datetime.now(timezone.utc)
22+
self.clean_old_requests(now)
23+
24+
self.requests.append(now)
25+
26+
def clean_old_requests(self, now):
27+
while len(self.requests) > 0 and self.requests[0] <= now - timedelta(
28+
seconds=self.window
29+
):
30+
self.requests.pop(0)

osc_sdk_python/outscale_gateway.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import sys
33
from .call import Call
44
from .credentials import Credentials
5+
from .limiter import RateLimiter
56
import ruamel.yaml
67
from osc_sdk_python import __version__
78

@@ -17,6 +18,10 @@
1718
LOG_ALL = 0
1819
LOG_KEEP_ONLY_LAST_REQ = 1
1920

21+
# Default
22+
DEFAULT_LIMITER_WINDOW = 1 # 1 second
23+
DEFAULT_LIMITER_MAX_REQUESTS = 5 # 5 requests / sec
24+
2025

2126
class ActionNotExists(NotImplementedError):
2227
pass
@@ -68,7 +73,13 @@ def __init__(self, **kwargs):
6873
self._load_gateway_structure()
6974
self._load_errors()
7075
self.log = Logger()
71-
self.call = Call(logger=self.log, version=self.endpoint_api_version, **kwargs)
76+
self.limiter = RateLimiter(DEFAULT_LIMITER_WINDOW, DEFAULT_LIMITER_MAX_REQUESTS)
77+
self.call = Call(
78+
logger=self.log,
79+
version=self.endpoint_api_version,
80+
limiter=self.limiter,
81+
**kwargs,
82+
)
7283

7384
def update_credentials(self, **kwargs):
7485
"""

0 commit comments

Comments
 (0)