Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/crewai/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ dependencies = [
"json5~=0.10.0",
"portalocker~=2.7.0",
"pydantic-settings~=2.10.1",
"httpx~=0.28.1",
"mcp~=1.26.0",
"uv~=0.9.13",
"aiosqlite~=0.21.0",
Expand Down
8 changes: 4 additions & 4 deletions lib/crewai/src/crewai/cli/authentication/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from typing import TYPE_CHECKING, Any, TypeVar, cast
import webbrowser

import httpx
from pydantic import BaseModel, Field
import requests
from rich.console import Console

from crewai.cli.authentication.utils import validate_jwt_token
Expand Down Expand Up @@ -98,7 +98,7 @@ def _get_device_code(self) -> dict[str, Any]:
"scope": " ".join(self.oauth2_provider.get_oauth_scopes()),
"audience": self.oauth2_provider.get_audience(),
}
response = requests.post(
response = httpx.post(
url=self.oauth2_provider.get_authorize_url(),
data=device_code_payload,
timeout=20,
Expand Down Expand Up @@ -130,7 +130,7 @@ def _poll_for_token(self, device_code_data: dict[str, Any]) -> None:

attempts = 0
while True and attempts < 10:
response = requests.post(
response = httpx.post(
self.oauth2_provider.get_token_url(), data=token_payload, timeout=30
)
token_data = response.json()
Expand All @@ -149,7 +149,7 @@ def _poll_for_token(self, device_code_data: dict[str, Any]) -> None:
return

if token_data["error"] not in ("authorization_pending", "slow_down"):
raise requests.HTTPError(
raise httpx.HTTPError(
token_data.get("error_description") or token_data.get("error")
)

Expand Down
13 changes: 7 additions & 6 deletions lib/crewai/src/crewai/cli/command.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import requests
from requests.exceptions import JSONDecodeError
import json

import httpx
from rich.console import Console

from crewai.cli.authentication.token import get_auth_token
Expand Down Expand Up @@ -30,16 +31,16 @@ def __init__(self, telemetry: Telemetry) -> None:
console.print("Run 'crewai login' to sign up/login.", style="bold green")
raise SystemExit from None

def _validate_response(self, response: requests.Response) -> None:
def _validate_response(self, response: httpx.Response) -> None:
"""
Handle and display error messages from API responses.

Args:
response (requests.Response): The response from the Plus API
response (httpx.Response): The response from the Plus API
"""
try:
json_response = response.json()
except (JSONDecodeError, ValueError):
except (json.JSONDecodeError, ValueError):
console.print(
"Failed to parse response from Enterprise API failed. Details:",
style="bold red",
Expand All @@ -62,7 +63,7 @@ def _validate_response(self, response: requests.Response) -> None:
)
raise SystemExit

if not response.ok:
if not response.is_success:
console.print(
"Request to Enterprise API failed. Details:", style="bold red"
)
Expand Down
10 changes: 5 additions & 5 deletions lib/crewai/src/crewai/cli/enterprise/main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
from typing import Any, cast

import requests
from requests.exceptions import JSONDecodeError, RequestException
import httpx
from rich.console import Console

from crewai.cli.authentication.main import Oauth2Settings, ProviderFactory
Expand Down Expand Up @@ -47,12 +47,12 @@ def _fetch_oauth_config(self, enterprise_url: str) -> dict[str, Any]:
"User-Agent": f"CrewAI-CLI/{get_crewai_version()}",
"X-Crewai-Version": get_crewai_version(),
}
response = requests.get(oauth_endpoint, timeout=30, headers=headers)
response = httpx.get(oauth_endpoint, timeout=30, headers=headers)
response.raise_for_status()

try:
oauth_config = response.json()
except JSONDecodeError as e:
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON response from {oauth_endpoint}") from e

self._validate_oauth_config(oauth_config)
Expand All @@ -62,7 +62,7 @@ def _fetch_oauth_config(self, enterprise_url: str) -> dict[str, Any]:
)
return cast(dict[str, Any], oauth_config)

except RequestException as e:
except httpx.HTTPError as e:
raise ValueError(f"Failed to connect to enterprise URL: {e!s}") from e
except Exception as e:
raise ValueError(f"Error fetching OAuth2 configuration: {e!s}") from e
Expand Down
14 changes: 7 additions & 7 deletions lib/crewai/src/crewai/cli/organization/main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from requests import HTTPError
from httpx import HTTPStatusError
from rich.console import Console
from rich.table import Table

Expand All @@ -10,11 +10,11 @@


class OrganizationCommand(BaseCommand, PlusAPIMixin):
def __init__(self):
def __init__(self) -> None:
BaseCommand.__init__(self)
PlusAPIMixin.__init__(self, telemetry=self._telemetry)

def list(self):
def list(self) -> None:
try:
response = self.plus_api_client.get_organizations()
response.raise_for_status()
Expand All @@ -33,7 +33,7 @@ def list(self):
table.add_row(org["name"], org["uuid"])

console.print(table)
except HTTPError as e:
except HTTPStatusError as e:
if e.response.status_code == 401:
console.print(
"You are not logged in to any organization. Use 'crewai login' to login.",
Expand All @@ -50,7 +50,7 @@ def list(self):
)
raise SystemExit(1) from e

def switch(self, org_id):
def switch(self, org_id: str) -> None:
try:
response = self.plus_api_client.get_organizations()
response.raise_for_status()
Expand All @@ -72,7 +72,7 @@ def switch(self, org_id):
f"Successfully switched to {org['name']} ({org['uuid']})",
style="bold green",
)
except HTTPError as e:
except HTTPStatusError as e:
if e.response.status_code == 401:
console.print(
"You are not logged in to any organization. Use 'crewai login' to login.",
Expand All @@ -87,7 +87,7 @@ def switch(self, org_id):
console.print(f"Failed to switch organization: {e!s}", style="bold red")
raise SystemExit(1) from e

def current(self):
def current(self) -> None:
settings = Settings()
if settings.org_uuid:
console.print(
Expand Down
59 changes: 27 additions & 32 deletions lib/crewai/src/crewai/cli/plus_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from urllib.parse import urljoin

import httpx
import requests

from crewai.cli.config import Settings
from crewai.cli.constants import DEFAULT_CREWAI_ENTERPRISE_URL
Expand Down Expand Up @@ -43,16 +42,16 @@ def __init__(self, api_key: str) -> None:

def _make_request(
self, method: str, endpoint: str, **kwargs: Any
) -> requests.Response:
) -> httpx.Response:
url = urljoin(self.base_url, endpoint)
session = requests.Session()
session.trust_env = False
return session.request(method, url, headers=self.headers, **kwargs)
verify = kwargs.pop("verify", True)
with httpx.Client(trust_env=False, verify=verify) as client:
return client.request(method, url, headers=self.headers, **kwargs)

def login_to_tool_repository(self) -> requests.Response:
def login_to_tool_repository(self) -> httpx.Response:
return self._make_request("POST", f"{self.TOOLS_RESOURCE}/login")

def get_tool(self, handle: str) -> requests.Response:
def get_tool(self, handle: str) -> httpx.Response:
return self._make_request("GET", f"{self.TOOLS_RESOURCE}/{handle}")

async def get_agent(self, handle: str) -> httpx.Response:
Expand All @@ -68,7 +67,7 @@ def publish_tool(
description: str | None,
encoded_file: str,
available_exports: list[dict[str, Any]] | None = None,
) -> requests.Response:
) -> httpx.Response:
params = {
"handle": handle,
"public": is_public,
Expand All @@ -79,54 +78,52 @@ def publish_tool(
}
return self._make_request("POST", f"{self.TOOLS_RESOURCE}", json=params)

def deploy_by_name(self, project_name: str) -> requests.Response:
def deploy_by_name(self, project_name: str) -> httpx.Response:
return self._make_request(
"POST", f"{self.CREWS_RESOURCE}/by-name/{project_name}/deploy"
)

def deploy_by_uuid(self, uuid: str) -> requests.Response:
def deploy_by_uuid(self, uuid: str) -> httpx.Response:
return self._make_request("POST", f"{self.CREWS_RESOURCE}/{uuid}/deploy")

def crew_status_by_name(self, project_name: str) -> requests.Response:
def crew_status_by_name(self, project_name: str) -> httpx.Response:
return self._make_request(
"GET", f"{self.CREWS_RESOURCE}/by-name/{project_name}/status"
)

def crew_status_by_uuid(self, uuid: str) -> requests.Response:
def crew_status_by_uuid(self, uuid: str) -> httpx.Response:
return self._make_request("GET", f"{self.CREWS_RESOURCE}/{uuid}/status")

def crew_by_name(
self, project_name: str, log_type: str = "deployment"
) -> requests.Response:
) -> httpx.Response:
return self._make_request(
"GET", f"{self.CREWS_RESOURCE}/by-name/{project_name}/logs/{log_type}"
)

def crew_by_uuid(
self, uuid: str, log_type: str = "deployment"
) -> requests.Response:
def crew_by_uuid(self, uuid: str, log_type: str = "deployment") -> httpx.Response:
return self._make_request(
"GET", f"{self.CREWS_RESOURCE}/{uuid}/logs/{log_type}"
)

def delete_crew_by_name(self, project_name: str) -> requests.Response:
def delete_crew_by_name(self, project_name: str) -> httpx.Response:
return self._make_request(
"DELETE", f"{self.CREWS_RESOURCE}/by-name/{project_name}"
)

def delete_crew_by_uuid(self, uuid: str) -> requests.Response:
def delete_crew_by_uuid(self, uuid: str) -> httpx.Response:
return self._make_request("DELETE", f"{self.CREWS_RESOURCE}/{uuid}")

def list_crews(self) -> requests.Response:
def list_crews(self) -> httpx.Response:
return self._make_request("GET", self.CREWS_RESOURCE)

def create_crew(self, payload: dict[str, Any]) -> requests.Response:
def create_crew(self, payload: dict[str, Any]) -> httpx.Response:
return self._make_request("POST", self.CREWS_RESOURCE, json=payload)

def get_organizations(self) -> requests.Response:
def get_organizations(self) -> httpx.Response:
return self._make_request("GET", self.ORGANIZATIONS_RESOURCE)

def initialize_trace_batch(self, payload: dict[str, Any]) -> requests.Response:
def initialize_trace_batch(self, payload: dict[str, Any]) -> httpx.Response:
return self._make_request(
"POST",
f"{self.TRACING_RESOURCE}/batches",
Expand All @@ -136,7 +133,7 @@ def initialize_trace_batch(self, payload: dict[str, Any]) -> requests.Response:

def initialize_ephemeral_trace_batch(
self, payload: dict[str, Any]
) -> requests.Response:
) -> httpx.Response:
return self._make_request(
"POST",
f"{self.EPHEMERAL_TRACING_RESOURCE}/batches",
Expand All @@ -145,7 +142,7 @@ def initialize_ephemeral_trace_batch(

def send_trace_events(
self, trace_batch_id: str, payload: dict[str, Any]
) -> requests.Response:
) -> httpx.Response:
return self._make_request(
"POST",
f"{self.TRACING_RESOURCE}/batches/{trace_batch_id}/events",
Expand All @@ -155,7 +152,7 @@ def send_trace_events(

def send_ephemeral_trace_events(
self, trace_batch_id: str, payload: dict[str, Any]
) -> requests.Response:
) -> httpx.Response:
return self._make_request(
"POST",
f"{self.EPHEMERAL_TRACING_RESOURCE}/batches/{trace_batch_id}/events",
Expand All @@ -165,7 +162,7 @@ def send_ephemeral_trace_events(

def finalize_trace_batch(
self, trace_batch_id: str, payload: dict[str, Any]
) -> requests.Response:
) -> httpx.Response:
return self._make_request(
"PATCH",
f"{self.TRACING_RESOURCE}/batches/{trace_batch_id}/finalize",
Expand All @@ -175,7 +172,7 @@ def finalize_trace_batch(

def finalize_ephemeral_trace_batch(
self, trace_batch_id: str, payload: dict[str, Any]
) -> requests.Response:
) -> httpx.Response:
return self._make_request(
"PATCH",
f"{self.EPHEMERAL_TRACING_RESOURCE}/batches/{trace_batch_id}/finalize",
Expand All @@ -185,21 +182,19 @@ def finalize_ephemeral_trace_batch(

def mark_trace_batch_as_failed(
self, trace_batch_id: str, error_message: str
) -> requests.Response:
) -> httpx.Response:
return self._make_request(
"PATCH",
f"{self.TRACING_RESOURCE}/batches/{trace_batch_id}",
json={"status": "failed", "failure_reason": error_message},
timeout=30,
)

def get_triggers(self) -> requests.Response:
def get_triggers(self) -> httpx.Response:
"""Get all available triggers from integrations."""
return self._make_request("GET", f"{self.INTEGRATIONS_RESOURCE}/apps")

def get_trigger_payload(
self, app_slug: str, trigger_slug: str
) -> requests.Response:
def get_trigger_payload(self, app_slug: str, trigger_slug: str) -> httpx.Response:
"""Get sample payload for a specific trigger."""
return self._make_request(
"GET", f"{self.INTEGRATIONS_RESOURCE}/{app_slug}/{trigger_slug}/payload"
Expand Down
20 changes: 10 additions & 10 deletions lib/crewai/src/crewai/cli/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import certifi
import click
import requests
import httpx

from crewai.cli.constants import JSON_URL, MODELS, PROVIDERS

Expand Down Expand Up @@ -165,20 +165,20 @@ def fetch_provider_data(cache_file: Path) -> dict[str, Any] | None:
ssl_config = os.environ["SSL_CERT_FILE"] = certifi.where()

try:
response = requests.get(JSON_URL, stream=True, timeout=60, verify=ssl_config)
response.raise_for_status()
data = download_data(response)
with open(cache_file, "w") as f:
json.dump(data, f)
return data
except requests.RequestException as e:
with httpx.stream("GET", JSON_URL, timeout=60, verify=ssl_config) as response:
response.raise_for_status()
data = download_data(response)
with open(cache_file, "w") as f:
json.dump(data, f)
return data
except httpx.HTTPError as e:
click.secho(f"Error fetching provider data: {e}", fg="red")
except json.JSONDecodeError:
click.secho("Error parsing provider data. Invalid JSON format.", fg="red")
return None


def download_data(response: requests.Response) -> dict[str, Any]:
def download_data(response: httpx.Response) -> dict[str, Any]:
"""Downloads data from a given HTTP response and returns the JSON content.

Args:
Expand All @@ -194,7 +194,7 @@ def download_data(response: requests.Response) -> dict[str, Any]:
with click.progressbar(
length=total_size, label="Downloading", show_pos=True
) as bar:
for chunk in response.iter_content(block_size):
for chunk in response.iter_bytes(block_size):
if chunk:
data_chunks.append(chunk)
bar.update(len(chunk))
Expand Down
Loading