From 5a9d925f455fa199a7d1386906f465add08b6876 Mon Sep 17 00:00:00 2001 From: radu-mocanu Date: Fri, 11 Apr 2025 17:56:55 +0300 Subject: [PATCH] feat: add support for folder path in action service --- pyproject.toml | 2 +- src/uipath/_folder_context.py | 11 +- src/uipath/_services/actions_service.py | 125 ++++++++++++++---- .../_services/context_grounding_service.py | 5 +- src/uipath/models/interrupt_models.py | 8 +- uv.lock | 2 +- 6 files changed, 111 insertions(+), 42 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index eac534194..96ef5fe59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath" -version = "2.0.6" +version = "2.0.7" 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.10" diff --git a/src/uipath/_folder_context.py b/src/uipath/_folder_context.py index 928d63090..46a1892e6 100644 --- a/src/uipath/_folder_context.py +++ b/src/uipath/_folder_context.py @@ -46,17 +46,12 @@ def folder_headers(self) -> dict[str, str]: Returns: dict[str, str]: A dictionary containing the appropriate folder - header (either folder key or folder path). - - Raises: - ValueError: If neither folder key nor folder path is set in - the environment. + header (either folder key or folder path). If no folder header is + set as environment variable, the function returns an empty dictionary. """ if self._folder_key is not None: return {HEADER_FOLDER_KEY: self._folder_key} elif self._folder_path is not None: return {HEADER_FOLDER_PATH: self._folder_path} else: - raise ValueError( - f"Folder key or path is not set ({ENV_FOLDER_KEY} or {ENV_FOLDER_PATH})" - ) + return {} diff --git a/src/uipath/_services/actions_service.py b/src/uipath/_services/actions_service.py index fdd4d451a..dbaa37cd0 100644 --- a/src/uipath/_services/actions_service.py +++ b/src/uipath/_services/actions_service.py @@ -7,17 +7,24 @@ from .._execution_context import ExecutionContext from .._folder_context import FolderContext from .._utils import Endpoint, RequestSpec -from .._utils.constants import ENV_TENANT_ID, HEADER_TENANT_ID +from .._utils.constants import ( + ENV_TENANT_ID, + HEADER_FOLDER_KEY, + HEADER_FOLDER_PATH, + HEADER_TENANT_ID, +) from ..models import Action, ActionSchema from ._base_service import BaseService def _create_spec( - title: str, data: Optional[Dict[str, Any]], action_schema: Optional[ActionSchema], + title: str, app_key: str = "", app_version: int = -1, + app_folder_key: str = "", + app_folder_path: str = "", ) -> RequestSpec: field_list = [] outcome_list = [] @@ -97,14 +104,18 @@ def _create_spec( else {}, } ), + headers=folder_headers(app_folder_key, app_folder_path), ) -def _retrieve_action_spec(action_key: str) -> RequestSpec: +def _retrieve_action_spec( + action_key: str, app_folder_key: str, app_folder_path: str +) -> RequestSpec: return RequestSpec( method="GET", endpoint=Endpoint("/orchestrator_/tasks/GenericTasks/GetTaskDataByKey"), params={"taskKey": action_key}, + headers=folder_headers(app_folder_key, app_folder_path), ) @@ -132,6 +143,15 @@ def _retrieve_app_key_spec(app_name: str) -> RequestSpec: ) +def folder_headers(app_folder_key: str, app_folder_path: str) -> Dict[str, str]: + headers = {} + if app_folder_key: + headers[HEADER_FOLDER_KEY] = app_folder_key + elif app_folder_path: + headers[HEADER_FOLDER_PATH] = app_folder_path + return headers + + class ActionsService(FolderContext, BaseService): """Service for managing UiPath Actions. @@ -162,6 +182,8 @@ async def create_async( *, app_name: str = "", app_key: str = "", + app_folder_path: str = "", + app_folder_key: str = "", app_version: int = -1, assignee: str = "", ) -> Action: @@ -175,6 +197,8 @@ async def create_async( data: Optional dictionary containing input data for the action app_name: The name of the application (if creating an app-specific action) app_key: The key of the application (if creating an app-specific action) + app_folder_path: Optional folder path for the action + app_folder_key: Optional folder key for the action app_version: The version of the application assignee: Optional username or email to assign the task to @@ -195,10 +219,12 @@ async def create_async( app_key=key, app_version=app_version, action_schema=action_schema, + app_folder_key=app_folder_key, + app_folder_path=app_folder_path, ) response = await self.request_async( - spec.method, spec.endpoint, content=spec.content + spec.method, spec.endpoint, content=spec.content, headers=spec.headers ) json_response = response.json() if assignee: @@ -213,6 +239,8 @@ def create( *, app_name: str = "", app_key: str = "", + app_folder_path: str = "", + app_folder_key: str = "", app_version: int = -1, assignee: str = "", ) -> Action: @@ -226,6 +254,8 @@ def create( data: Optional dictionary containing input data for the action app_name: The name of the application (if creating an app-specific action) app_key: The key of the application (if creating an app-specific action) + app_folder_path: Optional folder path for the action + app_folder_key: Optional folder key for the action app_version: The version of the application assignee: Optional username or email to assign the task to @@ -244,48 +274,63 @@ def create( app_key=key, app_version=app_version, action_schema=action_schema, + app_folder_key=app_folder_key, + app_folder_path=app_folder_path, ) - response = self.request(spec.method, spec.endpoint, content=spec.content) + response = self.request( + spec.method, spec.endpoint, content=spec.content, headers=spec.headers + ) json_response = response.json() if assignee: spec = _assign_task_spec(json_response["id"], assignee) - print(spec) self.request(spec.method, spec.endpoint, content=spec.content) return Action.model_validate(json_response) def retrieve( - self, - action_key: str, + self, action_key: str, app_folder_path: str = "", app_folder_key: str = "" ) -> Action: """Retrieves an action by its key synchronously. Args: action_key: The unique identifier of the action to retrieve + app_folder_path: Optional folder path for the action + app_folder_key: Optional folder key for the action Returns: Action: The retrieved action object """ - spec = _retrieve_action_spec(action_key=action_key) - response = self.request(spec.method, spec.endpoint, params=spec.params) + spec = _retrieve_action_spec( + action_key=action_key, + app_folder_key=app_folder_key, + app_folder_path=app_folder_path, + ) + response = self.request( + spec.method, spec.endpoint, params=spec.params, headers=spec.headers + ) return Action.model_validate(response.json()) async def retrieve_async( - self, - action_key: str, + self, action_key: str, app_folder_path: str = "", app_folder_key: str = "" ) -> Action: """Retrieves an action by its key asynchronously. Args: action_key: The unique identifier of the action to retrieve + app_folder_path: Optional folder path for the action + app_folder_key: Optional folder key for the action Returns: Action: The retrieved action object """ - spec = _retrieve_action_spec(action_key=action_key) + spec = _retrieve_action_spec( + action_key=action_key, + app_folder_key=app_folder_key, + app_folder_path=app_folder_path, + ) response = await self.request_async( - spec.method, spec.endpoint, params=spec.params + spec.method, spec.endpoint, params=spec.params, headers=spec.headers ) return Action.model_validate(response.json()) @@ -311,8 +356,25 @@ async def __get_app_key_and_schema_async( response = await self.request_org_scope_async( spec.method, spec.endpoint, params=spec.params, headers=spec.headers ) - deployed_app = response.json()["deployed"][0] - return (deployed_app["systemName"], deployed_app["actionSchema"]) + try: + deployed_app = response.json()["deployed"][0] + action_schema = deployed_app["actionSchema"] + deployed_app_key = deployed_app["systemName"] + except (KeyError, IndexError): + raise Exception("Action app not found") from None + try: + return ( + deployed_app_key, + ActionSchema( + key=action_schema["key"], + in_outs=action_schema["inOuts"], + inputs=action_schema["inputs"], + outputs=action_schema["outputs"], + outcomes=action_schema["outcomes"], + ), + ) + except KeyError: + raise Exception("Failed to deserialize action schema") from KeyError def __get_app_key_and_schema( self, app_name: str @@ -326,18 +388,25 @@ def __get_app_key_and_schema( spec.method, spec.endpoint, params=spec.params, headers=spec.headers ) - deployed_app = response.json()["deployed"][0] - action_schema = deployed_app["actionSchema"] - return ( - deployed_app["systemName"], - ActionSchema( - key=action_schema["key"], - in_outs=action_schema["inOuts"], - inputs=action_schema["inputs"], - outputs=action_schema["outputs"], - outcomes=action_schema["outcomes"], - ), - ) + try: + deployed_app = response.json()["deployed"][0] + action_schema = deployed_app["actionSchema"] + deployed_app_key = deployed_app["systemName"] + except (KeyError, IndexError): + raise Exception("Action app not found") from None + try: + return ( + deployed_app_key, + ActionSchema( + key=action_schema["key"], + in_outs=action_schema["inOuts"], + inputs=action_schema["inputs"], + outputs=action_schema["outputs"], + outcomes=action_schema["outcomes"], + ), + ) + except KeyError: + raise Exception("Failed to deserialize action schema") from KeyError @property def custom_headers(self) -> Dict[str, str]: diff --git a/src/uipath/_services/context_grounding_service.py b/src/uipath/_services/context_grounding_service.py index 80aa83d12..1e4dbe25e 100644 --- a/src/uipath/_services/context_grounding_service.py +++ b/src/uipath/_services/context_grounding_service.py @@ -9,6 +9,7 @@ from .._utils import Endpoint, RequestSpec from .._utils.constants import ( HEADER_FOLDER_KEY, + HEADER_FOLDER_PATH, ORCHESTRATOR_STORAGE_BUCKET_DATA_SOURCE, ) from ..models import IngestionInProgressException @@ -326,7 +327,9 @@ def custom_headers(self) -> Dict[str, str]: ) if self._folder_key is None: - raise ValueError(f"Folder key is not set ({HEADER_FOLDER_KEY})") + raise ValueError( + f"Neither the folder key nor the folder path is set ({HEADER_FOLDER_KEY}, {HEADER_FOLDER_PATH})" + ) return self.folder_headers diff --git a/src/uipath/models/interrupt_models.py b/src/uipath/models/interrupt_models.py index 1e98842f4..72c6b4a44 100644 --- a/src/uipath/models/interrupt_models.py +++ b/src/uipath/models/interrupt_models.py @@ -16,12 +16,14 @@ class WaitJob(BaseModel): class CreateAction(BaseModel): - name: Optional[str] = None - key: Optional[str] = None title: str data: Optional[Dict[str, Any]] = None - app_version: Optional[int] = 1 assignee: Optional[str] = "" + app_name: Optional[str] = None + app_folder_path: Optional[str] = None + app_folder_key: Optional[str] = None + app_key: Optional[str] = None + app_version: Optional[int] = 1 class WaitAction(BaseModel): diff --git a/uv.lock b/uv.lock index a692e7c82..d4c1d40cf 100644 --- a/uv.lock +++ b/uv.lock @@ -2320,7 +2320,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.0.5" +version = "2.0.7" source = { editable = "." } dependencies = [ { name = "click" },