diff --git a/src/crewai/cli/create_flow.py b/src/crewai/cli/create_flow.py index ec68611b5..01c16e881 100644 --- a/src/crewai/cli/create_flow.py +++ b/src/crewai/cli/create_flow.py @@ -28,7 +28,7 @@ def create_flow(name): (project_root / "tests").mkdir(exist_ok=True) # Create .env file - with open(project_root / ".env", "w") as file: + with open(project_root / ".env", "w", encoding="utf-8") as file: file.write("OPENAI_API_KEY=YOUR_API_KEY") package_dir = Path(__file__).parent @@ -58,7 +58,7 @@ def create_flow(name): content = content.replace("{{flow_name}}", class_name) content = content.replace("{{folder_name}}", folder_name) - with open(dst_file, "w") as file: + with open(dst_file, "w", encoding="utf-8") as file: file.write(content) # Copy and process root template files diff --git a/src/crewai/cli/provider.py b/src/crewai/cli/provider.py index 529ca5e26..3ebd696f8 100644 --- a/src/crewai/cli/provider.py +++ b/src/crewai/cli/provider.py @@ -147,7 +147,7 @@ def read_cache_file(cache_file): - dict or None: The JSON content of the cache file or None if the JSON is invalid. """ try: - with open(cache_file, "r") as f: + with open(cache_file, "r", encoding="utf-8") as f: return json.load(f) except json.JSONDecodeError: return None @@ -167,7 +167,7 @@ def fetch_provider_data(cache_file): response = requests.get(JSON_URL, stream=True, timeout=60) response.raise_for_status() data = download_data(response) - with open(cache_file, "w") as f: + with open(cache_file, "w", encoding="utf-8") as f: json.dump(data, f) return data except requests.RequestException as e: diff --git a/src/crewai/cli/utils.py b/src/crewai/cli/utils.py index a385e1f37..58cf10901 100644 --- a/src/crewai/cli/utils.py +++ b/src/crewai/cli/utils.py @@ -18,7 +18,7 @@ console = Console() def copy_template(src, dst, name, class_name, folder_name): """Copy a file from src to dst.""" - with open(src, "r") as file: + with open(src, "r", encoding="utf-8") as file: content = file.read() # Interpolate the content @@ -27,7 +27,7 @@ def copy_template(src, dst, name, class_name, folder_name): content = content.replace("{{folder_name}}", folder_name) # Write the interpolated content to the new file - with open(dst, "w") as file: + with open(dst, "w", encoding="utf-8") as file: file.write(content) click.secho(f" - Created {dst}", fg="green") @@ -78,7 +78,7 @@ def _get_project_attribute( attribute = None try: - with open(pyproject_path, "r") as f: + with open(pyproject_path, "r", encoding="utf-8") as f: pyproject_content = parse_toml(f.read()) dependencies = ( @@ -119,7 +119,7 @@ def fetch_and_json_env_file(env_file_path: str = ".env") -> dict: """Fetch the environment variables from a .env file and return them as a dictionary.""" try: # Read the .env file - with open(env_file_path, "r") as f: + with open(env_file_path, "r", encoding="utf-8") as f: env_content = f.read() # Parse the .env file content to a dictionary @@ -158,9 +158,9 @@ def tree_find_and_replace(directory, find, replace): for filename in files: filepath = os.path.join(path, filename) - with open(filepath, "r") as file: + with open(filepath, "r", encoding="utf-8") as file: contents = file.read() - with open(filepath, "w") as file: + with open(filepath, "w", encoding="utf-8") as file: file.write(contents.replace(find, replace)) if find in filename: @@ -189,7 +189,7 @@ def load_env_vars(folder_path): env_file_path = folder_path / ".env" env_vars = {} if env_file_path.exists(): - with open(env_file_path, "r") as file: + with open(env_file_path, "r", encoding="utf-8") as file: for line in file: key, _, value = line.strip().partition("=") if key and value: @@ -244,6 +244,6 @@ def write_env_file(folder_path, env_vars): - env_vars (dict): A dictionary of environment variables to write. """ env_file_path = folder_path / ".env" - with open(env_file_path, "w") as file: + with open(env_file_path, "w", encoding="utf-8") as file: for key, value in env_vars.items(): file.write(f"{key}={value}\n") diff --git a/tests/cli/test_create_crew.py b/tests/cli/test_create_crew.py new file mode 100644 index 000000000..d6e88f7df --- /dev/null +++ b/tests/cli/test_create_crew.py @@ -0,0 +1,77 @@ +import os +import tempfile +import unittest +from pathlib import Path +from unittest.mock import patch, MagicMock + +import click +from click.testing import CliRunner + +from crewai.cli.cli import create +from crewai.cli.create_crew import create_crew + + +class TestCreateCrew(unittest.TestCase): + def setUp(self): + self.runner = CliRunner() + self.temp_dir = tempfile.TemporaryDirectory() + self.test_dir = Path(self.temp_dir.name) + + def tearDown(self): + self.temp_dir.cleanup() + + @patch("crewai.cli.create_crew.get_provider_data") + @patch("crewai.cli.create_crew.select_provider") + @patch("crewai.cli.create_crew.select_model") + @patch("crewai.cli.create_crew.write_env_file") + @patch("crewai.cli.create_crew.load_env_vars") + @patch("click.confirm") + def test_create_crew_handles_unicode(self, mock_confirm, mock_load_env, + mock_write_env, mock_select_model, + mock_select_provider, mock_get_provider_data): + """Test that create_crew command handles Unicode properly.""" + mock_confirm.return_value = True + mock_load_env.return_value = {} + mock_get_provider_data.return_value = {"openai": ["gpt-4"]} + mock_select_provider.return_value = "openai" + mock_select_model.return_value = "gpt-4" + + templates_dir = Path("src/crewai/cli/templates/crew") + templates_dir.mkdir(parents=True, exist_ok=True) + + template_content = """ + Hello {{name}}! Unicode test: 你好, こんにちは, Привет 🚀 + Class: {{crew_name}} + Folder: {{folder_name}} + """ + + (templates_dir / "tools").mkdir(exist_ok=True) + (templates_dir / "config").mkdir(exist_ok=True) + + for file_name in [".gitignore", "pyproject.toml", "README.md", "__init__.py", "main.py", "crew.py"]: + with open(templates_dir / file_name, "w", encoding="utf-8") as f: + f.write(template_content) + + (templates_dir / "knowledge").mkdir(exist_ok=True) + with open(templates_dir / "knowledge" / "user_preference.txt", "w", encoding="utf-8") as f: + f.write(template_content) + + for file_path in ["tools/custom_tool.py", "tools/__init__.py", "config/agents.yaml", "config/tasks.yaml"]: + (templates_dir / file_path).parent.mkdir(exist_ok=True, parents=True) + with open(templates_dir / file_path, "w", encoding="utf-8") as f: + f.write(template_content) + + with patch("crewai.cli.create_crew.Path") as mock_path: + mock_path.return_value = self.test_dir + mock_path.side_effect = lambda x: self.test_dir / x if isinstance(x, str) else x + + create_crew("test_crew", skip_provider=True) + + crew_dir = self.test_dir / "test_crew" + for root, _, files in os.walk(crew_dir): + for file in files: + file_path = os.path.join(root, file) + with open(file_path, "r", encoding="utf-8") as f: + content = f.read() + self.assertIn("你好", content, f"Unicode characters not preserved in {file_path}") + self.assertIn("🚀", content, f"Emoji not preserved in {file_path}") diff --git a/tests/cli/test_encoding.py b/tests/cli/test_encoding.py new file mode 100644 index 000000000..d09e3957b --- /dev/null +++ b/tests/cli/test_encoding.py @@ -0,0 +1,84 @@ +import os +import tempfile +import unittest +from pathlib import Path +from unittest.mock import patch + +from crewai.cli.utils import copy_template, load_env_vars, write_env_file, tree_find_and_replace +from crewai.cli.provider import read_cache_file, fetch_provider_data + + +class TestEncoding(unittest.TestCase): + def setUp(self): + self.temp_dir = tempfile.TemporaryDirectory() + self.test_dir = Path(self.temp_dir.name) + + self.unicode_content = "Hello Unicode: 你好, こんにちは, Привет, مرحبا, 안녕하세요 🚀" + self.src_file = self.test_dir / "src_file.txt" + self.dst_file = self.test_dir / "dst_file.txt" + + with open(self.src_file, "w", encoding="utf-8") as f: + f.write(self.unicode_content) + + def tearDown(self): + self.temp_dir.cleanup() + + def test_copy_template_handles_unicode(self): + """Test that copy_template handles Unicode characters properly.""" + copy_template( + self.src_file, + self.dst_file, + "test_name", + "TestClass", + "test_folder" + ) + + with open(self.dst_file, "r", encoding="utf-8") as f: + content = f.read() + + self.assertIn("你好", content) + self.assertIn("こんにちは", content) + self.assertIn("🚀", content) + + def test_env_vars_handle_unicode(self): + """Test that environment variable functions handle Unicode characters properly.""" + test_env_path = self.test_dir / ".env" + test_env_vars = { + "KEY1": "Value with Unicode: 你好", + "KEY2": "More Unicode: こんにちは 🚀" + } + + write_env_file(self.test_dir, test_env_vars) + + loaded_vars = load_env_vars(self.test_dir) + + self.assertEqual(loaded_vars["KEY1"], "Value with Unicode: 你好") + self.assertEqual(loaded_vars["KEY2"], "More Unicode: こんにちは 🚀") + + def test_tree_find_and_replace_handles_unicode(self): + """Test that tree_find_and_replace handles Unicode characters properly.""" + test_file = self.test_dir / "replace_test.txt" + with open(test_file, "w", encoding="utf-8") as f: + f.write("Replace this: PLACEHOLDER with Unicode: 你好") + + tree_find_and_replace(self.test_dir, "PLACEHOLDER", "🚀") + + with open(test_file, "r", encoding="utf-8") as f: + content = f.read() + + self.assertIn("Replace this: 🚀 with Unicode: 你好", content) + + @patch("crewai.cli.provider.requests.get") + def test_provider_functions_handle_unicode(self, mock_get): + """Test that provider data functions handle Unicode properly.""" + mock_response = unittest.mock.Mock() + mock_response.iter_content.return_value = [self.unicode_content.encode("utf-8")] + mock_response.headers.get.return_value = str(len(self.unicode_content)) + mock_get.return_value = mock_response + + cache_file = self.test_dir / "cache.json" + with open(cache_file, "w", encoding="utf-8") as f: + f.write('{"model": "Unicode test: 你好 🚀"}') + + cache_data = read_cache_file(cache_file) + self.assertEqual(cache_data["model"], "Unicode test: 你好 🚀")