feat: migrate CLI http client from requests to httpx
Some checks failed
Build uv cache / build-cache (3.10) (push) Has been cancelled
Build uv cache / build-cache (3.11) (push) Has been cancelled
Build uv cache / build-cache (3.12) (push) Has been cancelled
Build uv cache / build-cache (3.13) (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled

This commit is contained in:
Greyson LaLonde
2026-02-20 18:21:05 -05:00
committed by GitHub
parent 71b4f8402a
commit 51754899a2
15 changed files with 138 additions and 130 deletions

View File

@@ -2,7 +2,7 @@ from datetime import datetime, timedelta
from unittest.mock import MagicMock, call, patch
import pytest
import requests
import httpx
from crewai.cli.authentication.main import AuthenticationCommand
from crewai.cli.constants import (
CREWAI_ENTERPRISE_DEFAULT_OAUTH2_AUDIENCE,
@@ -220,7 +220,7 @@ class TestAuthenticationCommand:
]
mock_console_print.assert_has_calls(expected_calls)
@patch("requests.post")
@patch("crewai.cli.authentication.main.httpx.post")
def test_get_device_code(self, mock_post):
mock_response = MagicMock()
mock_response.json.return_value = {
@@ -256,7 +256,7 @@ class TestAuthenticationCommand:
"verification_uri_complete": "https://example.com/auth",
}
@patch("requests.post")
@patch("crewai.cli.authentication.main.httpx.post")
@patch("crewai.cli.authentication.main.console.print")
def test_poll_for_token_success(self, mock_console_print, mock_post):
mock_response_success = MagicMock()
@@ -305,7 +305,7 @@ class TestAuthenticationCommand:
]
mock_console_print.assert_has_calls(expected_calls)
@patch("requests.post")
@patch("crewai.cli.authentication.main.httpx.post")
@patch("crewai.cli.authentication.main.console.print")
def test_poll_for_token_timeout(self, mock_console_print, mock_post):
mock_response_pending = MagicMock()
@@ -324,7 +324,7 @@ class TestAuthenticationCommand:
"Timeout: Failed to get the token. Please try again.", style="bold red"
)
@patch("requests.post")
@patch("crewai.cli.authentication.main.httpx.post")
def test_poll_for_token_error(self, mock_post):
"""Test the method to poll for token (error path)."""
# Setup mock to return error
@@ -338,5 +338,5 @@ class TestAuthenticationCommand:
device_code_data = {"device_code": "test_device_code", "interval": 1}
with pytest.raises(requests.HTTPError):
with pytest.raises(httpx.HTTPError):
self.auth_command._poll_for_token(device_code_data)

View File

@@ -4,10 +4,11 @@ from io import StringIO
from unittest.mock import MagicMock, Mock, patch
import pytest
import requests
import json
import httpx
from crewai.cli.deploy.main import DeployCommand
from crewai.cli.utils import parse_toml
from requests.exceptions import JSONDecodeError
class TestDeployCommand(unittest.TestCase):
@@ -37,18 +38,18 @@ class TestDeployCommand(unittest.TestCase):
DeployCommand()
def test_validate_response_successful_response(self):
mock_response = Mock(spec=requests.Response)
mock_response = Mock(spec=httpx.Response)
mock_response.json.return_value = {"message": "Success"}
mock_response.status_code = 200
mock_response.ok = True
mock_response.is_success = True
with patch("sys.stdout", new=StringIO()) as fake_out:
self.deploy_command._validate_response(mock_response)
assert fake_out.getvalue() == ""
def test_validate_response_json_decode_error(self):
mock_response = Mock(spec=requests.Response)
mock_response.json.side_effect = JSONDecodeError("Decode error", "", 0)
mock_response = Mock(spec=httpx.Response)
mock_response.json.side_effect = json.JSONDecodeError("Decode error", "", 0)
mock_response.status_code = 500
mock_response.content = b"Invalid JSON"
@@ -64,13 +65,13 @@ class TestDeployCommand(unittest.TestCase):
assert "Response:\nInvalid JSON" in output
def test_validate_response_422_error(self):
mock_response = Mock(spec=requests.Response)
mock_response = Mock(spec=httpx.Response)
mock_response.json.return_value = {
"field1": ["Error message 1"],
"field2": ["Error message 2"],
}
mock_response.status_code = 422
mock_response.ok = False
mock_response.is_success = False
with patch("sys.stdout", new=StringIO()) as fake_out:
with pytest.raises(SystemExit):
@@ -84,10 +85,10 @@ class TestDeployCommand(unittest.TestCase):
assert "Field2 Error message 2" in output
def test_validate_response_other_error(self):
mock_response = Mock(spec=requests.Response)
mock_response = Mock(spec=httpx.Response)
mock_response.json.return_value = {"error": "Something went wrong"}
mock_response.status_code = 500
mock_response.ok = False
mock_response.is_success = False
with patch("sys.stdout", new=StringIO()) as fake_out:
with pytest.raises(SystemExit):

View File

@@ -3,8 +3,9 @@ import unittest
from pathlib import Path
from unittest.mock import Mock, patch
import requests
from requests.exceptions import JSONDecodeError
import json
import httpx
from crewai.cli.enterprise.main import EnterpriseConfigureCommand
from crewai.cli.settings.main import SettingsCommand
@@ -25,7 +26,7 @@ class TestEnterpriseConfigureCommand(unittest.TestCase):
def tearDown(self):
shutil.rmtree(self.test_dir)
@patch('crewai.cli.enterprise.main.requests.get')
@patch('crewai.cli.enterprise.main.httpx.get')
@patch('crewai.cli.enterprise.main.get_crewai_version')
def test_successful_configuration(self, mock_get_version, mock_requests_get):
mock_get_version.return_value = "1.0.0"
@@ -73,19 +74,23 @@ class TestEnterpriseConfigureCommand(unittest.TestCase):
self.assertEqual(call_args[0], key)
self.assertEqual(call_args[1], value)
@patch('crewai.cli.enterprise.main.requests.get')
@patch('crewai.cli.enterprise.main.httpx.get')
@patch('crewai.cli.enterprise.main.get_crewai_version')
def test_http_error_handling(self, mock_get_version, mock_requests_get):
mock_get_version.return_value = "1.0.0"
mock_response = Mock()
mock_response.raise_for_status.side_effect = requests.HTTPError("404 Not Found")
mock_response.raise_for_status.side_effect = httpx.HTTPStatusError(
"404 Not Found",
request=httpx.Request("GET", "http://test"),
response=httpx.Response(404),
)
mock_requests_get.return_value = mock_response
with self.assertRaises(SystemExit):
self.enterprise_command.configure("https://enterprise.example.com")
@patch('crewai.cli.enterprise.main.requests.get')
@patch('crewai.cli.enterprise.main.httpx.get')
@patch('crewai.cli.enterprise.main.get_crewai_version')
def test_invalid_json_response(self, mock_get_version, mock_requests_get):
mock_get_version.return_value = "1.0.0"
@@ -93,13 +98,13 @@ class TestEnterpriseConfigureCommand(unittest.TestCase):
mock_response = Mock()
mock_response.status_code = 200
mock_response.raise_for_status.return_value = None
mock_response.json.side_effect = JSONDecodeError("Invalid JSON", "", 0)
mock_response.json.side_effect = json.JSONDecodeError("Invalid JSON", "", 0)
mock_requests_get.return_value = mock_response
with self.assertRaises(SystemExit):
self.enterprise_command.configure("https://enterprise.example.com")
@patch('crewai.cli.enterprise.main.requests.get')
@patch('crewai.cli.enterprise.main.httpx.get')
@patch('crewai.cli.enterprise.main.get_crewai_version')
def test_missing_required_fields(self, mock_get_version, mock_requests_get):
mock_get_version.return_value = "1.0.0"
@@ -115,7 +120,7 @@ class TestEnterpriseConfigureCommand(unittest.TestCase):
with self.assertRaises(SystemExit):
self.enterprise_command.configure("https://enterprise.example.com")
@patch('crewai.cli.enterprise.main.requests.get')
@patch('crewai.cli.enterprise.main.httpx.get')
@patch('crewai.cli.enterprise.main.get_crewai_version')
def test_settings_update_error(self, mock_get_version, mock_requests_get):
mock_get_version.return_value = "1.0.0"

View File

@@ -3,7 +3,7 @@ from unittest.mock import MagicMock, patch, call
import pytest
from click.testing import CliRunner
import requests
import httpx
from crewai.cli.organization.main import OrganizationCommand
from crewai.cli.cli import org_list, switch, current
@@ -115,7 +115,7 @@ class TestOrganizationCommand(unittest.TestCase):
def test_list_organizations_api_error(self, mock_console):
self.org_command.plus_api_client = MagicMock()
self.org_command.plus_api_client.get_organizations.side_effect = (
requests.exceptions.RequestException("API Error")
httpx.HTTPError("API Error")
)
with pytest.raises(SystemExit):
@@ -201,8 +201,10 @@ class TestOrganizationCommand(unittest.TestCase):
@patch("crewai.cli.organization.main.console")
def test_list_organizations_unauthorized(self, mock_console):
mock_response = MagicMock()
mock_http_error = requests.exceptions.HTTPError(
"401 Client Error: Unauthorized", response=MagicMock(status_code=401)
mock_http_error = httpx.HTTPStatusError(
"401 Client Error: Unauthorized",
request=httpx.Request("GET", "http://test"),
response=httpx.Response(401),
)
mock_response.raise_for_status.side_effect = mock_http_error
@@ -219,8 +221,10 @@ class TestOrganizationCommand(unittest.TestCase):
@patch("crewai.cli.organization.main.console")
def test_switch_organization_unauthorized(self, mock_console):
mock_response = MagicMock()
mock_http_error = requests.exceptions.HTTPError(
"401 Client Error: Unauthorized", response=MagicMock(status_code=401)
mock_http_error = httpx.HTTPStatusError(
"401 Client Error: Unauthorized",
request=httpx.Request("GET", "http://test"),
response=httpx.Response(401),
)
mock_response.raise_for_status.side_effect = mock_http_error

View File

@@ -33,9 +33,9 @@ class TestPlusAPI(unittest.TestCase):
self.assertEqual(response, mock_response)
def assert_request_with_org_id(
self, mock_make_request, method: str, endpoint: str, **kwargs
self, mock_client_instance, method: str, endpoint: str, **kwargs
):
mock_make_request.assert_called_once_with(
mock_client_instance.request.assert_called_once_with(
method,
f"{os.getenv('CREWAI_PLUS_URL')}{endpoint}",
headers={
@@ -49,24 +49,25 @@ class TestPlusAPI(unittest.TestCase):
)
@patch("crewai.cli.plus_api.Settings")
@patch("requests.Session.request")
@patch("crewai.cli.plus_api.httpx.Client")
def test_login_to_tool_repository_with_org_uuid(
self, mock_make_request, mock_settings_class
self, mock_client_class, mock_settings_class
):
mock_settings = MagicMock()
mock_settings.org_uuid = self.org_uuid
mock_settings.enterprise_base_url = os.getenv('CREWAI_PLUS_URL')
mock_settings_class.return_value = mock_settings
# re-initialize Client
self.api = PlusAPI(self.api_key)
mock_client_instance = MagicMock()
mock_response = MagicMock()
mock_make_request.return_value = mock_response
mock_client_instance.request.return_value = mock_response
mock_client_class.return_value.__enter__.return_value = mock_client_instance
response = self.api.login_to_tool_repository()
self.assert_request_with_org_id(
mock_make_request, "POST", "/crewai_plus/api/v1/tools/login"
mock_client_instance, "POST", "/crewai_plus/api/v1/tools/login"
)
self.assertEqual(response, mock_response)
@@ -82,23 +83,23 @@ class TestPlusAPI(unittest.TestCase):
self.assertEqual(response, mock_response)
@patch("crewai.cli.plus_api.Settings")
@patch("requests.Session.request")
def test_get_tool_with_org_uuid(self, mock_make_request, mock_settings_class):
@patch("crewai.cli.plus_api.httpx.Client")
def test_get_tool_with_org_uuid(self, mock_client_class, mock_settings_class):
mock_settings = MagicMock()
mock_settings.org_uuid = self.org_uuid
mock_settings.enterprise_base_url = os.getenv('CREWAI_PLUS_URL')
mock_settings_class.return_value = mock_settings
# re-initialize Client
self.api = PlusAPI(self.api_key)
# Set up mock response
mock_client_instance = MagicMock()
mock_response = MagicMock()
mock_make_request.return_value = mock_response
mock_client_instance.request.return_value = mock_response
mock_client_class.return_value.__enter__.return_value = mock_client_instance
response = self.api.get_tool("test_tool_handle")
self.assert_request_with_org_id(
mock_make_request, "GET", "/crewai_plus/api/v1/tools/test_tool_handle"
mock_client_instance, "GET", "/crewai_plus/api/v1/tools/test_tool_handle"
)
self.assertEqual(response, mock_response)
@@ -130,18 +131,18 @@ class TestPlusAPI(unittest.TestCase):
self.assertEqual(response, mock_response)
@patch("crewai.cli.plus_api.Settings")
@patch("requests.Session.request")
def test_publish_tool_with_org_uuid(self, mock_make_request, mock_settings_class):
@patch("crewai.cli.plus_api.httpx.Client")
def test_publish_tool_with_org_uuid(self, mock_client_class, mock_settings_class):
mock_settings = MagicMock()
mock_settings.org_uuid = self.org_uuid
mock_settings.enterprise_base_url = os.getenv('CREWAI_PLUS_URL')
mock_settings_class.return_value = mock_settings
# re-initialize Client
self.api = PlusAPI(self.api_key)
# Set up mock response
mock_client_instance = MagicMock()
mock_response = MagicMock()
mock_make_request.return_value = mock_response
mock_client_instance.request.return_value = mock_response
mock_client_class.return_value.__enter__.return_value = mock_client_instance
handle = "test_tool_handle"
public = True
@@ -153,7 +154,6 @@ class TestPlusAPI(unittest.TestCase):
handle, public, version, description, encoded_file
)
# Expected params including organization_uuid
expected_params = {
"handle": handle,
"public": public,
@@ -164,7 +164,7 @@ class TestPlusAPI(unittest.TestCase):
}
self.assert_request_with_org_id(
mock_make_request, "POST", "/crewai_plus/api/v1/tools", json=expected_params
mock_client_instance, "POST", "/crewai_plus/api/v1/tools", json=expected_params
)
self.assertEqual(response, mock_response)
@@ -195,20 +195,19 @@ class TestPlusAPI(unittest.TestCase):
)
self.assertEqual(response, mock_response)
@patch("crewai.cli.plus_api.requests.Session")
def test_make_request(self, mock_session):
@patch("crewai.cli.plus_api.httpx.Client")
def test_make_request(self, mock_client_class):
mock_client_instance = MagicMock()
mock_response = MagicMock()
mock_session_instance = mock_session.return_value
mock_session_instance.request.return_value = mock_response
mock_client_instance.request.return_value = mock_response
mock_client_class.return_value.__enter__.return_value = mock_client_instance
response = self.api._make_request("GET", "test_endpoint")
mock_session.assert_called_once()
mock_session_instance.request.assert_called_once_with(
mock_client_class.assert_called_once_with(trust_env=False, verify=True)
mock_client_instance.request.assert_called_once_with(
"GET", f"{self.api.base_url}/test_endpoint", headers=self.api.headers
)
mock_session_instance.trust_env = False
self.assertEqual(response, mock_response)
@patch("crewai.cli.plus_api.PlusAPI._make_request")

View File

@@ -351,7 +351,7 @@ def test_publish_api_error(
mock_response = MagicMock()
mock_response.status_code = 500
mock_response.json.return_value = {"error": "Internal Server Error"}
mock_response.ok = False
mock_response.is_success = False
mock_publish.return_value = mock_response
with raises(SystemExit):

View File

@@ -3,7 +3,7 @@ import subprocess
import unittest
from unittest.mock import Mock, patch
import requests
import httpx
from crewai.cli.triggers.main import TriggersCommand
@@ -21,7 +21,7 @@ class TestTriggersCommand(unittest.TestCase):
@patch("crewai.cli.triggers.main.console.print")
def test_list_triggers_success(self, mock_console_print):
mock_response = Mock(spec=requests.Response)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
mock_response.ok = True
mock_response.json.return_value = {
@@ -50,7 +50,7 @@ class TestTriggersCommand(unittest.TestCase):
@patch("crewai.cli.triggers.main.console.print")
def test_list_triggers_no_apps(self, mock_console_print):
mock_response = Mock(spec=requests.Response)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
mock_response.ok = True
mock_response.json.return_value = {"apps": []}
@@ -81,7 +81,7 @@ class TestTriggersCommand(unittest.TestCase):
@patch("crewai.cli.triggers.main.console.print")
@patch.object(TriggersCommand, "_run_crew_with_payload")
def test_execute_with_trigger_success(self, mock_run_crew, mock_console_print):
mock_response = Mock(spec=requests.Response)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
mock_response.ok = True
mock_response.json.return_value = {
@@ -99,7 +99,7 @@ class TestTriggersCommand(unittest.TestCase):
@patch("crewai.cli.triggers.main.console.print")
def test_execute_with_trigger_not_found(self, mock_console_print):
mock_response = Mock(spec=requests.Response)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 404
mock_response.json.return_value = {"error": "Trigger not found"}
self.mock_client.get_trigger_payload.return_value = mock_response
@@ -159,7 +159,7 @@ class TestTriggersCommand(unittest.TestCase):
@patch("crewai.cli.triggers.main.console.print")
def test_execute_with_trigger_with_default_error_message(self, mock_console_print):
mock_response = Mock(spec=requests.Response)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 404
mock_response.json.return_value = {}
self.mock_client.get_trigger_payload.return_value = mock_response