Skip to content

Commit 3318183

Browse files
WaelKarkoubekzhu
andauthored
Silences Pip Install Messages in Code Executors (#2105)
* fix * adds tests * check if windows * adds windows shells * modifies exit code * fix powershell --------- Co-authored-by: Eric Zhu <[email protected]>
1 parent fafc29e commit 3318183

5 files changed

+61
-18
lines changed

autogen/coding/docker_commandline_code_executor.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import docker
1111
from docker.errors import ImageNotFound
1212

13-
from .utils import _get_file_name_from_content
13+
from .utils import _get_file_name_from_content, silence_pip
1414
from .base import CommandLineCodeResult
1515

1616
from ..code_utils import TIMEOUT_MSG, _cmd
@@ -166,7 +166,7 @@ def execute_code_blocks(self, code_blocks: List[CodeBlock]) -> CommandLineCodeRe
166166
last_exit_code = 0
167167
for code_block in code_blocks:
168168
lang = code_block.language
169-
code = code_block.code
169+
code = silence_pip(code_block.code, lang)
170170

171171
try:
172172
# Check if there is a filename comment

autogen/coding/jupyter/jupyter_code_executor.py

+3-13
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from typing import Any, ClassVar, List, Optional, Type, Union
99
import sys
1010

11+
from autogen.coding.utils import silence_pip
12+
1113
if sys.version_info >= (3, 11):
1214
from typing import Self
1315
else:
@@ -91,7 +93,7 @@ def execute_code_blocks(self, code_blocks: List[CodeBlock]) -> IPythonCodeResult
9193
outputs = []
9294
output_files = []
9395
for code_block in code_blocks:
94-
code = self._process_code(code_block.code)
96+
code = silence_pip(code_block.code, code_block.language)
9597
result = self._jupyter_kernel_client.execute(code, timeout_seconds=self._timeout)
9698
if result.is_ok:
9799
outputs.append(result.output)
@@ -140,18 +142,6 @@ def _save_html(self, html_data: str) -> str:
140142
f.write(html_data)
141143
return os.path.abspath(path)
142144

143-
def _process_code(self, code: str) -> str:
144-
"""Process code before execution."""
145-
# Find lines that start with `! pip install` and make sure "-qqq" flag is added.
146-
lines = code.split("\n")
147-
for i, line in enumerate(lines):
148-
# use regex to find lines that start with `! pip install` or `!pip install`.
149-
match = re.search(r"^! ?pip install", line)
150-
if match is not None:
151-
if "-qqq" not in line:
152-
lines[i] = line.replace(match.group(0), match.group(0) + " -qqq")
153-
return "\n".join(lines)
154-
155145
def stop(self) -> None:
156146
"""Stop the kernel."""
157147
self._jupyter_client.delete_kernel(self._kernel_id)

autogen/coding/local_commandline_code_executor.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from .base import CodeBlock, CodeExecutor, CodeExtractor, CommandLineCodeResult
1313
from .markdown_code_extractor import MarkdownCodeExtractor
1414

15-
from .utils import _get_file_name_from_content
15+
from .utils import _get_file_name_from_content, silence_pip
1616

1717
import subprocess
1818

@@ -114,6 +114,7 @@ def execute_code_blocks(self, code_blocks: List[CodeBlock]) -> CommandLineCodeRe
114114
lang = lang.lower()
115115

116116
LocalCommandLineCodeExecutor.sanitize_command(lang, code)
117+
code = silence_pip(code, lang)
117118

118119
if WIN32 and lang in ["sh", "shell"]:
119120
lang = "ps1"

autogen/coding/utils.py

+21
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Will return the filename relative to the workspace path
2+
import re
23
from pathlib import Path
34
from typing import Optional
45

@@ -20,3 +21,23 @@ def _get_file_name_from_content(code: str, workspace_path: Path) -> Optional[str
2021
return str(relative)
2122

2223
return None
24+
25+
26+
def silence_pip(code: str, lang: str) -> str:
27+
"""Apply -qqq flag to pip install commands."""
28+
if lang == "python":
29+
regex = r"^! ?pip install"
30+
elif lang in ["bash", "shell", "sh", "pwsh", "powershell", "ps1"]:
31+
regex = r"^pip install"
32+
else:
33+
return code
34+
35+
# Find lines that start with pip install and make sure "-qqq" flag is added.
36+
lines = code.split("\n")
37+
for i, line in enumerate(lines):
38+
# use regex to find lines that start with pip install.
39+
match = re.search(regex, line)
40+
if match is not None:
41+
if "-qqq" not in line:
42+
lines[i] = line.replace(match.group(0), match.group(0) + " -qqq")
43+
return "\n".join(lines)

test/coding/test_commandline_code_executor.py

+33-2
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
from pathlib import Path
22
import sys
33
import tempfile
4+
import uuid
45
import pytest
56
from autogen.agentchat.conversable_agent import ConversableAgent
67
from autogen.code_utils import is_docker_running
78
from autogen.coding.base import CodeBlock, CodeExecutor
89
from autogen.coding.factory import CodeExecutorFactory
910
from autogen.coding.docker_commandline_code_executor import DockerCommandLineCodeExecutor
1011
from autogen.coding.local_commandline_code_executor import LocalCommandLineCodeExecutor
11-
from autogen.oai.openai_utils import config_list_from_json
1212

13-
from conftest import MOCK_OPEN_AI_API_KEY, skip_openai, skip_docker
13+
from conftest import MOCK_OPEN_AI_API_KEY, skip_docker
1414

1515
if skip_docker or not is_docker_running():
1616
classes_to_test = [LocalCommandLineCodeExecutor]
1717
else:
1818
classes_to_test = [LocalCommandLineCodeExecutor, DockerCommandLineCodeExecutor]
1919

20+
UNIX_SHELLS = ["bash", "sh", "shell"]
21+
WINDOWS_SHELLS = ["ps1", "pwsh", "powershell"]
22+
2023

2124
@pytest.mark.parametrize("cls", classes_to_test)
2225
def test_is_code_executor(cls) -> None:
@@ -218,3 +221,31 @@ def test_valid_relative_path(cls) -> None:
218221
assert "test.py" in result.code_file
219222
assert (temp_dir / "test.py").resolve() == Path(result.code_file).resolve()
220223
assert (temp_dir / "test.py").exists()
224+
225+
226+
@pytest.mark.parametrize("cls", classes_to_test)
227+
@pytest.mark.parametrize("lang", WINDOWS_SHELLS + UNIX_SHELLS)
228+
def test_silent_pip_install(cls, lang: str) -> None:
229+
# Ensure that the shell is supported.
230+
lang = "ps1" if lang in ["powershell", "pwsh"] else lang
231+
232+
if sys.platform in ["win32"] and lang in UNIX_SHELLS:
233+
pytest.skip("Linux shells are not supported on Windows.")
234+
elif sys.platform not in ["win32"] and lang in WINDOWS_SHELLS:
235+
pytest.skip("Windows shells are not supported on Unix.")
236+
237+
error_exit_code = 0 if sys.platform in ["win32"] else 1
238+
239+
executor = cls(timeout=600)
240+
241+
code = "pip install matplotlib numpy"
242+
code_blocks = [CodeBlock(code=code, language=lang)]
243+
code_result = executor.execute_code_blocks(code_blocks)
244+
assert code_result.exit_code == 0 and code_result.output.strip() == ""
245+
246+
none_existing_package = uuid.uuid4().hex
247+
248+
code = f"pip install matplotlib_{none_existing_package}"
249+
code_blocks = [CodeBlock(code=code, language=lang)]
250+
code_result = executor.execute_code_blocks(code_blocks)
251+
assert code_result.exit_code == error_exit_code and "ERROR: " in code_result.output

0 commit comments

Comments
 (0)