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 @@ -21,6 +21,8 @@
parse_doc,
get_task_list,
check_server_running,
dependencies_required,
api_keys_required,
)
from .token_counting import (
get_model_encoding,
Expand All @@ -44,4 +46,6 @@
'BaseTokenCounter',
'OpenAITokenCounter',
'OpenSourceTokenCounter',
'dependencies_required',
'api_keys_required',
]
91 changes: 91 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 inspect
import os
import re
Expand Down Expand Up @@ -241,3 +242,93 @@ def check_server_running(server_url: str) -> bool:

# if the port is open, the result should be 0.
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.

Parameters:
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...
ArnoldIOI marked this conversation as resolved.
Show resolved Hide resolved
"""

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.

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

Returns:
bool: True if the module can be imported, False otherwise.
ArnoldIOI marked this conversation as resolved.
Show resolved Hide resolved
"""
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.

Parameters:
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...
ArnoldIOI marked this conversation as resolved.
Show resolved Hide resolved
"""

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
44 changes: 43 additions & 1 deletion test/utils/test_commons.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
# 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_task_list
import os

import pytest

from camel.utils import api_keys_required, dependencies_required, get_task_list


def test_get_task_list():
Expand All @@ -37,3 +41,41 @@ def test_get_task_list():
assert isinstance(task_list, list)
assert isinstance(task_list[0], str)
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)
Loading