str counts as a Callable[..., str], but not as a Callable[..., TypeVar]
Bug Report
Hey folks!
I ran into what I think is a bug while trying to write a config retrieval library for a Flask server. I'd like to pass a return_type argument into my get_config function. When the argument is provided, I'd like the function to return a value as that type.
To Reproduce
Here's a pared-down version of my function that still causes MyPy to error out. Pyright seems to think this code is valid.
from typing import Callable, TypeVar
ValueType = TypeVar("ValueType")
def get_config(return_type: Callable[..., ValueType] = str) -> ValueType:
return return_type(42)
# mypy get_config_typed.py
# error: Incompatible default for argument "return_type" (default has type "type[str]", argument has type "Callable[..., ValueType]") [assignment]
The same code works fine when ValueType is replaced with str:
from typing import Callable
def get_config(return_type: Callable[..., str] = str) -> str:
return return_type(42)
# mypy get_config_typed.py
# Success: no issues found in 1 source file
Actual Behavior
error: Incompatible default for argument "return_type" (default has type "type[str]", argument has type "Callable[..., ValueType]") [assignment]
Your Environment
% mypy --version
mypy 1.8.0 (compiled: yes)
% python --version
Python 3.9.17
% uname -a
Darwin <...> 23.2.0 Darwin Kernel Version 23.2.0: Wed Nov 15 21:54:51 PST 2023; root:xnu-10002.61.3~2/RELEASE_ARM64_T6030 arm64
- Mypy version used: 1.8.0
- Mypy command-line flags: N/A
- Mypy configuration options from
mypy.ini(and other config files): N/A - Python version used: 3.9.17
One workaround is to use overloads.
from typing import Any, Callable, TypeVar, overload
T_co = TypeVar('T_co', covariant=True)
@overload
def get_config() -> str: ...
@overload
def get_config(return_type: Callable[..., T_co]) -> T_co: ...
def get_config(return_type: Callable[..., Any] = str) -> Any:
return return_type(42)
def foo(x: Any) -> dict[str, Any]:
return {}
reveal_type(get_config()) # builtins.str
reveal_type(get_config(int)) # builtins.int
reveal_type(get_config(foo)) # builtins.dict[builtins.str, Any]