Gracefully handle toolkit load errors (#67)

This commit is contained in:
Nate Barbettini 2024-09-25 12:46:18 -07:00 committed by GitHub
parent 894fa878f1
commit 5b9438da82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 41 additions and 12 deletions

View file

@ -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

View file

@ -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:

View file

@ -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.

View file

@ -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:

View file

@ -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