Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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",
Expand Down
91 changes: 72 additions & 19 deletions src/uipath/_cli/_evals/mocks/mockito_mocker.py
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -21,7 +17,6 @@
R,
T,
UiPathMockResponseGenerationError,
UiPathNoMockFoundError,
)


Expand All @@ -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."""

Expand All @@ -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
66 changes: 1 addition & 65 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.