Skip to content

Commit 1123b01

Browse files
authored
Add type annotations to _process_common.py and _process_win32.py (#15134)
Note in particular in that in _process_common, we change the name of the fist argument of arg_split to be `commandline` instead of `s` for consistency
2 parents cf9f2b2 + a1076b0 commit 1123b01

File tree

5 files changed

+27
-24
lines changed

5 files changed

+27
-24
lines changed

IPython/core/magic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,7 @@ def parse_options(
736736
if len(args) >= 1:
737737
# If the list of inputs only has 0 or 1 thing in it, there's no
738738
# need to look for options
739-
argv = arg_split(arg_str, posix, strict) # type: ignore[no-untyped-call]
739+
argv = arg_split(arg_str, posix, strict)
740740
# Do regular option processing
741741
try:
742742
opts, args = getopt(argv, opt_str, long_opts)

IPython/utils/_process_common.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,18 @@
1818
import shlex
1919
import subprocess
2020
import sys
21-
from typing import IO, Any, List, Union
21+
from typing import IO, List, TypeVar, Union
2222
from collections.abc import Callable
2323

24+
_T = TypeVar("_T")
25+
2426
from IPython.utils import py3compat
2527

2628
#-----------------------------------------------------------------------------
2729
# Function definitions
2830
#-----------------------------------------------------------------------------
2931

30-
def read_no_interrupt(stream: IO[Any]) -> bytes:
32+
def read_no_interrupt(stream: IO[bytes]) -> bytes | None:
3133
"""Read from a pipe ignoring EINTR errors.
3234
3335
This is necessary because when reading from pipes with GUI event loops
@@ -40,13 +42,14 @@ def read_no_interrupt(stream: IO[Any]) -> bytes:
4042
except IOError as err:
4143
if err.errno != errno.EINTR:
4244
raise
45+
return None
4346

4447

4548
def process_handler(
4649
cmd: Union[str, List[str]],
47-
callback: Callable[[subprocess.Popen], int | str | bytes],
48-
stderr=subprocess.PIPE,
49-
) -> int | str | bytes:
50+
callback: Callable[[subprocess.Popen[bytes]], _T],
51+
stderr: int = subprocess.PIPE,
52+
) -> _T | None:
5053
"""Open a command in a shell subprocess and execute a callback.
5154
5255
This function provides common scaffolding for creating subprocess.Popen()
@@ -118,7 +121,7 @@ def process_handler(
118121
return out
119122

120123

121-
def getoutput(cmd):
124+
def getoutput(cmd: str | list[str]) -> str:
122125
"""Run a command and return its stdout/stderr as a string.
123126
124127
Parameters
@@ -137,11 +140,10 @@ def getoutput(cmd):
137140
out = process_handler(cmd, lambda p: p.communicate()[0], subprocess.STDOUT)
138141
if out is None:
139142
return ''
140-
assert isinstance(out, bytes)
141143
return py3compat.decode(out)
142144

143145

144-
def getoutputerror(cmd):
146+
def getoutputerror(cmd: str | list[str]) -> tuple[str, str]:
145147
"""Return (standard output, standard error) of executing cmd in a shell.
146148
147149
Accepts the same arguments as os.system().
@@ -158,7 +160,8 @@ def getoutputerror(cmd):
158160
"""
159161
return get_output_error_code(cmd)[:2]
160162

161-
def get_output_error_code(cmd):
163+
164+
def get_output_error_code(cmd: str | list[str]) -> tuple[str, str, int | None]:
162165
"""Return (standard output, standard error, return code) of executing cmd
163166
in a shell.
164167
@@ -176,13 +179,13 @@ def get_output_error_code(cmd):
176179
returncode: int
177180
"""
178181

179-
out_err, p = process_handler(cmd, lambda p: (p.communicate(), p))
180-
if out_err is None:
181-
return '', '', p.returncode
182-
out, err = out_err
182+
result = process_handler(cmd, lambda p: (p.communicate(), p))
183+
if result is None:
184+
return '', '', None
185+
(out, err), p = result
183186
return py3compat.decode(out), py3compat.decode(err), p.returncode
184187

185-
def arg_split(s, posix=False, strict=True):
188+
def arg_split(commandline: str, posix: bool = False, strict: bool = True) -> list[str]:
186189
"""Split a command line's arguments in a shell-like manner.
187190
188191
This is a modified version of the standard library's shlex.split()
@@ -195,7 +198,7 @@ def arg_split(s, posix=False, strict=True):
195198
command-line args.
196199
"""
197200

198-
lex = shlex.shlex(s, posix=posix)
201+
lex = shlex.shlex(commandline, posix=posix)
199202
lex.whitespace_split = True
200203
# Extract tokens, ensuring that things like leaving open quotes
201204
# does not cause this to raise. This is important, because we

IPython/utils/_process_win32.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,13 @@ def __exit__(
5959
self,
6060
exc_type: Optional[type[BaseException]],
6161
exc_value: Optional[BaseException],
62-
traceback: TracebackType,
62+
traceback: Optional[TracebackType],
6363
) -> None:
6464
if self.is_unc_path:
6565
os.chdir(self.path)
6666

6767

68-
def _system_body(p: subprocess.Popen) -> int:
68+
def _system_body(p: subprocess.Popen[bytes]) -> int:
6969
"""Callback for _system."""
7070
enc = DEFAULT_ENCODING
7171

@@ -75,7 +75,7 @@ def _system_body(p: subprocess.Popen) -> int:
7575
def stdout_read() -> None:
7676
try:
7777
assert p.stdout is not None
78-
for byte_line in read_no_interrupt(p.stdout).splitlines():
78+
for byte_line in (read_no_interrupt(p.stdout) or b"").splitlines():
7979
line = byte_line.decode(enc, "replace")
8080
print(line, file=sys.stdout)
8181
except Exception as e:
@@ -84,7 +84,7 @@ def stdout_read() -> None:
8484
def stderr_read() -> None:
8585
try:
8686
assert p.stderr is not None
87-
for byte_line in read_no_interrupt(p.stderr).splitlines():
87+
for byte_line in (read_no_interrupt(p.stderr) or b"").splitlines():
8888
line = byte_line.decode(enc, "replace")
8989
print(line, file=sys.stderr)
9090
except Exception as e:
@@ -136,9 +136,7 @@ def system(cmd: str) -> Optional[int]:
136136
if path is not None:
137137
cmd = '"pushd %s &&"%s' % (path, cmd)
138138
res = process_handler(cmd, _system_body)
139-
assert isinstance(res, int | type(None))
140139
return res
141-
return None
142140

143141

144142
def getoutput(cmd: str) -> str:
@@ -210,6 +208,7 @@ def arg_split(
210208
arg_split = py_arg_split
211209

212210

211+
213212
def check_pid(pid: int) -> bool:
214213
# OpenProcess returns 0 if no such process (of ours) exists
215214
# positive int otherwise

IPython/utils/py3compat.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from .encoding import DEFAULT_ENCODING
1111

1212

13-
def decode(s, encoding=None):
13+
def decode(s: bytes, encoding: str | None = None) -> str:
1414
encoding = encoding or DEFAULT_ENCODING
1515
return s.decode(encoding, "replace")
1616

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
130130
module = [
131131
"IPython.core.events",
132132
"IPython.core.magic",
133+
"IPython.utils._process_common",
134+
"IPython.utils._process_win32",
133135
]
134136
# Strictest configuration, everything turned up to the max
135137
check_untyped_defs = true
@@ -302,7 +304,6 @@ module = [
302304
"IPython.terminal.shortcuts.auto_suggest",
303305
"IPython.terminal.shortcuts.filters",
304306
"IPython.utils._process_cli",
305-
"IPython.utils._process_common",
306307
"IPython.utils._process_emscripten",
307308
"IPython.utils.capture",
308309
"IPython.utils.coloransi",

0 commit comments

Comments
 (0)