Description
Hi! Hope you are well :)
In the project I am working on, it is important for me to be able to create a function that takes in a parameter of Optional[T]
type and return that same var of type T
if it is not None
, else raise a RuntimeError
. This way I can access properties of the expected object when I know that it should not be None (e.g., after everything is initialized).
Here is a simple example to illustrate this point:
import random
from typing import NoReturn, Optional, TypeVar, overload
T = TypeVar("T")
class MyClass:
def __init__(self, property: int):
self.property: int = property
def get_obj_or_none() -> Optional[MyClass]:
obj = MyClass(1)
return obj if random.random() > 0.001 else None
@overload
def require_type(x: None) -> NoReturn:
...
@overload
def require_type(x: T) -> T:
...
def require_type(x: Optional[T]) -> T:
if x is None:
raise RuntimeError("x is None")
return x
x = get_obj_or_none() # x should have type str | None
y = require_type(x) # y should have type str
y_property = y.property # property is accessible and correctly typed as int
print(y_property)
In this example, I find that y, although it I want it to be typed as MyClass
, is typed as MyClass | None
by pylance
/ pyright
. See screenshot below:
However, when I commend out the overloads, it is typed correctly:
This makes sense based on the pyright overload handling rules I found here: https://microsoft.github.io/pyright/#/type-concepts-advanced?id=overloads
Since the input to the require_type
function is of union type (Optional[T]
is syntax for T | Non
), I fall into case 5 in the documentation and the following happens:
If so, these union types are expanded into their constituent subtypes, and the entire process of overload matching is repeated with the expanded argument types. If two or more overloads match, the union of their respective return types form the final return type for the call expression.
This is problematic for me, because I also use mypy
and I need the overloads structured in this way to pass mypy
type checking and also ensure that mypy
correctly interprets the types of variables after I use the require_type
function python/mypy#9424 . Further, it says here #2660 (reply in thread) that at least as of Dec 2023 there is no plan to conform the way mypy
and pyright
handle overloads.
Thus, my question is, how can I require both pyright
/ pylance
and mypy
to interpret variables as non-optional after I call some kind of require_type
function on said variable? If there is no other way to do this specifically, how can i solve this problem of needing to caste optional variables as non-None
?
Thank you!