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?
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.