Skip to content

Commit 169d507

Browse files
committed
fix: handle empty trigger responses
1 parent 4001fe0 commit 169d507

6 files changed

Lines changed: 89 additions & 11 deletions

File tree

pyproject.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
[project]
22
name = "uipath"
3-
version = "2.2.24"
3+
version = "2.2.25"
44
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"
77
dependencies = [
88
"uipath-runtime>=0.2.3, <0.3.0",
9+
"uipath-core>=0.1.0, <0.2.0",
910
"click>=8.3.1",
1011
"httpx>=0.28.1",
1112
"pyjwt>=2.10.1",
@@ -126,9 +127,9 @@ init_typed = true
126127
warn_required_dynamic_aliases = true
127128

128129
[tool.pytest.ini_options]
129-
testpaths = ["tests"]
130-
python_files = "test_*.py"
131-
addopts = "-ra -q --cov"
130+
#testpaths = ["tests"]
131+
#python_files = "test_*.py"
132+
#addopts = "-ra -q --cov"
132133
asyncio_default_fixture_loop_scope = "function"
133134
asyncio_mode = "auto"
134135

src/uipath/platform/resume_triggers/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Init file for resume triggers module."""
22

3+
from ._enums import PropertyName, TriggerMarker
34
from ._protocol import (
45
UiPathResumeTriggerCreator,
56
UiPathResumeTriggerHandler,
@@ -10,4 +11,6 @@
1011
"UiPathResumeTriggerReader",
1112
"UiPathResumeTriggerCreator",
1213
"UiPathResumeTriggerHandler",
14+
"PropertyName",
15+
"TriggerMarker",
1316
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"""UiPath resume trigger enums."""
2+
3+
from enum import Enum
4+
5+
6+
class PropertyName(str, Enum):
7+
"""UiPath trigger property names."""
8+
9+
INTERNAL = "__internal"
10+
11+
12+
class TriggerMarker(str, Enum):
13+
"""UiPath trigger markers.
14+
15+
These markers are used as properties of resume triggers objects for special handling at runtime.
16+
"""
17+
18+
NO_CONTENT = "NO_CONTENT"

src/uipath/platform/resume_triggers/_protocol.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@
2929
WaitTask,
3030
)
3131
from uipath.platform.orchestrator.job import JobState
32+
from uipath.platform.resume_triggers._enums import PropertyName, TriggerMarker
3233

3334

34-
def _try_convert_to_json_format(value: str | None) -> str | None:
35+
def _try_convert_to_json_format(value: str | None) -> Any:
3536
"""Attempts to parse a string as JSON and returns the parsed object or original string.
3637
3738
Args:
@@ -119,7 +120,16 @@ async def read_trigger(self, trigger: UiPathResumeTrigger) -> Any | None:
119120
if trigger.trigger_name == UiPathResumeTriggerName.ESCALATION:
120121
return task
121122

122-
return task.data
123+
trigger_response = task.data
124+
if not bool(trigger_response):
125+
# 2.3.0 change to task.status.name
126+
assert isinstance(task.status, int)
127+
trigger_response = {
128+
"status": TaskStatus(task.status).name.lower(),
129+
PropertyName.INTERNAL.value: TriggerMarker.NO_CONTENT.value,
130+
}
131+
132+
return trigger_response
123133

124134
case UiPathResumeTriggerType.JOB:
125135
if trigger.item_key:
@@ -161,7 +171,10 @@ async def read_trigger(self, trigger: UiPathResumeTrigger) -> Any | None:
161171
trigger_response
162172
):
163173
# 2.3.0 change to job_state.value
164-
return {"state": job_state}
174+
trigger_response = {
175+
"state": job_state,
176+
PropertyName.INTERNAL.value: TriggerMarker.NO_CONTENT.value,
177+
}
165178

166179
return trigger_response
167180

@@ -255,7 +268,6 @@ async def create_trigger(self, suspend_value: Any) -> UiPathResumeTrigger:
255268
"Failed to create HITL action",
256269
f"{str(e)}",
257270
) from e
258-
259271
return resume_trigger
260272

261273
def _determine_trigger_type(self, value: Any) -> UiPathResumeTriggerType:

tests/cli/test_hitl.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import uuid
2+
from typing import Any
23
from unittest.mock import AsyncMock, patch
34

45
import pytest
@@ -12,12 +13,15 @@
1213
)
1314

1415
from uipath.platform.action_center import Task
16+
from uipath.platform.action_center.tasks import TaskStatus
1517
from uipath.platform.common import CreateTask, InvokeProcess, WaitJob, WaitTask
1618
from uipath.platform.orchestrator import (
1719
Job,
1820
JobErrorInfo,
1921
)
2022
from uipath.platform.resume_triggers import (
23+
PropertyName,
24+
TriggerMarker,
2125
UiPathResumeTriggerCreator,
2226
UiPathResumeTriggerReader,
2327
)
@@ -41,7 +45,7 @@ class TestHitlReader:
4145
"""Tests for the HitlReader class."""
4246

4347
@pytest.mark.anyio
44-
async def test_read_action_trigger(
48+
async def test_read_task_trigger(
4549
self,
4650
setup_test_env: None,
4751
) -> None:
@@ -72,6 +76,41 @@ async def test_read_action_trigger(
7276
app_name=None,
7377
)
7478

79+
@pytest.mark.anyio
80+
async def test_read_task_trigger_empty_response(
81+
self,
82+
setup_test_env: None,
83+
) -> None:
84+
"""Test reading an action trigger."""
85+
action_key = "test-action-key"
86+
action_data: dict[str, Any] = {}
87+
88+
mock_task = Task(key=action_key, data=action_data, status=2)
89+
mock_retrieve_async = AsyncMock(return_value=mock_task)
90+
91+
with patch(
92+
"uipath.platform.action_center._tasks_service.TasksService.retrieve_async",
93+
new=mock_retrieve_async,
94+
):
95+
resume_trigger = UiPathResumeTrigger(
96+
trigger_type=UiPathResumeTriggerType.TASK,
97+
item_key=action_key,
98+
folder_key="test-folder",
99+
folder_path="test-path",
100+
)
101+
reader = UiPathResumeTriggerReader()
102+
result = await reader.read_trigger(resume_trigger)
103+
assert result == {
104+
"status": TaskStatus(2).name.lower(),
105+
PropertyName.INTERNAL.value: TriggerMarker.NO_CONTENT.value,
106+
}
107+
mock_retrieve_async.assert_called_once_with(
108+
action_key,
109+
app_folder_key="test-folder",
110+
app_folder_path="test-path",
111+
app_name=None,
112+
)
113+
75114
@pytest.mark.anyio
76115
async def test_read_job_trigger_successful(
77116
self,
@@ -140,7 +179,10 @@ async def test_read_job_trigger_successful_empty_output(
140179
)
141180
reader = UiPathResumeTriggerReader()
142181
result = await reader.read_trigger(resume_trigger)
143-
assert result == {"state": job_state.lower()}
182+
assert result == {
183+
"state": job_state.lower(),
184+
PropertyName.INTERNAL.value: TriggerMarker.NO_CONTENT.value,
185+
}
144186
mock_retrieve_async.assert_called_once_with(
145187
job_key,
146188
folder_key="test-folder",

uv.lock

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)