I have a function with the following signature:
def wait_for_namespaced_objects_condition(
obj_type: Type[NamespacedAPIObject],
obj_condition_fun: Callable[[NamespacedAPIObject], bool],
) -> List[NamespacedAPIObject]:
...
Important part here are NamespacedAPIObject
parameters. This function takes an obj_type
as type spec, then creates an object(instance) of that type(class). Then some other objects of that type are added to a list, which is then filtered with obj_condition_fun
and returned as a result of type List[NamespacedAPIObject]. This works fine and also evaluates OK with
mypy`.
Now, I want to make this function generic, so that in the place of NamespacedAPIObject
any subtype of it can be used. My attempt was to do it like this:
T = TypeVar("T", bound=NamespacedAPIObject)
def wait_for_namespaced_objects_condition(
obj_type: Type[T],
obj_condition_fun: Callable[[T], bool],
) -> List[T]:
But Type[T]
is TypeVar
, so it's not the way to go. The question is: what should be the type of obj_type
parameter to make this work? I tried Generic[T]
, which seemed the most reasonable to me, but it doesn't work. If i put there just obj_type: T
, mypy
evaluates this OK, but it seems to wrong to me. In my opinion this means that obj_type
is an instance of a class that is a subtype of NamespacedAPIObject
, while what I want to say is that "T is a class variable that represents subtype of NamespacedAPIObject
. How to solve this?
[Edit]
To explain a little bit better (see comment below) why Type[T]
doesn't work for me: in my case all subtype of T implement class method objects()
, but when I try to write my code like this:
obj_type.objects()
mypy
returns:
pytest_helm_charts/utils.py:36: error: "Type[T]" has no attribute "objects"
But Type[T] is TypeVar, so it's not the way to go.
No, you are on the right track - TypeVar
is definitely the way to go. The problem here is rather in pykube.objects.APIObject
class being wrapped in a decorator that mypy
cannot deal with yet. Adding type stubs for pykube.objects
will resolve the issue. Create a directory _typeshed/pykube
and add minimal type stubs for pykube
:
_typeshed/pykube/__init__.pyi
:
from typing import Any
def __getattr__(name: str) -> Any: ... # incomplete
_typeshed/pykube/objects.pyi
:
from typing import Any, ClassVar, Optional
from pykube.query import Query
def __getattr__(name: str) -> Any: ... # incomplete
class ObjectManager:
def __getattr__(self, name: str) -> Any: ... # incomplete
def __call__(self, api: Any, namespace: Optional[Any] = None) -> Query: ...
class APIObject:
objects: ClassVar[ObjectManager]
def __getattr__(self, name: str) -> Any: ... # incomplete
class NamespacedAPIObject(APIObject): ...
Now running
$ MYPYPATH=_typeshed mypy pytest_helm_charts/
resolves obj_type.objects
correctly:
T = TypeVar('T', bound=NamespacedAPIObject)
def wait_for_namespaced_objects_condition(obj_type: Type[T]) -> List[T]:
reveal_type(obj_type.objects)
Output:
pytest_helm_charts/utils.py:29: note: Revealed type is 'pykube.objects.ObjectManager'
Why Type[T]
is not the way to go? The way I see it, it is quite similar to one of the examples:
However using Type[] and a type variable with an upper bound we can do much better:
U = TypeVar('U', bound=User)
def new_user(user_class: Type[U]) -> U:
...
Now when we call new_user() with a specific subclass of User a type checker will infer the correct type of the result:
joe = new_user(BasicUser) # Inferred type is BasicUser
In case of using classmethod
:
from typing import Callable, Type, List, TypeVar
T = TypeVar('T', bound=Base)
class Base:
@classmethod
def objects(cls: Type[T]) -> List[T]:
...
def run(self):
...
class Derived(Base):
def run(self):
...
def foo(d: Derived) -> bool:
return True
def wait_for_namespaced_objects_condition(
obj_type: Type[T],
obj_condition_fun: Callable[[T], bool],
) -> List[T]:
a = obj_type.objects()
return a
wait_for_namespaced_objects_condition(Derived, foo)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With