diff --git a/arcade/arcade/cli/serve.py b/arcade/arcade/cli/serve.py index de993876..aebcc035 100644 --- a/arcade/arcade/cli/serve.py +++ b/arcade/arcade/cli/serve.py @@ -17,7 +17,9 @@ except ImportError: try: import uvicorn except ImportError: - raise ImportError("Uvicorn is not installed. Please install it using `pip install uvicorn`.") + raise ImportError( + "Uvicorn is not installed. Please install it using `pip install arcade-ai[fastapi]`." + ) from arcade.actor.fastapi.actor import FastAPIActor from arcade.core.toolkit import Toolkit diff --git a/arcade/arcade/cli/utils.py b/arcade/arcade/cli/utils.py index cdbf614a..fd6b306c 100644 --- a/arcade/arcade/cli/utils.py +++ b/arcade/arcade/cli/utils.py @@ -9,6 +9,7 @@ from typer.models import Context from arcade.core.catalog import ToolCatalog from arcade.core.config_model import Config +from arcade.core.errors import ToolkitLoadError from arcade.core.toolkit import Toolkit if TYPE_CHECKING: @@ -34,10 +35,10 @@ def create_cli_catalog( try: prefixed_toolkit = "arcade_" + toolkit toolkits = [Toolkit.from_package(prefixed_toolkit)] - except ValueError: + except ToolkitLoadError: try: # try without prefix toolkits = [Toolkit.from_package(toolkit)] - except ValueError as e: + except ToolkitLoadError as e: console.print(f"❌ {e}", style="bold red") typer.Exit(code=1) else: diff --git a/arcade/arcade/core/errors.py b/arcade/arcade/core/errors.py index 0d9dee9e..5fbebea4 100644 --- a/arcade/arcade/core/errors.py +++ b/arcade/arcade/core/errors.py @@ -1,6 +1,22 @@ from typing import Optional +class ToolkitError(Exception): + """ + Base class for all errors related to toolkits. + """ + + pass + + +class ToolkitLoadError(ToolkitError): + """ + Raised when there is an error loading a toolkit. + """ + + pass + + class ToolError(Exception): """ Base class for all errors related to tools. diff --git a/arcade/arcade/core/toolkit.py b/arcade/arcade/core/toolkit.py index 0a6b58ed..93129a1e 100644 --- a/arcade/arcade/core/toolkit.py +++ b/arcade/arcade/core/toolkit.py @@ -1,5 +1,6 @@ import importlib.metadata import importlib.util +import logging import os import types from collections import defaultdict @@ -7,8 +8,11 @@ from pathlib import Path from pydantic import BaseModel, ConfigDict, field_validator +from arcade.core.errors import ToolkitLoadError from arcade.core.parse import get_tools_from_file +logger = logging.getLogger(__name__) + class Toolkit(BaseModel): model_config = ConfigDict(populate_by_name=True) @@ -64,23 +68,23 @@ class Toolkit(BaseModel): repo = metadata.get("Repository", None) # type: ignore[attr-defined] except importlib.metadata.PackageNotFoundError as e: - raise ValueError(f"Package {package} not found.") from e + raise ToolkitLoadError(f"Package {package} not found.") from e except KeyError as e: - raise ValueError(f"Metadata key error for package {package}.") from e + raise ToolkitLoadError(f"Metadata key error for package {package}.") from e except Exception as e: - raise ValueError(f"Failed to load metadata for package {package}.") from e + raise ToolkitLoadError(f"Failed to load metadata for package {package}.") from e # Get the package directory try: package_dir = Path(get_package_directory(package)) - except AttributeError as e: - raise ValueError(f"Failed to locate package directory for {package}.") from e + except (ImportError, AttributeError) as e: + raise ToolkitLoadError(f"Failed to locate package directory for {package}.") from e # Get all python files in the package directory try: modules = [f for f in package_dir.glob("**/*.py") if f.is_file()] except OSError as e: - raise ValueError( + raise ToolkitLoadError( f"Failed to locate Python files in package directory for {package}." ) from e @@ -101,7 +105,7 @@ class Toolkit(BaseModel): toolkit.tools[import_path] = get_tools_from_file(str(module_path)) if not toolkit.tools: - raise ValueError(f"No tools found in package {package}") + raise ToolkitLoadError(f"No tools found in package {package}") return toolkit @@ -123,7 +127,13 @@ class Toolkit(BaseModel): for dist in importlib.metadata.distributions(path=[site_packages_dir]) if dist.metadata["Name"].startswith("arcade_") ] - return [cls.from_package(package) for package in arcade_packages] + toolkits = [] + for package in arcade_packages: + try: + toolkits.append(cls.from_package(package)) + except ToolkitLoadError as e: + logger.warning(f"Warning: {e} Skipping toolkit {package}") + return toolkits def get_package_directory(package_name: str) -> str: diff --git a/toolkits/google/arcade_google/tools/utils.py b/toolkits/google/arcade_google/tools/utils.py index bcb77d67..3da3436f 100644 --- a/toolkits/google/arcade_google/tools/utils.py +++ b/toolkits/google/arcade_google/tools/utils.py @@ -2,7 +2,7 @@ import datetime import re from base64 import urlsafe_b64decode from enum import Enum -from typing import Any, Optional, dict +from typing import Any, Optional from bs4 import BeautifulSoup