diff --git a/pyproject.toml b/pyproject.toml index a41b92525..4cb3fafcb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath" -version = "2.2.17" +version = "2.2.18" description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools." readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" @@ -15,8 +15,6 @@ dependencies = [ "rich>=14.2.0", "truststore>=0.10.1", "mockito>=1.5.4", - "fromconfig>=0.7.2", - "setuptools>=80.9.0", "pydantic-function-models>=0.1.11", "pysignalr==1.3.0", "coverage>=7.8.2", diff --git a/src/uipath/_cli/_evals/mocks/mockito_mocker.py b/src/uipath/_cli/_evals/mocks/mockito_mocker.py index 56de44657..7303ff878 100644 --- a/src/uipath/_cli/_evals/mocks/mockito_mocker.py +++ b/src/uipath/_cli/_evals/mocks/mockito_mocker.py @@ -1,11 +1,7 @@ -"""Mockito mocker implementation. - -https://mockito-python.readthedocs.io/en/latest/ -""" +"""Mockito mocker implementation. https://mockito-python.readthedocs.io/en/latest .""" from typing import Any, Callable -import fromconfig # type: ignore[import-untyped] # explicit ignore from mockito import ( # type: ignore[import-untyped] # explicit ignore invocation, mocking, @@ -21,7 +17,6 @@ R, T, UiPathMockResponseGenerationError, - UiPathNoMockFoundError, ) @@ -32,12 +27,50 @@ def __getattr__(self, item): """Return a wrapper function that raises an exception.""" def func(*_args, **_kwargs): - """Not Implemented.""" raise NotImplementedError() return func +def _resolve_value(config: Any) -> Any: + # Handle {"_attr_": "mockito.any"} + if isinstance(config, dict) and "_attr_" in config: + attr = config["_attr_"] + if attr == "mockito.any": + from mockito import any as mockito_any + + return mockito_any() + + # Handle {"_target_": "..."} + if isinstance(config, dict) and "_target_" in config: + target = config["_target_"] + module_path, name = target.rsplit(".", 1) + + import importlib + + module = importlib.import_module(module_path) + obj = getattr(module, name) + + args = [_resolve_value(v) for v in config.get("_args_", [])] + kwargs = { + k: _resolve_value(v) + for k, v in config.items() + if k not in ("_target_", "_args_") + } + return obj(*args, **kwargs) + + if isinstance(config, dict): + return {k: _resolve_value(v) for k, v in config.items()} + + if isinstance(config, list): + return [_resolve_value(v) for v in config] + + if isinstance(config, tuple): + return tuple(_resolve_value(v) for v in config) + + return config + + class MockitoMocker(Mocker): """Mockito Mocker.""" @@ -50,34 +83,54 @@ def __init__(self, evaluation_item: EvaluationItem): mock_obj = mocking.Mock(self.stub) for behavior in self.evaluation_item.mocking_strategy.behaviors: + resolved_args = _resolve_value(behavior.arguments.args) + resolved_kwargs = _resolve_value(behavior.arguments.kwargs) + + args = resolved_args if resolved_args is not None else [] + kwargs = resolved_kwargs if resolved_kwargs is not None else {} + stubbed = invocation.StubbedInvocation(mock_obj, behavior.function)( - *fromconfig.fromconfig(behavior.arguments.args), - **fromconfig.fromconfig(behavior.arguments.kwargs), + *args, + **kwargs, ) + for answer in behavior.then: + answer_dict = answer.model_dump() + if answer.type == MockingAnswerType.RETURN: - stubbed = stubbed.thenReturn( - fromconfig.fromconfig(answer.model_dump())["value"] - ) + stubbed = stubbed.thenReturn(_resolve_value(answer_dict["value"])) + elif answer.type == MockingAnswerType.RAISE: - stubbed = stubbed.thenRaise( - fromconfig.fromconfig(answer.model_dump())["value"] - ) + stubbed = stubbed.thenRaise(_resolve_value(answer_dict["value"])) async def response( self, func: Callable[[T], R], params: dict[str, Any], *args: T, **kwargs ) -> R: - """Respond with mocked response.""" + """Return mocked response or raise appropriate errors.""" if not isinstance( self.evaluation_item.mocking_strategy, MockitoMockingStrategy ): raise UiPathMockResponseGenerationError("Mocking strategy misconfigured.") - if not any( + + # No behavior configured → call real function + is_mocked = any( behavior.function == params["name"] for behavior in self.evaluation_item.mocking_strategy.behaviors - ): - raise UiPathNoMockFoundError() + ) + + if not is_mocked: + import inspect + + if inspect.iscoroutinefunction(func): + return await func(*args, **kwargs) + return func(*args, **kwargs) + + # Behavioral mocking try: return getattr(self.stub, params["name"])(*args, **kwargs) + + except NotImplementedError: + raise + except Exception as e: raise UiPathMockResponseGenerationError() from e diff --git a/uv.lock b/uv.lock index 72c718a2b..9b9fae96b 100644 --- a/uv.lock +++ b/uv.lock @@ -135,12 +135,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] -[[package]] -name = "antlr4-python3-runtime" -version = "4.9.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz", hash = "sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b", size = 117034, upload-time = "2021-11-06T17:52:23.524Z" } - [[package]] name = "anyio" version = "4.12.0" @@ -556,29 +550,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" }, ] -[[package]] -name = "fire" -version = "0.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "termcolor" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c0/00/f8d10588d2019d6d6452653def1ee807353b21983db48550318424b5ff18/fire-0.7.1.tar.gz", hash = "sha256:3b208f05c736de98fb343310d090dcc4d8c78b2a89ea4f32b837c586270a9cbf", size = 88720, upload-time = "2025-08-16T20:20:24.175Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/4c/93d0f85318da65923e4b91c1c2ff03d8a458cbefebe3bc612a6693c7906d/fire-0.7.1-py3-none-any.whl", hash = "sha256:e43fd8a5033a9001e7e2973bab96070694b9f12f2e0ecf96d4683971b5ab1882", size = 115945, upload-time = "2025-08-16T20:20:22.87Z" }, -] - -[[package]] -name = "fromconfig" -version = "0.7.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "fire" }, - { name = "omegaconf" }, - { name = "pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/20/48/f00959c7f01cc52027747a05f283428151e9925f6cf9c90ebd97b6d45912/fromconfig-0.7.2.tar.gz", hash = "sha256:7661c82d6a9aefedc1df0e52f794b144764cd32e2ccf884bca7070249a1200f7", size = 27613, upload-time = "2022-10-12T15:04:46.885Z" } - [[package]] name = "frozenlist" version = "1.8.0" @@ -1441,19 +1412,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, ] -[[package]] -name = "omegaconf" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "antlr4-python3-runtime" }, - { name = "pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/09/48/6388f1bb9da707110532cb70ec4d2822858ddfb44f1cdf1233c20a80ea4b/omegaconf-2.3.0.tar.gz", hash = "sha256:d5d4b6d29955cc50ad50c46dc269bcd92c6e00f5f90d23ab5fee7bfca4ba4cc7", size = 3298120, upload-time = "2022-12-08T20:59:22.753Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/94/1843518e420fa3ed6919835845df698c7e27e183cb997394e4a670973a65/omegaconf-2.3.0-py3-none-any.whl", hash = "sha256:7b4df175cdb08ba400f45cae3bdcae7ba8365db4d165fc65fd04b050ab63b46b", size = 79500, upload-time = "2022-12-08T20:59:19.686Z" }, -] - [[package]] name = "opentelemetry-api" version = "1.39.0" @@ -2279,15 +2237,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8b/c7/6c818dcac06844608244855753a0afb367365661b40fe6b3288bc26726a6/rust_just-1.43.1-py3-none-win_amd64.whl", hash = "sha256:28f0d898d3e04846348277a4d47231c949398102c2f6f452f52ba6506c9c7572", size = 1731800, upload-time = "2025-11-19T07:49:17.344Z" }, ] -[[package]] -name = "setuptools" -version = "80.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, -] - [[package]] name = "six" version = "1.17.0" @@ -2342,15 +2291,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, ] -[[package]] -name = "termcolor" -version = "3.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/87/56/ab275c2b56a5e2342568838f0d5e3e66a32354adcc159b495e374cda43f5/termcolor-3.2.0.tar.gz", hash = "sha256:610e6456feec42c4bcd28934a8c87a06c3fa28b01561d46aa09a9881b8622c58", size = 14423, upload-time = "2025-10-25T19:11:42.586Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/d5/141f53d7c1eb2a80e6d3e9a390228c3222c27705cbe7f048d3623053f3ca/termcolor-3.2.0-py3-none-any.whl", hash = "sha256:a10343879eba4da819353c55cb8049b0933890c2ebf9ad5d3ecd2bb32ea96ea6", size = 7698, upload-time = "2025-10-25T19:11:41.536Z" }, -] - [[package]] name = "termynal" version = "0.13.1" @@ -2491,12 +2431,11 @@ wheels = [ [[package]] name = "uipath" -version = "2.2.17" +version = "2.2.18" source = { editable = "." } dependencies = [ { name = "click" }, { name = "coverage" }, - { name = "fromconfig" }, { name = "httpx" }, { name = "mermaid-builder" }, { name = "mockito" }, @@ -2506,7 +2445,6 @@ dependencies = [ { name = "pysignalr" }, { name = "python-dotenv" }, { name = "rich" }, - { name = "setuptools" }, { name = "tenacity" }, { name = "truststore" }, { name = "uipath-runtime" }, @@ -2543,7 +2481,6 @@ dev = [ requires-dist = [ { name = "click", specifier = ">=8.3.1" }, { name = "coverage", specifier = ">=7.8.2" }, - { name = "fromconfig", specifier = ">=0.7.2" }, { name = "httpx", specifier = ">=0.28.1" }, { name = "mermaid-builder", specifier = "==0.0.3" }, { name = "mockito", specifier = ">=1.5.4" }, @@ -2553,7 +2490,6 @@ requires-dist = [ { name = "pysignalr", specifier = "==1.3.0" }, { name = "python-dotenv", specifier = ">=1.0.1" }, { name = "rich", specifier = ">=14.2.0" }, - { name = "setuptools", specifier = ">=80.9.0" }, { name = "tenacity", specifier = ">=9.0.0" }, { name = "truststore", specifier = ">=0.10.1" }, { name = "uipath-runtime", specifier = ">=0.2.0,<0.3.0" },