Skip to content

Commit 79a661d

Browse files
authored
Merge pull request #1594 from realjohnward/issue1560
Issue1560
2 parents c852b8c + 518d5fc commit 79a661d

File tree

4 files changed

+96
-3
lines changed

4 files changed

+96
-3
lines changed

newsfragments/1594.bugfix.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Fixed hasattr overloader method in the web3.ContractEvent, web3.ContractFunction,
2+
and web3.ContractCaller classes by implementing a try/except handler
3+
that returns False if an exception is raised in the __getattr__ overloader method
4+
(since __getattr__ HAS to be called in every __hasattr__ call).
5+
6+
Created two new Exception classes, 'ABIEventFunctionNotFound' and 'ABIFunctionNotFound',
7+
which inherit from both AttributeError and MismatchedABI, and replaced the MismatchedABI
8+
raises in ContractEvent, ContractFunction, and ContractCaller with a raise to the created class
9+
in the __getattr__ overloader method of the object.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import pytest
2+
3+
from web3.exceptions import (
4+
ABIEventFunctionNotFound,
5+
ABIFunctionNotFound,
6+
)
7+
8+
9+
@pytest.fixture()
10+
def abi():
11+
return '''[{"anonymous":false,"inputs":[{"indexed":false,"name":"value","type":"uint256"}],"name":"Increased","type":"function"}, {"anonymous":false,"inputs":[{"indexed":false,"name":"value","type":"uint256"}],"name":"Increased","type":"event"}]''' # noqa: E501
12+
13+
14+
@pytest.mark.parametrize(
15+
'attribute',
16+
('functions', 'events', 'caller')
17+
)
18+
def test_getattr(web3, abi, attribute):
19+
contract = web3.eth.contract(abi=abi)
20+
contract_attribute = getattr(contract, attribute)
21+
assert getattr(contract_attribute, "Increased")
22+
23+
24+
@pytest.mark.parametrize(
25+
'attribute,error', (
26+
('functions', ABIFunctionNotFound),
27+
('events', ABIEventFunctionNotFound),
28+
('caller', ABIFunctionNotFound),
29+
)
30+
)
31+
def test_getattr_raises_error(web3, abi, attribute, error):
32+
contract = web3.eth.contract(abi=abi)
33+
contract_attribute = getattr(contract, attribute)
34+
35+
with pytest.raises(error):
36+
getattr(contract_attribute, "Decreased")
37+
38+
39+
@pytest.mark.parametrize(
40+
'attribute',
41+
('functions', 'events', 'caller')
42+
)
43+
def test_hasattr(web3, abi, attribute):
44+
contract = web3.eth.contract(abi=abi)
45+
contract_attribute = getattr(contract, attribute)
46+
47+
assert hasattr(contract_attribute, "Increased") is True
48+
assert hasattr(contract_attribute, "Decreased") is False

web3/contract.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@
110110
MutableAttributeDict,
111111
)
112112
from web3.exceptions import (
113+
ABIEventFunctionNotFound,
114+
ABIFunctionNotFound,
113115
BadFunctionCallOutput,
114116
BlockNumberOutofRange,
115117
FallbackNotFound,
@@ -185,7 +187,7 @@ def __getattr__(self, function_name: str) -> "ContractFunction":
185187
"Are you sure you provided the correct contract abi?"
186188
)
187189
elif function_name not in self.__dict__['_functions']:
188-
raise MismatchedABI(
190+
raise ABIFunctionNotFound(
189191
"The function '{}' was not found in this contract's abi. ".format(function_name),
190192
"Are you sure you provided the correct contract abi?"
191193
)
@@ -195,6 +197,12 @@ def __getattr__(self, function_name: str) -> "ContractFunction":
195197
def __getitem__(self, function_name: str) -> ABIFunction:
196198
return getattr(self, function_name)
197199

200+
def __hasattr__(self, event_name: str) -> bool:
201+
try:
202+
return event_name in self.__dict__['_events']
203+
except ABIFunctionNotFound:
204+
return False
205+
198206

199207
class ContractEvents:
200208
"""Class containing contract event objects
@@ -239,7 +247,7 @@ def __getattr__(self, event_name: str) -> "ContractEvent":
239247
"Are you sure you provided the correct contract abi?"
240248
)
241249
elif event_name not in self.__dict__['_events']:
242-
raise MismatchedABI(
250+
raise ABIEventFunctionNotFound(
243251
"The event '{}' was not found in this contract's abi. ".format(event_name),
244252
"Are you sure you provided the correct contract abi?"
245253
)
@@ -257,6 +265,12 @@ def __iter__(self) -> Iterable["ContractEvent"]:
257265
for event in self._events:
258266
yield self[event['name']]
259267

268+
def __hasattr__(self, event_name: str) -> bool:
269+
try:
270+
return event_name in self.__dict__['_events']
271+
except ABIEventFunctionNotFound:
272+
return False
273+
260274

261275
class Contract:
262276
"""Base class for Contract proxy classes.
@@ -1357,7 +1371,7 @@ def __getattr__(self, function_name: str) -> Any:
13571371
)
13581372
elif function_name not in set(fn['name'] for fn in self._functions):
13591373
functions_available = ', '.join([fn['name'] for fn in self._functions])
1360-
raise MismatchedABI(
1374+
raise ABIFunctionNotFound(
13611375
"The function '{}' was not found in this contract's ABI. ".format(function_name),
13621376
"Here is a list of all of the function names found: ",
13631377
"{}. ".format(functions_available),
@@ -1366,6 +1380,12 @@ def __getattr__(self, function_name: str) -> Any:
13661380
else:
13671381
return super().__getattribute__(function_name)
13681382

1383+
def __hasattr__(self, event_name: str) -> bool:
1384+
try:
1385+
return event_name in self.__dict__['_events']
1386+
except ABIFunctionNotFound:
1387+
return False
1388+
13691389
def __call__(
13701390
self, transaction: TxParams=None, block_identifier: BlockIdentifier='latest'
13711391
) -> 'ContractCaller':

web3/exceptions.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,22 @@ class MismatchedABI(Exception):
7070
pass
7171

7272

73+
class ABIEventFunctionNotFound(AttributeError, MismatchedABI):
74+
"""
75+
Raised when an attempt is made to access an event
76+
that does not exist in the ABI.
77+
"""
78+
pass
79+
80+
81+
class ABIFunctionNotFound(AttributeError, MismatchedABI):
82+
"""
83+
Raised when an attempt is made to access a function
84+
that does not exist in the ABI.
85+
"""
86+
pass
87+
88+
7389
class FallbackNotFound(Exception):
7490
"""
7591
Raised when fallback function doesn't exist in contract.

0 commit comments

Comments
 (0)