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
6 changes: 0 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,6 @@ class MyRuntimeFactory:
async def new_runtime(self, entrypoint: str, runtime_id: str) -> UiPathRuntimeProtocol:
return MyRuntime()

async def discover_runtimes(self) -> list[UiPathRuntimeProtocol]:
return []

def discover_entrypoints(self) -> list[str]:
return []

Expand Down Expand Up @@ -315,9 +312,6 @@ class ChildRuntimeFactory:
async def new_runtime(self, entrypoint: str) -> UiPathRuntimeProtocol:
return ChildRuntime(name=entrypoint)

async def discover_runtimes(self) -> list[UiPathRuntimeProtocol]:
return []

def discover_entrypoints(self) -> list[str]:
return []

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[project]
name = "uipath-runtime"
version = "0.5.1"
version = "0.6.0"
description = "Runtime abstractions and interfaces for building agents and automation scripts in the UiPath ecosystem"
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.11"
dependencies = [
"uipath-core>=0.1.1, <0.2.0",
"uipath-core>=0.2.0, <0.3.0",
]
classifiers = [
"Intended Audience :: Developers",
Expand Down
8 changes: 4 additions & 4 deletions src/uipath/runtime/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@
)
from uipath.runtime.events import UiPathRuntimeEvent
from uipath.runtime.factory import (
UiPathRuntimeCreatorProtocol,
UiPathRuntimeFactoryProtocol,
UiPathRuntimeScannerProtocol,
UiPathRuntimeFactorySettings,
)
from uipath.runtime.registry import UiPathRuntimeFactoryRegistry
from uipath.runtime.result import (
Expand All @@ -41,17 +40,18 @@
UiPathResumeTriggerType,
)
from uipath.runtime.schema import UiPathRuntimeSchema
from uipath.runtime.storage import UiPathRuntimeStorageProtocol

__all__ = [
"UiPathExecuteOptions",
"UiPathStreamOptions",
"UiPathRuntimeContext",
"UiPathRuntimeProtocol",
"UiPathExecutionRuntime",
"UiPathRuntimeCreatorProtocol",
"UiPathRuntimeScannerProtocol",
"UiPathRuntimeStorageProtocol",
"UiPathRuntimeFactoryProtocol",
"UiPathRuntimeFactoryRegistry",
"UiPathRuntimeFactorySettings",
"UiPathRuntimeResult",
"UiPathRuntimeStatus",
"UiPathRuntimeEvent",
Expand Down
43 changes: 26 additions & 17 deletions src/uipath/runtime/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,44 @@

from typing import Protocol

from uipath.runtime.base import UiPathDisposableProtocol, UiPathRuntimeProtocol
from pydantic import BaseModel
from uipath.core.tracing import UiPathTraceSettings

from uipath.runtime.base import (
UiPathDisposableProtocol,
UiPathRuntimeProtocol,
)
from uipath.runtime.storage import UiPathRuntimeStorageProtocol

class UiPathRuntimeScannerProtocol(Protocol):
"""Protocol for discovering all UiPath runtime instances."""

async def discover_runtimes(self) -> list[UiPathRuntimeProtocol]:
"""Discover all runtime classes."""
...
class UiPathRuntimeFactorySettings(BaseModel):
"""Runtime settings for execution behavior."""

model_config = {"arbitrary_types_allowed": True} # Needed for Callable

trace_settings: UiPathTraceSettings | None = None


class UiPathRuntimeFactoryProtocol(
UiPathDisposableProtocol,
Protocol,
):
"""Protocol for discovering and creating UiPath runtime instances."""

def discover_entrypoints(self) -> list[str]:
"""Discover all runtime entrypoints."""
...


class UiPathRuntimeCreatorProtocol(Protocol):
"""Protocol for creating a UiPath runtime given an entrypoint."""

async def new_runtime(
self, entrypoint: str, runtime_id: str, **kwargs
) -> UiPathRuntimeProtocol:
"""Create a new runtime instance."""
...

async def get_storage(self) -> UiPathRuntimeStorageProtocol | None:
"""Get the factory storage."""
...

class UiPathRuntimeFactoryProtocol(
UiPathRuntimeCreatorProtocol,
UiPathRuntimeScannerProtocol,
UiPathDisposableProtocol,
Protocol,
):
"""Protocol for discovering and creating UiPath runtime instances."""
async def get_settings(self) -> UiPathRuntimeFactorySettings | None:
"""Get factory settings."""
...
35 changes: 2 additions & 33 deletions src/uipath/runtime/resumable/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
from typing import Any, Protocol

from uipath.runtime.resumable.trigger import UiPathResumeTrigger
from uipath.runtime.storage import UiPathRuntimeStorageProtocol


class UiPathResumableStorageProtocol(Protocol):
class UiPathResumableStorageProtocol(UiPathRuntimeStorageProtocol, Protocol):
"""Protocol for storing and retrieving resume triggers."""

async def save_triggers(
Expand Down Expand Up @@ -46,38 +47,6 @@ async def delete_trigger(
"""
...

async def set_value(
self, runtime_id: str, namespace: str, key: str, value: Any
) -> None:
"""Store values for a specific runtime.

Args:
runtime_id: The runtime ID
namespace: The namespace of the persisted value
key: The key associated with the persisted value
value: The value to persist

Raises:
Exception: If storage operation fails
"""
...

async def get_value(self, runtime_id: str, namespace: str, key: str) -> Any:
"""Retrieve values for a specific runtime from storage.

Args:
runtime_id: The runtime ID
namespace: The namespace of the persisted value
key: The key associated with the persisted value

Returns:
The value matching the method's parameters, or None if it does not exist

Raises:
Exception: If retrieval operation fails
"""
...


class UiPathResumeTriggerCreatorProtocol(Protocol):
"""Protocol for creating resume triggers from suspend values."""
Expand Down
42 changes: 42 additions & 0 deletions src/uipath/runtime/storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Runtime storage protocol definition."""

from typing import (
Any,
Protocol,
)


class UiPathRuntimeStorageProtocol(Protocol):
"""Protocol for runtime storage operations."""

async def set_value(
self, runtime_id: str, namespace: str, key: str, value: Any
) -> None:
"""Store values for a specific runtime.

Args:
runtime_id: The runtime ID
namespace: The namespace of the persisted value
key: The key associated with the persisted value
value: The value to persist

Raises:
Exception: If storage operation fails
"""
...

async def get_value(self, runtime_id: str, namespace: str, key: str) -> Any:
"""Retrieve values for a specific runtime from storage.

Args:
runtime_id: The runtime ID
namespace: The namespace of the persisted value
key: The key associated with the persisted value

Returns:
The value matching the method's parameters, or None if it does not exist

Raises:
Exception: If retrieval operation fails
"""
...
37 changes: 33 additions & 4 deletions tests/test_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,29 @@
UiPathRuntimeProtocol,
UiPathRuntimeResult,
UiPathRuntimeSchema,
UiPathRuntimeStorageProtocol,
UiPathStreamOptions,
)
from uipath.runtime.factory import UiPathRuntimeCreatorProtocol
from uipath.runtime.factory import (
UiPathRuntimeFactoryProtocol,
UiPathRuntimeFactorySettings,
)


class MockStorage(UiPathRuntimeStorageProtocol):
"""Mock storage implementation"""

def __init__(self):
self._store = {}

async def set_value(self, runtime_id, namespace, key, value):
self._store.setdefault(runtime_id, {}).setdefault(namespace, {})[key] = value

async def get_value(self, runtime_id, namespace, key):
return self._store.get(runtime_id, {}).get(namespace, {}).get(key)


class MockRuntime:
class MockRuntime(UiPathRuntimeProtocol):
"""Mock runtime that implements UiPathRuntimeProtocol."""

def __init__(self, settings: dict[str, Any] | None = None) -> None:
Expand Down Expand Up @@ -49,24 +66,36 @@ async def dispose(self) -> None:
class CreatorWithKwargs:
"""Implementation with kwargs."""

def discover_entrypoints(self) -> list[str]:
return ["main.py"]

async def new_runtime(
self, entrypoint: str, runtime_id: str, **kwargs
) -> UiPathRuntimeProtocol:
return MockRuntime(kwargs.get("settings"))

async def get_storage(self) -> UiPathRuntimeStorageProtocol | None:
return MockStorage()

async def get_settings(self) -> UiPathRuntimeFactorySettings | None:
return UiPathRuntimeFactorySettings()

async def dispose(self) -> None:
pass


@pytest.mark.asyncio
async def test_protocol_works_with_kwargs_not_specified():
"""Test protocol works with implementation that has kwargs."""
creator: UiPathRuntimeCreatorProtocol = CreatorWithKwargs()
creator: UiPathRuntimeFactoryProtocol = CreatorWithKwargs()
runtime = await creator.new_runtime("main.py", "runtime-123")
assert isinstance(runtime, MockRuntime)


@pytest.mark.asyncio
async def test_protocol_works_with_kwargs_specified():
"""Test protocol works with implementation that has kwargs."""
creator: UiPathRuntimeCreatorProtocol = CreatorWithKwargs()
creator: UiPathRuntimeFactoryProtocol = CreatorWithKwargs()
runtime = await creator.new_runtime(
"main.py", "runtime-123", settings={"timeout": 30, "model": "gpt-4"}
)
Expand Down
36 changes: 30 additions & 6 deletions tests/test_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,29 @@
UiPathRuntimeEvent,
UiPathRuntimeFactoryProtocol,
UiPathRuntimeFactoryRegistry,
UiPathRuntimeFactorySettings,
UiPathRuntimeProtocol,
UiPathRuntimeResult,
UiPathRuntimeSchema,
UiPathRuntimeStatus,
UiPathRuntimeStorageProtocol,
UiPathStreamOptions,
)


class MockStorage(UiPathRuntimeStorageProtocol):
"""Mock storage implementation"""

def __init__(self):
self._store = {}

async def set_value(self, runtime_id, namespace, key, value):
self._store.setdefault(runtime_id, {}).setdefault(namespace, {})[key] = value

async def get_value(self, runtime_id, namespace, key):
return self._store.get(runtime_id, {}).get(namespace, {}).get(key)


class MockRuntime(UiPathRuntimeProtocol):
"""Mock runtime instance"""

Expand Down Expand Up @@ -59,8 +74,11 @@ def __init__(self, context: Optional[UiPathRuntimeContext] = None):
def discover_entrypoints(self) -> list[str]:
return ["main.py", "handler.py"]

async def discover_runtimes(self) -> list[UiPathRuntimeProtocol]:
return []
async def get_storage(self) -> UiPathRuntimeStorageProtocol | None:
return MockStorage()

async def get_settings(self) -> UiPathRuntimeFactorySettings | None:
return UiPathRuntimeFactorySettings()

async def new_runtime(
self, entrypoint: str, runtime_id: str, **kwargs
Expand All @@ -81,8 +99,11 @@ def __init__(self, context: Optional[UiPathRuntimeContext] = None):
def discover_entrypoints(self) -> list[str]:
return ["agent", "workflow"]

async def discover_runtimes(self) -> list[UiPathRuntimeProtocol]:
return []
async def get_storage(self) -> UiPathRuntimeStorageProtocol | None:
return MockStorage()

async def get_settings(self) -> UiPathRuntimeFactorySettings | None:
return UiPathRuntimeFactorySettings()

async def new_runtime(
self, entrypoint: str, runtime_id: str, **kwargs
Expand All @@ -103,8 +124,11 @@ def __init__(self, context: Optional[UiPathRuntimeContext] = None):
def discover_entrypoints(self) -> list[str]:
return ["chatbot", "rag"]

async def discover_runtimes(self) -> list[UiPathRuntimeProtocol]:
return []
async def get_storage(self) -> UiPathRuntimeStorageProtocol | None:
return MockStorage()

async def get_settings(self) -> UiPathRuntimeFactorySettings | None:
return UiPathRuntimeFactorySettings()

async def new_runtime(
self, entrypoint: str, runtime_id: str, **kwargs
Expand Down
Loading