Skip to content

Commit 6dbc0a8

Browse files
devin-ai-integration[bot]Jo\u00E3o
andcommitted
Fix #3149: Add missing create_directory parameter to Task class
- Add create_directory field with default value True for backward compatibility - Update _save_file method to respect create_directory parameter - Add comprehensive tests covering all scenarios - Maintain existing behavior when create_directory=True (default) The create_directory parameter was documented but missing from implementation. Users can now control directory creation behavior: - create_directory=True (default): Creates directories if they don't exist - create_directory=False: Raises RuntimeError if directory doesn't exist Fixes issue where users got TypeError when trying to use the documented create_directory parameter. Co-Authored-By: Jo\u00E3o <joao@crewai.com>
1 parent e7a5747 commit 6dbc0a8

File tree

2 files changed

+122
-1
lines changed

2 files changed

+122
-1
lines changed

src/crewai/task.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class Task(BaseModel):
6767
description: Descriptive text detailing task's purpose and execution.
6868
expected_output: Clear definition of expected task outcome.
6969
output_file: File path for storing task output.
70+
create_directory: Whether to create the directory for output_file if it doesn't exist.
7071
output_json: Pydantic model for structuring JSON output.
7172
output_pydantic: Pydantic model for task output.
7273
security_config: Security configuration including fingerprinting.
@@ -115,6 +116,10 @@ class Task(BaseModel):
115116
description="A file path to be used to create a file output.",
116117
default=None,
117118
)
119+
create_directory: Optional[bool] = Field(
120+
description="Whether to create the directory for output_file if it doesn't exist.",
121+
default=True,
122+
)
118123
output: Optional[TaskOutput] = Field(
119124
description="Task output, it's final result after being executed", default=None
120125
)
@@ -753,8 +758,10 @@ def _save_file(self, result: Union[Dict, str, Any]) -> None:
753758
resolved_path = Path(self.output_file).expanduser().resolve()
754759
directory = resolved_path.parent
755760

756-
if not directory.exists():
761+
if self.create_directory and not directory.exists():
757762
directory.mkdir(parents=True, exist_ok=True)
763+
elif not self.create_directory and not directory.exists():
764+
raise RuntimeError(f"Directory {directory} does not exist and create_directory is False")
758765

759766
with resolved_path.open("w", encoding="utf-8") as file:
760767
if isinstance(result, dict):

tests/task_test.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,6 +1133,120 @@ def test_output_file_validation():
11331133
)
11341134

11351135

1136+
def test_create_directory_true():
1137+
"""Test that directories are created when create_directory=True."""
1138+
import os
1139+
from pathlib import Path
1140+
1141+
output_path = "test_create_dir/output.txt"
1142+
1143+
task = Task(
1144+
description="Test task",
1145+
expected_output="Test output",
1146+
output_file=output_path,
1147+
create_directory=True,
1148+
)
1149+
1150+
resolved_path = Path(output_path).expanduser().resolve()
1151+
resolved_dir = resolved_path.parent
1152+
1153+
if resolved_path.exists():
1154+
resolved_path.unlink()
1155+
if resolved_dir.exists():
1156+
import shutil
1157+
shutil.rmtree(resolved_dir)
1158+
1159+
assert not resolved_dir.exists()
1160+
1161+
task._save_file("test content")
1162+
1163+
assert resolved_dir.exists()
1164+
assert resolved_path.exists()
1165+
1166+
if resolved_path.exists():
1167+
resolved_path.unlink()
1168+
if resolved_dir.exists():
1169+
import shutil
1170+
shutil.rmtree(resolved_dir)
1171+
1172+
1173+
def test_create_directory_false():
1174+
"""Test that directories are not created when create_directory=False."""
1175+
from pathlib import Path
1176+
1177+
output_path = "nonexistent_test_dir/output.txt"
1178+
1179+
task = Task(
1180+
description="Test task",
1181+
expected_output="Test output",
1182+
output_file=output_path,
1183+
create_directory=False,
1184+
)
1185+
1186+
resolved_path = Path(output_path).expanduser().resolve()
1187+
resolved_dir = resolved_path.parent
1188+
1189+
if resolved_dir.exists():
1190+
import shutil
1191+
shutil.rmtree(resolved_dir)
1192+
1193+
assert not resolved_dir.exists()
1194+
1195+
with pytest.raises(RuntimeError, match="Directory .* does not exist and create_directory is False"):
1196+
task._save_file("test content")
1197+
1198+
1199+
def test_create_directory_default():
1200+
"""Test that create_directory defaults to True for backward compatibility."""
1201+
task = Task(
1202+
description="Test task",
1203+
expected_output="Test output",
1204+
output_file="output.txt",
1205+
)
1206+
1207+
assert task.create_directory is True
1208+
1209+
1210+
def test_create_directory_with_existing_directory():
1211+
"""Test that create_directory=False works when directory already exists."""
1212+
from pathlib import Path
1213+
1214+
output_path = "existing_test_dir/output.txt"
1215+
1216+
resolved_path = Path(output_path).expanduser().resolve()
1217+
resolved_dir = resolved_path.parent
1218+
resolved_dir.mkdir(parents=True, exist_ok=True)
1219+
1220+
task = Task(
1221+
description="Test task",
1222+
expected_output="Test output",
1223+
output_file=output_path,
1224+
create_directory=False,
1225+
)
1226+
1227+
task._save_file("test content")
1228+
assert resolved_path.exists()
1229+
1230+
if resolved_path.exists():
1231+
resolved_path.unlink()
1232+
if resolved_dir.exists():
1233+
import shutil
1234+
shutil.rmtree(resolved_dir)
1235+
1236+
1237+
def test_github_issue_3149_reproduction():
1238+
"""Test that reproduces the exact issue from GitHub issue #3149."""
1239+
task = Task(
1240+
description="Test task for issue reproduction",
1241+
expected_output="Test output",
1242+
output_file="test_output.txt",
1243+
create_directory=True,
1244+
)
1245+
1246+
assert task.create_directory is True
1247+
assert task.output_file == "test_output.txt"
1248+
1249+
11361250
@pytest.mark.vcr(filter_headers=["authorization"])
11371251
def test_task_execution_times():
11381252
researcher = Agent(

0 commit comments

Comments
 (0)