Skip to content

Commit

Permalink
Change bidi._Throttle signature
Browse files Browse the repository at this point in the history
The commit renames the entry_cap parameter to access_limit, and
changes the type of the time_window argument from float to timedelta.
  • Loading branch information
plamut committed Jun 18, 2019
1 parent f8fc810 commit 2d9cc2a
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 25 deletions.
40 changes: 23 additions & 17 deletions api_core/google/api_core/bidi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand All @@ -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

Expand All @@ -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),
)


Expand Down Expand Up @@ -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

Expand Down
26 changes: 18 additions & 8 deletions api_core/tests/unit/test_bidi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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):
Expand Down

0 comments on commit 2d9cc2a

Please sign in to comment.