Search code examples
pythontypesdecoratormypy

Mypy thinks global variable can be None when decorator ensures it's not, only in function with arg


I'm trying to ensure the server has been set with a decorator, the code functions as intended, but mypy is giving me a union-attr error.

Here is an example:

from functools import wraps


class Server:
    def __init__(self, ip) -> None:
        self.ip = ip


_server: Server | None = None


def ensure_server(func):
    """Ensure a server connection has been set"""

    @wraps(func)
    def wrapper(*args, **kwargs):
        global _server
        if _server is None:
            raise ValueError("server is None")
        return func(*args, **kwargs)

    return wrapper


@ensure_server
def hello_server():
    print(_server.ip)

@ensure_server
def add_server(foo: str):
    print(_server.ip + foo)

Mypy handles hello_server without any errors, but gives Item "None" of "Server | None" has no attribute "ip" in add_server.

Why does mypy give an error for the function with an argument, but not for the one without any?


Solution

  • There are two problems here. The first problem is that you didn't put @ensure_server on add_server, so you don't actually have the guarantee you thought you had there.

    That's not what's triggering the mypy behavior, though. hello_server has no annotations, so mypy just doesn't check it at all. If you add a -> None return annotation to hello_server, mypy will report an error for that function too.

    mypy has no idea what the decorator does, and there's no way to tell mypy that the decorator ensures the invariants you want.