Skip to content

Commit 637870c

Browse files
committed
feat: added option to load multiple api keys selected at random order
1 parent 021b325 commit 637870c

File tree

3 files changed

+85
-10
lines changed

3 files changed

+85
-10
lines changed

.github/workflows/test.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ jobs:
8282
run: pytest -m "not fuzzing" -s --cov=src -n auto
8383
env:
8484
WEB3_INFURA_PROJECT_ID: ${{ secrets.WEB3_INFURA_PROJECT_ID }}
85+
WEB3_INFURA_PROJECT_IDS: ${{ secrets.WEB3_INFURA_PROJECT_IDS }}
8586

8687
# NOTE: uncomment this block after you've marked tests with @pytest.mark.fuzzing
8788
# fuzzing:

ape_infura/provider.py

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import random
23
from typing import Optional
34

45
from ape.api import UpstreamProvider
@@ -9,7 +10,12 @@
910
from web3.gas_strategies.rpc import rpc_gas_price_strategy
1011
from web3.middleware import geth_poa_middleware
1112

12-
_ENVIRONMENT_VARIABLE_NAMES = ("WEB3_INFURA_PROJECT_ID", "WEB3_INFURA_API_KEY")
13+
_ENVIRONMENT_VARIABLE_NAMES = (
14+
"WEB3_INFURA_PROJECT_ID",
15+
"WEB3_INFURA_API_KEY",
16+
"WEB3_INFURA_PROJECT_IDS",
17+
"WEB3_INFURA_API_KEYS",
18+
)
1319
# NOTE: https://docs.infura.io/learn/websockets#supported-networks
1420
_WEBSOCKET_CAPABLE_ECOSYSTEMS = {
1521
"ethereum",
@@ -34,6 +40,27 @@ def __init__(self):
3440

3541
class Infura(Web3Provider, UpstreamProvider):
3642
network_uris: dict[tuple[str, str], str] = {}
43+
api_keys: list[str] = []
44+
45+
def __init__(self, *args, **kwargs):
46+
super().__init__(*args, **kwargs)
47+
self.load_api_keys()
48+
49+
def load_api_keys(self):
50+
self.api_keys = []
51+
for env_var_name in _ENVIRONMENT_VARIABLE_NAMES:
52+
env_var = os.environ.get(env_var_name)
53+
if env_var:
54+
if env_var_name.endswith("S"): # Handle array-like environment variables
55+
self.api_keys.extend([key.strip() for key in env_var.split(",")])
56+
else:
57+
self.api_keys.append(env_var)
58+
59+
if not self.api_keys:
60+
raise MissingProjectKeyError()
61+
62+
def get_random_api_key(self):
63+
return random.choice(self.api_keys)
3764

3865
@property
3966
def uri(self) -> str:
@@ -42,15 +69,7 @@ def uri(self) -> str:
4269
if (ecosystem_name, network_name) in self.network_uris:
4370
return self.network_uris[(ecosystem_name, network_name)]
4471

45-
key = None
46-
for env_var_name in _ENVIRONMENT_VARIABLE_NAMES:
47-
env_var = os.environ.get(env_var_name)
48-
if env_var:
49-
key = env_var
50-
break
51-
52-
if not key:
53-
raise MissingProjectKeyError()
72+
key = self.get_random_api_key()
5473

5574
prefix = f"{ecosystem_name}-" if ecosystem_name != "ethereum" else ""
5675
network_uri = f"https://{prefix}{network_name}.infura.io/v3/{key}"

tests/test_provider.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from unittest.mock import patch
2+
13
import pytest
24
import websocket # type: ignore
35
from ape.utils import ZERO_ADDRESS
@@ -31,3 +33,56 @@ def test_infura_ws(provider):
3133

3234
except Exception as err:
3335
pytest.fail(f"Websocket URI not accessible. Reason: {err}")
36+
37+
38+
def test_load_multiple_api_keys(provider):
39+
with patch.dict(
40+
os.environ,
41+
{"WEB3_INFURA_PROJECT_IDS": "key1,key2,key3", "WEB3_INFURA_API_KEYS": "key4,key5,key6"},
42+
):
43+
provider.load_api_keys()
44+
assert len(provider.api_keys) == 6
45+
assert "key1" in provider.api_keys
46+
assert "key6" in provider.api_keys
47+
48+
49+
def test_load_single_and_multiple_api_keys(provider):
50+
with patch.dict(
51+
os.environ,
52+
{
53+
"WEB3_INFURA_PROJECT_ID": "single_key1",
54+
"WEB3_INFURA_API_KEY": "single_key2",
55+
"WEB3_INFURA_PROJECT_IDS": "multi_key1,multi_key2",
56+
},
57+
):
58+
provider.load_api_keys()
59+
assert len(provider.api_keys) == 4
60+
assert "single_key1" in provider.api_keys
61+
assert "multi_key2" in provider.api_keys
62+
63+
64+
def test_random_api_key_selection(provider):
65+
with patch.dict(os.environ, {"WEB3_INFURA_PROJECT_IDS": "key1,key2,key3,key4,key5"}):
66+
provider.load_api_keys()
67+
selected_keys = set()
68+
for _ in range(50): # Run multiple times to ensure randomness
69+
selected_keys.add(provider.get_random_api_key())
70+
assert len(selected_keys) > 1 # Ensure we're getting different keys
71+
72+
73+
def test_uri_with_random_api_key(provider):
74+
with patch.dict(os.environ, {"WEB3_INFURA_PROJECT_IDS": "key1,key2,key3"}):
75+
provider.load_api_keys()
76+
uris = set()
77+
for _ in range(10): # Generate multiple URIs
78+
uri = provider.uri
79+
uris.add(uri)
80+
assert uri.startswith("https")
81+
assert "/v3/key" in uri
82+
assert len(uris) > 1 # Ensure we're getting different URIs with different keys
83+
84+
85+
def test_missing_project_key_error_raised(provider):
86+
with patch.dict("os.environ", {}, clear=True):
87+
with pytest.raises(MissingProjectKeyError):
88+
provider.load_api_keys()

0 commit comments

Comments
 (0)