Skip to content

Commit f31036b

Browse files
committed
Fix the test case bugs for now, not the robust way.
Add test set up to ignore the annoying requests test warning. Add logs modules and test case. Add module gas tracker and test case. Add api key error. Modified README file. Modified setup.py file version.
1 parent afa8129 commit f31036b

12 files changed

+203
-7
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ Currently, only the following Etherscan.io API modules are available:
3636
- proxies
3737
- blocks
3838
- transactions
39+
- Logs
40+
- Gas Tracker
3941

4042
The remaining available modules provided by Etherscan.io will be added eventually...
4143

@@ -58,7 +60,6 @@ Jupyter notebooks area also included in each directory to show all examples
5860

5961
- Package and submit to PyPI
6062
- Add the following modules:
61-
- event logs
6263
- geth proxy
6364
- websockets
6465
- Add robust documentation

etherscan/client.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ class BadRequest(ClientException):
3030
"""Invalid request passed"""
3131

3232

33+
class InvalidAPIKey(ClientException):
34+
"""Invalid API key"""
35+
36+
3337
# Assume user puts his API key in the api_key.json
3438
# file under variable name "key"
3539
class Client(object):
@@ -59,6 +63,11 @@ class Client(object):
5963
TAG = '&tag='
6064
BOOLEAN = '&boolean='
6165
INDEX = '&index='
66+
FROM_BLOCK = '&fromBlock='
67+
TO_BLOCK = '&toBlock='
68+
TOPIC0 = '&topic0='
69+
TOPIC0_1_OPR = '&topic0_1_opr='
70+
TOPIC1 = '&topic1='
6271
API_KEY = '&apikey='
6372

6473
url_dict = {}
@@ -86,7 +95,12 @@ def __init__(self, address, api_key=''):
8695
(self.TAG, ''),
8796
(self.BOOLEAN, ''),
8897
(self.INDEX, ''),
89-
(self.API_KEY, api_key)])
98+
(self.API_KEY, api_key),
99+
(self.FROM_BLOCK, ''),
100+
(self.TO_BLOCK, ''),
101+
(self.TOPIC0, ''),
102+
(self.TOPIC0_1_OPR, ''),
103+
(self.TOPIC1, '')])
90104

91105
# Var initialization should take place within init
92106
self.url = None
@@ -119,6 +133,8 @@ def connect(self):
119133
status = data.get('status')
120134
if status == '1' or self.check_keys_api(data):
121135
return data
136+
elif status == '0' and data.get('result') == "Invalid API Key":
137+
raise InvalidAPIKey(data.get('result'))
122138
else:
123139
raise EmptyResponse(data.get('message', 'no message'))
124140
raise BadRequest(

etherscan/gas_tracker.py

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from .client import Client
2+
3+
4+
class GasTrackerException(Exception):
5+
"""Base class for exceptions in this module."""
6+
pass
7+
8+
9+
class GasTracker(Client):
10+
def __init__(self, api_key='YourApiKeyToken'):
11+
Client.__init__(self, address='', api_key=api_key)
12+
self.url_dict[self.MODULE] = 'gastracker'
13+
14+
def get_estimation_of_confirmation_time(self, gas_price: str) -> str:
15+
"""
16+
Returns the estimated time, in seconds, for a transaction to be confirmed on the blockchain.
17+
18+
Args:
19+
gas_price (str): the price paid per unit of gas, in wei
20+
21+
Returns:
22+
str: The result is returned in seconds.
23+
"""
24+
self.url_dict[self.ACTION] = 'gasestimate'
25+
self.url_dict[self.GAS_PRICE] = gas_price
26+
self.build_url()
27+
req = self.connect()
28+
return req['result']
29+
30+
def get_gas_oracle(self) -> dict:
31+
"""
32+
Returns the current Safe, Proposed and Fast gas prices.
33+
34+
Returns:
35+
dict: The gas prices are returned in Gwei.
36+
"""
37+
self.url_dict[self.ACTION] = 'gasoracle'
38+
self.build_url()
39+
req = self.connect()
40+
return req['result']
41+
42+
def get_daily_average_gas_limit(self, start_date, end_date) -> list:
43+
# TODO API Pro
44+
pass

etherscan/logs.py

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from .client import Client
2+
3+
4+
class LogsException(Exception):
5+
"""Base class for exceptions in this module."""
6+
pass
7+
8+
9+
class Logs(Client):
10+
"""
11+
The Event Log API was designed to provide an alternative to the native eth_getLogs.
12+
"""
13+
def __init__(self, api_key='YourApiKeyToken'):
14+
Client.__init__(self, address='', api_key=api_key)
15+
self.url_dict[self.MODULE] = 'logs'
16+
17+
def get_logs(self, from_block: str, to_block='latest',
18+
topic0='', topic1='', topic0_1_opr='and',) -> list:
19+
"""
20+
Get Event Logs from block number [from_block] to block [to_block] ,
21+
where log address = [address], topic[0] = [topic0] 'AND' topic[1] = [topic1]
22+
23+
Args:
24+
from_block (str): start block number
25+
to_block (str, optional): end block number. Defaults to 'latest'.
26+
topic0 (str, optional): Defaults to ''.
27+
topic1 (str, optional): Defaults to ''.
28+
topic0_1_opr (str, optional): and|or between topic0 & topic1. Defaults to 'and'.
29+
30+
Returns:
31+
list: [description]
32+
"""
33+
# TODO: support multi topics
34+
if not topic0 and topic1:
35+
raise(LogsException('can not only set topic1 while topic0 is empty'))
36+
self.url_dict[self.ACTION] = 'getLogs'
37+
self.url_dict[self.FROM_BLOCK] = from_block if type(
38+
from_block) is str else str(from_block)
39+
self.url_dict[self.TO_BLOCK] = to_block if type(
40+
to_block) is str else str(to_block)
41+
self.url_dict[self.TOPIC0] = topic0 if type(
42+
topic0) is str else hex(topic0)
43+
self.url_dict[self.TOPIC1] = topic1 if type(
44+
topic1) is str else hex(topic1)
45+
self.url_dict[self.TOPIC0_1_OPR] = topic0_1_opr
46+
self.build_url()
47+
req = self.connect()
48+
return req['result']

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
setuptools.setup(
44
name='py_etherscan_api',
5-
version='0.8.0',
5+
version='0.9.0',
66
packages=['examples', 'examples.stats', 'examples.tokens',
77
'examples.accounts', 'examples.blocks', 'examples.transactions', 'etherscan'],
88
url='https://github.com/corpetty/py-etherscan-api',

tests/test_accounts.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
11
import unittest
2+
import warnings
23

34
from etherscan.accounts import Account
45

5-
SINGLE_BALANCE = '40807178566070000000000'
6+
SINGLE_BALANCE = '40891626854930000000000'
67
SINGLE_ACCOUNT = '0xddbd2b932c763ba5b1b7ae3b362eac3e8d40121a'
78
MULTI_ACCOUNT = [
89
'0xddbd2b932c763ba5b1b7ae3b362eac3e8d40121a',
910
'0xddbd2b932c763ba5b1b7ae3b362eac3e8d40121a',
1011
]
1112
MULTI_BALANCE = [
1213
{'account': '0xddbd2b932c763ba5b1b7ae3b362eac3e8d40121a',
13-
'balance': '40807178566070000000000'},
14+
'balance': '40891626854930000000000'},
1415
{'account': '0xddbd2b932c763ba5b1b7ae3b362eac3e8d40121a',
15-
'balance': '40807178566070000000000'}
16+
'balance': '40891626854930000000000'}
1617
]
1718
API_KEY = 'YourAPIkey'
1819

1920

2021
class AccountsTestCase(unittest.TestCase):
2122

23+
def setUp(self):
24+
warnings.simplefilter('ignore', ResourceWarning)
25+
2226
def test_get_balance(self):
2327
api = Account(address=SINGLE_ACCOUNT, api_key=API_KEY)
2428
self.assertEqual(api.get_balance(), SINGLE_BALANCE)

tests/test_blocks.py

+4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import unittest
2+
import warnings
23

34
from etherscan.blocks import Blocks
45

@@ -10,6 +11,9 @@
1011

1112
class BlocksTestCase(unittest.TestCase):
1213

14+
def setUp(self):
15+
warnings.simplefilter('ignore', ResourceWarning)
16+
1317
def test_get_block_reward(self):
1418
api = Blocks(api_key=(API_KEY))
1519
reward_object = api.get_block_reward(BLOCK)

tests/test_gas_tracker.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import unittest
2+
import warnings
3+
4+
from etherscan.gas_tracker import GasTracker
5+
6+
GAS_PRICE = '2000000000'
7+
PRICE_ORACLE_RESULT_DICT_KEYS = ("SafeGasPrice",
8+
"ProposeGasPrice",
9+
"FastGasPrice",
10+
"suggestBaseFee")
11+
API_KEY = 'YourAPIkey'
12+
13+
14+
class BlocksTestCase(unittest.TestCase):
15+
16+
def setUp(self):
17+
warnings.simplefilter('ignore', ResourceWarning)
18+
self.api = GasTracker(api_key=API_KEY)
19+
20+
def test_get_estimation_of_confirmation_time(self):
21+
estimated_time = self.api.get_estimation_of_confirmation_time(GAS_PRICE)
22+
self.assertTrue(int(estimated_time) > 0)
23+
24+
def test_get_gas_oracle(self):
25+
oracle_price = self.api.get_gas_oracle()
26+
for key in PRICE_ORACLE_RESULT_DICT_KEYS:
27+
self.assertTrue(key in oracle_price and float(oracle_price[key]) > 0)

tests/test_logs.py

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import unittest
2+
import warnings
3+
4+
from etherscan.logs import Logs, LogsException
5+
from etherscan.client import InvalidAPIKey
6+
7+
FROM_BLOCK = 379224
8+
TO_BLOCK = 400000
9+
ADDRESS = '0x33990122638b9132ca29c723bdf037f1a891a70c'
10+
TOPIC0 = '0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545'
11+
TOPIC1 = '0x72657075746174696f6e00000000000000000000000000000000000000000000'
12+
TOPIC0_1_OPR = 'and'
13+
API_KEY = 'YourAPIkey'
14+
15+
16+
class BlocksTestCase(unittest.TestCase):
17+
18+
def setUp(self):
19+
warnings.simplefilter('ignore', ResourceWarning)
20+
self.api = Logs(api_key=(API_KEY))
21+
22+
def test_invalid_api_key(self):
23+
with self.assertRaises(InvalidAPIKey):
24+
api = Logs(api_key=('invalid' + API_KEY))
25+
api.get_logs(from_block=FROM_BLOCK, topic0=TOPIC0)
26+
27+
def test_get_logs_error(self):
28+
with self.assertRaises(LogsException):
29+
self.api.get_logs(from_block=FROM_BLOCK, topic1=TOPIC1)
30+
31+
def test_get_logs_one_topic(self):
32+
topics = self.api.get_logs(from_block=FROM_BLOCK, topic0=TOPIC0)
33+
for topic in topics:
34+
self.assertTrue(TOPIC0 in topic.get('topics', ''))
35+
36+
def test_get_logs_two_topics(self):
37+
topics = self.api.get_logs(from_block=FROM_BLOCK, topic0=TOPIC0, topic1=TOPIC1)
38+
for topic in topics:
39+
self.assertTrue(TOPIC0 in topic.get('topics', '')
40+
and TOPIC1 in topic.get('topics', ''))

tests/test_proxies.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import re
22
import unittest
3+
import warnings
34

45
from etherscan.proxies import Proxies
56

@@ -23,11 +24,14 @@
2324

2425
class ProxiesTestCase(unittest.TestCase):
2526

27+
def setUp(self):
28+
warnings.simplefilter('ignore', ResourceWarning)
29+
2630
def test_get_most_recent_block(self):
2731
api = Proxies(api_key=API_KEY)
2832
most_recent = int(api.get_most_recent_block(), 16)
2933
print(most_recent)
30-
p = re.compile('^[0-9]{7}$')
34+
p = re.compile('^[0-9]{8}$')
3135
self.assertTrue(p.match(str(most_recent)))
3236

3337
def test_get_block_by_number(self):

tests/test_token.py

+4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import unittest
2+
import warnings
23

34
from etherscan.tokens import Tokens
45

@@ -11,6 +12,9 @@
1112

1213
class TokensTestCase(unittest.TestCase):
1314

15+
def setUp(self):
16+
warnings.simplefilter('ignore', ResourceWarning)
17+
1418
def test_get_token_supply(self):
1519
api = Tokens(contract_address=CONTRACT_ADDRESS, api_key=(API_KEY))
1620
self.assertEqual(api.get_total_supply(), ELCOIN_TOKEN_SUPPLY)

tests/test_transactions.py

+4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import unittest
2+
import warnings
23

34
from etherscan.transactions import Transactions
45

@@ -10,6 +11,9 @@
1011

1112
class TransactionsTestCase(unittest.TestCase):
1213

14+
def setUp(self):
15+
warnings.simplefilter('ignore', ResourceWarning)
16+
1317
def test_get_status(self):
1418
api = Transactions(api_key=(API_KEY))
1519
status = api.get_status(TX_1)

0 commit comments

Comments
 (0)