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
Here 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 request
to 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 get
.)
The problem already existed in the stdlib. subprocess.Popen has a very long list of keyword arguments, which are also accepted by utility functions (run
, call
, check_call
, 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.