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

Ability to specify and check possible unhandled exceptions #11282

Closed
DrJackilD opened this issue Oct 6, 2021 · 5 comments
Closed

Ability to specify and check possible unhandled exceptions #11282

DrJackilD opened this issue Oct 6, 2021 · 5 comments
Labels

Comments

@DrJackilD
Copy link

DrJackilD commented Oct 6, 2021

Feature

So, the main idea is to be able to specify and then let Mypy check that some function probably could raise an exception and client code properly handle it during the call.

Initially, I posted some ideas of this feature request into #7326.

Pitch

Basically, the idea is simple - to make a fault-tolerant code we should know where exceptions could occur and handle it properly. Before the Mypy and typing this could be done only by empirical analysis of the code in Python, with these tools - there is a possibility to get some help from static analysis.

There were a few discussions here and here, but proposed solutions are too radical, IMHO. Rust-style error handling dictates a monadic style of code execution with unwrapping and Result[Ok, Err] return type which stands against the pythonic way of dealing with exceptions.

So, I propose a simple way (from the first point of view, of course, it'll be great to hear from dear maintainers regarding technical limitations of this) to mark that function could have a side effect - re-use the special NoReturn type alias, like this:

def divide(a: int, b: int) -> int | NoReturn[ZeroDivisionError]:
    return a // b


def main():
    res = divide(5, 4)
    total = 5 + res  # mypy will complain here because NoReturn type can't be using with integer
    print(total)

This style is quite close to what @JukkaL proposed in original conversation, but in this way, we could definitely distinguish between values that are returning from function (including exceptions) and actual unhandled exceptions, so this signature is perfectly normal and unambiguous:

def build_error(error_type: str) -> UnauthorizedApiError | NotFoundApiError | NoReturn[RuntimeError, NotFound]: ...

General Type Checking Rules

For Mypy this kind of definition with Union[some_particular_type, NoResult] could be checked as a regular type, but instead of if <condition> guards, NoResult type should be inferred/excluded with try .. except guard

def handler():
    try:
        result = f()  # some function with Union[str, NoResult] return type
        ...  # in all lines in the try block Mypy could safely assume that result will be str
    except Exception as exc:
        ...  # here I'm not sure, but at least we could assume the type of exc variable? In case we've got a few other functions that could raise an exception in a try block, this could be a union of all NoResults in try block for which this exception is an ancestor.

Also, in case our except block catching the exception which is not an ancestor for NoResult[SomeException], we could again assume that we do not properly handle the possible exception here

Disable the error checking

And of course, sometimes we need to ignore the possible errors, e. g. IndexError when we are 100% sure there can't be an empty list. For this, we could use some # type: ... hint. I proposing something like this (since unwrap is something that specifies we don't want to handle this error and wants to fail):

def get_index(l: list, i: int) -> Any | NoResult:  # this kind of NoResult means that we don't know the actual error, shortcut for NoResult[Exception]
    # with type: unwrap instruction Mypy will not complain about this line
    return l[i]  # type: unwrap

Or we could simply use # type: ignore here, as usual.

Of course, this feature should be optional, at least for some time, but I believe this optional "exhaustive error handling" checking could make our lives better :)

As an additional bonus, that could help IDE to generate try .. except blocks automatically and shows better documentation, but documentation is not a major point here, really.

@DrJackilD
Copy link
Author

Also, @VanyaDNDZ suggests that probably would be good to understand, that if the user-function propagates the same exception (or wider), Mypy could assume that it's ok to not try .. except in place. If I understand correctly, something similar we have in Java. Example:

def divide(a: int, b: int) -> int | NoReturn[ZeroDivisionError]:
    return a // b


def business_logic(a: int, b: int, c: int) -> int | NoReturn[ZeroDivisionError]]:
    return divide(a, b) + c

@sobolevn
Copy link
Member

sobolevn commented Oct 7, 2021

@DrJackilD
Copy link
Author

@sobolevn Thank you for this link, but that's exactly what I'm trying to avoid. That's a beautiful style of programming, but with this feature request I'm trying to bring exhaust error checking to imperative programming style (which is prevalent for most Python projects)

@erictraut
Copy link

This is a duplicate of #1773.

This idea would require a new PEP, so the discussion more properly belongs in the python/typing project rather than mypy.

@AlexWaygood
Copy link
Member

I agree with @erictraut

@AlexWaygood AlexWaygood closed this as not planned Won't fix, can't repro, duplicate, stale Aug 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants