mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-28 17:48:13 +00:00
Merge branch 'feat/ibm-memory' of https://github.com/joaomdmoura/crewAI into feat/ibm-memory
This commit is contained in:
38
src/crewai/cli/config.py
Normal file
38
src/crewai/cli/config.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
DEFAULT_CONFIG_PATH = Path.home() / ".config" / "crewai" / "settings.json"
|
||||||
|
|
||||||
|
class Settings(BaseModel):
|
||||||
|
tool_repository_username: Optional[str] = Field(None, description="Username for interacting with the Tool Repository")
|
||||||
|
tool_repository_password: Optional[str] = Field(None, description="Password for interacting with the Tool Repository")
|
||||||
|
config_path: Path = Field(default=DEFAULT_CONFIG_PATH, exclude=True)
|
||||||
|
|
||||||
|
def __init__(self, config_path: Path = DEFAULT_CONFIG_PATH, **data):
|
||||||
|
"""Load Settings from config path"""
|
||||||
|
config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
file_data = {}
|
||||||
|
if config_path.is_file():
|
||||||
|
try:
|
||||||
|
with config_path.open("r") as f:
|
||||||
|
file_data = json.load(f)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
file_data = {}
|
||||||
|
|
||||||
|
merged_data = {**file_data, **data}
|
||||||
|
super().__init__(config_path=config_path, **merged_data)
|
||||||
|
|
||||||
|
def dump(self) -> None:
|
||||||
|
"""Save current settings to settings.json"""
|
||||||
|
if self.config_path.is_file():
|
||||||
|
with self.config_path.open("r") as f:
|
||||||
|
existing_data = json.load(f)
|
||||||
|
else:
|
||||||
|
existing_data = {}
|
||||||
|
|
||||||
|
updated_data = {**existing_data, **self.model_dump(exclude_unset=True)}
|
||||||
|
with self.config_path.open("w") as f:
|
||||||
|
json.dump(updated_data, f, indent=4)
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
import base64
|
import base64
|
||||||
import os
|
import os
|
||||||
import platform
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from netrc import netrc
|
|
||||||
import stat
|
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
|
||||||
from crewai.cli import git
|
from crewai.cli import git
|
||||||
from crewai.cli.command import BaseCommand, PlusAPIMixin
|
from crewai.cli.command import BaseCommand, PlusAPIMixin
|
||||||
|
from crewai.cli.config import Settings
|
||||||
from crewai.cli.utils import (
|
from crewai.cli.utils import (
|
||||||
get_project_description,
|
get_project_description,
|
||||||
get_project_name,
|
get_project_name,
|
||||||
@@ -153,26 +151,16 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
|||||||
raise SystemExit
|
raise SystemExit
|
||||||
|
|
||||||
login_response_json = login_response.json()
|
login_response_json = login_response.json()
|
||||||
self._set_netrc_credentials(login_response_json["credential"])
|
|
||||||
|
settings = Settings()
|
||||||
|
settings.tool_repository_username = login_response_json["credential"]["username"]
|
||||||
|
settings.tool_repository_password = login_response_json["credential"]["password"]
|
||||||
|
settings.dump()
|
||||||
|
|
||||||
console.print(
|
console.print(
|
||||||
"Successfully authenticated to the tool repository.", style="bold green"
|
"Successfully authenticated to the tool repository.", style="bold green"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _set_netrc_credentials(self, credentials, netrc_path=None):
|
|
||||||
if not netrc_path:
|
|
||||||
netrc_filename = "_netrc" if platform.system() == "Windows" else ".netrc"
|
|
||||||
netrc_path = Path.home() / netrc_filename
|
|
||||||
netrc_path.touch(mode=stat.S_IRUSR | stat.S_IWUSR, exist_ok=True)
|
|
||||||
|
|
||||||
netrc_instance = netrc(file=netrc_path)
|
|
||||||
netrc_instance.hosts["app.crewai.com"] = (credentials["username"], "", credentials["password"])
|
|
||||||
|
|
||||||
with open(netrc_path, 'w') as file:
|
|
||||||
file.write(str(netrc_instance))
|
|
||||||
|
|
||||||
console.print(f"Added credentials to {netrc_path}", style="bold green")
|
|
||||||
|
|
||||||
def _add_package(self, tool_details):
|
def _add_package(self, tool_details):
|
||||||
tool_handle = tool_details["handle"]
|
tool_handle = tool_details["handle"]
|
||||||
repository_handle = tool_details["repository"]["handle"]
|
repository_handle = tool_details["repository"]["handle"]
|
||||||
@@ -187,7 +175,11 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
|||||||
tool_handle,
|
tool_handle,
|
||||||
]
|
]
|
||||||
add_package_result = subprocess.run(
|
add_package_result = subprocess.run(
|
||||||
add_package_command, capture_output=False, text=True, check=True
|
add_package_command,
|
||||||
|
capture_output=False,
|
||||||
|
env=self._build_env_with_credentials(repository_handle),
|
||||||
|
text=True,
|
||||||
|
check=True
|
||||||
)
|
)
|
||||||
|
|
||||||
if add_package_result.stderr:
|
if add_package_result.stderr:
|
||||||
@@ -206,3 +198,13 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
|
|||||||
"[bold yellow]Tip:[/bold yellow] Navigate to a different directory and try again."
|
"[bold yellow]Tip:[/bold yellow] Navigate to a different directory and try again."
|
||||||
)
|
)
|
||||||
raise SystemExit
|
raise SystemExit
|
||||||
|
|
||||||
|
def _build_env_with_credentials(self, repository_handle: str):
|
||||||
|
repository_handle = repository_handle.upper().replace("-", "_")
|
||||||
|
settings = Settings()
|
||||||
|
|
||||||
|
env = os.environ.copy()
|
||||||
|
env[f"UV_INDEX_{repository_handle}_USERNAME"] = str(settings.tool_repository_username or "")
|
||||||
|
env[f"UV_INDEX_{repository_handle}_PASSWORD"] = str(settings.tool_repository_password or "")
|
||||||
|
|
||||||
|
return env
|
||||||
|
|||||||
109
tests/cli/config_test.py
Normal file
109
tests/cli/config_test.py
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import unittest
|
||||||
|
import json
|
||||||
|
import tempfile
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
from crewai.cli.config import Settings
|
||||||
|
|
||||||
|
class TestSettings(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.test_dir = Path(tempfile.mkdtemp())
|
||||||
|
self.config_path = self.test_dir / "settings.json"
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
shutil.rmtree(self.test_dir)
|
||||||
|
|
||||||
|
def test_empty_initialization(self):
|
||||||
|
settings = Settings(config_path=self.config_path)
|
||||||
|
self.assertIsNone(settings.tool_repository_username)
|
||||||
|
self.assertIsNone(settings.tool_repository_password)
|
||||||
|
|
||||||
|
def test_initialization_with_data(self):
|
||||||
|
settings = Settings(
|
||||||
|
config_path=self.config_path,
|
||||||
|
tool_repository_username="user1"
|
||||||
|
)
|
||||||
|
self.assertEqual(settings.tool_repository_username, "user1")
|
||||||
|
self.assertIsNone(settings.tool_repository_password)
|
||||||
|
|
||||||
|
def test_initialization_with_existing_file(self):
|
||||||
|
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with self.config_path.open("w") as f:
|
||||||
|
json.dump({"tool_repository_username": "file_user"}, f)
|
||||||
|
|
||||||
|
settings = Settings(config_path=self.config_path)
|
||||||
|
self.assertEqual(settings.tool_repository_username, "file_user")
|
||||||
|
|
||||||
|
def test_merge_file_and_input_data(self):
|
||||||
|
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with self.config_path.open("w") as f:
|
||||||
|
json.dump({
|
||||||
|
"tool_repository_username": "file_user",
|
||||||
|
"tool_repository_password": "file_pass"
|
||||||
|
}, f)
|
||||||
|
|
||||||
|
settings = Settings(
|
||||||
|
config_path=self.config_path,
|
||||||
|
tool_repository_username="new_user"
|
||||||
|
)
|
||||||
|
self.assertEqual(settings.tool_repository_username, "new_user")
|
||||||
|
self.assertEqual(settings.tool_repository_password, "file_pass")
|
||||||
|
|
||||||
|
def test_dump_new_settings(self):
|
||||||
|
settings = Settings(
|
||||||
|
config_path=self.config_path,
|
||||||
|
tool_repository_username="user1"
|
||||||
|
)
|
||||||
|
settings.dump()
|
||||||
|
|
||||||
|
with self.config_path.open("r") as f:
|
||||||
|
saved_data = json.load(f)
|
||||||
|
|
||||||
|
self.assertEqual(saved_data["tool_repository_username"], "user1")
|
||||||
|
|
||||||
|
def test_update_existing_settings(self):
|
||||||
|
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with self.config_path.open("w") as f:
|
||||||
|
json.dump({"existing_setting": "value"}, f)
|
||||||
|
|
||||||
|
settings = Settings(
|
||||||
|
config_path=self.config_path,
|
||||||
|
tool_repository_username="user1"
|
||||||
|
)
|
||||||
|
settings.dump()
|
||||||
|
|
||||||
|
with self.config_path.open("r") as f:
|
||||||
|
saved_data = json.load(f)
|
||||||
|
|
||||||
|
self.assertEqual(saved_data["existing_setting"], "value")
|
||||||
|
self.assertEqual(saved_data["tool_repository_username"], "user1")
|
||||||
|
|
||||||
|
def test_none_values(self):
|
||||||
|
settings = Settings(
|
||||||
|
config_path=self.config_path,
|
||||||
|
tool_repository_username=None
|
||||||
|
)
|
||||||
|
settings.dump()
|
||||||
|
|
||||||
|
with self.config_path.open("r") as f:
|
||||||
|
saved_data = json.load(f)
|
||||||
|
|
||||||
|
self.assertIsNone(saved_data.get("tool_repository_username"))
|
||||||
|
|
||||||
|
def test_invalid_json_in_config(self):
|
||||||
|
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with self.config_path.open("w") as f:
|
||||||
|
f.write("invalid json")
|
||||||
|
|
||||||
|
try:
|
||||||
|
settings = Settings(config_path=self.config_path)
|
||||||
|
self.assertIsNone(settings.tool_repository_username)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
self.fail("Settings initialization should handle invalid JSON")
|
||||||
|
|
||||||
|
def test_empty_config_file(self):
|
||||||
|
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
self.config_path.touch()
|
||||||
|
|
||||||
|
settings = Settings(config_path=self.config_path)
|
||||||
|
self.assertIsNone(settings.tool_repository_username)
|
||||||
@@ -82,6 +82,7 @@ def test_install_success(mock_get, mock_subprocess_run):
|
|||||||
capture_output=False,
|
capture_output=False,
|
||||||
text=True,
|
text=True,
|
||||||
check=True,
|
check=True,
|
||||||
|
env=unittest.mock.ANY
|
||||||
)
|
)
|
||||||
|
|
||||||
assert "Succesfully installed sample-tool" in output
|
assert "Succesfully installed sample-tool" in output
|
||||||
|
|||||||
Reference in New Issue
Block a user