From 7393ab1f1456347efb92fea65dd834821546158f Mon Sep 17 00:00:00 2001 From: Davor Runje Date: Wed, 9 Oct 2024 20:12:08 +0000 Subject: [PATCH] import tests added for docs_src and examples --- docs/docs/SUMMARY.md | 1 + .../base/check_register_decorator.md | 11 ++++ docs/docs_src/getting_started/main_console.py | 2 +- docs/docs_src/getting_started/main_mesop.py | 2 +- docs/docs_src/tutorial/giphy/main.py | 2 +- docs/docs_src/tutorial/giphy/simple_main.py | 2 +- .../custom_user_interactions/main.py | 4 +- .../user_guide/external_rest_apis/main.py | 2 +- .../user_guide/external_rest_apis/security.py | 2 +- .../user_guide/ui/mesop/main_mesop.py | 2 +- fastagency/base.py | 11 ++++ fastagency/runtimes/autogen/autogen.py | 3 +- tests/docs_src/test_import.py | 27 ++++++++ tests/examples/test_import.py | 27 ++++++++ tests/helpers.py | 63 ++++++++++++++++++- tests/test_import.py | 36 +---------- tests/ui/console/test_base.py | 3 +- 17 files changed, 155 insertions(+), 45 deletions(-) create mode 100644 docs/docs/en/api/fastagency/base/check_register_decorator.md create mode 100644 tests/docs_src/test_import.py create mode 100644 tests/examples/test_import.py diff --git a/docs/docs/SUMMARY.md b/docs/docs/SUMMARY.md index a51e2f7b..58dba6f0 100644 --- a/docs/docs/SUMMARY.md +++ b/docs/docs/SUMMARY.md @@ -82,6 +82,7 @@ search: - [UI](api/fastagency/base/UI.md) - [WSGIProtocol](api/fastagency/base/WSGIProtocol.md) - [WorkflowsProtocol](api/fastagency/base/WorkflowsProtocol.md) + - [check_register_decorator](api/fastagency/base/check_register_decorator.md) - [run_workflow](api/fastagency/base/run_workflow.md) - cli - cli diff --git a/docs/docs/en/api/fastagency/base/check_register_decorator.md b/docs/docs/en/api/fastagency/base/check_register_decorator.md new file mode 100644 index 00000000..5c81d853 --- /dev/null +++ b/docs/docs/en/api/fastagency/base/check_register_decorator.md @@ -0,0 +1,11 @@ +--- +# 0.5 - API +# 2 - Release +# 3 - Contributing +# 5 - Template Page +# 10 - Default +search: + boost: 0.5 +--- + +::: fastagency.base.check_register_decorator diff --git a/docs/docs_src/getting_started/main_console.py b/docs/docs_src/getting_started/main_console.py index 980340f3..33416ee9 100644 --- a/docs/docs_src/getting_started/main_console.py +++ b/docs/docs_src/getting_started/main_console.py @@ -3,7 +3,7 @@ from autogen.agentchat import ConversableAgent -from fastagency import UI, FastAgency, WorkflowsProtocol +from fastagency import UI, FastAgency from fastagency.runtimes.autogen import AutoGenWorkflows from fastagency.ui.console import ConsoleUI diff --git a/docs/docs_src/getting_started/main_mesop.py b/docs/docs_src/getting_started/main_mesop.py index af059982..5d74d02d 100644 --- a/docs/docs_src/getting_started/main_mesop.py +++ b/docs/docs_src/getting_started/main_mesop.py @@ -3,7 +3,7 @@ from autogen.agentchat import ConversableAgent -from fastagency import UI, FastAgency, WorkflowsProtocol +from fastagency import UI, FastAgency from fastagency.runtimes.autogen import AutoGenWorkflows from fastagency.ui.mesop import MesopUI diff --git a/docs/docs_src/tutorial/giphy/main.py b/docs/docs_src/tutorial/giphy/main.py index bb71439a..faffebd8 100644 --- a/docs/docs_src/tutorial/giphy/main.py +++ b/docs/docs_src/tutorial/giphy/main.py @@ -4,7 +4,7 @@ from autogen import register_function from autogen.agentchat import ConversableAgent -from fastagency import UI, FastAgency, WorkflowsProtocol +from fastagency import UI, FastAgency from fastagency.api.openapi.client import OpenAPI from fastagency.api.openapi.security import APIKeyQuery from fastagency.messages import TextInput diff --git a/docs/docs_src/tutorial/giphy/simple_main.py b/docs/docs_src/tutorial/giphy/simple_main.py index 2745d892..045202f8 100644 --- a/docs/docs_src/tutorial/giphy/simple_main.py +++ b/docs/docs_src/tutorial/giphy/simple_main.py @@ -3,7 +3,7 @@ from autogen import ConversableAgent, UserProxyAgent -from fastagency import UI, FastAgency, WorkflowsProtocol +from fastagency import UI, FastAgency from fastagency.api.openapi.client import OpenAPI from fastagency.api.openapi.security import APIKeyQuery from fastagency.runtimes.autogen.autogen import AutoGenWorkflows diff --git a/docs/docs_src/user_guide/custom_user_interactions/main.py b/docs/docs_src/user_guide/custom_user_interactions/main.py index 11dbe090..ef4c03a0 100644 --- a/docs/docs_src/user_guide/custom_user_interactions/main.py +++ b/docs/docs_src/user_guide/custom_user_interactions/main.py @@ -4,7 +4,7 @@ from autogen import register_function from autogen.agentchat import ConversableAgent -from fastagency import UI, FastAgency, WorkflowsProtocol +from fastagency import UI, FastAgency from fastagency.messages import MultipleChoice, SystemMessage, TextInput from fastagency.runtimes.autogen.autogen import AutoGenWorkflows from fastagency.ui.console import ConsoleUI @@ -23,7 +23,7 @@ @wf.register(name="exam_practice", description="Student and teacher chat") -def exam_learning(ui: UI, workflow_uuid: str, param: dict[str, Any]) -> str: +def exam_learning(ui: UI, workflow_uuid: str, params: dict[str, Any]) -> str: initial_message = ui.text_input( sender="Workflow", recipient="User", diff --git a/docs/docs_src/user_guide/external_rest_apis/main.py b/docs/docs_src/user_guide/external_rest_apis/main.py index e625b518..770b9136 100644 --- a/docs/docs_src/user_guide/external_rest_apis/main.py +++ b/docs/docs_src/user_guide/external_rest_apis/main.py @@ -3,7 +3,7 @@ from autogen import UserProxyAgent from autogen.agentchat import ConversableAgent -from fastagency import UI, FastAgency, WorkflowsProtocol +from fastagency import UI, FastAgency from fastagency.api.openapi import OpenAPI from fastagency.runtimes.autogen.autogen import AutoGenWorkflows from fastagency.ui.console import ConsoleUI diff --git a/docs/docs_src/user_guide/external_rest_apis/security.py b/docs/docs_src/user_guide/external_rest_apis/security.py index 71c6e285..efc9f44b 100644 --- a/docs/docs_src/user_guide/external_rest_apis/security.py +++ b/docs/docs_src/user_guide/external_rest_apis/security.py @@ -3,7 +3,7 @@ from autogen import UserProxyAgent from autogen.agentchat import ConversableAgent -from fastagency import UI, FastAgency, WorkflowsProtocol +from fastagency import UI, FastAgency from fastagency.api.openapi.client import OpenAPI from fastagency.api.openapi.security import APIKeyHeader from fastagency.runtimes.autogen.autogen import AutoGenWorkflows diff --git a/docs/docs_src/user_guide/ui/mesop/main_mesop.py b/docs/docs_src/user_guide/ui/mesop/main_mesop.py index c09da119..0e3d4523 100644 --- a/docs/docs_src/user_guide/ui/mesop/main_mesop.py +++ b/docs/docs_src/user_guide/ui/mesop/main_mesop.py @@ -4,7 +4,7 @@ import mesop as me from autogen.agentchat import ConversableAgent -from fastagency import UI, FastAgency, WorkflowsProtocol +from fastagency import UI, FastAgency from fastagency.runtimes.autogen import AutoGenWorkflows from fastagency.ui.mesop import MesopUI from fastagency.ui.mesop.styles import ( diff --git a/fastagency/base.py b/fastagency/base.py index 41000d56..464983c8 100644 --- a/fastagency/base.py +++ b/fastagency/base.py @@ -1,3 +1,4 @@ +import inspect from collections.abc import Awaitable, Generator, Iterable, Iterator, Mapping from contextlib import contextmanager from typing import ( @@ -123,6 +124,16 @@ def register_api( ) -> None: ... +def check_register_decorator(func: Workflow) -> None: + # get names of all parameters in the function signature + sig = inspect.signature(func) + params = list(sig.parameters.keys()) + if params != ["ui", "workflow_uuid", "params"]: + raise ValueError( + f"Expected function signature to be 'def func(ui: UI, workflow_uuid: str, params: dict[str, Any]) -> str', got {sig}" + ) + + @runtime_checkable class AdapterProtocol(Protocol): @classmethod diff --git a/fastagency/runtimes/autogen/autogen.py b/fastagency/runtimes/autogen/autogen.py index 2110e2fd..94f8a006 100644 --- a/fastagency/runtimes/autogen/autogen.py +++ b/fastagency/runtimes/autogen/autogen.py @@ -16,7 +16,7 @@ from autogen.agentchat import ConversableAgent from autogen.io import IOStream -from ...base import UI, Workflow, WorkflowsProtocol +from ...base import UI, Workflow, WorkflowsProtocol, check_register_decorator from ...logging import get_logger from ...messages import ( AskingMessage, @@ -278,6 +278,7 @@ def register( self, name: str, description: str, *, fail_on_redefintion: bool = False ) -> Callable[[Workflow], Workflow]: def decorator(func: Workflow) -> Workflow: + check_register_decorator(func) if name in self._workflows: if fail_on_redefintion: raise ValueError(f"A workflow with name '{name}' already exists.") diff --git a/tests/docs_src/test_import.py b/tests/docs_src/test_import.py new file mode 100644 index 00000000..32dcda5d --- /dev/null +++ b/tests/docs_src/test_import.py @@ -0,0 +1,27 @@ +import importlib +from pathlib import Path + +import pytest + +from ..helpers import add_to_sys_path, list_submodules + +root_path = (Path(__file__).parents[2] / "docs").resolve() +module_name = "docs_src" + + +def test_list_submodules() -> None: + # Specify the name of the module you want to inspect + + # Get the list of submodules for the specified module + submodules = list_submodules(module_name, include_path=root_path) + + assert len(submodules) > 0 + assert "docs_src" in submodules + assert "docs_src.getting_started" in submodules + assert "docs_src.getting_started.main_console" in submodules + + +@pytest.mark.parametrize("module", list_submodules(module_name, include_path=root_path)) +def test_submodules(module: str) -> None: + with add_to_sys_path(root_path): + importlib.import_module(module) diff --git a/tests/examples/test_import.py b/tests/examples/test_import.py new file mode 100644 index 00000000..b6b6a833 --- /dev/null +++ b/tests/examples/test_import.py @@ -0,0 +1,27 @@ +import importlib +from pathlib import Path + +import pytest + +from ..helpers import add_to_sys_path, list_submodules + +root_path = (Path(__file__).parents[2]).resolve() +module_name = "examples" + + +def test_list_submodules() -> None: + # Specify the name of the module you want to inspect + + # Get the list of submodules for the specified module + submodules = list_submodules(module_name, include_path=root_path) + + assert len(submodules) > 0 + assert "examples" in submodules + assert "examples.cli" in submodules + assert "examples.cli.main_console" in submodules + + +@pytest.mark.parametrize("module", list_submodules(module_name, include_path=root_path)) +def test_submodules(module: str) -> None: + with add_to_sys_path(root_path): + importlib.import_module(module) diff --git a/tests/helpers.py b/tests/helpers.py index 92bd4d62..4d675cde 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -1,9 +1,15 @@ import functools +import importlib import inspect +import pkgutil import random +import sys import types from asyncio import iscoroutinefunction -from typing import Any, Callable, TypeVar +from collections.abc import Iterator +from contextlib import contextmanager +from pathlib import Path +from typing import Any, Callable, Optional, TypeVar import pytest import pytest_asyncio @@ -170,3 +176,58 @@ def decorator(f: F) -> list[F]: return retval return decorator + + +@contextmanager +def add_to_sys_path(path: Optional[Path]) -> Iterator[None]: + if path is None: + yield + return + + if not path.exists(): + raise ValueError(f"Path {path} does not exist") + + sys.path.append(str(path)) + try: + yield + finally: + sys.path.remove(str(path)) + + +def list_submodules( + module_name: str, *, include_path: Optional[Path] = None, include_root: bool = True +) -> list[str]: + """List all submodules of a given module. + + Args: + module_name (str): The name of the module to list submodules for. + include_path (Optional[Path], optional): The path to the module. Defaults to None. + include_root (bool, optional): Whether to include the root module in the list. Defaults to True. + + Returns: + list: A list of submodule names. + """ + with add_to_sys_path(include_path): + try: + module = importlib.import_module(module_name) + except Exception: + return [] + + # Get the path of the module. This is necessary to find its submodules. + module_path = module.__path__ + + # Initialize an empty list to store the names of submodules + submodules = [module_name] if include_root else [] + + # Iterate over the submodules in the module's path + for _, name, ispkg in pkgutil.iter_modules( + module_path, prefix=f"{module_name}." + ): + # Add the name of each submodule to the list + submodules.append(name) + + if ispkg: + submodules.extend(list_submodules(name, include_root=False)) + + # Return the list of submodule names + return submodules diff --git a/tests/test_import.py b/tests/test_import.py index a7e95686..b9c4b119 100644 --- a/tests/test_import.py +++ b/tests/test_import.py @@ -1,5 +1,4 @@ import importlib -import pkgutil import sys # Import the pkgutil module to work with the import system import pytest @@ -8,38 +7,7 @@ FastAgencyCLIPythonVersionError, # Import the importlib module to dynamically import modules ) - -def list_submodules(module_name: str) -> list[str]: - """List all submodules of a given module. - - Args: - module_name (str): The name of the module to list submodules for. - - Returns: - list: A list of submodule names. - """ - # Import the module dynamically using its name - try: - module = importlib.import_module(module_name) - except Exception: - return [] - - # Get the path of the module. This is necessary to find its submodules. - module_path = module.__path__ - - # Initialize an empty list to store the names of submodules - submodules = [] - - # Iterate over the submodules in the module's path - for _, name, ispkg in pkgutil.iter_modules(module_path, prefix=f"{module_name}."): - # Add the name of each submodule to the list - submodules.append(name) - - if ispkg: - submodules.extend(list_submodules(name)) - - # Return the list of submodule names - return submodules +from .helpers import list_submodules def test_list_submodules() -> None: @@ -49,6 +17,8 @@ def test_list_submodules() -> None: # Get the list of submodules for the specified module submodules = list_submodules(module_name) + assert len(submodules) > 0 + assert "fastagency" in submodules assert "fastagency.ui" in submodules assert "fastagency.ui.mesop" in submodules assert "fastagency.ui.console.console" in submodules diff --git a/tests/ui/console/test_base.py b/tests/ui/console/test_base.py index c5269c12..ca526f6d 100644 --- a/tests/ui/console/test_base.py +++ b/tests/ui/console/test_base.py @@ -4,6 +4,7 @@ import pytest from fastagency.app import FastAgency +from fastagency.base import UI from fastagency.runtimes.autogen import AutoGenWorkflows from fastagency.ui.console import ConsoleUI @@ -20,7 +21,7 @@ def app(import_string: str) -> Iterator[FastAgency]: app = FastAgency(provider=wf, ui=console) @wf.register(name="noop", description="No operation") - def noop(*args: Any, **kwargs: Any) -> str: + def noop(ui: UI, workflow_uuid: str, params: dict[str, Any]) -> str: return "ok" try: