Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue1560 #1594

Merged
merged 2 commits into from
Mar 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions newsfragments/1594.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Fixed hasattr overloader method in the web3.ContractEvent, web3.ContractFunction,
and web3.ContractCaller classes by implementing a try/except handler
that returns False if an exception is raised in the __getattr__ overloader method
(since __getattr__ HAS to be called in every __hasattr__ call).

Created two new Exception classes, 'ABIEventFunctionNotFound' and 'ABIFunctionNotFound',
which inherit from both AttributeError and MismatchedABI, and replaced the MismatchedABI
raises in ContractEvent, ContractFunction, and ContractCaller with a raise to the created class
in the __getattr__ overloader method of the object.
48 changes: 48 additions & 0 deletions tests/core/contracts/test_contract_attributes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import pytest

from web3.exceptions import (
ABIEventFunctionNotFound,
ABIFunctionNotFound,
)


@pytest.fixture()
def abi():
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


@pytest.mark.parametrize(
'attribute',
('functions', 'events', 'caller')
)
def test_getattr(web3, abi, attribute):
contract = web3.eth.contract(abi=abi)
contract_attribute = getattr(contract, attribute)
assert getattr(contract_attribute, "Increased")


@pytest.mark.parametrize(
'attribute,error', (
('functions', ABIFunctionNotFound),
('events', ABIEventFunctionNotFound),
('caller', ABIFunctionNotFound),
)
)
def test_getattr_raises_error(web3, abi, attribute, error):
contract = web3.eth.contract(abi=abi)
contract_attribute = getattr(contract, attribute)

with pytest.raises(error):
getattr(contract_attribute, "Decreased")


@pytest.mark.parametrize(
'attribute',
('functions', 'events', 'caller')
)
def test_hasattr(web3, abi, attribute):
contract = web3.eth.contract(abi=abi)
contract_attribute = getattr(contract, attribute)

assert hasattr(contract_attribute, "Increased") is True
assert hasattr(contract_attribute, "Decreased") is False
26 changes: 23 additions & 3 deletions web3/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@
MutableAttributeDict,
)
from web3.exceptions import (
ABIEventFunctionNotFound,
ABIFunctionNotFound,
BadFunctionCallOutput,
BlockNumberOutofRange,
FallbackNotFound,
Expand Down Expand Up @@ -185,7 +187,7 @@ def __getattr__(self, function_name: str) -> "ContractFunction":
"Are you sure you provided the correct contract abi?"
)
elif function_name not in self.__dict__['_functions']:
raise MismatchedABI(
raise ABIFunctionNotFound(
"The function '{}' was not found in this contract's abi. ".format(function_name),
"Are you sure you provided the correct contract abi?"
)
Expand All @@ -195,6 +197,12 @@ def __getattr__(self, function_name: str) -> "ContractFunction":
def __getitem__(self, function_name: str) -> ABIFunction:
return getattr(self, function_name)

def __hasattr__(self, event_name: str) -> bool:
try:
return event_name in self.__dict__['_events']
except ABIFunctionNotFound:
return False


class ContractEvents:
"""Class containing contract event objects
Expand Down Expand Up @@ -239,7 +247,7 @@ def __getattr__(self, event_name: str) -> "ContractEvent":
"Are you sure you provided the correct contract abi?"
)
elif event_name not in self.__dict__['_events']:
raise MismatchedABI(
raise ABIEventFunctionNotFound(
"The event '{}' was not found in this contract's abi. ".format(event_name),
"Are you sure you provided the correct contract abi?"
)
Expand All @@ -257,6 +265,12 @@ def __iter__(self) -> Iterable["ContractEvent"]:
for event in self._events:
yield self[event['name']]

def __hasattr__(self, event_name: str) -> bool:
try:
return event_name in self.__dict__['_events']
except ABIEventFunctionNotFound:
return False


class Contract:
"""Base class for Contract proxy classes.
Expand Down Expand Up @@ -1353,7 +1367,7 @@ def __getattr__(self, function_name: str) -> Any:
)
elif function_name not in set(fn['name'] for fn in self._functions):
functions_available = ', '.join([fn['name'] for fn in self._functions])
raise MismatchedABI(
raise ABIFunctionNotFound(
"The function '{}' was not found in this contract's ABI. ".format(function_name),
"Here is a list of all of the function names found: ",
"{}. ".format(functions_available),
Expand All @@ -1362,6 +1376,12 @@ def __getattr__(self, function_name: str) -> Any:
else:
return super().__getattribute__(function_name)

def __hasattr__(self, event_name: str) -> bool:
try:
return event_name in self.__dict__['_events']
except ABIFunctionNotFound:
return False

def __call__(
self, transaction: TxParams=None, block_identifier: BlockIdentifier='latest'
) -> 'ContractCaller':
Expand Down
16 changes: 16 additions & 0 deletions web3/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,22 @@ class MismatchedABI(Exception):
pass


class ABIEventFunctionNotFound(AttributeError, MismatchedABI):
"""
Raised when an attempt is made to access an event
that does not exist in the ABI.
"""
pass


class ABIFunctionNotFound(AttributeError, MismatchedABI):
"""
Raised when an attempt is made to access a function
that does not exist in the ABI.
"""
pass


class FallbackNotFound(Exception):
"""
Raised when fallback function doesn't exist in contract.
Expand Down