When creating a base class with a request method that will be exactly the same for all subclasses. There does not seem to be an easy way of having separate type-hints based on which subclass made the request.
The example below is how I currently am solving this issue, but it seems as if there is a better way.
class Response(TypeDict): ...
class FooResponse(Response): ...
class BarResponse(Response): ...
class Request:
@classmethod
def make_request(cls, args: Any) -> Response:
# This will return a dict response based on cls.TYPE (if cls.TYPE == Foo, FooResponse will be returned)
return execute_db_query(cls, args)
class FooRequest(Request):
@classmethod
def make_request(cls, args: Any) -> FooResponse:
return FooResponse(**super().make_request(cls, args))
class BarRequest(Request):
@classmethod
def make_request(cls, args: Any) -> BarResponse:
return BarResponse(**super().make_request(cls, args))
Is there a better way of doing this, either by specifying Response type on the subclass or by just overriding the method signature and no the functionality?
Something like (I know this does not work):
class FooRequest(Request):
@classmethod
def make_request(...) -> FooResponse: ...
I would assume that using something like Generic[T] and TypeVar() could be a way to go?
CodePudding user response:
Perhaps typing.overload
is what you seek.
from __future__ import annotations
from typing import overload
class Request:
@overload
@classmethod
def make_request(cls: FooRequest, args: Any) -> FooResponse: ...
@overload
@classmethod
def make_request(cls: BarRequest, args: Any) -> BarResponse: ...
def make_request(cls, args):
return actual_implementation(cls, args)
Now your FooRequest
and BarRequest
no longer need to supply their implementation of (or type hints for) make_request
.
If on the other hand your only goal is to type hint the IDE, you could:
from __future__ import annotations
from typing import TypeVar
class Response: ...
class FooResponse(Response): ...
class BarResponse(Response): ...
T = TypeVar('T', bound=Response, covariant=True)
class Request:
@classmethod
def make_request(cls: Request, args: Any) -> T:
return actual_implementation(cls, args
class FooRequest(Request):
@classmethod
def make_request(cls: FooRequest, args: Any) -> FooResponse:
return super(FooRequest, cls).make_request(args)
This would make the IDE accept
fooresponse: FooResponse = FooRequest.make_request(None)
but reject
fooresponse: FooResponse = Request.make_request(None)