diff --git a/api_core/google/api_core/bidi.py b/api_core/google/api_core/bidi.py index fed8d6f03d2e..3b69e91be16c 100644 --- a/api_core/google/api_core/bidi.py +++ b/api_core/google/api_core/bidi.py @@ -140,8 +140,8 @@ def __iter__(self): class _Throttle(object): """A context manager limiting the total entries in a sliding time window. - If more than ``entry_cap`` attempts are made to enter the context manager - instance in the last ``time window`` seconds, the exceeding requests block + If more than ``access_limit`` attempts are made to enter the context manager + instance in the last ``time window`` interval, the exceeding requests block until enough time elapses. The context manager instances are thread-safe and can be shared between @@ -150,27 +150,29 @@ class _Throttle(object): Example:: - max_three_per_second = Throttle(time_window=1, entry_cap=3) + max_three_per_second = _Throttle( + access_limit=3, time_window=datetime.timedelta(seconds=1) + ) for i in range(5): with max_three_per_second as time_waited: print("{}: Waited {} seconds to enter".format(i, time_waited)) Args: - time_window (float): the width of the sliding time window in seconds - entry_cap (int): the maximum number of entries allowed in the time window + access_limit (int): the maximum number of entries allowed in the time window + time_window (datetime.timedelta): the width of the sliding time window """ - def __init__(self, time_window, entry_cap): - if time_window <= 0.0: - raise ValueError("time_window argument must be positive") + def __init__(self, access_limit, time_window): + if access_limit < 1: + raise ValueError("access_limit argument must be positive") - if entry_cap < 1: - raise ValueError("entry_cap argument must be positive") + if time_window <= datetime.timedelta(0): + raise ValueError("time_window argument must be a positive timedelta") - self._time_window = datetime.timedelta(seconds=time_window) - self._entry_cap = entry_cap - self._past_entries = collections.deque(maxlen=entry_cap) # least recent first + self._time_window = time_window + self._access_limit = access_limit + self._past_entries = collections.deque(maxlen=access_limit) # least recent first self._entry_lock = threading.Lock() def __enter__(self): @@ -181,7 +183,7 @@ def __enter__(self): while self._past_entries and self._past_entries[0] < cutoff_time: self._past_entries.popleft() - if len(self._past_entries) < self._entry_cap: + if len(self._past_entries) < self._access_limit: self._past_entries.append(datetime.datetime.now()) return 0.0 # no waiting was needed @@ -195,8 +197,10 @@ def __exit__(self, *_): pass def __repr__(self): - return "{}(time_window={}, entry_cap={})".format( - self.__class__.__name__, self._time_window.total_seconds(), self._entry_cap + return "{}(access_limit={}, time_window={})".format( + self.__class__.__name__, + self._access_limit, + repr(self._time_window), ) @@ -408,7 +412,9 @@ def __init__( self._finalize_lock = threading.Lock() if throttle_reopen: - self._reopen_throttle = _Throttle(entry_cap=5, time_window=10) + self._reopen_throttle = _Throttle( + access_limit=5, time_window=datetime.timedelta(seconds=10), + ) else: self._reopen_throttle = None diff --git a/api_core/tests/unit/test_bidi.py b/api_core/tests/unit/test_bidi.py index 934522b8a233..8e9f26202fde 100644 --- a/api_core/tests/unit/test_bidi.py +++ b/api_core/tests/unit/test_bidi.py @@ -119,22 +119,30 @@ def test_exit_with_stop(self): class Test_Throttle(object): def test_repr(self): - instance = bidi._Throttle(time_window=4.5, entry_cap=42) - assert repr(instance) == "_Throttle(time_window=4.5, entry_cap=42)" + delta = datetime.timedelta(seconds=4.5) + instance = bidi._Throttle(access_limit=42, time_window=delta) + assert repr(instance) == \ + "_Throttle(access_limit=42, time_window={})".format(repr(delta)) def test_raises_error_on_invalid_init_arguments(self): with pytest.raises(ValueError) as exc_info: - bidi._Throttle(time_window=0.0, entry_cap=10) + bidi._Throttle( + access_limit=10, time_window=datetime.timedelta(seconds=0.0) + ) assert "time_window" in str(exc_info.value) - assert "must be positive" in str(exc_info.value) + assert "must be a positive timedelta" in str(exc_info.value) with pytest.raises(ValueError) as exc_info: - bidi._Throttle(time_window=10, entry_cap=0) - assert "entry_cap" in str(exc_info.value) + bidi._Throttle( + access_limit=0, time_window=datetime.timedelta(seconds=10) + ) + assert "access_limit" in str(exc_info.value) assert "must be positive" in str(exc_info.value) def test_does_not_delay_entry_attempts_under_threshold(self): - throttle = bidi._Throttle(time_window=1, entry_cap=3) + throttle = bidi._Throttle( + access_limit=3, time_window=datetime.timedelta(seconds=1) + ) entries = [] for _ in range(3): @@ -155,7 +163,9 @@ def test_does_not_delay_entry_attempts_under_threshold(self): assert delta.total_seconds() < 0.1 def test_delays_entry_attempts_above_threshold(self): - throttle = bidi._Throttle(time_window=1, entry_cap=3) + throttle = bidi._Throttle( + access_limit=3, time_window=datetime.timedelta(seconds=1) + ) entries = [] for _ in range(6):