Skip to content

cannot accurately check the type of a decorated function's parameter #12945

@davidism

Description

@davidism

In Flask, @app.errorhandler() is used to create a decorator that will decorate a user function that takes one argument, a type of exception. We've tried the following to type annotate this decorator, but have been unsuccessful in getting useful type checking out.

from __future__ import annotations
import typing as t

# ErrorBound = t.TypeVar("ErrorBound", bound=t.Type[Exception])
# HandlerCallable = t.Callable[[ErrorBound], None]
HandlerCallable = t.Callable[[Exception], None]
HandlerDecorator = t.TypeVar("HandlerDecorator", bound=HandlerCallable)

def handler(code: t.Type[Exception]) -> t.Callable[[HandlerDecorator], HandlerDecorator]:
    def wrapper(f: HandlerDecorator) -> HandlerDecorator:
        return f

    return wrapper

# This is the basic use case. It should pass.
@handler(ValueError)
def one(e: ValueError) -> None:
    pass

# This should pass. It's not required to match the decorator
# argument to the decorated parameter, although that would b a nice bonus.
@handler(ValueError)
def two(e: Exception) -> None:
    pass

# This should also pass. It is possible to stack the decorator,
# and the user should be able to annotate that their function
# takes a union of Exceptions.
@handler(ValueError)
@handler(TypeError)
def three(e: ValueError | TypeError) -> None:
    pass

# This should fail, str is not a type of exception.
@handler(ValueError)
@handler(TypeError)
def four(e: str) -> None:
    pass

With the uncommented types at the top, only the two function passes. Mypy requires that the argument type is exactly Exception.

If the TypeVar and alternate HandlerCallable are uncommented, all four functions pass, but the four function should fail. I've come to understand that this is not the correct way to use TypeVar because it's treated as Any. However, I can't think of another way to express "the argument of this function can be any exception type".

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrong

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions