Skip to content

Commit 02027b0

Browse files
committed
fix RateLimitManager (again)
1 parent ba82046 commit 02027b0

File tree

3 files changed

+58
-62
lines changed

3 files changed

+58
-62
lines changed

src/bitvavo_client/auth/rate_limit.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,9 @@ def __call__(self, manager: RateLimitManager, idx: int, _: int) -> None:
2020

2121

2222
class RateLimitManager:
23-
"""Manages rate limiting for multiple API keys and keyless requests.
23+
"""Manages rate limiting for multiple API keys.
2424
25-
Each API key index has its own rate limit state. Index -1 is reserved
26-
for keyless requests.
25+
Each API key index has its own rate limit state.
2726
"""
2827

2928
def __init__(self, default_remaining: int, buffer: int, strategy: RateLimitStrategy | None = None) -> None:
@@ -35,21 +34,21 @@ def __init__(self, default_remaining: int, buffer: int, strategy: RateLimitStrat
3534
strategy: Optional strategy callback when rate limit exceeded
3635
"""
3736
self.default_remaining: int = default_remaining
38-
self.state: dict[int, dict[str, int]] = {-1: {"remaining": default_remaining, "resetAt": 0}}
37+
self.state: dict[int, dict[str, int]] = {}
3938
self.buffer: int = buffer
4039

4140
self._strategy: RateLimitStrategy = strategy or DefaultRateLimitStrategy()
4241

4342
def ensure_key(self, idx: int) -> None:
4443
"""Ensure a key index exists in the state."""
4544
if idx not in self.state:
46-
self.state[idx] = {"remaining": self.state[-1]["remaining"], "resetAt": 0}
45+
self.state[idx] = {"remaining": self.default_remaining, "resetAt": 0}
4746

4847
def has_budget(self, idx: int, weight: int) -> bool:
4948
"""Check if there's enough rate limit budget for a request.
5049
5150
Args:
52-
idx: API key index (-1 for keyless)
51+
idx: API key index
5352
weight: Weight of the request
5453
5554
Returns:
@@ -66,7 +65,7 @@ def record_call(self, idx: int, weight: int) -> None:
6665
the response doesn't include rate limit headers.
6766
6867
Args:
69-
idx: API key index (-1 for keyless)
68+
idx: API key index
7069
weight: Weight of the request
7170
"""
7271
self.ensure_key(idx)

tests/bitvavo_client/auth/test_rate_limit.py

Lines changed: 51 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,25 @@ def test_init_default_state(self) -> None:
1616
"""Test initialization creates correct default state."""
1717
manager = RateLimitManager(default_remaining=1000, buffer=50)
1818

19-
# Should initialize with keyless state (index -1)
20-
assert -1 in manager.state
21-
assert manager.state[-1]["remaining"] == 1000
22-
assert manager.state[-1]["resetAt"] == 0
19+
# Should initialize with empty state
20+
assert manager.state == {}
21+
assert manager.default_remaining == 1000
2322
assert manager.buffer == 50
2423

2524
def test_init_with_different_values(self) -> None:
2625
"""Test initialization with different parameter values."""
2726
manager = RateLimitManager(default_remaining=500, buffer=25)
2827

29-
assert manager.state[-1]["remaining"] == 500
30-
assert manager.state[-1]["resetAt"] == 0
28+
assert manager.state == {}
29+
assert manager.default_remaining == 500
3130
assert manager.buffer == 25
3231

3332
def test_init_with_zero_values(self) -> None:
3433
"""Test initialization with zero values."""
3534
manager = RateLimitManager(default_remaining=0, buffer=0)
3635

37-
assert manager.state[-1]["remaining"] == 0
36+
assert manager.state == {}
37+
assert manager.default_remaining == 0
3838
assert manager.buffer == 0
3939

4040

@@ -45,14 +45,14 @@ def test_ensure_key_creates_new_key(self) -> None:
4545
"""Test that ensure_key creates new key entries."""
4646
manager = RateLimitManager(default_remaining=1000, buffer=50)
4747

48-
# Initially only keyless (-1) exists
48+
# Initially state is empty
4949
assert 0 not in manager.state
5050
assert 1 not in manager.state
5151

5252
# Ensure key 0 exists
5353
manager.ensure_key(0)
5454
assert 0 in manager.state
55-
assert manager.state[0]["remaining"] == 1000 # Inherits from keyless
55+
assert manager.state[0]["remaining"] == 1000 # Uses default remaining
5656
assert manager.state[0]["resetAt"] == 0
5757

5858
# Ensure key 1 exists
@@ -79,7 +79,7 @@ def test_ensure_key_negative_index(self) -> None:
7979
"""Test ensure_key with negative indices."""
8080
manager = RateLimitManager(default_remaining=1000, buffer=50)
8181

82-
# Keyless (-1) already exists
82+
# Create negative index key
8383
manager.ensure_key(-1)
8484
assert manager.state[-1]["remaining"] == 1000
8585

@@ -88,17 +88,13 @@ def test_ensure_key_negative_index(self) -> None:
8888
assert -5 in manager.state
8989
assert manager.state[-5]["remaining"] == 1000
9090

91-
def test_ensure_key_inherits_from_keyless(self) -> None:
92-
"""Test that new keys inherit values from keyless state."""
91+
def test_ensure_key_uses_default_remaining(self) -> None:
92+
"""Test that new keys use the default remaining value."""
9393
manager = RateLimitManager(default_remaining=800, buffer=30)
9494

95-
# Modify keyless state
96-
manager.state[-1]["remaining"] = 600
97-
manager.state[-1]["resetAt"] = 54321
98-
99-
# New key should inherit from keyless
95+
# New key should use default remaining value
10096
manager.ensure_key(2)
101-
assert manager.state[2]["remaining"] == 600
97+
assert manager.state[2]["remaining"] == 800
10298
assert manager.state[2]["resetAt"] == 0 # resetAt is always initialized to 0
10399

104100

@@ -109,20 +105,20 @@ def test_has_budget_sufficient_remaining(self) -> None:
109105
"""Test has_budget when there's sufficient remaining budget."""
110106
manager = RateLimitManager(default_remaining=1000, buffer=50)
111107

112-
# Keyless request with plenty of budget
113-
assert manager.has_budget(-1, 100) is True
114-
assert manager.has_budget(-1, 900) is True
108+
# Request with plenty of budget (any key index)
109+
assert manager.has_budget(0, 100) is True
110+
assert manager.has_budget(0, 900) is True
115111

116112
# Request right at the buffer boundary
117-
assert manager.has_budget(-1, 950) is True # 1000 - 950 = 50 (equals buffer)
113+
assert manager.has_budget(0, 950) is True # 1000 - 950 = 50 (equals buffer)
118114

119115
def test_has_budget_insufficient_remaining(self) -> None:
120116
"""Test has_budget when there's insufficient remaining budget."""
121117
manager = RateLimitManager(default_remaining=1000, buffer=50)
122118

123119
# Request that would go below buffer
124-
assert manager.has_budget(-1, 951) is False # 1000 - 951 = 49 (less than buffer)
125-
assert manager.has_budget(-1, 1000) is False
120+
assert manager.has_budget(0, 951) is False # 1000 - 951 = 49 (less than buffer)
121+
assert manager.has_budget(0, 1000) is False
126122

127123
def test_has_budget_creates_key_if_needed(self) -> None:
128124
"""Test that has_budget creates key index if it doesn't exist."""
@@ -155,9 +151,9 @@ def test_has_budget_zero_buffer(self) -> None:
155151
"""Test has_budget behavior with zero buffer."""
156152
manager = RateLimitManager(default_remaining=100, buffer=0)
157153

158-
assert manager.has_budget(-1, 99) is True # 100 - 99 = 1 > 0
159-
assert manager.has_budget(-1, 100) is True # 100 - 100 = 0 = 0 (equals buffer)
160-
assert manager.has_budget(-1, 101) is False # 100 - 101 = -1 < 0
154+
assert manager.has_budget(0, 99) is True # 100 - 99 = 1 > 0
155+
assert manager.has_budget(0, 100) is True # 100 - 100 = 0 = 0 (equals buffer)
156+
assert manager.has_budget(0, 101) is False # 100 - 101 = -1 < 0
161157

162158

163159
class TestRateLimitManagerCallRecording:
@@ -168,8 +164,8 @@ def test_record_call_updates_remaining(self) -> None:
168164
manager = RateLimitManager(default_remaining=1000, buffer=50)
169165

170166
# Record a call and verify remaining budget decreased
171-
manager.record_call(-1, 100)
172-
assert manager.get_remaining(-1) == 900
167+
manager.record_call(0, 100)
168+
assert manager.get_remaining(0) == 900
173169

174170
def test_record_call_creates_key(self) -> None:
175171
"""record_call should ensure key exists before updating."""
@@ -431,12 +427,13 @@ def test_get_remaining_existing_key(self) -> None:
431427
"""Test getting remaining limit for existing key."""
432428
manager = RateLimitManager(default_remaining=1000, buffer=50)
433429

434-
# Test keyless
435-
assert manager.get_remaining(-1) == 1000
430+
# Test a key
431+
assert manager.get_remaining(1) == 1000
436432

437433
# Test after modification
438-
manager.state[-1]["remaining"] = 750
439-
assert manager.get_remaining(-1) == 750
434+
manager.ensure_key(1)
435+
manager.state[1]["remaining"] = 750
436+
assert manager.get_remaining(1) == 750
440437

441438
# Test specific key
442439
manager.ensure_key(0)
@@ -453,18 +450,19 @@ def test_get_remaining_creates_key_if_needed(self) -> None:
453450
remaining = manager.get_remaining(4)
454451

455452
assert 4 in manager.state
456-
assert remaining == 800 # Should inherit from keyless
453+
assert remaining == 800 # Should use default remaining
457454

458455
def test_get_reset_at_existing_key(self) -> None:
459456
"""Test getting reset timestamp for existing key."""
460457
manager = RateLimitManager(default_remaining=1000, buffer=50)
461458

462-
# Test keyless (initially 0)
463-
assert manager.get_reset_at(-1) == 0
459+
# Test a key (initially 0)
460+
assert manager.get_reset_at(1) == 0
464461

465462
# Test after modification
466-
manager.state[-1]["resetAt"] = 1693843200000
467-
assert manager.get_reset_at(-1) == 1693843200000
463+
manager.ensure_key(1)
464+
manager.state[1]["resetAt"] = 1693843200000
465+
assert manager.get_reset_at(1) == 1693843200000
468466

469467
# Test specific key
470468
manager.ensure_key(0)
@@ -567,28 +565,27 @@ def test_multiple_keys_independence(self) -> None:
567565
assert manager.get_remaining(0) == 0 # Affected by error
568566
assert manager.get_remaining(1) == 200 # Unaffected
569567

570-
def test_keyless_vs_keyed_requests(self) -> None:
571-
"""Test behavior differences between keyless and keyed requests."""
568+
def test_independent_key_state_management(self) -> None:
569+
"""Test that different API keys have independent state."""
572570
manager = RateLimitManager(default_remaining=500, buffer=25)
573571

574-
# Initially both keyless and new keys have same state
575-
assert manager.get_remaining(-1) == 500
576-
assert manager.get_remaining(0) == 500 # Inherits from keyless
572+
# Initially new keys use default remaining
573+
assert manager.get_remaining(0) == 500
574+
assert manager.get_remaining(1) == 500
577575

578-
# Update keyless state
579-
headers_keyless = {"bitvavo-ratelimit-remaining": "300"}
580-
manager.update_from_headers(-1, headers_keyless)
576+
# Update different keys independently
577+
headers_key0 = {"bitvavo-ratelimit-remaining": "300"}
578+
manager.update_from_headers(0, headers_key0)
581579

582-
# Update keyed state
583-
headers_keyed = {"bitvavo-ratelimit-remaining": "400"}
584-
manager.update_from_headers(0, headers_keyed)
580+
headers_key1 = {"bitvavo-ratelimit-remaining": "400"}
581+
manager.update_from_headers(1, headers_key1)
585582

586-
# States should now be independent
587-
assert manager.get_remaining(-1) == 300
588-
assert manager.get_remaining(0) == 400
583+
# States should be independent
584+
assert manager.get_remaining(0) == 300
585+
assert manager.get_remaining(1) == 400
589586

590-
# New keys should still inherit from keyless
591-
assert manager.get_remaining(1) == 300 # Inherits current keyless state
587+
# New keys should still use default remaining
588+
assert manager.get_remaining(2) == 500 # Uses default remaining
592589

593590
@patch("time.sleep")
594591
@patch("time.time")

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)