Merge pull request #1269 from crewAIInc/tm-fix-cli-for-py310

Add py 3.10 support back to CLI + fixes
This commit is contained in:
Thiago Moretto
2024-08-30 13:39:04 -03:00
committed by GitHub
5 changed files with 146 additions and 29 deletions

View File

@@ -2,6 +2,8 @@ from os import getenv
import requests import requests
from crewai.cli.deploy.utils import get_crewai_version
class CrewAPI: class CrewAPI:
""" """
@@ -13,6 +15,7 @@ class CrewAPI:
self.headers = { self.headers = {
"Authorization": f"Bearer {api_key}", "Authorization": f"Bearer {api_key}",
"Content-Type": "application/json", "Content-Type": "application/json",
"User-Agent": f"CrewAI-CLI/{get_crewai_version()}",
} }
self.base_url = getenv( self.base_url = getenv(
"CREWAI_BASE_URL", "https://dev.crewai.com/crewai_plus/api/v1/crews" "CREWAI_BASE_URL", "https://dev.crewai.com/crewai_plus/api/v1/crews"

View File

@@ -39,6 +39,10 @@ class DeployCommand:
raise SystemExit raise SystemExit
self.project_name = get_project_name() self.project_name = get_project_name()
if self.project_name is None:
console.print("No project name found. Please ensure your project has a valid pyproject.toml file.", style="bold red")
raise SystemExit
self.client = CrewAPI(api_key=access_token) self.client = CrewAPI(api_key=access_token)
def _handle_error(self, json_response: Dict[str, Any]) -> None: def _handle_error(self, json_response: Dict[str, Any]) -> None:
@@ -123,6 +127,11 @@ class DeployCommand:
env_vars = fetch_and_json_env_file() env_vars = fetch_and_json_env_file()
remote_repo_url = get_git_remote_url() remote_repo_url = get_git_remote_url()
if remote_repo_url is None:
console.print("No remote repository URL found.", style="bold red")
console.print("Please ensure your project has a valid remote repository.", style="yellow")
return
self._confirm_input(env_vars, remote_repo_url) self._confirm_input(env_vars, remote_repo_url)
payload = self._create_payload(env_vars, remote_repo_url) payload = self._create_payload(env_vars, remote_repo_url)

View File

@@ -1,12 +1,46 @@
import sys
import re import re
import subprocess import subprocess
import tomllib from rich.console import Console
from ..authentication.utils import TokenManager from ..authentication.utils import TokenManager
console = Console()
def get_git_remote_url() -> str:
if sys.version_info >= (3, 11):
import tomllib
# Drop the simple_toml_parser when we move to python3.11
def simple_toml_parser(content):
result = {}
current_section = result
for line in content.split('\n'):
line = line.strip()
if line.startswith('[') and line.endswith(']'):
# New section
section = line[1:-1].split('.')
current_section = result
for key in section:
current_section = current_section.setdefault(key, {})
elif '=' in line:
key, value = line.split('=', 1)
key = key.strip()
value = value.strip().strip('"')
current_section[key] = value
return result
def parse_toml(content):
if sys.version_info >= (3, 11):
return tomllib.loads(content)
else:
return simple_toml_parser(content)
def get_git_remote_url() -> str | None:
"""Get the Git repository's remote URL.""" """Get the Git repository's remote URL."""
try: try:
# Run the git remote -v command # Run the git remote -v command
@@ -23,21 +57,22 @@ def get_git_remote_url() -> str:
if matches: if matches:
return matches[0] # Return the first match (origin URL) return matches[0] # Return the first match (origin URL)
else: else:
print("No origin remote found.") console.print("No origin remote found.", style="bold red")
return "No remote URL found"
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
return f"Error running trying to fetch the Git Repository: {e}" console.print(f"Error running trying to fetch the Git Repository: {e}", style="bold red")
except FileNotFoundError: except FileNotFoundError:
return "Git command not found. Make sure Git is installed and in your PATH." console.print("Git command not found. Make sure Git is installed and in your PATH.", style="bold red")
return None
def get_project_name(pyproject_path: str = "pyproject.toml"): def get_project_name(pyproject_path: str = "pyproject.toml") -> str | None:
"""Get the project name from the pyproject.toml file.""" """Get the project name from the pyproject.toml file."""
try: try:
# Read the pyproject.toml file # Read the pyproject.toml file
with open(pyproject_path, "rb") as f: with open(pyproject_path, "r") as f:
pyproject_content = tomllib.load(f) pyproject_content = parse_toml(f.read())
# Extract the project name # Extract the project name
project_name = pyproject_content["tool"]["poetry"]["name"] project_name = pyproject_content["tool"]["poetry"]["name"]
@@ -51,36 +86,39 @@ def get_project_name(pyproject_path: str = "pyproject.toml"):
print(f"Error: {pyproject_path} not found.") print(f"Error: {pyproject_path} not found.")
except KeyError: except KeyError:
print(f"Error: {pyproject_path} is not a valid pyproject.toml file.") print(f"Error: {pyproject_path} is not a valid pyproject.toml file.")
except tomllib.TOMLDecodeError: except tomllib.TOMLDecodeError if sys.version_info >= (3, 11) else Exception as e: # type: ignore
print(f"Error: {pyproject_path} is not a valid TOML file.") print(
f"Error: {pyproject_path} is not a valid TOML file."
if sys.version_info >= (3, 11)
else f"Error reading the pyproject.toml file: {e}"
)
except Exception as e: except Exception as e:
print(f"Error reading the pyproject.toml file: {e}") print(f"Error reading the pyproject.toml file: {e}")
return None return None
def get_crewai_version(pyproject_path: str = "pyproject.toml") -> str: def get_crewai_version(poetry_lock_path: str = "poetry.lock") -> str:
"""Get the version number of crewai from the pyproject.toml file.""" """Get the version number of crewai from the poetry.lock file."""
try: try:
# Read the pyproject.toml file with open(poetry_lock_path, "r") as f:
with open("pyproject.toml", "rb") as f: lock_content = f.read()
pyproject_content = tomllib.load(f)
# Extract the version number of crewai match = re.search(
crewai_version = pyproject_content["tool"]["poetry"]["dependencies"]["crewai"][ r'\[\[package\]\]\s*name\s*=\s*"crewai"\s*version\s*=\s*"([^"]+)"',
"version" lock_content,
] re.DOTALL,
)
return crewai_version if match:
return match.group(1)
else:
print("crewai package not found in poetry.lock")
return "no-version-found"
except FileNotFoundError: except FileNotFoundError:
print(f"Error: {pyproject_path} not found.") print(f"Error: {poetry_lock_path} not found.")
except KeyError:
print(f"Error: {pyproject_path} is not a valid pyproject.toml file.")
except tomllib.TOMLDecodeError:
print(f"Error: {pyproject_path} is not a valid TOML file.")
except Exception as e: except Exception as e:
print(f"Error reading the pyproject.toml file: {e}") print(f"Error reading the poetry.lock file: {e}")
return "no-version-found" return "no-version-found"

View File

@@ -17,6 +17,7 @@ class TestCrewAPI(unittest.TestCase):
{ {
"Authorization": f"Bearer {self.api_key}", "Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json", "Content-Type": "application/json",
"User-Agent": "CrewAI-CLI/no-version-found"
}, },
) )

View File

@@ -1,9 +1,10 @@
import unittest import unittest
from io import StringIO from io import StringIO
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import sys
from crewai.cli.deploy.main import DeployCommand from crewai.cli.deploy.main import DeployCommand
from crewai.cli.deploy.utils import parse_toml
class TestDeployCommand(unittest.TestCase): class TestDeployCommand(unittest.TestCase):
@patch("crewai.cli.deploy.main.get_auth_token") @patch("crewai.cli.deploy.main.get_auth_token")
@@ -151,3 +152,68 @@ class TestDeployCommand(unittest.TestCase):
self.assertIn( self.assertIn(
"Crew 'test_project' removed successfully", fake_out.getvalue() "Crew 'test_project' removed successfully", fake_out.getvalue()
) )
@unittest.skipIf(sys.version_info < (3, 11), "Requires Python 3.11+")
def test_parse_toml_python_311_plus(self):
toml_content = """
[tool.poetry]
name = "test_project"
version = "0.1.0"
[tool.poetry.dependencies]
python = "^3.11"
crewai = { extras = ["tools"], version = ">=0.51.0,<1.0.0" }
"""
parsed = parse_toml(toml_content)
self.assertEqual(parsed['tool']['poetry']['name'], 'test_project')
@patch('builtins.open', new_callable=unittest.mock.mock_open, read_data="""
[tool.poetry]
name = "test_project"
version = "0.1.0"
[tool.poetry.dependencies]
python = "^3.10"
crewai = { extras = ["tools"], version = ">=0.51.0,<1.0.0" }
""")
def test_get_project_name_python_310(self, mock_open):
from crewai.cli.deploy.utils import get_project_name
project_name = get_project_name()
self.assertEqual(project_name, 'test_project')
@unittest.skipIf(sys.version_info < (3, 11), "Requires Python 3.11+")
@patch('builtins.open', new_callable=unittest.mock.mock_open, read_data="""
[tool.poetry]
name = "test_project"
version = "0.1.0"
[tool.poetry.dependencies]
python = "^3.11"
crewai = { extras = ["tools"], version = ">=0.51.0,<1.0.0" }
""")
def test_get_project_name_python_311_plus(self, mock_open):
from crewai.cli.deploy.utils import get_project_name
project_name = get_project_name()
self.assertEqual(project_name, 'test_project')
@patch('builtins.open', new_callable=unittest.mock.mock_open, read_data="""
[[package]]
name = "crewai"
version = "0.51.1"
description = "Some description"
category = "main"
optional = false
python-versions = ">=3.10,<4.0"
""")
def test_get_crewai_version(self, mock_open):
from crewai.cli.deploy.utils import get_crewai_version
version = get_crewai_version()
self.assertEqual(version, '0.51.1')
@patch('builtins.open', side_effect=FileNotFoundError)
def test_get_crewai_version_file_not_found(self, mock_open):
from crewai.cli.deploy.utils import get_crewai_version
with patch('sys.stdout', new=StringIO()) as fake_out:
version = get_crewai_version()
self.assertEqual(version, 'no-version-found')
self.assertIn("Error: poetry.lock not found.", fake_out.getvalue())