Fix UnicodeDecodeError when running crewai create crew on Windows (issue #2715)

Co-Authored-By: Joe Moura <joao@crewai.com>
This commit is contained in:
Devin AI
2025-04-29 11:48:50 +00:00
parent 409892d65f
commit 200ec42613
5 changed files with 173 additions and 12 deletions

View File

@@ -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

View File

@@ -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:

View File

@@ -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")

View File

@@ -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}")

View File

@@ -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: 你好 🚀")