Skip to content

Commit

Permalink
[Feature] Add dependencies_required and api_keys_required (#405)
Browse files Browse the repository at this point in the history
Co-authored-by: 0xAC <n0_0ne@126.com>
Co-authored-by: Wendong-Fan <133094783+Wendong-Fan@users.noreply.github.com>
  • Loading branch information
3 people committed May 27, 2024
1 parent b4bbc19 commit 8d4d005
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 1 deletion.
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

0 comments on commit 8d4d005

Please sign in to comment.