Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CompletedProcess is a Generic for mypy

I am using subprocess.CompletedProcess as a return type of a function. When running mypy with --strict, it complains about Missing type parameters for generic type "CompletedProcess". This is a short example for which the problem occurs:

import subprocess

def run_command() -> subprocess.CompletedProcess:
    return subprocess.CompletedProcess(args=[], returncode=0, stdout='')

I do not understand how CompletedProcess is a generic. I checked the reference (https://docs.python.org/3.9/library/subprocess.html#subprocess.CompletedProcess) and can't find any mention.

Randomly adding a generic parameter, for example -> subprocess.CompletedProcess[str], removes the error, but obviously that's not the point. I'd like to be able to hint the return type without defining a generic parameter.

I am using python 3.8.5 and mypy 0.761 and 0.910 (both give the same output).

like image 876
Bert Avatar asked Oct 15 '22 20:10

Bert


1 Answers

The output (stdout/stderr) of a subprocess can be either str or bytes, depending on the arguments. The generic parameter to CompletedProcess is required to indicate the type of this.

import subprocess

def run_some_command() -> 'subprocess.CompletedProcess[str]':
    """Run some subprocess that captures output as ``str``"""
    return subprocess.CompletedProcess(args=[], returncode=0, stdout='')

def run_other_command() -> 'subprocess.CompletedProcess[bytes]':
    """Run some subprocess that captures output as ``bytes``"""
    return subprocess.CompletedProcess(args=[], returncode=0, stdout=b'')

reveal_type(run_some_command().stdout)  # note: Revealed type is 'builtins.str*'

Since only the type hint but not the runtime subprocess.CompletedProcess type is generic, it must be quoted.


The type declarations for the standard library are part of typeshed. In specific, CompletedProcess is currently defined as follows:

class CompletedProcess(Generic[_T]):
    # morally: _CMD
    args: Any
    returncode: int
    # These are really both Optional, but requiring checks would be tedious
    # and writing all the overloads would be horrific.
    stdout: _T
    stderr: _T
    def __init__(self, args: _CMD, returncode: int, stdout: Optional[_T] = ..., stderr: Optional[_T] = ...) -> None: ...
    def check_returncode(self) -> None: ...
    if sys.version_info >= (3, 9):
        def __class_getitem__(cls, item: Any) -> GenericAlias: ...
like image 194
MisterMiyagi Avatar answered Oct 18 '22 12:10

MisterMiyagi