I am currently trying to create a requests wrapper that is typed and allows you to provide a response handler. This response handler, might alter the return value of the http request.
Unfortunately I am not able to overcome some typing issues (I am fairly new to typing with python). This seems like a general issue or misunderstanding on my side, which is why I am creating this post. I have read into several concepts like Generics, or Protocols, yet I was not able to create a working solution, nor I think I fully understand which solution might be the best for this.
Here is my example code:
from collections.abc import Callable
from typing import TypeVar
from requests import Response
_T = TypeVar("_T", default=Response)
def identity(response: Response) -> Response:
return response
def extract_text(response: Response) -> str:
return response.text
class HTTPRequestsAPI:
def fake_request(self, response_handler: Callable[[Response], _T] = identity) -> _T:
response = Response()
processed_response = response_handler(response)
return processed_response
if __name__ == "__main__":
request_api = HTTPRequestsAPI()
resp = request_api.fake_request()
print(type(resp))
resp_text = request_api.fake_request(response_handler=extract_text)
print(type(resp_text))
I can run the main without any issues and get the following output (types), which are as expected:
<class 'requests.models.Response'>
<class 'str'>
When I try to run mypy, I get an error, and I do not understand why this happens, as I assume that the types should be correctly inferred:
error: Incompatible default for argument "response_handler" (default has type "Callable[[Response], Response]", argument has type "Callable[[Response], _T]") [assignment]
What am I not getting here, might this be a mypy related issue? I have not yet tried another type-checker like "pyright". Thank you very much for you help!
**I am using python = 3.13 and mypy = 1.13.0 in this example. I have also tried without a "default" for the TypeVar, but this made no difference and I thought a default might help the typer.
As stated by @Gaberocksall
in the comments, the above problem is mypy specific (see https://github.com/python/mypy/issues/3737). An option is to stick with mypy and use typing.overload
.
I have created an example that implements the same behavior but is accepted by mypy:
from collections.abc import Callable
from typing import TypeVar, overload
from requests import Response
_T = TypeVar("_T", default=Response)
def identity(response: Response) -> Response:
return response
def extract_text(response: Response) -> str:
return response.text
class HTTPRequestsAPI:
@overload
def fake_request(self, response_handler: Callable[[Response], _T]) -> _T:
...
@overload
def fake_request(self) -> Response:
...
def fake_request(self, response_handler=identity):
response = Response()
processed_response = response_handler(response)
return processed_response
if __name__ == "__main__":
request_api = HTTPRequestsAPI()
resp = request_api.fake_request()
print(type(resp))
resp_text = request_api.fake_request(response_handler=extract_text)
print(type(resp_text))
On the other side, @InSync
mentionend in the comments that a possible solution is to switch to pyright, as pyright will parse the posted code without any errors (I can verify this).
In summary, this might be a personal perference, but I will switch to pyright to avoid "unneccessary" overloads. In my "real" project this would blow up the code base and make it quite unreadable in my opinion.
Many Thanks for the help provided in the comments!