Skip to content

Commit 7acfd8a

Browse files
Docker Code Exec delete temp files (#6211)
This pull request introduces a new feature to the `DockerCommandLineCodeExecutor` class, which allows temporary files generated by code execution to be deleted after code execution. The most important changes include adding a new configuration option, updating the class to handle this option, and adding tests to verify the new functionality. ### New Feature: Temporary File Deletion * [`python/packages/autogen-ext/src/autogen_ext/code_executors/docker/_docker_code_executor.py`](diffhunk://#diff-8ef47c21141ed8b0a757b0e6f9d1491561fc31684756d22ed0253edbfcfcdf91R81): Added `delete_tmp_files` attribute to the `DockerCommandLineCodeExecutorConfig` class and updated the `DockerCommandLineCodeExecutor` class to handle this attribute. This includes initializing the attribute, adding it to the configuration methods, and implementing the file deletion logic in the `_execute_code_dont_check_setup` method. [[1]](diffhunk://#diff-8ef47c21141ed8b0a757b0e6f9d1491561fc31684756d22ed0253edbfcfcdf91R81) [[2]](diffhunk://#diff-8ef47c21141ed8b0a757b0e6f9d1491561fc31684756d22ed0253edbfcfcdf91R128) [[3]](diffhunk://#diff-8ef47c21141ed8b0a757b0e6f9d1491561fc31684756d22ed0253edbfcfcdf91R177) [[4]](diffhunk://#diff-8ef47c21141ed8b0a757b0e6f9d1491561fc31684756d22ed0253edbfcfcdf91R231) [[5]](diffhunk://#diff-8ef47c21141ed8b0a757b0e6f9d1491561fc31684756d22ed0253edbfcfcdf91R318) [[6]](diffhunk://#diff-8ef47c21141ed8b0a757b0e6f9d1491561fc31684756d22ed0253edbfcfcdf91R346-R352) [[7]](diffhunk://#diff-8ef47c21141ed8b0a757b0e6f9d1491561fc31684756d22ed0253edbfcfcdf91R527) [[8]](diffhunk://#diff-8ef47c21141ed8b0a757b0e6f9d1491561fc31684756d22ed0253edbfcfcdf91R547) ### Testing * [`python/packages/autogen-ext/tests/code_executors/test_docker_commandline_code_executor.py`](diffhunk://#diff-635dbdcdeca161e620283399d5cd43ca756ec0f88d4429f059ee4f6b346874e4R318-R363): Added a new test `test_delete_tmp_files` to verify the behavior of the `delete_tmp_files` attribute. This test checks that temporary files are correctly deleted or retained based on the configuration.<!-- Thank you for your contribution! Please review https://microsoft.github.io/autogen/docs/Contribute before opening a pull request. -->
1 parent b670511 commit 7acfd8a

File tree

2 files changed

+87
-27
lines changed

2 files changed

+87
-27
lines changed

python/packages/autogen-ext/src/autogen_ext/code_executors/docker/_docker_code_executor.py

+41-27
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class DockerCommandLineCodeExecutorConfig(BaseModel):
7878
extra_volumes: Dict[str, Dict[str, str]] = {}
7979
extra_hosts: Dict[str, str] = {}
8080
init_command: Optional[str] = None
81+
delete_tmp_files: bool = False
8182

8283

8384
class DockerCommandLineCodeExecutor(CodeExecutor, Component[DockerCommandLineCodeExecutorConfig]):
@@ -124,6 +125,7 @@ class DockerCommandLineCodeExecutor(CodeExecutor, Component[DockerCommandLineCod
124125
Example: extra_hosts = {"kubernetes.docker.internal": "host-gateway"}
125126
init_command (Optional[str], optional): A shell command to run before each shell operation execution. Defaults to None.
126127
Example: init_command="kubectl config use-context docker-hub"
128+
delete_tmp_files (bool, optional): If true, will delete temporary files after execution. Defaults to False.
127129
128130
.. note::
129131
Using the current directory (".") as working directory is deprecated. Using it will raise a deprecation warning.
@@ -172,6 +174,7 @@ def __init__(
172174
extra_volumes: Optional[Dict[str, Dict[str, str]]] = None,
173175
extra_hosts: Optional[Dict[str, str]] = None,
174176
init_command: Optional[str] = None,
177+
delete_tmp_files: bool = False,
175178
):
176179
if timeout < 1:
177180
raise ValueError("Timeout must be greater than or equal to 1.")
@@ -225,6 +228,7 @@ def __init__(
225228
self._extra_volumes = extra_volumes if extra_volumes is not None else {}
226229
self._extra_hosts = extra_hosts if extra_hosts is not None else {}
227230
self._init_command = init_command
231+
self._delete_tmp_files = delete_tmp_files
228232

229233
# Setup could take some time so we intentionally wait for the first code block to do it.
230234
if len(functions) > 0:
@@ -311,33 +315,41 @@ async def _execute_code_dont_check_setup(
311315
outputs: List[str] = []
312316
files: List[Path] = []
313317
last_exit_code = 0
314-
for code_block in code_blocks:
315-
lang = code_block.language.lower()
316-
code = silence_pip(code_block.code, lang)
317-
318-
# Check if there is a filename comment
319-
try:
320-
filename = get_file_name_from_content(code, self.work_dir)
321-
except ValueError:
322-
outputs.append("Filename is not in the workspace")
323-
last_exit_code = 1
324-
break
325-
326-
if not filename:
327-
filename = f"tmp_code_{sha256(code.encode()).hexdigest()}.{lang}"
328-
329-
code_path = self.work_dir / filename
330-
with code_path.open("w", encoding="utf-8") as fout:
331-
fout.write(code)
332-
files.append(code_path)
333-
334-
command = ["timeout", str(self._timeout), lang_to_cmd(lang), filename]
335-
336-
output, exit_code = await self._execute_command(command, cancellation_token)
337-
outputs.append(output)
338-
last_exit_code = exit_code
339-
if exit_code != 0:
340-
break
318+
try:
319+
for code_block in code_blocks:
320+
lang = code_block.language.lower()
321+
code = silence_pip(code_block.code, lang)
322+
323+
# Check if there is a filename comment
324+
try:
325+
filename = get_file_name_from_content(code, self.work_dir)
326+
except ValueError:
327+
outputs.append("Filename is not in the workspace")
328+
last_exit_code = 1
329+
break
330+
331+
if not filename:
332+
filename = f"tmp_code_{sha256(code.encode()).hexdigest()}.{lang}"
333+
334+
code_path = self.work_dir / filename
335+
with code_path.open("w", encoding="utf-8") as fout:
336+
fout.write(code)
337+
files.append(code_path)
338+
339+
command = ["timeout", str(self._timeout), lang_to_cmd(lang), filename]
340+
341+
output, exit_code = await self._execute_command(command, cancellation_token)
342+
outputs.append(output)
343+
last_exit_code = exit_code
344+
if exit_code != 0:
345+
break
346+
finally:
347+
if self._delete_tmp_files:
348+
for file in files:
349+
try:
350+
file.unlink()
351+
except (OSError, FileNotFoundError):
352+
pass
341353

342354
code_file = str(files[0]) if files else None
343355
return CommandLineCodeResult(exit_code=last_exit_code, output="".join(outputs), code_file=code_file)
@@ -512,6 +524,7 @@ def _to_config(self) -> DockerCommandLineCodeExecutorConfig:
512524
extra_volumes=self._extra_volumes,
513525
extra_hosts=self._extra_hosts,
514526
init_command=self._init_command,
527+
delete_tmp_files=self._delete_tmp_files,
515528
)
516529

517530
@classmethod
@@ -531,4 +544,5 @@ def _from_config(cls, config: DockerCommandLineCodeExecutorConfig) -> Self:
531544
extra_volumes=config.extra_volumes,
532545
extra_hosts=config.extra_hosts,
533546
init_command=config.init_command,
547+
delete_tmp_files=config.delete_tmp_files,
534548
)

python/packages/autogen-ext/tests/code_executors/test_docker_commandline_code_executor.py

+46
Original file line numberDiff line numberDiff line change
@@ -315,3 +315,49 @@ async def test_directory_creation_cleanup() -> None:
315315
await executor.stop()
316316

317317
assert not Path(directory).exists()
318+
319+
320+
@pytest.mark.asyncio
321+
async def test_delete_tmp_files() -> None:
322+
if not docker_tests_enabled():
323+
pytest.skip("Docker tests are disabled")
324+
325+
with tempfile.TemporaryDirectory() as temp_dir:
326+
# Test with delete_tmp_files=False (default)
327+
async with DockerCommandLineCodeExecutor(work_dir=temp_dir) as executor:
328+
cancellation_token = CancellationToken()
329+
code_blocks = [CodeBlock(code="print('test output')", language="python")]
330+
result = await executor.execute_code_blocks(code_blocks, cancellation_token)
331+
assert result.exit_code == 0
332+
assert result.code_file is not None
333+
# Verify file exists after execution
334+
assert Path(result.code_file).exists()
335+
336+
# Test with delete_tmp_files=True
337+
async with DockerCommandLineCodeExecutor(work_dir=temp_dir, delete_tmp_files=True) as executor:
338+
cancellation_token = CancellationToken()
339+
code_blocks = [CodeBlock(code="print('test output')", language="python")]
340+
result = await executor.execute_code_blocks(code_blocks, cancellation_token)
341+
assert result.exit_code == 0
342+
assert result.code_file is not None
343+
# Verify file is deleted after execution
344+
assert not Path(result.code_file).exists()
345+
346+
# Test with multiple code blocks
347+
code_blocks = [
348+
CodeBlock(code="print('first block')", language="python"),
349+
CodeBlock(code="print('second block')", language="python"),
350+
]
351+
result = await executor.execute_code_blocks(code_blocks, cancellation_token)
352+
assert result.exit_code == 0
353+
assert result.code_file is not None
354+
# Verify files are deleted after execution
355+
assert not Path(result.code_file).exists()
356+
357+
# Test deletion with execution error
358+
code_blocks = [CodeBlock(code="raise Exception('test error')", language="python")]
359+
result = await executor.execute_code_blocks(code_blocks, cancellation_token)
360+
assert result.exit_code != 0
361+
assert result.code_file is not None
362+
# Verify file is deleted even after error
363+
assert not Path(result.code_file).exists()

0 commit comments

Comments
 (0)