Skip to content

Commit 6eb4250

Browse files
committed
fix: add disposable, runtime factory protocol
1 parent d3fa0e9 commit 6eb4250

File tree

6 files changed

+55
-27
lines changed

6 files changed

+55
-27
lines changed

src/uipath/runtime/__init__.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99
)
1010
from uipath.runtime.context import UiPathRuntimeContext
1111
from uipath.runtime.events import UiPathRuntimeEvent
12-
from uipath.runtime.factory import UiPathRuntimeCreator, UiPathRuntimeScanner
12+
from uipath.runtime.factory import (
13+
UiPathRuntimeCreatorProtocol,
14+
UiPathRuntimeFactoryProtocol,
15+
UiPathRuntimeScannerProtocol,
16+
)
1317
from uipath.runtime.result import (
1418
UiPathApiTrigger,
1519
UiPathBreakpointResult,
@@ -25,8 +29,9 @@
2529
"UiPathRuntimeContext",
2630
"UiPathRuntimeProtocol",
2731
"UiPathExecutionRuntime",
28-
"UiPathRuntimeCreator",
29-
"UiPathRuntimeScanner",
32+
"UiPathRuntimeCreatorProtocol",
33+
"UiPathRuntimeScannerProtocol",
34+
"UiPathRuntimeFactoryProtocol",
3035
"UiPathRuntimeResult",
3136
"UiPathRuntimeStatus",
3237
"UiPathRuntimeEvent",

src/uipath/runtime/base.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
AsyncGenerator,
88
Literal,
99
Optional,
10+
Protocol,
1011
)
1112

1213
from pydantic import BaseModel, Field
@@ -52,7 +53,7 @@ class UiPathStreamOptions(UiPathExecuteOptions):
5253
pass
5354

5455

55-
class UiPathExecutable(typing.Protocol):
56+
class UiPathExecutableProtocol(typing.Protocol):
5657
"""UiPath execution interface."""
5758

5859
async def execute(
@@ -64,7 +65,7 @@ async def execute(
6465
...
6566

6667

67-
class UiPathStreamable(typing.Protocol):
68+
class UiPathStreamableProtocol(typing.Protocol):
6869
"""UiPath streaming interface."""
6970

7071
async def stream(
@@ -111,7 +112,7 @@ async def stream(
111112
yield
112113

113114

114-
class HasSchema(typing.Protocol):
115+
class UiPathSchemaProtocol(typing.Protocol):
115116
"""Contains runtime input and output schema."""
116117

117118
async def get_schema(self) -> UiPathRuntimeSchema:
@@ -122,13 +123,25 @@ async def get_schema(self) -> UiPathRuntimeSchema:
122123
...
123124

124125

126+
class UiPathDisposableProtocol(typing.Protocol):
127+
"""UiPath disposable interface."""
128+
129+
async def dispose(self) -> None:
130+
"""Close and clean up resources."""
131+
...
132+
133+
125134
# Note: explicitly marking it as a protocol for mypy.
126135
# https://mypy.readthedocs.io/en/stable/protocols.html#defining-subprotocols-and-subclassing-protocols
127136
# Note that inheriting from an existing protocol does not automatically turn the subclass into a protocol
128137
# – it just creates a regular (non-protocol) class or ABC that implements the given protocol (or protocols).
129138
# The Protocol base class must always be explicitly present if you are defining a protocol.
130139
class UiPathRuntimeProtocol(
131-
UiPathExecutable, UiPathStreamable, HasSchema, typing.Protocol
140+
UiPathExecutableProtocol,
141+
UiPathStreamableProtocol,
142+
UiPathSchemaProtocol,
143+
UiPathDisposableProtocol,
144+
Protocol,
132145
):
133146
"""UiPath Runtime Protocol."""
134147

src/uipath/runtime/debug/runtime.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,13 @@ async def _stream_and_debug(
122122

123123
return final_result
124124

125-
async def cleanup(self) -> None:
125+
async def get_schema(self) -> UiPathRuntimeSchema:
126+
"""Passthrough schema for the delegate."""
127+
return await self.delegate.get_schema()
128+
129+
async def dispose(self) -> None:
126130
"""Cleanup runtime resources."""
127131
try:
128132
await self.debug_bridge.disconnect()
129133
except Exception as e:
130134
logger.warning(f"Error disconnecting debug bridge: {e}")
131-
132-
async def get_schema(self) -> UiPathRuntimeSchema:
133-
"""Passthrough schema for the delegate."""
134-
return await self.delegate.get_schema()

src/uipath/runtime/factory.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,27 @@
55
from uipath.runtime.base import UiPathRuntimeProtocol
66

77

8-
class UiPathRuntimeScanner(Protocol):
8+
class UiPathRuntimeScannerProtocol(Protocol):
99
"""Protocol for discovering all UiPath runtime instances."""
1010

1111
def discover_runtimes(self) -> list[UiPathRuntimeProtocol]:
1212
"""Discover all runtime classes."""
1313
...
1414

15+
def discover_entrypoints(self) -> list[str]:
16+
"""Discover all runtime entrypoints."""
17+
...
18+
1519

16-
class UiPathRuntimeCreator(Protocol):
20+
class UiPathRuntimeCreatorProtocol(Protocol):
1721
"""Protocol for creating a UiPath runtime given an entrypoint."""
1822

19-
def new_runtime(self, entrypoint: str) -> UiPathRuntimeProtocol:
23+
async def new_runtime(self, entrypoint: str) -> UiPathRuntimeProtocol:
2024
"""Create a new runtime instance."""
2125
...
26+
27+
28+
class UiPathRuntimeFactoryProtocol(
29+
UiPathRuntimeCreatorProtocol, UiPathRuntimeScannerProtocol, Protocol
30+
):
31+
"""Protocol for discovering and creating UiPath runtime instances."""

tests/test_debugger.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def __init__(
6464

6565
self.execute_called: bool = False
6666

67-
async def cleanup(self) -> None:
67+
async def dispose(self) -> None:
6868
pass
6969

7070
async def execute(
@@ -271,8 +271,8 @@ async def test_debug_runtime_execute_reports_errors_and_marks_faulted():
271271

272272

273273
@pytest.mark.asyncio
274-
async def test_debug_runtime_cleanup_calls_disconnect():
275-
"""cleanup() should call debug bridge disconnect."""
274+
async def test_debug_runtime_dispose_calls_disconnect():
275+
"""dispose() should call debug bridge disconnect."""
276276

277277
runtime_impl = StreamingMockRuntime(node_sequence=["node-1"])
278278
bridge = make_debug_bridge_mock()
@@ -282,14 +282,14 @@ async def test_debug_runtime_cleanup_calls_disconnect():
282282
debug_bridge=bridge,
283283
)
284284

285-
await debug_runtime.cleanup()
285+
await debug_runtime.dispose()
286286

287287
cast(AsyncMock, bridge.disconnect).assert_awaited_once()
288288

289289

290290
@pytest.mark.asyncio
291-
async def test_debug_runtime_cleanup_suppresses_disconnect_errors():
292-
"""Errors from debug_bridge.disconnect should be suppressed, inner cleanup still runs."""
291+
async def test_debug_runtime_dispose_suppresses_disconnect_errors():
292+
"""Errors from debug_bridge.disconnect should be suppressed."""
293293

294294
runtime_impl = StreamingMockRuntime(node_sequence=["node-1"])
295295
bridge = make_debug_bridge_mock()
@@ -300,7 +300,7 @@ async def test_debug_runtime_cleanup_suppresses_disconnect_errors():
300300
debug_bridge=bridge,
301301
)
302302

303-
# No exception should bubble up from cleanup()
304-
await debug_runtime.cleanup()
303+
# No exception should bubble up from dispose()
304+
await debug_runtime.dispose()
305305

306306
cast(AsyncMock, bridge.disconnect).assert_awaited_once()

tests/test_executor.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ class UiPathTestRuntimeFactory:
109109
def __init__(self, runtime_class: type[UiPathRuntimeProtocol]):
110110
self.runtime_class = runtime_class
111111

112-
def new_runtime(self, entrypoint: str) -> UiPathRuntimeProtocol:
112+
async def new_runtime(self, entrypoint: str) -> UiPathRuntimeProtocol:
113113
return self.runtime_class()
114114

115115

@@ -124,21 +124,21 @@ async def test_multiple_factories_same_executor():
124124
factory_c = UiPathTestRuntimeFactory(MockRuntimeC)
125125

126126
# Execute runtime A
127-
runtime_a = factory_a.new_runtime(entrypoint="")
127+
runtime_a = await factory_a.new_runtime(entrypoint="")
128128
execution_runtime_a = UiPathExecutionRuntime(
129129
runtime_a, trace_manager, "runtime-a-span", execution_id="exec-a"
130130
)
131131
result_a = await execution_runtime_a.execute({"input": "a"})
132132

133133
# Execute runtime B
134-
runtime_b = factory_b.new_runtime(entrypoint="")
134+
runtime_b = await factory_b.new_runtime(entrypoint="")
135135
execution_runtime_b = UiPathExecutionRuntime(
136136
runtime_b, trace_manager, "runtime-b-span", execution_id="exec-b"
137137
)
138138
result_b = await execution_runtime_b.execute({"input": "b"})
139139

140140
# Execute runtime C with custom spans
141-
runtime_c = factory_c.new_runtime(entrypoint="")
141+
runtime_c = await factory_c.new_runtime(entrypoint="")
142142
execution_runtime_c = UiPathExecutionRuntime(
143143
runtime_c, trace_manager, "runtime-c-span", execution_id="exec-c"
144144
)

0 commit comments

Comments
 (0)