This commit is contained in:
Сергей Маринкевич
2026-06-05 15:09:07 +07:00
parent 808de8562f
commit 99dc9f173d
3 changed files with 511 additions and 0 deletions
+77
View File
@@ -1,6 +1,8 @@
import os
import pickle
import random
import sqlite3
import time
from datetime import datetime, date, timezone
from typing import Tuple, Dict, List, Optional
import getpass
@@ -15,6 +17,80 @@ import json
GITLAB_URL_DEFAULT = "https://mmgit.eltex-co.ru"
COOKIE_FILE_DEFAULT = "cookies.pkl"
THROTTLE_DEFAULTS = {
"min_delay": 0.4,
"max_delay": 1.8,
"burst_pause_every": 12,
"burst_pause_min": 3.0,
"burst_pause_max": 8.0,
"file_delay_multiplier": 1.5,
}
class RequestThrottler:
"""Paces API calls with jitter and occasional longer pauses."""
def __init__(self, **opts):
self.min_delay = opts["min_delay"]
self.max_delay = opts["max_delay"]
self.burst_pause_every = opts["burst_pause_every"]
self.burst_pause_min = opts["burst_pause_min"]
self.burst_pause_max = opts["burst_pause_max"]
self.file_delay_multiplier = opts["file_delay_multiplier"]
self._request_count = 0
self._last_request_at = 0.0
def wait(self, endpoint: str = "") -> None:
self._request_count += 1
if self.burst_pause_every and self._request_count % self.burst_pause_every == 0:
delay = random.uniform(self.burst_pause_min, self.burst_pause_max)
else:
delay = random.uniform(self.min_delay, self.max_delay)
if endpoint.startswith("/files/"):
delay *= self.file_delay_multiplier
elapsed = time.monotonic() - self._last_request_at
if elapsed < delay:
time.sleep(delay - elapsed)
self._last_request_at = time.monotonic()
def _parse_throttle_config(config: dict) -> Optional[dict]:
throttle = config.get("throttle")
if not throttle:
return None
if throttle is True:
return dict(THROTTLE_DEFAULTS)
if isinstance(throttle, dict):
if not throttle.get("enabled", True):
return None
opts = dict(THROTTLE_DEFAULTS)
opts.update({k: v for k, v in throttle.items() if k != "enabled"})
return opts
return None
def apply_throttle(d: Driver, config: dict) -> None:
opts = _parse_throttle_config(config)
if not opts:
return
throttler = RequestThrottler(**opts)
client = d.client
original_make_request = client.make_request
def throttled_make_request(method, endpoint, options=None, params=None, data=None, files=None, basepath=None):
throttler.wait(endpoint)
return original_make_request(method, endpoint, options, params, data, files, basepath)
client.make_request = throttled_make_request
print(
"Request throttling enabled "
f"(delay {opts['min_delay']}-{opts['max_delay']}s, "
f"pause every {opts['burst_pause_every']} requests)"
)
def connect(host: str, login_token: str = None, username: str = None, password: str = None) -> Driver:
d = Driver({
@@ -568,6 +644,7 @@ if __name__ == '__main__':
config.get("username", None),
config.get("password", None),
)
apply_throttle(d, config)
except Exception as e:
# If token-based authentication failed, drop the token and ask
# the user to rerun the script so that fresh credentials can be