From 0ff0257d98a440f671af30df46691d8b77f0803f Mon Sep 17 00:00:00 2001 From: Lucas Gomide Date: Thu, 26 Jun 2025 12:02:19 -0300 Subject: [PATCH] fix: ensure env-vars are written in upper case When creating a Crew via the CLI and selecting the Azure provider, the generated .env file had environment variables in lowercase. This commit ensures that all environment variables are written in uppercase. --- src/crewai/cli/utils.py | 2 +- tests/cli/test_create_crew.py | 94 +++++++++++++++++++++++------------ 2 files changed, 64 insertions(+), 32 deletions(-) diff --git a/src/crewai/cli/utils.py b/src/crewai/cli/utils.py index bcf6030ca..3fff637de 100644 --- a/src/crewai/cli/utils.py +++ b/src/crewai/cli/utils.py @@ -252,7 +252,7 @@ def write_env_file(folder_path, env_vars): env_file_path = folder_path / ".env" with open(env_file_path, "w") as file: for key, value in env_vars.items(): - file.write(f"{key}={value}\n") + file.write(f"{key.upper()}={value}\n") def get_crews(crew_path: str = "crew.py", require: bool = False) -> list[Crew]: diff --git a/tests/cli/test_create_crew.py b/tests/cli/test_create_crew.py index b645d8522..323b7aa18 100644 --- a/tests/cli/test_create_crew.py +++ b/tests/cli/test_create_crew.py @@ -9,7 +9,6 @@ from click.testing import CliRunner from crewai.cli.create_crew import create_crew, create_folder_structure - @pytest.fixture def runner(): return CliRunner() @@ -25,7 +24,7 @@ def temp_dir(): def test_create_folder_structure_strips_single_trailing_slash(): with tempfile.TemporaryDirectory() as temp_dir: folder_path, folder_name, class_name = create_folder_structure("hello/", parent_folder=temp_dir) - + assert folder_name == "hello" assert class_name == "Hello" assert folder_path.name == "hello" @@ -36,7 +35,7 @@ def test_create_folder_structure_strips_single_trailing_slash(): def test_create_folder_structure_strips_multiple_trailing_slashes(): with tempfile.TemporaryDirectory() as temp_dir: folder_path, folder_name, class_name = create_folder_structure("hello///", parent_folder=temp_dir) - + assert folder_name == "hello" assert class_name == "Hello" assert folder_path.name == "hello" @@ -47,7 +46,7 @@ def test_create_folder_structure_strips_multiple_trailing_slashes(): def test_create_folder_structure_handles_complex_name_with_trailing_slash(): with tempfile.TemporaryDirectory() as temp_dir: folder_path, folder_name, class_name = create_folder_structure("my-awesome_project/", parent_folder=temp_dir) - + assert folder_name == "my_awesome_project" assert class_name == "MyAwesomeProject" assert folder_path.name == "my_awesome_project" @@ -58,7 +57,7 @@ def test_create_folder_structure_handles_complex_name_with_trailing_slash(): def test_create_folder_structure_normal_name_unchanged(): with tempfile.TemporaryDirectory() as temp_dir: folder_path, folder_name, class_name = create_folder_structure("hello", parent_folder=temp_dir) - + assert folder_name == "hello" assert class_name == "Hello" assert folder_path.name == "hello" @@ -73,9 +72,9 @@ def test_create_folder_structure_with_parent_folder(): with tempfile.TemporaryDirectory() as temp_dir: parent_path = Path(temp_dir) / "parent" parent_path.mkdir() - + folder_path, folder_name, class_name = create_folder_structure("child/", parent_folder=parent_path) - + assert folder_name == "child" assert class_name == "Child" assert folder_path.name == "child" @@ -88,18 +87,18 @@ def test_create_folder_structure_with_parent_folder(): @mock.patch("crewai.cli.create_crew.load_env_vars") def test_create_crew_with_trailing_slash_creates_valid_project(mock_load_env, mock_write_env, mock_copy_template, temp_dir): mock_load_env.return_value = {} - + with tempfile.TemporaryDirectory() as work_dir: with mock.patch("crewai.cli.create_crew.create_folder_structure") as mock_create_folder: mock_folder_path = Path(work_dir) / "test_project" mock_create_folder.return_value = (mock_folder_path, "test_project", "TestProject") - + create_crew("test-project/", skip_provider=True) - + mock_create_folder.assert_called_once_with("test-project/", None) mock_copy_template.assert_called() copy_calls = mock_copy_template.call_args_list - + for call in copy_calls: args = call[0] if len(args) >= 5: @@ -112,14 +111,14 @@ def test_create_crew_with_trailing_slash_creates_valid_project(mock_load_env, mo @mock.patch("crewai.cli.create_crew.load_env_vars") def test_create_crew_with_multiple_trailing_slashes(mock_load_env, mock_write_env, mock_copy_template, temp_dir): mock_load_env.return_value = {} - + with tempfile.TemporaryDirectory() as work_dir: with mock.patch("crewai.cli.create_crew.create_folder_structure") as mock_create_folder: mock_folder_path = Path(work_dir) / "test_project" mock_create_folder.return_value = (mock_folder_path, "test_project", "TestProject") - + create_crew("test-project///", skip_provider=True) - + mock_create_folder.assert_called_once_with("test-project///", None) @@ -128,21 +127,21 @@ def test_create_crew_with_multiple_trailing_slashes(mock_load_env, mock_write_en @mock.patch("crewai.cli.create_crew.load_env_vars") def test_create_crew_normal_name_still_works(mock_load_env, mock_write_env, mock_copy_template, temp_dir): mock_load_env.return_value = {} - + with tempfile.TemporaryDirectory() as work_dir: with mock.patch("crewai.cli.create_crew.create_folder_structure") as mock_create_folder: mock_folder_path = Path(work_dir) / "normal_project" mock_create_folder.return_value = (mock_folder_path, "normal_project", "NormalProject") - + create_crew("normal-project", skip_provider=True) - + mock_create_folder.assert_called_once_with("normal-project", None) def test_create_folder_structure_handles_spaces_and_dashes_with_slash(): with tempfile.TemporaryDirectory() as temp_dir: folder_path, folder_name, class_name = create_folder_structure("My Cool-Project/", parent_folder=temp_dir) - + assert folder_name == "my_cool_project" assert class_name == "MyCoolProject" assert folder_path.name == "my_cool_project" @@ -155,7 +154,7 @@ def test_create_folder_structure_raises_error_for_invalid_names(): invalid_cases = [ ("123project/", "cannot start with a digit"), ("True/", "reserved Python keyword"), - ("False/", "reserved Python keyword"), + ("False/", "reserved Python keyword"), ("None/", "reserved Python keyword"), ("class/", "reserved Python keyword"), ("def/", "reserved Python keyword"), @@ -163,7 +162,7 @@ def test_create_folder_structure_raises_error_for_invalid_names(): ("", "empty or contain only whitespace"), ("@#$/", "contains no valid characters"), ] - + for invalid_name, expected_error in invalid_cases: with pytest.raises(ValueError, match=expected_error): create_folder_structure(invalid_name, parent_folder=temp_dir) @@ -179,20 +178,20 @@ def test_create_folder_structure_validates_names(): ("hello.world/", "helloworld", "HelloWorld"), ("hello@world/", "helloworld", "HelloWorld"), ] - + for valid_name, expected_folder, expected_class in valid_cases: folder_path, folder_name, class_name = create_folder_structure(valid_name, parent_folder=temp_dir) assert folder_name == expected_folder assert class_name == expected_class - + assert folder_name.isidentifier(), f"folder_name '{folder_name}' should be valid Python identifier" assert not keyword.iskeyword(folder_name), f"folder_name '{folder_name}' should not be Python keyword" assert not folder_name[0].isdigit(), f"folder_name '{folder_name}' should not start with digit" - + assert class_name.isidentifier(), f"class_name '{class_name}' should be valid Python identifier" assert not keyword.iskeyword(class_name), f"class_name '{class_name}' should not be Python keyword" assert folder_path.parent == Path(temp_dir) - + if folder_path.exists(): shutil.rmtree(folder_path) @@ -202,13 +201,13 @@ def test_create_folder_structure_validates_names(): @mock.patch("crewai.cli.create_crew.load_env_vars") def test_create_crew_with_parent_folder_and_trailing_slash(mock_load_env, mock_write_env, mock_copy_template, temp_dir): mock_load_env.return_value = {} - + with tempfile.TemporaryDirectory() as work_dir: parent_path = Path(work_dir) / "parent" parent_path.mkdir() - + create_crew("child-crew/", skip_provider=True, parent_folder=parent_path) - + crew_path = parent_path / "child_crew" assert crew_path.exists() assert not (crew_path / "src").exists() @@ -224,23 +223,56 @@ def test_create_folder_structure_folder_name_validation(): ("for/", "reserved Python keyword"), ("@#$invalid/", "contains no valid characters.*Python module name"), ] - + for invalid_name, expected_error in folder_invalid_cases: with pytest.raises(ValueError, match=expected_error): create_folder_structure(invalid_name, parent_folder=temp_dir) - + valid_cases = [ ("hello-world/", "hello_world"), ("my.project/", "myproject"), ("test@123/", "test123"), ("valid_name/", "valid_name"), ] - + for valid_name, expected_folder in valid_cases: folder_path, folder_name, class_name = create_folder_structure(valid_name, parent_folder=temp_dir) assert folder_name == expected_folder assert folder_name.isidentifier() assert not keyword.iskeyword(folder_name) - + if folder_path.exists(): shutil.rmtree(folder_path) + +@mock.patch("crewai.cli.create_crew.create_folder_structure") +@mock.patch("crewai.cli.create_crew.copy_template") +@mock.patch("crewai.cli.create_crew.load_env_vars") +@mock.patch("crewai.cli.create_crew.get_provider_data") +@mock.patch("crewai.cli.create_crew.select_provider") +@mock.patch("crewai.cli.create_crew.select_model") +@mock.patch("click.prompt") +def test_env_vars_are_uppercased_in_env_file( + mock_prompt, + mock_select_model, + mock_select_provider, + mock_get_provider_data, + mock_load_env_vars, + mock_copy_template, + mock_create_folder_structure, + tmp_path +): + crew_path = tmp_path / "test_crew" + crew_path.mkdir() + mock_create_folder_structure.return_value = (crew_path, "test_crew", "TestCrew") + + mock_load_env_vars.return_value = {} + mock_get_provider_data.return_value = {"openai": ["gpt-4"]} + mock_select_provider.return_value = "azure" + mock_select_model.return_value = "azure/openai" + mock_prompt.return_value = "fake-api-key" + + create_crew("Test Crew") + + env_file_path = crew_path / ".env" + content = env_file_path.read_text() + assert "MODEL=" in content \ No newline at end of file