Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Add dependencies_required and api_keys_required #405

4 changes: 4 additions & 0 deletions camel/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
from .commons import (
PYDANTIC_V2,
api_key_required,
api_keys_required,
check_server_running,
dependencies_required,
download_tasks,
get_first_int,
get_prompt_template_key_words,
Expand Down Expand Up @@ -48,5 +50,7 @@
'BaseTokenCounter',
'OpenAITokenCounter',
'OpenSourceTokenCounter',
'dependencies_required',
'api_keys_required',
'role_playing_with_function',
]
93 changes: 93 additions & 0 deletions camel/utils/commons.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
import importlib
import os
import platform
import re
Expand Down Expand Up @@ -217,6 +218,98 @@ def check_server_running(server_url: str) -> bool:
return result == 0


def dependencies_required(*required_modules: str) -> Callable[[F], F]:
r"""A decorator to ensure that specified Python modules
are available before a function executes.

Args:
required_modules (str): The required modules to be checked for
availability.

Returns:
Callable[[F], F]: The original function with the added check for
required module dependencies.

Raises:
ImportError: If any of the required modules are not available.

Example:
::

@dependencies_required('numpy', 'pandas')
def data_processing_function():
# Function implementation...
"""

def decorator(func: F) -> F:
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
missing_modules = [
m for m in required_modules if not is_module_available(m)
]
if missing_modules:
raise ImportError(
f"Missing required modules: {', '.join(missing_modules)}"
)
return func(*args, **kwargs)

return cast(F, wrapper)

return decorator


def is_module_available(module_name: str) -> bool:
r"""Check if a module is available for import.

Args:
module_name (str): The name of the module to check for availability.

Returns:
bool: True if the module can be imported, False otherwise.
"""
try:
importlib.import_module(module_name)
return True
except ImportError:
return False


def api_keys_required(*required_keys: str) -> Callable[[F], F]:
r"""A decorator to check if the required API keys are
present in the environment variables.

Args:
required_keys (str): The required API keys to be checked.

Returns:
Callable[[F], F]: The original function with the added check
for required API keys.

Raises:
ValueError: If any of the required API keys are missing in the
environment variables.

Example:
::

@api_keys_required('API_KEY_1', 'API_KEY_2')
def some_api_function():
# Function implementation...
"""

def decorator(func: F) -> F:
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
missing_keys = [k for k in required_keys if k not in os.environ]
if missing_keys:
raise ValueError(f"Missing API keys: {', '.join(missing_keys)}")
return func(*args, **kwargs)

return cast(F, wrapper)

return decorator


def get_system_information():
r"""Gathers information about the operating system.

Expand Down
49 changes: 48 additions & 1 deletion test/utils/test_commons.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
from camel.utils import get_system_information, get_task_list, to_pascal
import os

import pytest

from camel.utils import (
api_keys_required,
dependencies_required,
get_system_information,
get_task_list,
to_pascal,
)


def test_get_task_list():
Expand Down Expand Up @@ -39,6 +49,43 @@ def test_get_task_list():
assert len(task_list) == 1


def test_dependencies_required(monkeypatch):
@dependencies_required('os')
def mock_dependencies_present():
return True

assert True if mock_dependencies_present() else False

@dependencies_required('some_module_not_exist')
def mock_dependencies_not_present():
return True

with pytest.raises(ImportError) as exc:
mock_dependencies_not_present()

assert "Missing required modules: some_module_not_exist" in str(exc.value)


def test_api_keys_required():
os.environ['API_KEY_1'] = 'API_KEY_1_VALUE'
os.environ['API_KEY_2'] = 'API_KEY_2_VALUE'

@api_keys_required('API_KEY_1', 'API_KEY_2')
def mock_api_keys_exist():
return True

assert True if mock_api_keys_exist() else False

@api_keys_required('API_KEY_1', 'API_KEY_2', 'API_KEY_3')
def mock_api_keys_not_exist():
return True

with pytest.raises(ValueError) as exc:
mock_api_keys_not_exist()

assert "Missing API keys: API_KEY_3" in str(exc.value)


def test_get_system_information():
# Call the function
sys_info = get_system_information()
Expand Down
Loading