Skip to content

Commit d215115

Browse files
authored
Merge pull request #138 from UiPath/feat/runtime-create-triggers-protocol
feat: support multiple resume triggers per interrupt
2 parents eb0e7b7 + 97ac966 commit d215115

8 files changed

Lines changed: 614 additions & 45 deletions

File tree

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#!/usr/bin/env python3
2+
"""Force cross-repo uv resolves to use the runtime wheel under test."""
3+
4+
from __future__ import annotations
5+
6+
import re
7+
import sys
8+
from pathlib import Path
9+
10+
11+
def _quoted(value: str) -> str:
12+
return '"' + value.replace("\\", "\\\\").replace('"', '\\"') + '"'
13+
14+
15+
def _add_override(pyproject_path: Path, override: str) -> None:
16+
text = pyproject_path.read_text()
17+
quoted_override = _quoted(override)
18+
19+
if override in text:
20+
return
21+
22+
tool_uv_match = re.search(
23+
r"(?ms)^\[tool\.uv\]\n(?P<body>.*?)(?=^\[|\Z)",
24+
text,
25+
)
26+
if not tool_uv_match:
27+
text = (
28+
text.rstrip()
29+
+ f"\n\n[tool.uv]\noverride-dependencies = [{quoted_override}]\n"
30+
)
31+
pyproject_path.write_text(text)
32+
return
33+
34+
body = tool_uv_match.group("body")
35+
override_match = re.search(
36+
r"(?ms)^override-dependencies\s*=\s*\[(?P<items>.*?)\]\s*",
37+
body,
38+
)
39+
if override_match:
40+
items = override_match.group("items").strip()
41+
if quoted_override in items:
42+
return
43+
44+
if items:
45+
replacement = f"override-dependencies = [{items}, {quoted_override}]\n"
46+
else:
47+
replacement = f"override-dependencies = [{quoted_override}]\n"
48+
49+
body = (
50+
body[: override_match.start()] + replacement + body[override_match.end() :]
51+
)
52+
else:
53+
body = f"override-dependencies = [{quoted_override}]\n" + body
54+
55+
text = (
56+
text[: tool_uv_match.start("body")] + body + text[tool_uv_match.end("body") :]
57+
)
58+
pyproject_path.write_text(text)
59+
60+
61+
def main() -> None:
62+
"""Add a local runtime wheel override to a pyproject and print the wheel path."""
63+
if len(sys.argv) != 3:
64+
print(
65+
"usage: force-runtime-override.py <pyproject.toml> <runtime-wheel-dir>",
66+
file=sys.stderr,
67+
)
68+
raise SystemExit(2)
69+
70+
pyproject_path = Path(sys.argv[1])
71+
wheel_dir = Path(sys.argv[2])
72+
wheels = sorted(wheel_dir.glob("uipath_runtime-*.whl"))
73+
if not wheels:
74+
print(f"no uipath-runtime wheel found in {wheel_dir}", file=sys.stderr)
75+
raise SystemExit(1)
76+
if len(wheels) > 1:
77+
wheel_names = ", ".join(str(wheel) for wheel in wheels)
78+
print(
79+
f"expected one uipath-runtime wheel in {wheel_dir}, found: {wheel_names}",
80+
file=sys.stderr,
81+
)
82+
raise SystemExit(1)
83+
84+
wheel = wheels[0].resolve()
85+
86+
_add_override(pyproject_path, f"uipath-runtime @ {wheel.as_uri()}")
87+
print(wheel)
88+
89+
90+
if __name__ == "__main__":
91+
main()

‎.github/workflows/test-uipath-langchain.yml‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ jobs:
4545
- name: Update uipath-runtime version
4646
shell: bash
4747
working-directory: uipath-langchain-python
48-
run: uv add ../uipath-runtime-python/dist/*.whl --dev
48+
run: |
49+
runtime_wheel="$(python ../uipath-runtime-python/.github/scripts/force-runtime-override.py pyproject.toml ../uipath-runtime-python/dist)"
50+
uv add "$runtime_wheel" --dev
4951
5052
- name: Run uipath-langchain tests
5153
working-directory: uipath-langchain-python
@@ -89,7 +91,9 @@ jobs:
8991
- name: Update uipath-runtime version
9092
shell: bash
9193
working-directory: uipath-langchain-python
92-
run: uv add ../uipath-runtime-python/dist/*.whl --dev
94+
run: |
95+
runtime_wheel="$(python ../uipath-runtime-python/.github/scripts/force-runtime-override.py pyproject.toml ../uipath-runtime-python/dist)"
96+
uv add "$runtime_wheel" --dev
9397
9498
- name: Run typecheck
9599
working-directory: uipath-langchain-python
@@ -163,7 +167,9 @@ jobs:
163167
- name: Update uipath-runtime version
164168
shell: bash
165169
working-directory: uipath-langchain-python
166-
run: uv add ../uipath-runtime-python/dist/*.whl --dev
170+
run: |
171+
runtime_wheel="$(python ../uipath-runtime-python/.github/scripts/force-runtime-override.py pyproject.toml ../uipath-runtime-python/dist)"
172+
uv add "$runtime_wheel" --dev
167173
168174
- name: Install dependencies
169175
working-directory: uipath-langchain-python
@@ -182,8 +188,8 @@ jobs:
182188
echo "Environment: ${{ matrix.environment }}"
183189
echo "LLM: ${{ matrix.use_azure_chat && 'UiPathAzureChatOpenAI' || 'UiPathChat' }}"
184190
echo "USE_AZURE_CHAT: ${{ matrix.use_azure_chat }}"
191+
python ../../../uipath-runtime-python/.github/scripts/force-runtime-override.py pyproject.toml ../../../uipath-runtime-python/dist
185192
186193
# Execute the testcase run script directly
187194
bash run.sh
188195
bash ../common/validate_output.sh
189-

‎.github/workflows/test-uipath.yml‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ jobs:
4545
- name: Update uipath-runtime version
4646
shell: bash
4747
working-directory: uipath-python/packages/uipath
48-
run: uv add ../../../uipath-runtime-python/dist/*.whl --dev
48+
run: |
49+
runtime_wheel="$(python ../../../uipath-runtime-python/.github/scripts/force-runtime-override.py pyproject.toml ../../../uipath-runtime-python/dist)"
50+
uv add "$runtime_wheel" --dev
4951
5052
- name: Run uipath tests
5153
working-directory: uipath-python/packages/uipath
@@ -89,7 +91,9 @@ jobs:
8991
- name: Update uipath-runtime version
9092
shell: bash
9193
working-directory: uipath-python/packages/uipath
92-
run: uv add ../../../uipath-runtime-python/dist/*.whl --dev
94+
run: |
95+
runtime_wheel="$(python ../../../uipath-runtime-python/.github/scripts/force-runtime-override.py pyproject.toml ../../../uipath-runtime-python/dist)"
96+
uv add "$runtime_wheel" --dev
9397
9498
- name: Run typecheck
9599
working-directory: uipath-python/packages/uipath
@@ -162,7 +166,9 @@ jobs:
162166
- name: Update uipath-runtime version
163167
shell: bash
164168
working-directory: uipath-python/packages/uipath
165-
run: uv add ../../../uipath-runtime-python/dist/*.whl --dev
169+
run: |
170+
runtime_wheel="$(python ../../../uipath-runtime-python/.github/scripts/force-runtime-override.py pyproject.toml ../../../uipath-runtime-python/dist)"
171+
uv add "$runtime_wheel" --dev
166172
167173
- name: Install dependencies
168174
working-directory: uipath-python/packages/uipath
@@ -179,8 +185,8 @@ jobs:
179185
run: |
180186
echo "Running testcase: ${{ matrix.testcase }}"
181187
echo "Environment: ${{ matrix.environment }}"
188+
python ../../../../../uipath-runtime-python/.github/scripts/force-runtime-override.py pyproject.toml ../../../../../uipath-runtime-python/dist
182189
183190
# Execute the testcase run script directly
184191
bash run.sh
185192
bash ../common/validate_output.sh
186-

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-runtime"
3-
version = "0.11.8"
3+
version = "0.12.0"
44
description = "Runtime abstractions and interfaces for building agents and automation scripts in the UiPath ecosystem"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

‎src/uipath/runtime/resumable/protocols.py‎

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
from uipath.core.triggers import UiPathResumeTrigger
66

7+
from uipath.runtime.errors import UiPathRuntimeError
8+
from uipath.runtime.errors.codes import UiPathErrorCode
79
from uipath.runtime.storage import UiPathRuntimeStorageProtocol
810

911

@@ -34,6 +36,20 @@ async def get_triggers(self, runtime_id: str) -> list[UiPathResumeTrigger] | Non
3436
"""
3537
...
3638

39+
async def delete_triggers(
40+
self, runtime_id: str, triggers: list[UiPathResumeTrigger]
41+
) -> None:
42+
"""Delete resume triggers from storage.
43+
44+
Args:
45+
runtime_id: The runtime ID
46+
triggers: The resume triggers to delete
47+
48+
Raises:
49+
Exception: If deletion operation fails
50+
"""
51+
...
52+
3753
async def delete_trigger(
3854
self, runtime_id: str, trigger: UiPathResumeTrigger
3955
) -> None:
@@ -46,7 +62,8 @@ async def delete_trigger(
4662
Raises:
4763
Exception: If deletion operation fails
4864
"""
49-
...
65+
# TODO: Remove this compatibility alias in the next minor version.
66+
await self.delete_triggers(runtime_id, [trigger])
5067

5168

5269
class UiPathResumeTriggerCreatorProtocol(Protocol):
@@ -63,6 +80,34 @@ async def create_trigger(self, suspend_value: Any) -> UiPathResumeTrigger:
6380
Returns:
6481
UiPathResumeTrigger ready to be persisted
6582
83+
Raises:
84+
UiPathRuntimeError: If trigger creation fails
85+
"""
86+
# TODO: Remove this compatibility alias in the next minor version.
87+
triggers = await self.create_triggers(suspend_value)
88+
if len(triggers) != 1:
89+
raise UiPathRuntimeError(
90+
code=UiPathErrorCode.CREATE_RESUME_TRIGGER_ERROR,
91+
title="Unexpected resume trigger count",
92+
detail=(
93+
"create_trigger() cannot return multiple resume triggers. "
94+
"Use create_triggers() instead."
95+
),
96+
)
97+
return triggers[0]
98+
99+
async def create_triggers(self, suspend_value: Any) -> list[UiPathResumeTrigger]:
100+
"""Create resume triggers from a suspend value.
101+
102+
Most suspend values produce one trigger. Composite values may produce
103+
multiple sibling triggers for the same interrupt.
104+
105+
Args:
106+
suspend_value: The value that caused the suspension.
107+
108+
Returns:
109+
UiPathResumeTrigger objects ready to be persisted.
110+
66111
Raises:
67112
UiPathRuntimeError: If trigger creation fails
68113
"""

0 commit comments

Comments
 (0)