Skip to content

Inspect dependencies to detect unhandled exceptions #281

Description

@oliversheridanmethven

With py.typed files tools like mypy can look into the dependency code and check that you are using third party libraries correctly.

The input types and return types are covered by this, but there is a definite blind spot, for example, what is raised. I imagine a world where there is a third party library called third_party which implements some foo:

def foo() -> None:
    """
    Does something.

    Raises
    ------
    TypeError
        ...
    ValueError
        ...
    RuntimeError
        ...
    """
    ...

and then in an application code where you write some try-catch loop you might get

from third_party import foo

# Example 1
try:
    foo()   
except TypeError:
    pass
except RuntimeError:
    pass
# Did we mean to catch ValueError here?    
    

# Example 2
try:
    foo()   
except TypeError:
    pass
except RuntimeError:
    pass
except Exception as e:  # Probably not intended to catch value-error.
    raise CustomError("An error occurred") from e

I suspect we could then enable options where we could handle these scenarios in the following ways:

# Should raise an issue.
try:
    foo()   
except TypeError:
    pass
except RuntimeError:
    pass

# Should not raise any issue.
try: # pydoclint: ignore[missing-exception=ValueError]
    foo()   
except TypeError:
    pass
except RuntimeError:
    pass
    

# Should raise an issue.
try:
    foo()   
except TypeError:
    pass
except RuntimeError:
    pass
except Exception as e: 
    raise CustomError("An error occurred") from e

# Should not raise an issue.
try: # pydoclint: ignore[missing-exception=ValueError]
    foo()   
except TypeError:
    pass
except RuntimeError:
    pass
except Exception as e:  
    raise CustomError("An error occurred") from e

# Should not raise an issue if pydoclint is passed the option --warn_catch_all_exceptions=false
try: # pydoclint: ignore[missing-exception=ValueError]
    foo()   
except TypeError:
    pass
except RuntimeError:
    pass
except Exception as e:  
    raise CustomError("An error occurred") from e

To my knowledge, the idea of allowing functions to declare what they raise in their signature is un-implemented (and strongly opposed by Guido), so I think the only place this information resides is perhaps in the docstring, and if we inspect our dependencies it should be possible to catch all sort of places where we are not handling behaviour declared in the documentation of the dependencies. I suspect if there is a missing docstring or one that does not have a raises section, then something akin to a Any would be sensible. Similarly, pydoclint should not check the dependencies are correct.

Of course, another way to tackle this is to instead inspect the source code, in which case then a conventional static analyser like mypy could tackle this, although I am not sure if they plan to adopt this feature.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions