From 6bb596d2e09ef6887fb8f87c9edc08ed24cfbb9f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 1 Aug 2025 02:54:46 +0000 Subject: [PATCH] feat: add --active flag to crewai run command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add --active flag to use currently active environment instead of creating virtual environment - Implement --no-sync flag usage when --active is specified - Add comprehensive tests for new functionality - Fixes #3249 Co-Authored-By: João --- src/crewai/cli/cli.py | 5 +- src/crewai/cli/run_crew.py | 17 +++- tests/cli/test_run_crew.py | 154 +++++++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 6 deletions(-) create mode 100644 tests/cli/test_run_crew.py diff --git a/src/crewai/cli/cli.py b/src/crewai/cli/cli.py index 42cb04f5e..8d3354588 100644 --- a/src/crewai/cli/cli.py +++ b/src/crewai/cli/cli.py @@ -214,9 +214,10 @@ def install(context): @crewai.command() -def run(): +@click.option("--active", is_flag=True, help="Use the currently active environment instead of creating a virtual environment") +def run(active): """Run the Crew.""" - run_crew() + run_crew(active) @crewai.command() diff --git a/src/crewai/cli/run_crew.py b/src/crewai/cli/run_crew.py index 62241a4b5..2abb4e89a 100644 --- a/src/crewai/cli/run_crew.py +++ b/src/crewai/cli/run_crew.py @@ -14,13 +14,16 @@ class CrewType(Enum): FLOW = "flow" -def run_crew() -> None: +def run_crew(active: bool = False) -> None: """ Run the crew or flow by running a command in the UV environment. Starting from version 0.103.0, this command can be used to run both standard crews and flows. For flows, it detects the type from pyproject.toml and automatically runs the appropriate command. + + Args: + active: If True, use the currently active environment instead of creating a virtual environment """ crewai_version = get_crewai_version() min_required_version = "0.71.0" @@ -44,17 +47,23 @@ def run_crew() -> None: click.echo(f"Running the {'Flow' if is_flow else 'Crew'}") # Execute the appropriate command - execute_command(crew_type) + execute_command(crew_type, active) -def execute_command(crew_type: CrewType) -> None: +def execute_command(crew_type: CrewType, active: bool = False) -> None: """ Execute the appropriate command based on crew type. Args: crew_type: The type of crew to run + active: If True, use the currently active environment instead of creating a virtual environment """ - command = ["uv", "run", "kickoff" if crew_type == CrewType.FLOW else "run_crew"] + command = ["uv", "run"] + + if active: + command.append("--no-sync") + + command.append("kickoff" if crew_type == CrewType.FLOW else "run_crew") try: subprocess.run(command, capture_output=False, text=True, check=True) diff --git a/tests/cli/test_run_crew.py b/tests/cli/test_run_crew.py new file mode 100644 index 000000000..63731d473 --- /dev/null +++ b/tests/cli/test_run_crew.py @@ -0,0 +1,154 @@ +import subprocess +from unittest import mock + +import pytest +from click.testing import CliRunner + +from crewai.cli.cli import run +from crewai.cli.run_crew import run_crew, execute_command, CrewType + + +@pytest.fixture +def runner(): + return CliRunner() + + +@mock.patch("crewai.cli.run_crew.execute_command") +@mock.patch("crewai.cli.run_crew.read_toml") +@mock.patch("crewai.cli.run_crew.get_crewai_version") +def test_run_crew_without_active_flag(mock_version, mock_toml, mock_execute, runner): + """Test that run command works without --active flag (default behavior).""" + mock_version.return_value = "0.148.0" + mock_toml.return_value = {"tool": {"crewai": {"type": "standard"}}} + + result = runner.invoke(run) + + assert result.exit_code == 0 + mock_execute.assert_called_once_with(CrewType.STANDARD, False) + + +@mock.patch("crewai.cli.run_crew.execute_command") +@mock.patch("crewai.cli.run_crew.read_toml") +@mock.patch("crewai.cli.run_crew.get_crewai_version") +def test_run_crew_with_active_flag(mock_version, mock_toml, mock_execute, runner): + """Test that run command works with --active flag.""" + mock_version.return_value = "0.148.0" + mock_toml.return_value = {"tool": {"crewai": {"type": "standard"}}} + + result = runner.invoke(run, ["--active"]) + + assert result.exit_code == 0 + mock_execute.assert_called_once_with(CrewType.STANDARD, True) + + +@mock.patch("crewai.cli.run_crew.execute_command") +@mock.patch("crewai.cli.run_crew.read_toml") +@mock.patch("crewai.cli.run_crew.get_crewai_version") +def test_run_flow_with_active_flag(mock_version, mock_toml, mock_execute, runner): + """Test that run command works with --active flag for flows.""" + mock_version.return_value = "0.148.0" + mock_toml.return_value = {"tool": {"crewai": {"type": "flow"}}} + + result = runner.invoke(run, ["--active"]) + + assert result.exit_code == 0 + mock_execute.assert_called_once_with(CrewType.FLOW, True) + + +@mock.patch("crewai.cli.run_crew.subprocess.run") +def test_execute_command_standard_crew_without_active(mock_subprocess_run): + """Test execute_command for standard crew without active flag.""" + mock_subprocess_run.return_value = subprocess.CompletedProcess( + args=["uv", "run", "run_crew"], returncode=0 + ) + + execute_command(CrewType.STANDARD, active=False) + + mock_subprocess_run.assert_called_once_with( + ["uv", "run", "run_crew"], + capture_output=False, + text=True, + check=True, + ) + + +@mock.patch("crewai.cli.run_crew.subprocess.run") +def test_execute_command_standard_crew_with_active(mock_subprocess_run): + """Test execute_command for standard crew with active flag.""" + mock_subprocess_run.return_value = subprocess.CompletedProcess( + args=["uv", "run", "--no-sync", "run_crew"], returncode=0 + ) + + execute_command(CrewType.STANDARD, active=True) + + mock_subprocess_run.assert_called_once_with( + ["uv", "run", "--no-sync", "run_crew"], + capture_output=False, + text=True, + check=True, + ) + + +@mock.patch("crewai.cli.run_crew.subprocess.run") +def test_execute_command_flow_with_active(mock_subprocess_run): + """Test execute_command for flow with active flag.""" + mock_subprocess_run.return_value = subprocess.CompletedProcess( + args=["uv", "run", "--no-sync", "kickoff"], returncode=0 + ) + + execute_command(CrewType.FLOW, active=True) + + mock_subprocess_run.assert_called_once_with( + ["uv", "run", "--no-sync", "kickoff"], + capture_output=False, + text=True, + check=True, + ) + + +@mock.patch("crewai.cli.run_crew.subprocess.run") +def test_execute_command_flow_without_active(mock_subprocess_run): + """Test execute_command for flow without active flag.""" + mock_subprocess_run.return_value = subprocess.CompletedProcess( + args=["uv", "run", "kickoff"], returncode=0 + ) + + execute_command(CrewType.FLOW, active=False) + + mock_subprocess_run.assert_called_once_with( + ["uv", "run", "kickoff"], + capture_output=False, + text=True, + check=True, + ) + + +@mock.patch("crewai.cli.run_crew.subprocess.run") +@mock.patch("crewai.cli.run_crew.click.echo") +def test_execute_command_handles_subprocess_error(mock_echo, mock_subprocess_run): + """Test that execute_command properly handles subprocess errors.""" + mock_subprocess_run.side_effect = subprocess.CalledProcessError( + returncode=1, + cmd=["uv", "run", "--no-sync", "run_crew"], + output="Error output" + ) + + execute_command(CrewType.STANDARD, active=True) + + mock_subprocess_run.assert_called_once_with( + ["uv", "run", "--no-sync", "run_crew"], + capture_output=False, + text=True, + check=True, + ) + + +@mock.patch("crewai.cli.run_crew.subprocess.run") +@mock.patch("crewai.cli.run_crew.click.echo") +def test_execute_command_handles_general_exception(mock_echo, mock_subprocess_run): + """Test that execute_command properly handles general exceptions.""" + mock_subprocess_run.side_effect = Exception("Unexpected error") + + execute_command(CrewType.STANDARD, active=True) + + mock_echo.assert_called_once_with("An unexpected error occurred: Unexpected error", err=True)