From 3aff14d5fef7ec3cb211a38a46030732237ec81a Mon Sep 17 00:00:00 2001 From: chetanyauipath <107464888+chetanyauipath@users.noreply.github.com> Date: Tue, 26 May 2026 22:14:07 +0530 Subject: [PATCH 01/12] feat(tasks): add create_quickform_async / create_quickform for QuickForm HITL Adds SDK support for the new TaskType.QuickFormTask (=6) path that targets Orchestrator's GenericTasks/CreateTask endpoint. QuickForm is the schema-first HITL flow where the form is rendered by FormLib in Action Center from a .hitl.json schema persisted in the TaskSchemas table; tasks reference it via taskSchemaKey. Mirrors the existing create_async path but: - POSTs to /orchestrator_/tasks/GenericTasks/CreateTask instead of AppTasks - Sets type=6 (QuickFormTask) and taskSchemaKey (Guid) - Skips the AppTask app-key fetch and action-schema-derived fieldSet/actionSet - Supports optional inline schema body + creatorJobKey for the publish-time race window (when AC has not yet persisted the schema) - Reuses the same OData.AssignTasks flow for recipient assignment Wire contract: UiPath/Orchestrator TaskCreateRequest (Core/Application/Dto/Tasks). Co-Authored-By: Claude Opus 4.7 --- .../platform/action_center/_tasks_service.py | 236 ++++++++++++++++++ 1 file changed, 236 insertions(+) diff --git a/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py b/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py index 662109ce4..9372065d0 100644 --- a/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py +++ b/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py @@ -196,6 +196,93 @@ def _normalize_priority(priority: str | None) -> str | None: return normalized +# TaskType.QuickFormTask value, matching the Orchestrator enum (UiPath.Orchestrator.DataContracts.TaskType). +_TASK_TYPE_QUICKFORM = 6 + + +def _create_quickform_spec( + data: Optional[Dict[str, Any]], + title: str, + task_schema_key: str, + schema: Optional[Dict[str, Any]] = None, + creator_job_key: Optional[str] = None, + folder_key: Optional[str] = None, + folder_path: Optional[str] = None, + priority: Optional[str] = None, + labels: Optional[List[str]] = None, + is_actionable_message_enabled: Optional[bool] = None, + actionable_message_metadata: Optional[Dict[str, Any]] = None, + source_name: str = "Agent", +) -> RequestSpec: + """Build the RequestSpec for Orchestrator's GenericTasks/CreateTask endpoint with + TaskType=QuickFormTask. Mirrors _create_spec but skips the AppTask-specific shape + (no appId, no action-schema-derived fieldSet/actionSet) and adds taskSchemaKey + + optional inline schema fallback. + + Wire contract: UiPath/Orchestrator/src/Core/Application/Dto/Tasks/TaskCreateRequest.cs. + """ + json_payload: Dict[str, Any] = { + "type": _TASK_TYPE_QUICKFORM, + "taskSchemaKey": task_schema_key, + "title": title, + "data": data if data is not None else {}, + } + + if schema is not None: + # Inline schema fallback for the publish-time race window: when AC's + # package.uploaded subscriber hasn't yet persisted the schema into TaskSchemas, + # Orchestrator creates the schema first using task_schema_key as the key, then + # creates the task. + json_payload["schema"] = schema + if creator_job_key is not None: + json_payload["creatorJobKey"] = creator_job_key + + if priority and (normalized_priority := _normalize_priority(priority)): + json_payload["priority"] = normalized_priority + if labels is not None: + json_payload["tags"] = [ + { + "name": label, + "displayName": label, + "value": label, + "displayValue": label, + } + for label in labels + ] + if is_actionable_message_enabled is not None: + json_payload["isActionableMessageEnabled"] = is_actionable_message_enabled + if actionable_message_metadata is not None: + json_payload["actionableMessageMetaData"] = actionable_message_metadata + + project_id = UiPathConfig.project_id + trace_id = UiPathConfig.trace_id + + if project_id and trace_id: + cfg_folder_key = UiPathConfig.folder_key + job_key = UiPathConfig.job_key + process_key = UiPathConfig.process_uuid + + task_source_metadata: Dict[str, Any] = { + "InstanceId": trace_id, + "FolderKey": cfg_folder_key, + "JobKey": job_key, + "ProcessKey": process_key, + } + + json_payload["taskSource"] = { + "sourceName": source_name, + "sourceId": project_id, + "taskSourceMetadata": task_source_metadata, + } + + return RequestSpec( + method="POST", + endpoint=Endpoint("/orchestrator_/tasks/GenericTasks/CreateTask"), + json=json_payload, + headers=header_folder(folder_key, folder_path), + ) + + def _retrieve_action_spec( action_key: str, app_folder_key: Optional[str], @@ -506,6 +593,155 @@ def create( ) return Task.model_validate(json_response) + @traced(name="tasks_create_quickform", run_type="uipath") + async def create_quickform_async( + self, + title: str, + task_schema_key: str, + data: Optional[Dict[str, Any]] = None, + *, + folder_path: Optional[str] = None, + folder_key: Optional[str] = None, + assignee: Optional[str] = None, + recipient: Optional[TaskRecipient] = None, + priority: Optional[str] = None, + labels: Optional[List[str]] = None, + is_actionable_message_enabled: Optional[bool] = None, + actionable_message_metadata: Optional[Dict[str, Any]] = None, + schema: Optional[Dict[str, Any]] = None, + creator_job_key: Optional[str] = None, + source_name: str = "Agent", + ) -> Task: + """Creates a new QuickForm task asynchronously. + + QuickForm tasks are schema-first HITL tasks rendered by FormLib in Action + Center. The schema is resolved server-side via task_schema_key against the + TaskSchemas table (populated by AC's package.uploaded subscriber when the + agent package is published). + + Args: + title: The title of the task. + task_schema_key: UUID key of the .hitl.json schema this task should + use. Required. + data: Optional dictionary containing input data for the task. + folder_path: Optional folder path for the task. Required by the + Orchestrator controller (RequireOrganizationUnit) unless + folder_key is provided. + folder_key: Optional folder key, alternative to folder_path. + assignee: Optional username or email to assign the task to. + recipient: Optional structured recipient (user id / group id / + email). Resolved via identity service before assignment. + priority: Optional priority. Low / Medium / High / Critical. + labels: Optional list of labels for the task. + is_actionable_message_enabled: Whether actionable notifications are + enabled for this task. + actionable_message_metadata: Optional metadata override. For + QuickForm, when null, Orchestrator derives it from the + referenced TaskSchema. + schema: Optional inline schema body. Used only during the + publish-time race window when the package.uploaded subscriber + hasn't yet persisted the schema into TaskSchemas. When + provided, Orchestrator creates the schema first using + task_schema_key as the key, then creates the task. + creator_job_key: Optional paired with schema; identifies the job + that triggered the inline schema creation. + source_name: Source name on TaskSource. Defaults to 'Agent'. + + Returns: + Task: The created task object. + """ + spec = _create_quickform_spec( + title=title, + data=data, + task_schema_key=task_schema_key, + schema=schema, + creator_job_key=creator_job_key, + folder_key=folder_key, + folder_path=folder_path, + priority=priority, + labels=labels, + is_actionable_message_enabled=is_actionable_message_enabled, + actionable_message_metadata=actionable_message_metadata, + source_name=source_name, + ) + + response = await self.request_async( + spec.method, + spec.endpoint, + json=spec.json, + content=spec.content, + headers=spec.headers, + ) + json_response = response.json() + if assignee or recipient: + assign_spec = await _assign_task_spec( + self, json_response["id"], assignee, recipient + ) + await self.request_async( + assign_spec.method, + assign_spec.endpoint, + json=assign_spec.json, + content=assign_spec.content, + ) + return Task.model_validate(json_response) + + @traced(name="tasks_create_quickform", run_type="uipath") + def create_quickform( + self, + title: str, + task_schema_key: str, + data: Optional[Dict[str, Any]] = None, + *, + folder_path: Optional[str] = None, + folder_key: Optional[str] = None, + assignee: Optional[str] = None, + recipient: Optional[TaskRecipient] = None, + priority: Optional[str] = None, + labels: Optional[List[str]] = None, + is_actionable_message_enabled: Optional[bool] = None, + actionable_message_metadata: Optional[Dict[str, Any]] = None, + schema: Optional[Dict[str, Any]] = None, + creator_job_key: Optional[str] = None, + source_name: str = "Agent", + ) -> Task: + """Creates a new QuickForm task synchronously. See + :meth:`create_quickform_async` for parameter docs. + """ + spec = _create_quickform_spec( + title=title, + data=data, + task_schema_key=task_schema_key, + schema=schema, + creator_job_key=creator_job_key, + folder_key=folder_key, + folder_path=folder_path, + priority=priority, + labels=labels, + is_actionable_message_enabled=is_actionable_message_enabled, + actionable_message_metadata=actionable_message_metadata, + source_name=source_name, + ) + + response = self.request( + spec.method, + spec.endpoint, + json=spec.json, + content=spec.content, + headers=spec.headers, + ) + json_response = response.json() + if assignee or recipient: + assign_spec = asyncio.run( + _assign_task_spec(self, json_response["id"], assignee, recipient) + ) + self.request( + assign_spec.method, + assign_spec.endpoint, + json=assign_spec.json, + content=assign_spec.content, + ) + return Task.model_validate(json_response) + @resource_override( resource_type="app", resource_identifier="app_name", From 64207076d62599f9cf0809080561197d9c0a7acb Mon Sep 17 00:00:00 2001 From: chetanyauipath <107464888+chetanyauipath@users.noreply.github.com> Date: Tue, 26 May 2026 22:25:12 +0530 Subject: [PATCH 02/12] refactor(tasks): make schema required on create_quickform; always send inline Co-Authored-By: Claude Opus 4.7 --- .../platform/action_center/_tasks_service.py | 48 +++++++++---------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py b/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py index 9372065d0..5a83114d0 100644 --- a/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py +++ b/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py @@ -204,7 +204,7 @@ def _create_quickform_spec( data: Optional[Dict[str, Any]], title: str, task_schema_key: str, - schema: Optional[Dict[str, Any]] = None, + schema: Dict[str, Any], creator_job_key: Optional[str] = None, folder_key: Optional[str] = None, folder_path: Optional[str] = None, @@ -216,26 +216,26 @@ def _create_quickform_spec( ) -> RequestSpec: """Build the RequestSpec for Orchestrator's GenericTasks/CreateTask endpoint with TaskType=QuickFormTask. Mirrors _create_spec but skips the AppTask-specific shape - (no appId, no action-schema-derived fieldSet/actionSet) and adds taskSchemaKey + - optional inline schema fallback. + (no appId, no action-schema-derived fieldSet/actionSet) and instead sends + taskSchemaKey + inline schema together. + + Both taskSchemaKey AND schema are sent on every call: the Agents runtime has no + Action Center package.uploaded subscriber populating the TaskSchemas table, so + Orchestrator upserts the schema (keyed by taskSchemaKey) and then creates the task + in the same call. Wire contract: UiPath/Orchestrator/src/Core/Application/Dto/Tasks/TaskCreateRequest.cs. """ json_payload: Dict[str, Any] = { "type": _TASK_TYPE_QUICKFORM, "taskSchemaKey": task_schema_key, + "schema": schema, "title": title, "data": data if data is not None else {}, } - if schema is not None: - # Inline schema fallback for the publish-time race window: when AC's - # package.uploaded subscriber hasn't yet persisted the schema into TaskSchemas, - # Orchestrator creates the schema first using task_schema_key as the key, then - # creates the task. - json_payload["schema"] = schema - if creator_job_key is not None: - json_payload["creatorJobKey"] = creator_job_key + if creator_job_key is not None: + json_payload["creatorJobKey"] = creator_job_key if priority and (normalized_priority := _normalize_priority(priority)): json_payload["priority"] = normalized_priority @@ -598,6 +598,7 @@ async def create_quickform_async( self, title: str, task_schema_key: str, + schema: Dict[str, Any], data: Optional[Dict[str, Any]] = None, *, folder_path: Optional[str] = None, @@ -608,21 +609,23 @@ async def create_quickform_async( labels: Optional[List[str]] = None, is_actionable_message_enabled: Optional[bool] = None, actionable_message_metadata: Optional[Dict[str, Any]] = None, - schema: Optional[Dict[str, Any]] = None, creator_job_key: Optional[str] = None, source_name: str = "Agent", ) -> Task: """Creates a new QuickForm task asynchronously. QuickForm tasks are schema-first HITL tasks rendered by FormLib in Action - Center. The schema is resolved server-side via task_schema_key against the - TaskSchemas table (populated by AC's package.uploaded subscriber when the - agent package is published). + Center. Both task_schema_key AND schema are required: the Agents runtime + does not pre-populate TaskSchemas via a package.uploaded subscriber, so + Orchestrator upserts the schema (keyed by task_schema_key) and creates + the task in the same call. Args: title: The title of the task. - task_schema_key: UUID key of the .hitl.json schema this task should - use. Required. + task_schema_key: UUID key of the schema. Used as the key under which + Orchestrator stores/looks up the schema in TaskSchemas. + schema: The HITL schema body to register/upsert. Sent inline on every + call. data: Optional dictionary containing input data for the task. folder_path: Optional folder path for the task. Required by the Orchestrator controller (RequireOrganizationUnit) unless @@ -638,13 +641,8 @@ async def create_quickform_async( actionable_message_metadata: Optional metadata override. For QuickForm, when null, Orchestrator derives it from the referenced TaskSchema. - schema: Optional inline schema body. Used only during the - publish-time race window when the package.uploaded subscriber - hasn't yet persisted the schema into TaskSchemas. When - provided, Orchestrator creates the schema first using - task_schema_key as the key, then creates the task. - creator_job_key: Optional paired with schema; identifies the job - that triggered the inline schema creation. + creator_job_key: Optional. Identifies the job that triggered the + inline schema creation/upsert. source_name: Source name on TaskSource. Defaults to 'Agent'. Returns: @@ -690,6 +688,7 @@ def create_quickform( self, title: str, task_schema_key: str, + schema: Dict[str, Any], data: Optional[Dict[str, Any]] = None, *, folder_path: Optional[str] = None, @@ -700,7 +699,6 @@ def create_quickform( labels: Optional[List[str]] = None, is_actionable_message_enabled: Optional[bool] = None, actionable_message_metadata: Optional[Dict[str, Any]] = None, - schema: Optional[Dict[str, Any]] = None, creator_job_key: Optional[str] = None, source_name: str = "Agent", ) -> Task: From 4677f200ec38135c767bd9e4f046f212b2607503 Mon Sep 17 00:00:00 2001 From: chetanyauipath <107464888+chetanyauipath@users.noreply.github.com> Date: Tue, 26 May 2026 22:39:40 +0530 Subject: [PATCH 03/12] feat(agent): add QuickForm escalation type + schemaId/schema on channel - AgentEscalationChannel: new optional schema_id and schema fields, carried through agent.json for QuickForm escalations. - AgentQuickFormEscalationResourceConfig: new resource config for escalationType=2, mirroring the IXP pattern. - EscalationResourceConfig discriminated union extended with Tag(2). Co-Authored-By: Claude Opus 4.7 --- .../uipath/src/uipath/agent/models/agent.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/uipath/src/uipath/agent/models/agent.py b/packages/uipath/src/uipath/agent/models/agent.py index 7123a43e0..a639eb1f7 100644 --- a/packages/uipath/src/uipath/agent/models/agent.py +++ b/packages/uipath/src/uipath/agent/models/agent.py @@ -726,6 +726,12 @@ class AgentEscalationChannel(BaseCfg): ) priority: Optional[str] = None labels: List[str] = Field(default_factory=list) + # QuickForm fields — set only on channels backed by a .hitl.json schema. + # schema_id is the UUID key under which the schema is registered in Orchestrator's + # TaskSchemas table. schema is the inline schema body, sent on every task creation + # so Orchestrator can upsert it (the Agents runtime has no separate registration step). + schema_id: Optional[str] = Field(None, alias="schemaId") + schema: Optional[Dict[str, Any]] = Field(None, alias="schema") @model_validator(mode="before") @classmethod @@ -769,6 +775,23 @@ class AgentIxpVsEscalationResourceConfig(BaseAgentResourceConfig): ) +class AgentQuickFormEscalationResourceConfig(BaseAgentResourceConfig): + """Quick Form Agent escalation resource configuration model (escalationType=2). + + Quick Form escalations render a schema-first HITL task in Action Center via FormLib. + The schema (and its key) live on the channel (see AgentEscalationChannel.schema_id / + schema) and are sent inline to Orchestrator's GenericTasks/CreateTask endpoint. + """ + + id: Optional[str] = Field(None, alias="id") + resource_type: Literal[AgentResourceType.ESCALATION] = Field( + alias="$resourceType", default=AgentResourceType.ESCALATION, frozen=True + ) + channels: List[AgentEscalationChannel] = Field(alias="channels") + is_agent_memory_enabled: bool = Field(default=False, alias="isAgentMemoryEnabled") + escalation_type: Literal[2] = Field(default=2, alias="escalationType") + + class BaseAgentToolResourceConfig(BaseAgentResourceConfig): """Base agent tool resource configuration model.""" @@ -978,6 +1001,7 @@ class AgentUnknownToolResourceConfig(BaseAgentToolResourceConfig): Union[ Annotated[AgentEscalationResourceConfig, Tag(0)], Annotated[AgentIxpVsEscalationResourceConfig, Tag(1)], + Annotated[AgentQuickFormEscalationResourceConfig, Tag(2)], ], Discriminator(lambda v: v.get("escalation_type") or v.get("escalationType") or 0), ] From 7cd617770653211482cb2cb76e50dba1a747c719 Mon Sep 17 00:00:00 2001 From: chetanyauipath <107464888+chetanyauipath@users.noreply.github.com> Date: Tue, 2 Jun 2026 15:41:14 +0530 Subject: [PATCH 04/12] chore: bump uipath 2.10.74->2.10.75 and uipath-platform 0.1.59->0.1.60 Co-Authored-By: Claude Opus 4.7 --- packages/uipath-platform/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/uipath-platform/pyproject.toml b/packages/uipath-platform/pyproject.toml index 215882460..dfc759f4d 100644 --- a/packages/uipath-platform/pyproject.toml +++ b/packages/uipath-platform/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath-platform" -version = "0.1.59" +version = "0.1.60" description = "HTTP client library for programmatic access to UiPath Platform" readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" From c2ff2b0b4f183e43b445b71227d48d48f1ca3bda Mon Sep 17 00:00:00 2001 From: chetanyauipath <107464888+chetanyauipath@users.noreply.github.com> Date: Tue, 2 Jun 2026 15:41:22 +0530 Subject: [PATCH 05/12] fix: rename channel.schema to schema_body (alias preserved); fix D205 docstrings Co-Authored-By: Claude Opus 4.7 --- .../uipath/platform/action_center/_tasks_service.py | 12 +++++++----- packages/uipath/src/uipath/agent/models/agent.py | 6 ++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py b/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py index 5a83114d0..4dc5fd818 100644 --- a/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py +++ b/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py @@ -214,9 +214,10 @@ def _create_quickform_spec( actionable_message_metadata: Optional[Dict[str, Any]] = None, source_name: str = "Agent", ) -> RequestSpec: - """Build the RequestSpec for Orchestrator's GenericTasks/CreateTask endpoint with - TaskType=QuickFormTask. Mirrors _create_spec but skips the AppTask-specific shape - (no appId, no action-schema-derived fieldSet/actionSet) and instead sends + """Build the RequestSpec for Orchestrator's GenericTasks/CreateTask endpoint. + + Sets TaskType=QuickFormTask. Mirrors _create_spec but skips the AppTask-specific + shape (no appId, no action-schema-derived fieldSet/actionSet) and instead sends taskSchemaKey + inline schema together. Both taskSchemaKey AND schema are sent on every call: the Agents runtime has no @@ -702,8 +703,9 @@ def create_quickform( creator_job_key: Optional[str] = None, source_name: str = "Agent", ) -> Task: - """Creates a new QuickForm task synchronously. See - :meth:`create_quickform_async` for parameter docs. + """Create a new QuickForm task synchronously. + + See :meth:`create_quickform_async` for parameter docs. """ spec = _create_quickform_spec( title=title, diff --git a/packages/uipath/src/uipath/agent/models/agent.py b/packages/uipath/src/uipath/agent/models/agent.py index a639eb1f7..99eaaba49 100644 --- a/packages/uipath/src/uipath/agent/models/agent.py +++ b/packages/uipath/src/uipath/agent/models/agent.py @@ -728,10 +728,12 @@ class AgentEscalationChannel(BaseCfg): labels: List[str] = Field(default_factory=list) # QuickForm fields — set only on channels backed by a .hitl.json schema. # schema_id is the UUID key under which the schema is registered in Orchestrator's - # TaskSchemas table. schema is the inline schema body, sent on every task creation + # TaskSchemas table. schema_body is the inline schema body, sent on every task creation # so Orchestrator can upsert it (the Agents runtime has no separate registration step). + # The Python attribute is schema_body (not schema) to avoid shadowing + # pydantic.BaseModel.schema(); the JSON alias is "schema" to match the wire format. schema_id: Optional[str] = Field(None, alias="schemaId") - schema: Optional[Dict[str, Any]] = Field(None, alias="schema") + schema_body: Optional[Dict[str, Any]] = Field(None, alias="schema") @model_validator(mode="before") @classmethod From 7accd7be17eb1bdfe49d7e27390f828b1dc57417 Mon Sep 17 00:00:00 2001 From: chetanyauipath <107464888+chetanyauipath@users.noreply.github.com> Date: Tue, 2 Jun 2026 15:46:46 +0530 Subject: [PATCH 06/12] chore: bump uv.lock versions to match pyproject.toml Co-Authored-By: Claude Opus 4.7 --- packages/uipath-platform/uv.lock | 2 +- packages/uipath/uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/uipath-platform/uv.lock b/packages/uipath-platform/uv.lock index dabbd63ad..084f3efb8 100644 --- a/packages/uipath-platform/uv.lock +++ b/packages/uipath-platform/uv.lock @@ -1095,7 +1095,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.1.59" +version = "0.1.60" source = { editable = "." } dependencies = [ { name = "httpx" }, diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index b4399ef58..731a80919 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2691,7 +2691,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.1.59" +version = "0.1.60" source = { editable = "../uipath-platform" } dependencies = [ { name = "httpx" }, From 9bdfcdc114fc44f8cd757efde84a0924da2a1abe Mon Sep 17 00:00:00 2001 From: chetanyauipath <107464888+chetanyauipath@users.noreply.github.com> Date: Tue, 2 Jun 2026 15:53:38 +0530 Subject: [PATCH 07/12] chore: raise uipath-platform pin in uipath to >=0.1.60 Co-Authored-By: Claude Opus 4.7 --- packages/uipath/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/uipath/pyproject.toml b/packages/uipath/pyproject.toml index 72b6e5c56..a76e9d434 100644 --- a/packages/uipath/pyproject.toml +++ b/packages/uipath/pyproject.toml @@ -7,7 +7,7 @@ requires-python = ">=3.11" dependencies = [ "uipath-core>=0.5.17, <0.6.0", "uipath-runtime>=0.11.0, <0.12.0", - "uipath-platform>=0.1.59, <0.2.0", + "uipath-platform>=0.1.60, <0.2.0", "click>=8.3.1", "httpx>=0.28.1", "pyjwt>=2.10.1", From 222caaa2bfecc8af25d171de8b11c5d88174ffec Mon Sep 17 00:00:00 2001 From: chetanyauipath <107464888+chetanyauipath@users.noreply.github.com> Date: Tue, 2 Jun 2026 20:05:10 +0530 Subject: [PATCH 08/12] test(tasks): add coverage for create_quickform / create_quickform_async Co-Authored-By: Claude Opus 4.7 --- .../tests/services/test_actions_service.py | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) diff --git a/packages/uipath-platform/tests/services/test_actions_service.py b/packages/uipath-platform/tests/services/test_actions_service.py index b97d326e8..924bea24e 100644 --- a/packages/uipath-platform/tests/services/test_actions_service.py +++ b/packages/uipath-platform/tests/services/test_actions_service.py @@ -555,3 +555,190 @@ def test_create_raises_when_no_folder_key_or_path_provided( app_name="my-app", app_folder_path=None, ) + + +# --------------------------------------------------------------------------- +# QuickForm task tests +# --------------------------------------------------------------------------- + +_QF_SCHEMA: dict[str, Any] = { + "id": "7ebef452-fee9-45df-8fc2-01f1d0248540", + "fields": [ + {"id": "f1", "type": "text", "label": "F1", "direction": "input"}, + {"id": "f2", "type": "text", "label": "F2", "direction": "output"}, + ], + "outcomes": [ + {"id": "approve", "name": "Approve", "type": "string", "isPrimary": True}, + ], +} +_QF_KEY = _QF_SCHEMA["id"] +_QF_TITLE = "QF task" +_QF_CREATE_RESPONSE = {"id": 42, "title": _QF_TITLE} + + +def _qf_endpoint(base_url: str, org: str, tenant: str) -> str: + return f"{base_url}{org}{tenant}/orchestrator_/tasks/GenericTasks/CreateTask" + + +def _assign_endpoint(base_url: str, org: str, tenant: str) -> str: + return ( + f"{base_url}{org}{tenant}" + "/orchestrator_/odata/Tasks/UiPath.Server.Configuration.OData.AssignTasks" + ) + + +def _stub_create(httpx_mock: HTTPXMock, base_url: str, org: str, tenant: str) -> None: + httpx_mock.add_response( + url=_qf_endpoint(base_url, org, tenant), + status_code=200, + json=_QF_CREATE_RESPONSE, + ) + + +def _stub_assign(httpx_mock: HTTPXMock, base_url: str, org: str, tenant: str) -> None: + httpx_mock.add_response( + url=_assign_endpoint(base_url, org, tenant), + status_code=200, + json={}, + ) + + +def _posted_body(httpx_mock: HTTPXMock, url: str) -> dict[str, Any]: + import json as _json + + for req in httpx_mock.get_requests(): + if str(req.url) == url: + return _json.loads(req.content) + raise AssertionError(f"no request was POSTed to {url}") + + +@pytest.mark.parametrize( + "data,expected_data", + [(None, {}), ({"x": 1}, {"x": 1})], +) +def test_create_quickform_payload_shape( + httpx_mock: HTTPXMock, + service: TasksService, + base_url: str, + org: str, + tenant: str, + data: dict[str, Any] | None, + expected_data: dict[str, Any], +) -> None: + _stub_create(httpx_mock, base_url, org, tenant) + + task = service.create_quickform( + title=_QF_TITLE, + task_schema_key=_QF_KEY, + schema=_QF_SCHEMA, + data=data, + ) + + body = _posted_body(httpx_mock, _qf_endpoint(base_url, org, tenant)) + assert body["type"] == 6 + assert body["taskSchemaKey"] == _QF_KEY + assert body["schema"] == _QF_SCHEMA + assert body["title"] == _QF_TITLE + assert body["data"] == expected_data + assert isinstance(task, Task) + assert task.id == 42 + + +async def test_create_quickform_async_matches_sync( + httpx_mock: HTTPXMock, + service: TasksService, + base_url: str, + org: str, + tenant: str, +) -> None: + _stub_create(httpx_mock, base_url, org, tenant) + + task = await service.create_quickform_async( + title=_QF_TITLE, + task_schema_key=_QF_KEY, + schema=_QF_SCHEMA, + ) + + body = _posted_body(httpx_mock, _qf_endpoint(base_url, org, tenant)) + assert body["type"] == 6 + assert body["taskSchemaKey"] == _QF_KEY + assert task.id == 42 + + +def test_create_quickform_omits_optional_fields_when_unset( + httpx_mock: HTTPXMock, + service: TasksService, + base_url: str, + org: str, + tenant: str, +) -> None: + _stub_create(httpx_mock, base_url, org, tenant) + + service.create_quickform( + title=_QF_TITLE, + task_schema_key=_QF_KEY, + schema=_QF_SCHEMA, + ) + + body = _posted_body(httpx_mock, _qf_endpoint(base_url, org, tenant)) + for omitted in ( + "creatorJobKey", + "priority", + "tags", + "isActionableMessageEnabled", + "actionableMessageMetaData", + ): + assert omitted not in body + + +def test_create_quickform_includes_optional_fields_when_set( + httpx_mock: HTTPXMock, + service: TasksService, + base_url: str, + org: str, + tenant: str, +) -> None: + _stub_create(httpx_mock, base_url, org, tenant) + + service.create_quickform( + title=_QF_TITLE, + task_schema_key=_QF_KEY, + schema=_QF_SCHEMA, + priority="High", + labels=["a", "b"], + is_actionable_message_enabled=True, + actionable_message_metadata={"fieldSet": {}, "actionSet": {}}, + creator_job_key="3fa85f64-5717-4562-b3fc-2c963f66afa6", + ) + + body = _posted_body(httpx_mock, _qf_endpoint(base_url, org, tenant)) + assert body["priority"] == "High" + assert {tag["name"] for tag in body["tags"]} == {"a", "b"} + assert body["isActionableMessageEnabled"] is True + assert body["actionableMessageMetaData"] == {"fieldSet": {}, "actionSet": {}} + assert body["creatorJobKey"] == "3fa85f64-5717-4562-b3fc-2c963f66afa6" + + +def test_create_quickform_with_assignee_triggers_assign_call( + httpx_mock: HTTPXMock, + service: TasksService, + base_url: str, + org: str, + tenant: str, +) -> None: + _stub_create(httpx_mock, base_url, org, tenant) + _stub_assign(httpx_mock, base_url, org, tenant) + + service.create_quickform( + title=_QF_TITLE, + task_schema_key=_QF_KEY, + schema=_QF_SCHEMA, + assignee="user@example.com", + ) + + assign_body = _posted_body( + httpx_mock, _assign_endpoint(base_url, org, tenant) + ) + assignment = assign_body["taskAssignments"][0] + assert assignment["taskId"] == 42 + assert assignment["UserNameOrEmail"] == "user@example.com" From 6d07f113cad8733fa288ea6dec6d2880fa455427 Mon Sep 17 00:00:00 2001 From: chetanyauipath <107464888+chetanyauipath@users.noreply.github.com> Date: Tue, 2 Jun 2026 20:26:09 +0530 Subject: [PATCH 09/12] chore: bump uipath 2.10.75->2.10.76 (2.10.75 already on PyPI) Co-Authored-By: Claude Opus 4.7 --- packages/uipath/pyproject.toml | 2 +- packages/uipath/uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/uipath/pyproject.toml b/packages/uipath/pyproject.toml index a76e9d434..fae01ad9c 100644 --- a/packages/uipath/pyproject.toml +++ b/packages/uipath/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath" -version = "2.10.75" +version = "2.10.76" 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.11" diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index 731a80919..96764f7fb 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2552,7 +2552,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.10.75" +version = "2.10.76" source = { editable = "." } dependencies = [ { name = "applicationinsights" }, From 2399ce3ecfe984ce70e4e853f8182d00d17d87f8 Mon Sep 17 00:00:00 2001 From: chetanyauipath <107464888+chetanyauipath@users.noreply.github.com> Date: Tue, 2 Jun 2026 20:26:25 +0530 Subject: [PATCH 10/12] test(tasks): refactor QF tests to runner-fixture pattern; add async assignee coverage Co-Authored-By: Claude Opus 4.7 --- .../tests/services/test_actions_service.py | 209 +++++++----------- 1 file changed, 83 insertions(+), 126 deletions(-) diff --git a/packages/uipath-platform/tests/services/test_actions_service.py b/packages/uipath-platform/tests/services/test_actions_service.py index 924bea24e..54595489b 100644 --- a/packages/uipath-platform/tests/services/test_actions_service.py +++ b/packages/uipath-platform/tests/services/test_actions_service.py @@ -571,38 +571,27 @@ def test_create_raises_when_no_folder_key_or_path_provided( {"id": "approve", "name": "Approve", "type": "string", "isPrimary": True}, ], } -_QF_KEY = _QF_SCHEMA["id"] -_QF_TITLE = "QF task" -_QF_CREATE_RESPONSE = {"id": 42, "title": _QF_TITLE} +_QF_DEFAULTS = { + "title": "QF task", + "task_schema_key": _QF_SCHEMA["id"], + "schema": _QF_SCHEMA, +} +_QF_CREATE_RESPONSE = {"id": 42, "title": _QF_DEFAULTS["title"]} -def _qf_endpoint(base_url: str, org: str, tenant: str) -> str: +@pytest.fixture +def qf_create_url(base_url: str, org: str, tenant: str) -> str: return f"{base_url}{org}{tenant}/orchestrator_/tasks/GenericTasks/CreateTask" -def _assign_endpoint(base_url: str, org: str, tenant: str) -> str: +@pytest.fixture +def qf_assign_url(base_url: str, org: str, tenant: str) -> str: return ( f"{base_url}{org}{tenant}" "/orchestrator_/odata/Tasks/UiPath.Server.Configuration.OData.AssignTasks" ) -def _stub_create(httpx_mock: HTTPXMock, base_url: str, org: str, tenant: str) -> None: - httpx_mock.add_response( - url=_qf_endpoint(base_url, org, tenant), - status_code=200, - json=_QF_CREATE_RESPONSE, - ) - - -def _stub_assign(httpx_mock: HTTPXMock, base_url: str, org: str, tenant: str) -> None: - httpx_mock.add_response( - url=_assign_endpoint(base_url, org, tenant), - status_code=200, - json={}, - ) - - def _posted_body(httpx_mock: HTTPXMock, url: str) -> dict[str, Any]: import json as _json @@ -612,106 +601,62 @@ def _posted_body(httpx_mock: HTTPXMock, url: str) -> dict[str, Any]: raise AssertionError(f"no request was POSTed to {url}") -@pytest.mark.parametrize( - "data,expected_data", - [(None, {}), ({"x": 1}, {"x": 1})], -) -def test_create_quickform_payload_shape( - httpx_mock: HTTPXMock, - service: TasksService, - base_url: str, - org: str, - tenant: str, - data: dict[str, Any] | None, - expected_data: dict[str, Any], -) -> None: - _stub_create(httpx_mock, base_url, org, tenant) - - task = service.create_quickform( - title=_QF_TITLE, - task_schema_key=_QF_KEY, - schema=_QF_SCHEMA, - data=data, - ) - - body = _posted_body(httpx_mock, _qf_endpoint(base_url, org, tenant)) - assert body["type"] == 6 - assert body["taskSchemaKey"] == _QF_KEY - assert body["schema"] == _QF_SCHEMA - assert body["title"] == _QF_TITLE - assert body["data"] == expected_data - assert isinstance(task, Task) - assert task.id == 42 +@pytest.fixture +def qf_runner( + httpx_mock: HTTPXMock, service: TasksService, qf_create_url: str +) -> Any: + """Factory: stub the QF endpoint, call create_quickform with overrides, + return (task, posted_body). One call per test eliminates setup duplication. + """ + httpx_mock.add_response(url=qf_create_url, status_code=200, json=_QF_CREATE_RESPONSE) + def _run(**overrides: Any) -> tuple[Task, dict[str, Any]]: + task = service.create_quickform(**{**_QF_DEFAULTS, **overrides}) + return task, _posted_body(httpx_mock, qf_create_url) -async def test_create_quickform_async_matches_sync( - httpx_mock: HTTPXMock, - service: TasksService, - base_url: str, - org: str, - tenant: str, -) -> None: - _stub_create(httpx_mock, base_url, org, tenant) + return _run - task = await service.create_quickform_async( - title=_QF_TITLE, - task_schema_key=_QF_KEY, - schema=_QF_SCHEMA, - ) - body = _posted_body(httpx_mock, _qf_endpoint(base_url, org, tenant)) - assert body["type"] == 6 - assert body["taskSchemaKey"] == _QF_KEY +@pytest.fixture +def qf_runner_async( + httpx_mock: HTTPXMock, service: TasksService, qf_create_url: str +) -> Any: + """Async variant of qf_runner.""" + httpx_mock.add_response(url=qf_create_url, status_code=200, json=_QF_CREATE_RESPONSE) + + async def _run(**overrides: Any) -> tuple[Task, dict[str, Any]]: + task = await service.create_quickform_async(**{**_QF_DEFAULTS, **overrides}) + return task, _posted_body(httpx_mock, qf_create_url) + + return _run + + +def test_create_quickform_baseline_payload(qf_runner: Any) -> None: + task, body = qf_runner() + assert body == { + "type": 6, + "taskSchemaKey": _QF_DEFAULTS["task_schema_key"], + "schema": _QF_SCHEMA, + "title": _QF_DEFAULTS["title"], + "data": {}, + } + assert isinstance(task, Task) assert task.id == 42 -def test_create_quickform_omits_optional_fields_when_unset( - httpx_mock: HTTPXMock, - service: TasksService, - base_url: str, - org: str, - tenant: str, -) -> None: - _stub_create(httpx_mock, base_url, org, tenant) - - service.create_quickform( - title=_QF_TITLE, - task_schema_key=_QF_KEY, - schema=_QF_SCHEMA, - ) - - body = _posted_body(httpx_mock, _qf_endpoint(base_url, org, tenant)) - for omitted in ( - "creatorJobKey", - "priority", - "tags", - "isActionableMessageEnabled", - "actionableMessageMetaData", - ): - assert omitted not in body - +def test_create_quickform_data_passthrough(qf_runner: Any) -> None: + _, body = qf_runner(data={"x": 1}) + assert body["data"] == {"x": 1} -def test_create_quickform_includes_optional_fields_when_set( - httpx_mock: HTTPXMock, - service: TasksService, - base_url: str, - org: str, - tenant: str, -) -> None: - _stub_create(httpx_mock, base_url, org, tenant) - service.create_quickform( - title=_QF_TITLE, - task_schema_key=_QF_KEY, - schema=_QF_SCHEMA, +def test_create_quickform_includes_optional_fields_when_set(qf_runner: Any) -> None: + _, body = qf_runner( priority="High", labels=["a", "b"], is_actionable_message_enabled=True, actionable_message_metadata={"fieldSet": {}, "actionSet": {}}, creator_job_key="3fa85f64-5717-4562-b3fc-2c963f66afa6", ) - - body = _posted_body(httpx_mock, _qf_endpoint(base_url, org, tenant)) assert body["priority"] == "High" assert {tag["name"] for tag in body["tags"]} == {"a", "b"} assert body["isActionableMessageEnabled"] is True @@ -719,26 +664,38 @@ def test_create_quickform_includes_optional_fields_when_set( assert body["creatorJobKey"] == "3fa85f64-5717-4562-b3fc-2c963f66afa6" +def test_create_quickform_omits_optional_fields_when_unset(qf_runner: Any) -> None: + _, body = qf_runner() + for omitted in ( + "creatorJobKey", + "priority", + "tags", + "isActionableMessageEnabled", + "actionableMessageMetaData", + ): + assert omitted not in body + + +async def test_create_quickform_async_baseline_payload(qf_runner_async: Any) -> None: + task, body = await qf_runner_async() + assert body["type"] == 6 + assert body["taskSchemaKey"] == _QF_DEFAULTS["task_schema_key"] + assert task.id == 42 + + def test_create_quickform_with_assignee_triggers_assign_call( - httpx_mock: HTTPXMock, - service: TasksService, - base_url: str, - org: str, - tenant: str, + httpx_mock: HTTPXMock, qf_runner: Any, qf_assign_url: str ) -> None: - _stub_create(httpx_mock, base_url, org, tenant) - _stub_assign(httpx_mock, base_url, org, tenant) - - service.create_quickform( - title=_QF_TITLE, - task_schema_key=_QF_KEY, - schema=_QF_SCHEMA, - assignee="user@example.com", - ) + httpx_mock.add_response(url=qf_assign_url, status_code=200, json={}) + qf_runner(assignee="user@example.com") + body = _posted_body(httpx_mock, qf_assign_url) + assert body["taskAssignments"][0]["UserNameOrEmail"] == "user@example.com" - assign_body = _posted_body( - httpx_mock, _assign_endpoint(base_url, org, tenant) - ) - assignment = assign_body["taskAssignments"][0] - assert assignment["taskId"] == 42 - assert assignment["UserNameOrEmail"] == "user@example.com" + +async def test_create_quickform_async_with_assignee_triggers_assign_call( + httpx_mock: HTTPXMock, qf_runner_async: Any, qf_assign_url: str +) -> None: + httpx_mock.add_response(url=qf_assign_url, status_code=200, json={}) + await qf_runner_async(assignee="user@example.com") + body = _posted_body(httpx_mock, qf_assign_url) + assert body["taskAssignments"][0]["UserNameOrEmail"] == "user@example.com" From efb88183b12a1c3bedbbe6705386117c07b27331 Mon Sep 17 00:00:00 2001 From: chetanyauipath <107464888+chetanyauipath@users.noreply.github.com> Date: Tue, 2 Jun 2026 20:44:34 +0530 Subject: [PATCH 11/12] refactor(tasks): extract shared helpers for priority/tags/taskSource to drop duplication Co-Authored-By: Claude Opus 4.7 --- .../platform/action_center/_tasks_service.py | 113 ++++++++---------- .../tests/services/test_actions_service.py | 12 +- 2 files changed, 56 insertions(+), 69 deletions(-) diff --git a/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py b/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py index 4dc5fd818..2ead5edf3 100644 --- a/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py +++ b/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py @@ -118,10 +118,34 @@ def _create_spec( ), } + _apply_priority_labels_and_actionable_toggle( + json_payload, priority, labels, is_actionable_message_enabled + ) + _apply_task_source(json_payload, source_name) + + return RequestSpec( + method="POST", + endpoint=Endpoint("/orchestrator_/tasks/AppTasks/CreateAppTask"), + json=json_payload, + headers=header_folder(app_folder_key, app_folder_path), + ) + + +def _apply_priority_labels_and_actionable_toggle( + payload: Dict[str, Any], + priority: Optional[str], + labels: Optional[List[str]], + is_actionable_message_enabled: Optional[bool], +) -> None: + """Apply priority / tags / isActionableMessageEnabled to ``payload`` in-place. + + Shared between AppTask and QuickForm spec builders — they handle these three + optional fields identically. + """ if priority and (normalized_priority := _normalize_priority(priority)): - json_payload["priority"] = normalized_priority + payload["priority"] = normalized_priority if labels is not None: - json_payload["tags"] = [ + payload["tags"] = [ { "name": label, "displayName": label, @@ -131,37 +155,29 @@ def _create_spec( for label in labels ] if is_actionable_message_enabled is not None: - json_payload["isActionableMessageEnabled"] = is_actionable_message_enabled + payload["isActionableMessageEnabled"] = is_actionable_message_enabled - project_id = UiPathConfig.project_id - trace_id = UiPathConfig.trace_id - if project_id and trace_id: - folder_key = UiPathConfig.folder_key - job_key = UiPathConfig.job_key - process_key = UiPathConfig.process_uuid +def _apply_task_source(payload: Dict[str, Any], source_name: str) -> None: + """Populate ``payload["taskSource"]`` when UiPathConfig has project_id + trace_id. - task_source_metadata: Dict[str, Any] = { + Shared between AppTask and QuickForm spec builders — the taskSource block is + identical for both task types. + """ + project_id = UiPathConfig.project_id + trace_id = UiPathConfig.trace_id + if not (project_id and trace_id): + return + payload["taskSource"] = { + "sourceName": source_name, + "sourceId": project_id, + "taskSourceMetadata": { "InstanceId": trace_id, - "FolderKey": folder_key, - "JobKey": job_key, - "ProcessKey": process_key, - } - - task_source = { - "sourceName": source_name, - "sourceId": project_id, - "taskSourceMetadata": task_source_metadata, - } - - json_payload["taskSource"] = task_source - - return RequestSpec( - method="POST", - endpoint=Endpoint("/orchestrator_/tasks/AppTasks/CreateAppTask"), - json=json_payload, - headers=header_folder(app_folder_key, app_folder_path), - ) + "FolderKey": UiPathConfig.folder_key, + "JobKey": UiPathConfig.job_key, + "ProcessKey": UiPathConfig.process_uuid, + }, + } def _normalize_priority(priority: str | None) -> str | None: @@ -238,43 +254,12 @@ def _create_quickform_spec( if creator_job_key is not None: json_payload["creatorJobKey"] = creator_job_key - if priority and (normalized_priority := _normalize_priority(priority)): - json_payload["priority"] = normalized_priority - if labels is not None: - json_payload["tags"] = [ - { - "name": label, - "displayName": label, - "value": label, - "displayValue": label, - } - for label in labels - ] - if is_actionable_message_enabled is not None: - json_payload["isActionableMessageEnabled"] = is_actionable_message_enabled + _apply_priority_labels_and_actionable_toggle( + json_payload, priority, labels, is_actionable_message_enabled + ) if actionable_message_metadata is not None: json_payload["actionableMessageMetaData"] = actionable_message_metadata - - project_id = UiPathConfig.project_id - trace_id = UiPathConfig.trace_id - - if project_id and trace_id: - cfg_folder_key = UiPathConfig.folder_key - job_key = UiPathConfig.job_key - process_key = UiPathConfig.process_uuid - - task_source_metadata: Dict[str, Any] = { - "InstanceId": trace_id, - "FolderKey": cfg_folder_key, - "JobKey": job_key, - "ProcessKey": process_key, - } - - json_payload["taskSource"] = { - "sourceName": source_name, - "sourceId": project_id, - "taskSourceMetadata": task_source_metadata, - } + _apply_task_source(json_payload, source_name) return RequestSpec( method="POST", diff --git a/packages/uipath-platform/tests/services/test_actions_service.py b/packages/uipath-platform/tests/services/test_actions_service.py index 54595489b..dd9a27340 100644 --- a/packages/uipath-platform/tests/services/test_actions_service.py +++ b/packages/uipath-platform/tests/services/test_actions_service.py @@ -602,13 +602,13 @@ def _posted_body(httpx_mock: HTTPXMock, url: str) -> dict[str, Any]: @pytest.fixture -def qf_runner( - httpx_mock: HTTPXMock, service: TasksService, qf_create_url: str -) -> Any: +def qf_runner(httpx_mock: HTTPXMock, service: TasksService, qf_create_url: str) -> Any: """Factory: stub the QF endpoint, call create_quickform with overrides, return (task, posted_body). One call per test eliminates setup duplication. """ - httpx_mock.add_response(url=qf_create_url, status_code=200, json=_QF_CREATE_RESPONSE) + httpx_mock.add_response( + url=qf_create_url, status_code=200, json=_QF_CREATE_RESPONSE + ) def _run(**overrides: Any) -> tuple[Task, dict[str, Any]]: task = service.create_quickform(**{**_QF_DEFAULTS, **overrides}) @@ -622,7 +622,9 @@ def qf_runner_async( httpx_mock: HTTPXMock, service: TasksService, qf_create_url: str ) -> Any: """Async variant of qf_runner.""" - httpx_mock.add_response(url=qf_create_url, status_code=200, json=_QF_CREATE_RESPONSE) + httpx_mock.add_response( + url=qf_create_url, status_code=200, json=_QF_CREATE_RESPONSE + ) async def _run(**overrides: Any) -> tuple[Task, dict[str, Any]]: task = await service.create_quickform_async(**{**_QF_DEFAULTS, **overrides}) From d6a36eb76b5e34bd6f7e15f98a0079c14648acc9 Mon Sep 17 00:00:00 2001 From: chetanyauipath <107464888+chetanyauipath@users.noreply.github.com> Date: Tue, 2 Jun 2026 21:02:01 +0530 Subject: [PATCH 12/12] refactor(tasks): make sync create_quickform a thin asyncio.run wrapper Co-Authored-By: Claude Opus 4.7 --- .../platform/action_center/_tasks_service.py | 59 +++++-------------- 1 file changed, 14 insertions(+), 45 deletions(-) diff --git a/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py b/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py index 2ead5edf3..4bf94d63e 100644 --- a/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py +++ b/packages/uipath-platform/src/uipath/platform/action_center/_tasks_service.py @@ -676,56 +676,25 @@ def create_quickform( task_schema_key: str, schema: Dict[str, Any], data: Optional[Dict[str, Any]] = None, - *, - folder_path: Optional[str] = None, - folder_key: Optional[str] = None, - assignee: Optional[str] = None, - recipient: Optional[TaskRecipient] = None, - priority: Optional[str] = None, - labels: Optional[List[str]] = None, - is_actionable_message_enabled: Optional[bool] = None, - actionable_message_metadata: Optional[Dict[str, Any]] = None, - creator_job_key: Optional[str] = None, - source_name: str = "Agent", + **kwargs: Any, ) -> Task: """Create a new QuickForm task synchronously. - See :meth:`create_quickform_async` for parameter docs. + Thin ``asyncio.run`` wrapper over :meth:`create_quickform_async`. All + keyword arguments (``folder_path``, ``folder_key``, ``assignee``, + ``recipient``, ``priority``, ``labels``, ``is_actionable_message_enabled``, + ``actionable_message_metadata``, ``creator_job_key``, ``source_name``) + are forwarded as-is. See that method for parameter docs. """ - spec = _create_quickform_spec( - title=title, - data=data, - task_schema_key=task_schema_key, - schema=schema, - creator_job_key=creator_job_key, - folder_key=folder_key, - folder_path=folder_path, - priority=priority, - labels=labels, - is_actionable_message_enabled=is_actionable_message_enabled, - actionable_message_metadata=actionable_message_metadata, - source_name=source_name, - ) - - response = self.request( - spec.method, - spec.endpoint, - json=spec.json, - content=spec.content, - headers=spec.headers, - ) - json_response = response.json() - if assignee or recipient: - assign_spec = asyncio.run( - _assign_task_spec(self, json_response["id"], assignee, recipient) + return asyncio.run( + self.create_quickform_async( + title=title, + task_schema_key=task_schema_key, + schema=schema, + data=data, + **kwargs, ) - self.request( - assign_spec.method, - assign_spec.endpoint, - json=assign_spec.json, - content=assign_spec.content, - ) - return Task.model_validate(json_response) + ) @resource_override( resource_type="app",