Skip to content

Commit a941cfb

Browse files
authored
Fix warning about asyncio.get_event_loop() in Python 3.12 (#1071)
In Python 3.12, the code in `shell_tools.py` produces the following warnings when running `check/pytest`: ``` dev_tools/shell_tools_test.py::test_run_cmd_raise_on_fail dev_tools/shell_tools.py:185: DeprecationWarning: There is no current event loop result = asyncio.get_event_loop().run_until_complete( ``` The fix involves managing the event loop. The updated functions now try to get the current running event loop; if no loop is running, they create a new one, use it, and then close it. Note: commit [c3ab0fa](c3ab0fa) has the actual code changes. Commit [ba775f5](ba775f5) contains purely formatting changes from applying `check/format-incremental`.
1 parent ab74d3b commit a941cfb

File tree

1 file changed

+77
-56
lines changed

1 file changed

+77
-56
lines changed

dev_tools/shell_tools.py

Lines changed: 77 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,13 @@
1515
import asyncio
1616
import subprocess
1717
import sys
18-
from typing import (
19-
List,
20-
Optional,
21-
Tuple,
22-
Union,
23-
IO,
24-
Any,
25-
cast,
26-
NamedTuple,
27-
)
18+
from typing import List, Optional, Tuple, Union, IO, Any, cast, NamedTuple
2819

2920
from collections.abc import AsyncIterable
3021

31-
CommandOutput = NamedTuple("CommandOutput", [
32-
('out', Optional[str]),
33-
('err', Optional[str]),
34-
('exit_code', int),
35-
])
22+
CommandOutput = NamedTuple(
23+
"CommandOutput", [('out', Optional[str]), ('err', Optional[str]), ('exit_code', int)]
24+
)
3625

3726
BOLD = 1
3827
DIM = 2
@@ -52,11 +41,7 @@ def highlight(text: str, color_code: int, bold: bool = False) -> str:
5241
Returns:
5342
The highlighted string.
5443
"""
55-
return '{}\033[{}m{}\033[0m'.format(
56-
'\033[1m' if bold else '',
57-
color_code,
58-
text,
59-
)
44+
return '{}\033[{}m{}\033[0m'.format('\033[1m' if bold else '', color_code, text)
6045

6146

6247
class TeeCapture:
@@ -70,9 +55,9 @@ def __init__(self, out_pipe: Optional[IO[str]] = None) -> None:
7055
self.out_pipe = out_pipe
7156

7257

73-
async def _async_forward(async_chunks: AsyncIterable,
74-
out: Optional[Union[TeeCapture, IO[str]]]
75-
) -> Optional[str]:
58+
async def _async_forward(
59+
async_chunks: AsyncIterable, out: Optional[Union[TeeCapture, IO[str]]]
60+
) -> Optional[str]:
7661
"""Prints/captures output from the given asynchronous iterable.
7762
7863
Args:
@@ -99,9 +84,9 @@ async def _async_forward(async_chunks: AsyncIterable,
9984

10085

10186
async def _async_wait_for_process(
102-
future_process: Any,
103-
out: Optional[Union[TeeCapture, IO[str]]] = sys.stdout,
104-
err: Optional[Union[TeeCapture, IO[str]]] = sys.stderr
87+
future_process: Any,
88+
out: Optional[Union[TeeCapture, IO[str]]] = sys.stdout,
89+
err: Optional[Union[TeeCapture, IO[str]]] = sys.stderr,
10590
) -> CommandOutput:
10691
"""Awaits the creation and completion of an asynchronous process.
10792
@@ -122,8 +107,7 @@ async def _async_wait_for_process(
122107
return CommandOutput(output, err_output, process.returncode)
123108

124109

125-
def abbreviate_command_arguments_after_switches(cmd: Tuple[str, ...]
126-
) -> Tuple[str, ...]:
110+
def abbreviate_command_arguments_after_switches(cmd: Tuple[str, ...]) -> Tuple[str, ...]:
127111
result = [cmd[0]]
128112
for i in range(1, len(cmd)):
129113
if not cmd[i].startswith('-'):
@@ -133,13 +117,15 @@ def abbreviate_command_arguments_after_switches(cmd: Tuple[str, ...]
133117
return tuple(result)
134118

135119

136-
def run_cmd(*cmd: Optional[str],
137-
out: Optional[Union[TeeCapture, IO[str]]] = sys.stdout,
138-
err: Optional[Union[TeeCapture, IO[str]]] = sys.stderr,
139-
raise_on_fail: bool = True,
140-
log_run_to_stderr: bool = True,
141-
abbreviate_non_option_arguments: bool = False,
142-
**kwargs) -> CommandOutput:
120+
def run_cmd(
121+
*cmd: Optional[str],
122+
out: Optional[Union[TeeCapture, IO[str]]] = sys.stdout,
123+
err: Optional[Union[TeeCapture, IO[str]]] = sys.stderr,
124+
raise_on_fail: bool = True,
125+
log_run_to_stderr: bool = True,
126+
abbreviate_non_option_arguments: bool = False,
127+
**kwargs,
128+
) -> CommandOutput:
143129
"""Invokes a subprocess and waits for it to finish.
144130
145131
Args:
@@ -182,23 +168,44 @@ def run_cmd(*cmd: Optional[str],
182168
if abbreviate_non_option_arguments:
183169
cmd_desc = abbreviate_command_arguments_after_switches(cmd_desc)
184170
print('run:', cmd_desc, file=sys.stderr)
185-
result = asyncio.get_event_loop().run_until_complete(
186-
_async_wait_for_process(
187-
asyncio.create_subprocess_exec(*kept_cmd,
188-
stdout=asyncio.subprocess.PIPE,
189-
stderr=asyncio.subprocess.PIPE,
190-
**kwargs), out, err))
171+
172+
loop = None
173+
try:
174+
loop = asyncio.get_running_loop()
175+
except RuntimeError:
176+
loop = asyncio.new_event_loop()
177+
asyncio.set_event_loop(loop)
178+
179+
try:
180+
result = loop.run_until_complete(
181+
_async_wait_for_process(
182+
asyncio.create_subprocess_exec(
183+
*kept_cmd,
184+
stdout=asyncio.subprocess.PIPE,
185+
stderr=asyncio.subprocess.PIPE,
186+
**kwargs,
187+
),
188+
out,
189+
err,
190+
)
191+
)
192+
finally:
193+
if loop != None:
194+
loop.close()
195+
191196
if raise_on_fail and result[2]:
192197
raise subprocess.CalledProcessError(result[2], kept_cmd)
193198
return result
194199

195200

196-
def run_shell(cmd: str,
197-
out: Optional[Union[TeeCapture, IO[str]]] = sys.stdout,
198-
err: Optional[Union[TeeCapture, IO[str]]] = sys.stderr,
199-
raise_on_fail: bool = True,
200-
log_run_to_stderr: bool = True,
201-
**kwargs) -> CommandOutput:
201+
def run_shell(
202+
cmd: str,
203+
out: Optional[Union[TeeCapture, IO[str]]] = sys.stdout,
204+
err: Optional[Union[TeeCapture, IO[str]]] = sys.stderr,
205+
raise_on_fail: bool = True,
206+
log_run_to_stderr: bool = True,
207+
**kwargs,
208+
) -> CommandOutput:
202209
"""Invokes a shell command and waits for it to finish.
203210
204211
Args:
@@ -233,12 +240,28 @@ def run_shell(cmd: str,
233240
"""
234241
if log_run_to_stderr:
235242
print('shell:', cmd, file=sys.stderr)
236-
result = asyncio.get_event_loop().run_until_complete(
237-
_async_wait_for_process(
238-
asyncio.create_subprocess_shell(cmd,
239-
stdout=asyncio.subprocess.PIPE,
240-
stderr=asyncio.subprocess.PIPE,
241-
**kwargs), out, err))
243+
244+
loop = None
245+
try:
246+
loop = asyncio.get_running_loop()
247+
except RuntimeError:
248+
loop = asyncio.new_event_loop()
249+
asyncio.set_event_loop(loop)
250+
251+
try:
252+
result = loop.run_until_complete(
253+
_async_wait_for_process(
254+
asyncio.create_subprocess_shell(
255+
cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, **kwargs
256+
),
257+
out,
258+
err,
259+
)
260+
)
261+
finally:
262+
if loop != None:
263+
loop.close()
264+
242265
if raise_on_fail and result[2]:
243266
raise subprocess.CalledProcessError(result[2], cmd)
244267
return result
@@ -261,9 +284,7 @@ def output_of(*cmd: Optional[str], **kwargs) -> str:
261284
subprocess.CalledProcessError: The process returned a non-zero error
262285
code and raise_on_fail was set.
263286
"""
264-
result = cast(
265-
str,
266-
run_cmd(*cmd, log_run_to_stderr=False, out=TeeCapture(), **kwargs).out)
287+
result = cast(str, run_cmd(*cmd, log_run_to_stderr=False, out=TeeCapture(), **kwargs).out)
267288

268289
# Strip final newline.
269290
if result.endswith('\n'):

0 commit comments

Comments
 (0)