Repository: crewAIInc/crewAI
Pull request #1348 (2024-09-25 12:35:15)

Create client for Tools API
This commit creates a class for the new Tools API. It extracts common methods from crewai.cli.deploy.api.CrewAPI to a parent class.


Patch:
diff --git a/mkdocs.yml b/mkdocs.yml
index 2c9bd09f..fbdfffb0 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -162,7 +162,7 @@ nav:
     - Directory RAG Search: 'tools/DirectorySearchTool.md'
     - Directory Read: 'tools/DirectoryReadTool.md'
     - Docx Rag Search: 'tools/DOCXSearchTool.md'
-    - EXA Serch Web Loader: 'tools/EXASearchTool.md'
+    - EXA Search Web Loader: 'tools/EXASearchTool.md'
     - File Read: 'tools/FileReadTool.md'
     - File Write: 'tools/FileWriteTool.md'
     - Firecrawl Crawl Website Tool: 'tools/FirecrawlCrawlWebsiteTool.md'
@@ -210,6 +210,6 @@ extra:
     property: G-N3Q505TMQ6
   social:
     - icon: fontawesome/brands/twitter
-      link: https://twitter.com/joaomdmoura
+      link: https://x.com/crewAIInc
     - icon: fontawesome/brands/github
-      link: https://github.com/joaomdmoura/crewAI
+      link: https://github.com/crewAIInc/crewAI
diff --git a/src/crewai/cli/deploy/api.py b/src/crewai/cli/deploy/api.py
index a7807dda..4a8954cd 100644
--- a/src/crewai/cli/deploy/api.py
+++ b/src/crewai/cli/deploy/api.py
@@ -1,66 +1,56 @@
-from os import getenv
-
 import requests
-
-from crewai.cli.deploy.utils import get_crewai_version
+from crewai.cli.plus_api import PlusAPI
 
 
-class CrewAPI:
+class CrewAPI(PlusAPI):
     """
-    CrewAPI class to interact with the crewAI+ API.
+    CrewAPI class to interact with the Crew resource in CrewAI+ API.
     """
 
-    def __init__(self, api_key: str) -> None:
-        self.api_key = api_key
-        self.headers = {
-            "Authorization": f"Bearer {api_key}",
-            "Content-Type": "application/json",
-            "User-Agent": f"CrewAI-CLI/{get_crewai_version()}",
-        }
-        self.base_url = getenv(
-            "CREWAI_BASE_URL", "https://app.crewai.com/crewai_plus/api/v1/crews"
-        )
-
-    def _make_request(self, method: str, endpoint: str, **kwargs) -> requests.Response:
-        url = f"{self.base_url}/{endpoint}"
-        return requests.request(method, url, headers=self.headers, **kwargs)
+    RESOURCE = "/crewai_plus/api/v1/crews"
 
     # Deploy
     def deploy_by_name(self, project_name: str) -> requests.Response:
-        return self._make_request("POST", f"by-name/{project_name}/deploy")
+        return self._make_request(
+            "POST", f"{self.RESOURCE}/by-name/{project_name}/deploy"
+        )
 
     def deploy_by_uuid(self, uuid: str) -> requests.Response:
-        return self._make_request("POST", f"{uuid}/deploy")
+        return self._make_request("POST", f"{self.RESOURCE}/{uuid}/deploy")
 
     # Status
     def status_by_name(self, project_name: str) -> requests.Response:
-        return self._make_request("GET", f"by-name/{project_name}/status")
+        return self._make_request(
+            "GET", f"{self.RESOURCE}/by-name/{project_name}/status"
+        )
 
     def status_by_uuid(self, uuid: str) -> requests.Response:
-        return self._make_request("GET", f"{uuid}/status")
+        return self._make_request("GET", f"{self.RESOURCE}/{uuid}/status")
 
     # Logs
     def logs_by_name(
         self, project_name: str, log_type: str = "deployment"
     ) -> requests.Response:
-        return self._make_request("GET", f"by-name/{project_name}/logs/{log_type}")
+        return self._make_request(
+            "GET", f"{self.RESOURCE}/by-name/{project_name}/logs/{log_type}"
+        )
 
     def logs_by_uuid(
         self, uuid: str, log_type: str = "deployment"
     ) -> requests.Response:
-        return self._make_request("GET", f"{uuid}/logs/{log_type}")
+        return self._make_request("GET", f"{self.RESOURCE}/{uuid}/logs/{log_type}")
 
     # Delete
     def delete_by_name(self, project_name: str) -> requests.Response:
-        return self._make_request("DELETE", f"by-name/{project_name}")
+        return self._make_request("DELETE", f"{self.RESOURCE}/by-name/{project_name}")
 
     def delete_by_uuid(self, uuid: str) -> requests.Response:
-        return self._make_request("DELETE", f"{uuid}")
+        return self._make_request("DELETE", f"{self.RESOURCE}/{uuid}")
 
     # List
     def list_crews(self) -> requests.Response:
-        return self._make_request("GET", "")
+        return self._make_request("GET", self.RESOURCE)
 
     # Create
     def create_crew(self, payload) -> requests.Response:
-        return self._make_request("POST", "", json=payload)
+        return self._make_request("POST", self.RESOURCE, json=payload)
diff --git a/src/crewai/cli/plus_api.py b/src/crewai/cli/plus_api.py
new file mode 100644
index 00000000..22ccc835
--- /dev/null
+++ b/src/crewai/cli/plus_api.py
@@ -0,0 +1,23 @@
+import requests
+from os import getenv
+from crewai.cli.deploy.utils import get_crewai_version
+
+
+class PlusAPI:
+    """
+    This class exposes methods for working with the CrewAI+ API.
+    """
+
+    def __init__(self, api_key: str) -> None:
+        self.api_key = api_key
+        self.headers = {
+            "Authorization": f"Bearer {api_key}",
+            "Content-Type": "application/json",
+            "User-Agent": f"CrewAI-CLI/{get_crewai_version()}",
+            "X-Crewai-Version": get_crewai_version(),
+        }
+        self.base_url = getenv("CREWAI_BASE_URL", "https://app.crewai.com")
+
+    def _make_request(self, method: str, endpoint: str, **kwargs) -> requests.Response:
+        url = f"{self.base_url}/{endpoint}"
+        return requests.request(method, url, headers=self.headers, **kwargs)
diff --git a/src/crewai/cli/tools/__init__.py b/src/crewai/cli/tools/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/crewai/cli/tools/api.py b/src/crewai/cli/tools/api.py
new file mode 100644
index 00000000..c93fdb9c
--- /dev/null
+++ b/src/crewai/cli/tools/api.py
@@ -0,0 +1,26 @@
+from typing import Optional
+from crewai.cli.plus_api import PlusAPI
+
+
+class ToolsAPI(PlusAPI):
+    RESOURCE = "/crewai_plus/api/v1/tools"
+
+    def get(self, handle: str):
+        return self._make_request("GET", f"{self.RESOURCE}/{handle}")
+
+    def publish(
+        self,
+        handle: str,
+        public: bool,
+        version: str,
+        description: Optional[str],
+        encoded_file: str,
+    ):
+        params = {
+            "handle": handle,
+            "public": public,
+            "version": version,
+            "file": encoded_file,
+            "description": description,
+        }
+        return self._make_request("POST", f"{self.RESOURCE}", json=params)


Test patch:
diff --git a/tests/cli/deploy/test_api.py b/tests/cli/deploy/test_api.py
index 616a9ff3..77a5ff84 100644
--- a/tests/cli/deploy/test_api.py
+++ b/tests/cli/deploy/test_api.py
@@ -17,11 +17,12 @@ class TestCrewAPI(unittest.TestCase):
             {
                 "Authorization": f"Bearer {self.api_key}",
                 "Content-Type": "application/json",
-                "User-Agent": "CrewAI-CLI/no-version-found"
+                "User-Agent": "CrewAI-CLI/no-version-found",
+                "X-Crewai-Version": "no-version-found",
             },
         )
 
-    @patch("crewai.cli.deploy.api.requests.request")
+    @patch("crewai.cli.plus_api.requests.request")
     def test_make_request(self, mock_request):
         mock_response = MagicMock()
         mock_request.return_value = mock_response
@@ -33,66 +34,84 @@ class TestCrewAPI(unittest.TestCase):
         )
         self.assertEqual(response, mock_response)
 
-    @patch("crewai.cli.deploy.api.CrewAPI._make_request")
+    @patch("crewai.cli.plus_api.PlusAPI._make_request")
     def test_deploy_by_name(self, mock_make_request):
         self.api.deploy_by_name("test_project")
-        mock_make_request.assert_called_once_with("POST", "by-name/test_project/deploy")
+        mock_make_request.assert_called_once_with(
+            "POST", "/crewai_plus/api/v1/crews/by-name/test_project/deploy"
+        )
 
-    @patch("crewai.cli.deploy.api.CrewAPI._make_request")
+    @patch("crewai.cli.plus_api.PlusAPI._make_request")
     def test_deploy_by_uuid(self, mock_make_request):
         self.api.deploy_by_uuid("test_uuid")
-        mock_make_request.assert_called_once_with("POST", "test_uuid/deploy")
+        mock_make_request.assert_called_once_with(
+            "POST", "/crewai_plus/api/v1/crews/test_uuid/deploy"
+        )
 
-    @patch("crewai.cli.deploy.api.CrewAPI._make_request")
+    @patch("crewai.cli.plus_api.PlusAPI._make_request")
     def test_status_by_name(self, mock_make_request):
         self.api.status_by_name("test_project")
-        mock_make_request.assert_called_once_with("GET", "by-name/test_project/status")
+        mock_make_request.assert_called_once_with(
+            "GET", "/crewai_plus/api/v1/crews/by-name/test_project/status"
+        )
 
-    @patch("crewai.cli.deploy.api.CrewAPI._make_request")
+    @patch("crewai.cli.plus_api.PlusAPI._make_request")
     def test_status_by_uuid(self, mock_make_request):
         self.api.status_by_uuid("test_uuid")
-        mock_make_request.assert_called_once_with("GET", "test_uuid/status")
+        mock_make_request.assert_called_once_with(
+            "GET", "/crewai_plus/api/v1/crews/test_uuid/status"
+        )
 
-    @patch("crewai.cli.deploy.api.CrewAPI._make_request")
+    @patch("crewai.cli.plus_api.PlusAPI._make_request")
     def test_logs_by_name(self, mock_make_request):
         self.api.logs_by_name("test_project")
         mock_make_request.assert_called_once_with(
-            "GET", "by-name/test_project/logs/deployment"
+            "GET", "/crewai_plus/api/v1/crews/by-name/test_project/logs/deployment"
         )
 
         self.api.logs_by_name("test_project", "custom_log")
         mock_make_request.assert_called_with(
-            "GET", "by-name/test_project/logs/custom_log"
+            "GET", "/crewai_plus/api/v1/crews/by-name/test_project/logs/custom_log"
         )
 
-    @patch("crewai.cli.deploy.api.CrewAPI._make_request")
+    @patch("crewai.cli.plus_api.PlusAPI._make_request")
     def test_logs_by_uuid(self, mock_make_request):
         self.api.logs_by_uuid("test_uuid")
-        mock_make_request.assert_called_once_with("GET", "test_uuid/logs/deployment")
+        mock_make_request.assert_called_once_with(
+            "GET", "/crewai_plus/api/v1/crews/test_uuid/logs/deployment"
+        )
 
         self.api.logs_by_uuid("test_uuid", "custom_log")
-        mock_make_request.assert_called_with("GET", "test_uuid/logs/custom_log")
+        mock_make_request.assert_called_with(
+            "GET", "/crewai_plus/api/v1/crews/test_uuid/logs/custom_log"
+        )
 
-    @patch("crewai.cli.deploy.api.CrewAPI._make_request")
+    @patch("crewai.cli.plus_api.PlusAPI._make_request")
     def test_delete_by_name(self, mock_make_request):
         self.api.delete_by_name("test_project")
-        mock_make_request.assert_called_once_with("DELETE", "by-name/test_project")
+        mock_make_request.assert_called_once_with(
+            "DELETE", "/crewai_plus/api/v1/crews/by-name/test_project"
+        )
 
-    @patch("crewai.cli.deploy.api.CrewAPI._make_request")
+    @patch("crewai.cli.plus_api.PlusAPI._make_request")
     def test_delete_by_uuid(self, mock_make_request):
         self.api.delete_by_uuid("test_uuid")
-        mock_make_request.assert_called_once_with("DELETE", "test_uuid")
+        mock_make_request.assert_called_once_with(
+            "DELETE", "/crewai_plus/api/v1/crews/test_uuid"
+        )
 
-    @patch("crewai.cli.deploy.api.CrewAPI._make_request")
+    @patch("crewai.cli.plus_api.PlusAPI._make_request")
     def test_list_crews(self, mock_make_request):
         self.api.list_crews()
-        mock_make_request.assert_called_once_with("GET", "")
+        mock_make_request.assert_called_once_with("GET", "/crewai_plus/api/v1/crews")
 
     @patch("crewai.cli.deploy.api.CrewAPI._make_request")
     def test_create_crew(self, mock_make_request):
         payload = {"name": "test_crew"}
         self.api.create_crew(payload)
-        mock_make_request.assert_called_once_with("POST", "", json=payload)
+        mock_make_request.assert_called_once_with(
+            "POST", "/crewai_plus/api/v1/crews", json=payload
+        )
 
     @patch.dict(environ, {"CREWAI_BASE_URL": "https://custom-url.com/api"})
     def test_custom_base_url(self):
diff --git a/tests/cli/tools/__init__.py b/tests/cli/tools/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/cli/tools/test_api.py b/tests/cli/tools/test_api.py
new file mode 100644
index 00000000..17c10067
--- /dev/null
+++ b/tests/cli/tools/test_api.py
@@ -0,0 +1,69 @@
+import unittest
+from unittest.mock import MagicMock, patch
+from crewai.cli.tools.api import ToolsAPI
+
+
+class TestToolsAPI(unittest.TestCase):
+    def setUp(self):
+        self.api_key = "test_api_key"
+        self.api = ToolsAPI(self.api_key)
+
+    @patch("crewai.cli.plus_api.PlusAPI._make_request")
+    def test_get_tool(self, mock_make_request):
+        mock_response = MagicMock()
+        mock_make_request.return_value = mock_response
+
+        response = self.api.get("test_tool_handle")
+
+        mock_make_request.assert_called_once_with(
+            "GET", "/crewai_plus/api/v1/tools/test_tool_handle"
+        )
+        self.assertEqual(response, mock_response)
+
+    @patch("crewai.cli.plus_api.PlusAPI._make_request")
+    def test_publish_tool(self, mock_make_request):
+        mock_response = MagicMock()
+        mock_make_request.return_value = mock_response
+        handle = "test_tool_handle"
+        public = True
+        version = "1.0.0"
+        description = "Test tool description"
+        encoded_file = "encoded_test_file"
+
+        response = self.api.publish(handle, public, version, description, encoded_file)
+
+        params = {
+            "handle": handle,
+            "public": public,
+            "version": version,
+            "file": encoded_file,
+            "description": description,
+        }
+        mock_make_request.assert_called_once_with(
+            "POST", "/crewai_plus/api/v1/tools", json=params
+        )
+        self.assertEqual(response, mock_response)
+
+    @patch("crewai.cli.plus_api.PlusAPI._make_request")
+    def test_publish_tool_without_description(self, mock_make_request):
+        mock_response = MagicMock()
+        mock_make_request.return_value = mock_response
+        handle = "test_tool_handle"
+        public = False
+        version = "2.0.0"
+        description = None
+        encoded_file = "encoded_test_file"
+
+        response = self.api.publish(handle, public, version, description, encoded_file)
+
+        params = {
+            "handle": handle,
+            "public": public,
+            "version": version,
+            "file": encoded_file,
+            "description": description,
+        }
+        mock_make_request.assert_called_once_with(
+            "POST", "/crewai_plus/api/v1/tools", json=params
+        )
+        self.assertEqual(response, mock_response)