Suppose we have the following code (a simplified version of
requests.get, type annotated):
def request(method: str, url: str, *, params: Optional[dict[str, str]] = None, data: Optional[dict[str, str]] = None, # And tons of more options here! ) -> Response: ... # Actually perform the request. def get(url: str, **kwargs) -> Response: # Do some pre-processing for get requests. r = request('get', url, **kwargs) # Do some post-processing for get requests. return r
get forwards the keyword arguments it receives to
request. The problem is that calls to
get will not be properly type checked. We expect
get('url', no_such_option=123) to fail type checking, but
mypy cannot catch it as
**kwargs is treated as
**kwargs: Any and not type checked at all. Is there a way to fully type check the forwarded keyword arguments without having to copy all the keyword arguments definition from
get verbatim in the source code? (By doing so, whenever we add a new option to
request, we also need to explicitly add it to
The problem already existed in the stdlib. subprocess.Popen has a very long list of keyword arguments, which are also accepted by utility functions (
check_output). The documentation uses
**other_popen_kwargs to denote this. However, in the type stub for
subprocess, those keyword arguments are copied again and again.
I know one way to make the code type check is to define
def get(url: str, kwargs: SomeTypedDict) instead of
def get(url: str, **kwargs). But for an existing codebase, modifying the API would cause compatibility problems.
The new PEP 612 doesn’t seem to be able to solve the problem easily. Looks like PEP 612 is most suited for writing decorators, but in my example here we don’t have outer functions and inner functions.