Skip to content

Add Web3Exception to all exception classes #1478

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

Merged
merged 5 commits into from
Jan 13, 2023
Merged

Conversation

jpic
Copy link
Contributor

@jpic jpic commented Oct 22, 2019

Exception mixin inherited by all exceptions of web3.py

This allows:

try:
    some_call()
except Web3Exception as e:
    # deal with web3 exception
except:
    # deal with other exceptions

What was wrong?

I currently have this code:

        try:    
            result = something_that_calls_web3()
        except (    
            web3.exceptions.BadFunctionCallOutput    
            web3.exceptions.BlockNotFound    
            web3.exceptions.BlockNumberOutofRange    
            web3.exceptions.CannotHandleRequest    
            web3.exceptions.FallbackNotFound    
            web3.exceptions.InfuraKeyNotFound    
            web3.exceptions.InsufficientData    
            web3.exceptions.InvalidAddress    
            web3.exceptions.InvalidEventABI    
            web3.exceptions.LogTopicError    
            web3.exceptions.ManifestValidationError    
            web3.exceptions.MismatchedABI    
            web3.exceptions.NameNotFound    
            web3.exceptions.NoABIEventsFound    
            web3.exceptions.NoABIFound    
            web3.exceptions.NoABIFunctionsFound    
            web3.exceptions.PMError    
            web3.exceptions.StaleBlockchain    
            web3.exceptions.TimeExhausted    
            web3.exceptions.TransactionNotFound    
            web3.exceptions.ValidationError    
        ) as e:    
            deal_with_web3_exception()

This has readability problem, and also not forward-compatible with new exceptions, and also will break if an exception is removed from web3 package.

How was it fixed?

With this patch, I could replace the above with:

        try:    
            result = something_that_calls_web3()
        except web3.exceptions.Web3Exception as e:    
            deal_with_web3_exception()

This fixes readability and forward-compatibility.

Cute Animal Picture

Put a link to a cute animal picture inside the parenthesis-->

@jpic jpic changed the title Add Web3Exception (OOP/Exception handling best practice) Add Web3Exception to all exception classes Oct 22, 2019
"""


class BadFunctionCallOutput(Exception, Web3Exception):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd advocate for Web3Exception to inherit from Exception and to keep the inheritance chain for all of these child exception classes to only have a single parent of Web3Exception.

Copy link
Contributor Author

@jpic jpic Oct 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for review and permission to break BC, notes added in new v6 migration document.

"""
Raised when an ABI is present, but doesn't contain any functions.
"""
pass


class NoABIFound(AttributeError):
class NoABIFound(AttributeError, Web3Exception):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kclowes can you make a note in your breaking v6 changes tracking issue to remove AttributeError from these exception classes bases.

"""
Raised when there is no Infura Project Id set.
"""
pass


class LogTopicError(ValueError):
class LogTopicError(ValueError, Web3Exception):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kclowes same here.

@jpic jpic force-pushed the web3exception branch 2 times, most recently from 6fa55e9 to 8a396f7 Compare October 22, 2019 16:27
@kclowes kclowes mentioned this pull request Oct 24, 2019
22 tasks
@jpic
Copy link
Contributor Author

jpic commented Oct 25, 2019

Added v6 migration notes

@kclowes
Copy link
Collaborator

kclowes commented Oct 27, 2022

Thank you for this @jpic! We're finally about ready to pull it in. I just fixed a bunch of merge conflicts that have accumulated, and need to make some updates in the v6 migration guide, but then it looks good to go!

@kclowes kclowes force-pushed the web3exception branch 4 times, most recently from 4dd52c7 to ff1158a Compare January 11, 2023 18:04
@kclowes kclowes requested review from fselmo and pacrob January 11, 2023 18:06
jpic and others added 3 commits January 11, 2023 12:04
Exception mixin inherited by all exceptions of each module.

This allows:

    try:
        some_call()
    except Web3Exception as e:
        # deal with web3 exception
    except:
        # deal with other exceptions
Copy link
Contributor

@pacrob pacrob left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

Copy link
Collaborator

@fselmo fselmo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to see this making it in. I just need a bit of clarification on some things.

  • eth_utils has a ValidationError that we use in a few places within web3.py while in others we use web3.exceptions.ValidationError. It looks like at some point there was an idea to use the eth_utils.exceptions.ValidationError across all of the python libraries. Indeed this isn't used anywhere within the actual eth-utils library. Do we want to keep this design? Are there good arguments for or against it? This seems like a good time to revisit this for web3.py and it wouldn't be so hard of a change to make. If we think a better design is for each project to have their own ValidationError class, do we want to go toward namespace validation errors, as we did with EthPMValidationError?

  • nit: I slightly prefer the less lengthy EthPMException over PyEthPMException. The Py seems redundant and the module name is ethpm. But I can take or leave it. It does make EthPMValidationError seem like it needs a Py if we keep it... but I prefer both without it.


edit: link to py-evm PR for this change. Also, note, this can (and probably should?) be a separate PR... just noticing that it is related and not on the list for v6 breaking change considerations.

@kclowes
Copy link
Collaborator

kclowes commented Jan 12, 2023

  • eth_utils has a ValidationError that we use in a few places within web3.py while in others we use web3.exceptions.ValidationError. It looks like at some point there was an idea to use the eth_utils.exceptions.ValidationError Add ValidationError eth-utils#114. Indeed this isn't used anywhere within the actual eth-utils library. Do we want to keep this design? Are there good arguments for or against it? This seems like a good time to revisit this for web3.py and it wouldn't be so hard of a change to make. If we think a better design is for each project to have their own ValidationError class, do we want to go toward namespace validation errors, as we did with EthPMValidationError?

I lean toward each project having their own ValidationError, because the whole point of this PR is to be able to catch the top-level Web3Exception. If the ValidationError is from eth-utils, then you'd have to catch Web3Exception and eth_utils.exceptions.ValidationError. I guess another route we could go is to have the ValidationError from web3 inherit from the eth-utils exception, but I'm not sure what would be gained from that. I guess I should rename to Web3ValidationError then? Also curious if you have a different opinion @fselmo. It looks like in the py-evm PR they hit an issue where they were catching the wrong ValidationError but that would be remedied if the ValidationError class was renamed to Web3ValidationError. And if we're going to prefix the ValidationError with Web3 I could see an argument for prefixing all Exceptions with Web3 but that seems redundant.

  • nit: I slightly prefer the less lengthy EthPMException over PyEthPMException. The Py seems redundant and the module name is ethpm. But I can take or leave it. It does make EthPMValidationError seem like it needs a Py if we keep it... but I prefer both without it.

Yeah, agree the Py seems redundant. Will update!

@fselmo
Copy link
Collaborator

fselmo commented Jan 13, 2023

I personally don't see a scenario where I'd want to catch only web3 validation errors instead of catching them across all web3 libraries that web3.py interacts with. web3.py hooks up its codec from eth-abi, makes calls through eth-tester to py-evm, uses eth-account for the account logic... etc... I think that catching a single ValidationError between all these still makes sense.

However... if we want the most fine tuning, I don't see why we can't make Web3ValidationError, similarly to EthPMValidationError (which you mentioned as well). And if we go this route, I don't think we need to prefix all web3.py exception classes with "Web3"... only the ones that are shared across all the libraries (so far only the ValidationError).

Without a general ValidationError, catching all validation errors across stack calls would look something like:

from web3.exceptions import Web3ValidationError
from eth_tester.exceptions import EthTesterValidationError
from eth.exceptions import PyEVMValidationError
...

ALL_WEB3_VALIDATION_ERRORS = [
    Web3ValidationError, EthTesterValidationError, PyEvmValidationError, ...,
]

try:
    ...
except Exception as e:
    if isinstance(e, ALL_WEB3_VALIDATION_ERRORS):
        ...

which looks a bit similar to the original issue filed for web3 exceptions in general.

With a general ValidationError within eth-utils:

from eth_utils.exceptions import ValidationError

try:
    ...
except ValidationError:
        ...

For even more fine tuning, if we still opt for name-spaced validation errors which inherit from the general eth-utils class:

from eth_utils.exceptions import ValidationError

class Web3ValidationError(Web3Exception, ValidationError):
    pass
    
 
try:
    ...
    
# catch all web3 exceptions, including Web3ValidationError
except Web3Exception:
    ...
    
# OR catch only web3 validation exceptions
except Web3ValidationError:
    ...
    
# OR catch any validation errors that may happen on calls to other web3 libraries
except ValidationError:
    ...

My vote is either using ValidationError across all libraries (the old intended way discussed in 2018) or inheriting each library's validation error from that eth-utils class. Thoughts?

@kclowes
Copy link
Collaborator

kclowes commented Jan 13, 2023

I personally don't see a scenario where I'd want to catch only web3 validation errors instead of catching them across all web3 libraries that web3.py interacts with

Yeah, that's a good point. It feels a little like clunky to me to expect a user to import from eth-utils exceptions through the web3 library. Let's go with the inheritance, since that offers the most customization and I'll update ValidationError -> Web3ValidationError here.

@fselmo
Copy link
Collaborator

fselmo commented Jan 13, 2023

Makes sense. We would only need to import ValidationError into the web3.exceptions file in order to pass it through but that's a pretty roundabout way of doing it and not immediately obvious what's happening to someone reading the code. I dig the inheritance approach 👍🏼

@kclowes kclowes force-pushed the web3exception branch 3 times, most recently from ae7f5fe to 6ed320b Compare January 13, 2023 19:48
Copy link
Collaborator

@fselmo fselmo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm 👍🏼

We can definitely benefit from some brief documentation on this hierarchy, perhaps in a separate PR?

@kclowes
Copy link
Collaborator

kclowes commented Jan 13, 2023

Good thought! I'll add it to the migration guide PR that was created off of this branch. As soon as this gets in, I'll PR that.

@kclowes kclowes merged commit b365dc3 into ethereum:master Jan 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants