From 5d01927070e2cd3c4920b8a4c984c3d151abf12e Mon Sep 17 00:00:00 2001 From: Priyadarshini Piramanayagam Date: Wed, 11 Feb 2026 18:04:24 +0530 Subject: [PATCH 01/12] add wrapper for work-item-execute api --- docs/api_reference/work_item.rst | 1 + docs/getting_started.rst | 4 +-- examples/work_item/work_items.py | 20 +++++++++++++ .../clients/work_item/_work_item_client.py | 21 +++++++++++++- .../clients/work_item/models/__init__.py | 3 ++ .../models/_execute_work_item_request.py | 8 +++++ .../models/_execute_work_item_response.py | 29 +++++++++++++++++++ .../work_item/test_work_item_client.py | 24 ++++++++++++++- 8 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 nisystemlink/clients/work_item/models/_execute_work_item_request.py create mode 100644 nisystemlink/clients/work_item/models/_execute_work_item_response.py diff --git a/docs/api_reference/work_item.rst b/docs/api_reference/work_item.rst index c94ecc81..c924f96c 100644 --- a/docs/api_reference/work_item.rst +++ b/docs/api_reference/work_item.rst @@ -13,6 +13,7 @@ nisystemlink.clients.work_item .. automethod:: update_work_items .. automethod:: schedule_work_items .. automethod:: delete_work_items + .. automethod:: execute_work_item .. automethod:: create_work_item_templates .. automethod:: query_work_item_templates .. automethod:: update_work_item_templates diff --git a/docs/getting_started.rst b/docs/getting_started.rst index c6962edc..d44fa861 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -456,13 +456,13 @@ default connection. The default connection depends on your environment. With a :class:`.WorkItemClient` object, you can: -* Create, query, get, update, schedule and delete work items +* Create, query, get, update, schedule, delete and execute work items * Create, query, update and delete work item templates Examples ~~~~~~~~ -Create, query, get, update, schedule and delete work items +Create, query, get, update, schedule, delete and execute work items .. literalinclude:: ../examples/work_item/work_items.py :language: python diff --git a/examples/work_item/work_items.py b/examples/work_item/work_items.py index 699b1984..e0fce22d 100644 --- a/examples/work_item/work_items.py +++ b/examples/work_item/work_items.py @@ -5,6 +5,7 @@ from nisystemlink.clients.work_item.models import ( CreateWorkItemRequest, Dashboard, + ExecuteWorkItemRequest, Job, JobExecution, ManualExecution, @@ -105,6 +106,7 @@ dashboard=Dashboard( id="DashboardId", variables={"product": "PXIe-4080", "location": "Lab1"} ), + workflow_id="312064", execution_actions=[ ManualExecution(action="boot", type="MANUAL"), JobExecution( @@ -202,6 +204,24 @@ f"Scheduled work item with ID: {schedule_work_items_response.scheduled_work_items[0].id}" ) +# Execute work item action +if created_work_item_id is not None: + execute_request = ExecuteWorkItemRequest(action="START") + try: + execute_response = client.execute_work_item( + work_item_id=created_work_item_id, execute_request=execute_request + ) + if execute_response.result is not None: + print( + f"Executed action successfully. Type: {execute_response.result.type}" + ) + if execute_response.result.execution_id: + print(f"Execution ID: {execute_response.result.execution_id}") + elif execute_response.error is not None: + print(f"Execution returned an error: {execute_response.error.message}") + except Exception as e: + print(f"Could not execute action: {e}") + # Delete work item if created_work_item_id is not None: client.delete_work_items(ids=[created_work_item_id]) diff --git a/nisystemlink/clients/work_item/_work_item_client.py b/nisystemlink/clients/work_item/_work_item_client.py index c700815e..2b499780 100644 --- a/nisystemlink/clients/work_item/_work_item_client.py +++ b/nisystemlink/clients/work_item/_work_item_client.py @@ -1,11 +1,12 @@ from typing import List +from uplink import Body, Field, Path, retry + from nisystemlink.clients import core from nisystemlink.clients.core._http_configuration import HttpConfiguration from nisystemlink.clients.core._uplink._base_client import BaseClient from nisystemlink.clients.core._uplink._methods import get, post from nisystemlink.clients.work_item import models -from uplink import Field, retry @retry( @@ -131,6 +132,24 @@ def delete_work_items( ApiException: if unable to communicate with the `/niworkitem` service or provided invalid arguments. """ ... + + @post("workitems/{workItemId}/execute", args=[Path(name="workItemId"), Body]) + def execute_work_item( + self, work_item_id: str, execute_request: models.ExecuteWorkItemRequest + ) -> models.ExecuteWorkItemResponse: + """Executes the specified action for the work item. + + Args: + work_item_id: The ID of the work item the action will be performed on. + execute_request: The parameters for executing a work item action. + + Returns: + The response containing the execution result or error information. + + Raises: + ApiException: if unable to communicate with the `/niworkitem` service or provided invalid arguments. + """ + ... @post("workitem-templates", args=[Field("workItemTemplates")]) def create_work_item_templates( diff --git a/nisystemlink/clients/work_item/models/__init__.py b/nisystemlink/clients/work_item/models/__init__.py index df6106e3..1ca9dfb1 100644 --- a/nisystemlink/clients/work_item/models/__init__.py +++ b/nisystemlink/clients/work_item/models/__init__.py @@ -58,6 +58,9 @@ ) from ._update_work_items_request import UpdateWorkItemsRequest +from ._execute_work_item_request import ExecuteWorkItemRequest +from ._execute_work_item_response import ExecuteWorkItemResponse + from ._create_work_item_template_request import CreateWorkItemTemplateRequest from ._create_work_item_templates_partial_success_response import ( CreateWorkItemTemplatesPartialSuccessResponse, diff --git a/nisystemlink/clients/work_item/models/_execute_work_item_request.py b/nisystemlink/clients/work_item/models/_execute_work_item_request.py new file mode 100644 index 00000000..f7269c27 --- /dev/null +++ b/nisystemlink/clients/work_item/models/_execute_work_item_request.py @@ -0,0 +1,8 @@ +from nisystemlink.clients.core._uplink._json_model import JsonModel + + +class ExecuteWorkItemRequest(JsonModel): + """Request for executing a work item action.""" + + action: str + """The action to execute on the work item.""" diff --git a/nisystemlink/clients/work_item/models/_execute_work_item_response.py b/nisystemlink/clients/work_item/models/_execute_work_item_response.py new file mode 100644 index 00000000..1b257cab --- /dev/null +++ b/nisystemlink/clients/work_item/models/_execute_work_item_response.py @@ -0,0 +1,29 @@ +from typing import Literal + +from pydantic import Field + +from nisystemlink.clients.core._api_error import ApiError +from nisystemlink.clients.core._uplink._json_model import JsonModel + + +class ExecuteWorkItemResult(JsonModel): + """Represents the result of a work item execution action.""" + + type: Literal["MANUAL", "JOB", "NOTEBOOK", "NONE"] + """Type of execution that was triggered (MANUAL, JOB, NOTEBOOK, or NONE).""" + + execution_id: str | None = None + """The execution ID if applicable.""" + + error: ApiError | None = None + """Error information if the execution encountered an error.""" + + +class ExecuteWorkItemResponse(JsonModel): + """Response for executing a work item action.""" + + error: ApiError | None = None + """Error information if the action failed.""" + + result: ExecuteWorkItemResult | None = None + """Result of the action execution.""" diff --git a/tests/integration/work_item/test_work_item_client.py b/tests/integration/work_item/test_work_item_client.py index 5b4729ef..cdfb7f8f 100644 --- a/tests/integration/work_item/test_work_item_client.py +++ b/tests/integration/work_item/test_work_item_client.py @@ -3,6 +3,7 @@ from typing import List import pytest + from nisystemlink.clients.core._http_configuration import HttpConfiguration from nisystemlink.clients.work_item import WorkItemClient from nisystemlink.clients.work_item.models import ( @@ -11,6 +12,7 @@ CreateWorkItemTemplateRequest, CreateWorkItemTemplatesPartialSuccessResponse, Dashboard, + ExecuteWorkItemRequest, ExecutionDefinition, Job, JobExecution, @@ -136,7 +138,7 @@ class TestWorkItemClient: CreateWorkItemRequest( name="Python integration work item", type="testplan", - state="NEW", + state="SCHEDULED", description="Work item for verifying integration flow", assigned_to="test.user@example.com", requested_by="test.manager@example.com", @@ -196,6 +198,7 @@ class TestWorkItemClient: properties={"env": "staging", "priority": "high"}, dashboard=_dashboard, execution_actions=_execution_actions, + workflow_id="312064", ) ] """create work item request object.""" @@ -420,6 +423,25 @@ def test__delete_work_item(self, client: WorkItemClient, create_work_items): ) assert len(query_deleted_work_item_response.work_items) == 0 + def test__execute_work_item__returns_execution_result( + self, client: WorkItemClient, create_work_items + ): + create_work_item_response = create_work_items(self._create_work_item_request) + assert create_work_item_response.created_work_items is not None + created_work_item = create_work_item_response.created_work_items[0] + + execute_request = ExecuteWorkItemRequest(action="START") + execute_response = client.execute_work_item( + work_item_id=created_work_item.id, execute_request=execute_request + ) + + assert execute_response is not None + assert execute_response.result is not None or execute_response.error is not None + if execute_response.result is not None: + assert execute_response.result.type == "MANUAL" + if execute_response.error is not None: + assert execute_response.error.name is not None + def test__create_work_item_template__returns_created_work_item_template( self, create_work_item_templates ): From c7a8e1d150e42a8e0ccdf9dc0f6a8be1429aa061 Mon Sep 17 00:00:00 2001 From: Priyadarshini Piramanayagam Date: Wed, 11 Feb 2026 18:12:49 +0530 Subject: [PATCH 02/12] lint --- examples/work_item/work_items.py | 4 +--- nisystemlink/clients/work_item/_work_item_client.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/work_item/work_items.py b/examples/work_item/work_items.py index e0fce22d..1c10842f 100644 --- a/examples/work_item/work_items.py +++ b/examples/work_item/work_items.py @@ -212,9 +212,7 @@ work_item_id=created_work_item_id, execute_request=execute_request ) if execute_response.result is not None: - print( - f"Executed action successfully. Type: {execute_response.result.type}" - ) + print(f"Executed action successfully. Type: {execute_response.result.type}") if execute_response.result.execution_id: print(f"Execution ID: {execute_response.result.execution_id}") elif execute_response.error is not None: diff --git a/nisystemlink/clients/work_item/_work_item_client.py b/nisystemlink/clients/work_item/_work_item_client.py index 2b499780..0e015aae 100644 --- a/nisystemlink/clients/work_item/_work_item_client.py +++ b/nisystemlink/clients/work_item/_work_item_client.py @@ -132,7 +132,7 @@ def delete_work_items( ApiException: if unable to communicate with the `/niworkitem` service or provided invalid arguments. """ ... - + @post("workitems/{workItemId}/execute", args=[Path(name="workItemId"), Body]) def execute_work_item( self, work_item_id: str, execute_request: models.ExecuteWorkItemRequest From 8b69fd823f268ba7b2ce3b91ca0186f18298a0c0 Mon Sep 17 00:00:00 2001 From: Priyadarshini Piramanayagam Date: Wed, 11 Feb 2026 18:23:47 +0530 Subject: [PATCH 03/12] import fixes --- nisystemlink/clients/work_item/_work_item_client.py | 5 +++-- .../clients/work_item/models/_execute_work_item_response.py | 2 -- tests/integration/work_item/test_work_item_client.py | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/nisystemlink/clients/work_item/_work_item_client.py b/nisystemlink/clients/work_item/_work_item_client.py index 0e015aae..97340f29 100644 --- a/nisystemlink/clients/work_item/_work_item_client.py +++ b/nisystemlink/clients/work_item/_work_item_client.py @@ -1,13 +1,13 @@ from typing import List -from uplink import Body, Field, Path, retry - from nisystemlink.clients import core from nisystemlink.clients.core._http_configuration import HttpConfiguration from nisystemlink.clients.core._uplink._base_client import BaseClient from nisystemlink.clients.core._uplink._methods import get, post from nisystemlink.clients.work_item import models +from uplink import Body, Field, Path, retry + @retry( when=retry.when.status(408, 429, 502, 503, 504), @@ -219,3 +219,4 @@ def delete_work_item_templates( ApiException: if unable to communicate with the `/niworkitem` service or provided invalid arguments. """ ... + ... diff --git a/nisystemlink/clients/work_item/models/_execute_work_item_response.py b/nisystemlink/clients/work_item/models/_execute_work_item_response.py index 1b257cab..d62f4b34 100644 --- a/nisystemlink/clients/work_item/models/_execute_work_item_response.py +++ b/nisystemlink/clients/work_item/models/_execute_work_item_response.py @@ -1,7 +1,5 @@ from typing import Literal -from pydantic import Field - from nisystemlink.clients.core._api_error import ApiError from nisystemlink.clients.core._uplink._json_model import JsonModel diff --git a/tests/integration/work_item/test_work_item_client.py b/tests/integration/work_item/test_work_item_client.py index cdfb7f8f..be123544 100644 --- a/tests/integration/work_item/test_work_item_client.py +++ b/tests/integration/work_item/test_work_item_client.py @@ -3,7 +3,6 @@ from typing import List import pytest - from nisystemlink.clients.core._http_configuration import HttpConfiguration from nisystemlink.clients.work_item import WorkItemClient from nisystemlink.clients.work_item.models import ( @@ -584,3 +583,4 @@ def test__delete_work_item_template( ) ) assert len(query_deleted_work_item_template_response.work_item_templates) == 0 + assert len(query_deleted_work_item_template_response.work_item_templates) == 0 From a776a0f4ee0ac0480151a4c2f0a82145ea6efccd Mon Sep 17 00:00:00 2001 From: Priyadarshini Piramanayagam Date: Wed, 11 Feb 2026 22:18:09 +0530 Subject: [PATCH 04/12] lint fix --- nisystemlink/clients/work_item/_work_item_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nisystemlink/clients/work_item/_work_item_client.py b/nisystemlink/clients/work_item/_work_item_client.py index 97340f29..31519e6b 100644 --- a/nisystemlink/clients/work_item/_work_item_client.py +++ b/nisystemlink/clients/work_item/_work_item_client.py @@ -5,7 +5,6 @@ from nisystemlink.clients.core._uplink._base_client import BaseClient from nisystemlink.clients.core._uplink._methods import get, post from nisystemlink.clients.work_item import models - from uplink import Body, Field, Path, retry @@ -220,3 +219,4 @@ def delete_work_item_templates( """ ... ... + ... From 4a95f265d1fbb346eeb121c04a7068469a360ebc Mon Sep 17 00:00:00 2001 From: Priyadarshini Piramanayagam Date: Sun, 15 Feb 2026 20:30:59 +0530 Subject: [PATCH 05/12] add responses mock for execute api --- examples/work_item/work_items.py | 2 +- .../clients/work_item/_work_item_client.py | 5 +-- .../work_item/test_work_item_client.py | 38 ++++++++++++------- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/examples/work_item/work_items.py b/examples/work_item/work_items.py index 1c10842f..9681f2c6 100644 --- a/examples/work_item/work_items.py +++ b/examples/work_item/work_items.py @@ -106,7 +106,7 @@ dashboard=Dashboard( id="DashboardId", variables={"product": "PXIe-4080", "location": "Lab1"} ), - workflow_id="312064", + workflow_id="example-workflow-id", execution_actions=[ ManualExecution(action="boot", type="MANUAL"), JobExecution( diff --git a/nisystemlink/clients/work_item/_work_item_client.py b/nisystemlink/clients/work_item/_work_item_client.py index 31519e6b..0e015aae 100644 --- a/nisystemlink/clients/work_item/_work_item_client.py +++ b/nisystemlink/clients/work_item/_work_item_client.py @@ -1,11 +1,12 @@ from typing import List +from uplink import Body, Field, Path, retry + from nisystemlink.clients import core from nisystemlink.clients.core._http_configuration import HttpConfiguration from nisystemlink.clients.core._uplink._base_client import BaseClient from nisystemlink.clients.core._uplink._methods import get, post from nisystemlink.clients.work_item import models -from uplink import Body, Field, Path, retry @retry( @@ -218,5 +219,3 @@ def delete_work_item_templates( ApiException: if unable to communicate with the `/niworkitem` service or provided invalid arguments. """ ... - ... - ... diff --git a/tests/integration/work_item/test_work_item_client.py b/tests/integration/work_item/test_work_item_client.py index be123544..35c19684 100644 --- a/tests/integration/work_item/test_work_item_client.py +++ b/tests/integration/work_item/test_work_item_client.py @@ -3,6 +3,7 @@ from typing import List import pytest +import responses from nisystemlink.clients.core._http_configuration import HttpConfiguration from nisystemlink.clients.work_item import WorkItemClient from nisystemlink.clients.work_item.models import ( @@ -42,6 +43,8 @@ WorkItemTemplateField, ) +BASE_URL = "https://test-api.lifecyclesolutions.ni.com" + @pytest.fixture(scope="class") def client(enterprise_config: HttpConfiguration) -> WorkItemClient: @@ -197,7 +200,6 @@ class TestWorkItemClient: properties={"env": "staging", "priority": "high"}, dashboard=_dashboard, execution_actions=_execution_actions, - workflow_id="312064", ) ] """create work item request object.""" @@ -422,24 +424,34 @@ def test__delete_work_item(self, client: WorkItemClient, create_work_items): ) assert len(query_deleted_work_item_response.work_items) == 0 - def test__execute_work_item__returns_execution_result( - self, client: WorkItemClient, create_work_items - ): - create_work_item_response = create_work_items(self._create_work_item_request) - assert create_work_item_response.created_work_items is not None - created_work_item = create_work_item_response.created_work_items[0] + @responses.activate + def test__execute_work_item__returns_execution_result(self, client: WorkItemClient): + work_item_id = "test-work-item-id" + + return_value = { + "result": { + "type": "MANUAL", + "action": "START", + }, + "error": None, + } + + responses.add( + responses.POST, + f"{BASE_URL}/niworkitem/v1/workitems/{work_item_id}/execute", + json=return_value, + status=200, + ) execute_request = ExecuteWorkItemRequest(action="START") execute_response = client.execute_work_item( - work_item_id=created_work_item.id, execute_request=execute_request + work_item_id=work_item_id, execute_request=execute_request ) assert execute_response is not None - assert execute_response.result is not None or execute_response.error is not None - if execute_response.result is not None: - assert execute_response.result.type == "MANUAL" - if execute_response.error is not None: - assert execute_response.error.name is not None + assert execute_response.result is not None + assert execute_response.result.type == "MANUAL" + assert execute_response.error is None def test__create_work_item_template__returns_created_work_item_template( self, create_work_item_templates From a7398d3dd87f624b8437d73268be64294d5b3895 Mon Sep 17 00:00:00 2001 From: Priyadarshini Piramanayagam Date: Sun, 15 Feb 2026 20:33:47 +0530 Subject: [PATCH 06/12] lint --- nisystemlink/clients/work_item/_work_item_client.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nisystemlink/clients/work_item/_work_item_client.py b/nisystemlink/clients/work_item/_work_item_client.py index 0e015aae..0dfb1767 100644 --- a/nisystemlink/clients/work_item/_work_item_client.py +++ b/nisystemlink/clients/work_item/_work_item_client.py @@ -1,12 +1,11 @@ from typing import List -from uplink import Body, Field, Path, retry - from nisystemlink.clients import core from nisystemlink.clients.core._http_configuration import HttpConfiguration from nisystemlink.clients.core._uplink._base_client import BaseClient from nisystemlink.clients.core._uplink._methods import get, post from nisystemlink.clients.work_item import models +from uplink import Body, Field, Path, retry @retry( From e40ff49aca1fce73595b6e99d3fb6e0b3ddee173 Mon Sep 17 00:00:00 2001 From: Priyadarshini Piramanayagam Date: Sun, 15 Feb 2026 20:35:24 +0530 Subject: [PATCH 07/12] remove unnecessary line --- tests/integration/work_item/test_work_item_client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/work_item/test_work_item_client.py b/tests/integration/work_item/test_work_item_client.py index 35c19684..727a911d 100644 --- a/tests/integration/work_item/test_work_item_client.py +++ b/tests/integration/work_item/test_work_item_client.py @@ -595,4 +595,3 @@ def test__delete_work_item_template( ) ) assert len(query_deleted_work_item_template_response.work_item_templates) == 0 - assert len(query_deleted_work_item_template_response.work_item_templates) == 0 From bac1be5e8148731f29187b2fe87b2210a5ff8bc0 Mon Sep 17 00:00:00 2001 From: Priyadarshini Piramanayagam Date: Sun, 15 Feb 2026 20:37:32 +0530 Subject: [PATCH 08/12] Remove change --- tests/integration/work_item/test_work_item_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/work_item/test_work_item_client.py b/tests/integration/work_item/test_work_item_client.py index 727a911d..b0692895 100644 --- a/tests/integration/work_item/test_work_item_client.py +++ b/tests/integration/work_item/test_work_item_client.py @@ -140,7 +140,7 @@ class TestWorkItemClient: CreateWorkItemRequest( name="Python integration work item", type="testplan", - state="SCHEDULED", + state="NEW", description="Work item for verifying integration flow", assigned_to="test.user@example.com", requested_by="test.manager@example.com", From 06711c55634767235c83fb4016d6954c06a6847e Mon Sep 17 00:00:00 2001 From: Priyadarshini Piramanayagam Date: Mon, 16 Feb 2026 12:43:54 +0530 Subject: [PATCH 09/12] fix: resolve comments --- examples/work_item/work_items.py | 30 +++-- .../clients/work_item/_work_item_client.py | 6 +- .../clients/work_item/models/__init__.py | 6 +- .../models/_execute_work_item_request.py | 8 -- .../models/_execute_work_item_response.py | 21 +-- .../work_item/test_work_item_client.py | 124 +++++++++++++++++- 6 files changed, 158 insertions(+), 37 deletions(-) delete mode 100644 nisystemlink/clients/work_item/models/_execute_work_item_request.py diff --git a/examples/work_item/work_items.py b/examples/work_item/work_items.py index 9681f2c6..df84bba7 100644 --- a/examples/work_item/work_items.py +++ b/examples/work_item/work_items.py @@ -5,7 +5,6 @@ from nisystemlink.clients.work_item.models import ( CreateWorkItemRequest, Dashboard, - ExecuteWorkItemRequest, Job, JobExecution, ManualExecution, @@ -206,17 +205,32 @@ # Execute work item action if created_work_item_id is not None: - execute_request = ExecuteWorkItemRequest(action="START") try: execute_response = client.execute_work_item( - work_item_id=created_work_item_id, execute_request=execute_request + work_item_id=created_work_item_id, action="START" ) - if execute_response.result is not None: + if execute_response.error is not None: + print(f"Execution failed: {execute_response.error.message}") + elif execute_response.result is not None: print(f"Executed action successfully. Type: {execute_response.result.type}") - if execute_response.result.execution_id: - print(f"Execution ID: {execute_response.result.execution_id}") - elif execute_response.error is not None: - print(f"Execution returned an error: {execute_response.error.message}") + + # Check for result-level error + if execute_response.result.error is not None: + print(f"Execution error: {execute_response.result.error.message}") + else: + # Handle type-specific fields + if execute_response.result.type == "NOTEBOOK": + if execute_response.result.execution_id: + print(f"Notebook execution ID: {execute_response.result.execution_id}") + elif execute_response.result.type == "JOB": + if execute_response.result.job_ids: + print(f"Job IDs: {', '.join(execute_response.result.job_ids)}") + elif execute_response.result.type == "MANUAL": + print("Manual execution completed") + elif execute_response.result.type == "SCHEDULE": + print("Work item scheduled") + elif execute_response.result.type == "UNSCHEDULE": + print("Work item unscheduled") except Exception as e: print(f"Could not execute action: {e}") diff --git a/nisystemlink/clients/work_item/_work_item_client.py b/nisystemlink/clients/work_item/_work_item_client.py index 0dfb1767..fe76b11a 100644 --- a/nisystemlink/clients/work_item/_work_item_client.py +++ b/nisystemlink/clients/work_item/_work_item_client.py @@ -132,15 +132,15 @@ def delete_work_items( """ ... - @post("workitems/{workItemId}/execute", args=[Path(name="workItemId"), Body]) + @post("workitems/{workItemId}/execute", args=[Path(name="workItemId"), Field("action")]) def execute_work_item( - self, work_item_id: str, execute_request: models.ExecuteWorkItemRequest + self, work_item_id: str, action: str ) -> models.ExecuteWorkItemResponse: """Executes the specified action for the work item. Args: work_item_id: The ID of the work item the action will be performed on. - execute_request: The parameters for executing a work item action. + action: The action to execute on the work item. Returns: The response containing the execution result or error information. diff --git a/nisystemlink/clients/work_item/models/__init__.py b/nisystemlink/clients/work_item/models/__init__.py index 1ca9dfb1..4200b678 100644 --- a/nisystemlink/clients/work_item/models/__init__.py +++ b/nisystemlink/clients/work_item/models/__init__.py @@ -58,8 +58,10 @@ ) from ._update_work_items_request import UpdateWorkItemsRequest -from ._execute_work_item_request import ExecuteWorkItemRequest -from ._execute_work_item_response import ExecuteWorkItemResponse +from ._execute_work_item_response import ( + ExecuteWorkItemResponse, + ExecutionResult, +) from ._create_work_item_template_request import CreateWorkItemTemplateRequest from ._create_work_item_templates_partial_success_response import ( diff --git a/nisystemlink/clients/work_item/models/_execute_work_item_request.py b/nisystemlink/clients/work_item/models/_execute_work_item_request.py deleted file mode 100644 index f7269c27..00000000 --- a/nisystemlink/clients/work_item/models/_execute_work_item_request.py +++ /dev/null @@ -1,8 +0,0 @@ -from nisystemlink.clients.core._uplink._json_model import JsonModel - - -class ExecuteWorkItemRequest(JsonModel): - """Request for executing a work item action.""" - - action: str - """The action to execute on the work item.""" diff --git a/nisystemlink/clients/work_item/models/_execute_work_item_response.py b/nisystemlink/clients/work_item/models/_execute_work_item_response.py index d62f4b34..f3b12ea4 100644 --- a/nisystemlink/clients/work_item/models/_execute_work_item_response.py +++ b/nisystemlink/clients/work_item/models/_execute_work_item_response.py @@ -1,21 +1,24 @@ -from typing import Literal +from typing import List, Literal from nisystemlink.clients.core._api_error import ApiError from nisystemlink.clients.core._uplink._json_model import JsonModel -class ExecuteWorkItemResult(JsonModel): - """Represents the result of a work item execution action.""" +class ExecutionResult(JsonModel): + """Result of executing a work item action.""" - type: Literal["MANUAL", "JOB", "NOTEBOOK", "NONE"] - """Type of execution that was triggered (MANUAL, JOB, NOTEBOOK, or NONE).""" - - execution_id: str | None = None - """The execution ID if applicable.""" + type: Literal["NONE", "MANUAL", "NOTEBOOK", "JOB", "SCHEDULE", "UNSCHEDULE"] + """Type of execution.""" error: ApiError | None = None """Error information if the execution encountered an error.""" + execution_id: str | None = None + """The notebook execution ID. Only populated when type is NOTEBOOK.""" + + job_ids: List[str] | None = None + """The list of job IDs. Only populated when type is JOB.""" + class ExecuteWorkItemResponse(JsonModel): """Response for executing a work item action.""" @@ -23,5 +26,5 @@ class ExecuteWorkItemResponse(JsonModel): error: ApiError | None = None """Error information if the action failed.""" - result: ExecuteWorkItemResult | None = None + result: ExecutionResult | None = None """Result of the action execution.""" diff --git a/tests/integration/work_item/test_work_item_client.py b/tests/integration/work_item/test_work_item_client.py index b0692895..6e9c6d9b 100644 --- a/tests/integration/work_item/test_work_item_client.py +++ b/tests/integration/work_item/test_work_item_client.py @@ -12,7 +12,6 @@ CreateWorkItemTemplateRequest, CreateWorkItemTemplatesPartialSuccessResponse, Dashboard, - ExecuteWorkItemRequest, ExecutionDefinition, Job, JobExecution, @@ -424,14 +423,42 @@ def test__delete_work_item(self, client: WorkItemClient, create_work_items): ) assert len(query_deleted_work_item_response.work_items) == 0 + @pytest.mark.parametrize( + "execution_type,action,extra_fields,expected_values", + [ + ("MANUAL", "START", {}, {}), + ( + "NOTEBOOK", + "RUN_NOTEBOOK", + {"execution_id": "notebook-execution-123"}, + {"execution_id": "notebook-execution-123"}, + ), + ( + "JOB", + "RUN_JOBS", + {"job_ids": ["job-1", "job-2", "job-3"]}, + {"job_ids": ["job-1", "job-2", "job-3"]}, + ), + ("SCHEDULE", "SCHEDULE", {}, {}), + ("UNSCHEDULE", "UNSCHEDULE", {}, {}), + ], + ) @responses.activate - def test__execute_work_item__returns_execution_result(self, client: WorkItemClient): + def test__execute_work_item_with_action__returns_execution_result( + self, + client: WorkItemClient, + execution_type: str, + action: str, + extra_fields: dict, + expected_values: dict, + ): work_item_id = "test-work-item-id" return_value = { "result": { - "type": "MANUAL", - "action": "START", + "type": execution_type, + "error": None, + **extra_fields, }, "error": None, } @@ -443,15 +470,98 @@ def test__execute_work_item__returns_execution_result(self, client: WorkItemClie status=200, ) - execute_request = ExecuteWorkItemRequest(action="START") execute_response = client.execute_work_item( - work_item_id=work_item_id, execute_request=execute_request + work_item_id=work_item_id, action=action ) assert execute_response is not None + assert execute_response.error is None assert execute_response.result is not None - assert execute_response.result.type == "MANUAL" + assert execute_response.result.type == execution_type + assert execute_response.result.error is None + + # Verify type-specific fields + for field, expected_value in expected_values.items(): + assert getattr(execute_response.result, field) == expected_value + + @responses.activate + def test__execute_work_item_with_response_level_error__returns_error_no_result( + self, client: WorkItemClient + ): + work_item_id = "invalid-work-item-id" + + return_value = { + "result": None, + "error": { + "name": "Skyline.ResourceNotFound", + "code": -251040, + "message": "The requested work item was not found.", + "resourceType": "WorkItem", + "resourceId": work_item_id, + "args": [], + "innerErrors": [], + }, + } + + responses.add( + responses.POST, + f"{BASE_URL}/niworkitem/v1/workitems/{work_item_id}/execute", + json=return_value, + status=200, + ) + + execute_response = client.execute_work_item( + work_item_id=work_item_id, action="START" + ) + + assert execute_response is not None + assert execute_response.result is None + assert execute_response.error is not None + assert execute_response.error.code == -251040 + assert execute_response.error.message is not None + assert "not found" in execute_response.error.message.lower() + + @responses.activate + def test__execute_work_item_with_result_level_error__returns_result_with_error( + self, client: WorkItemClient + ): + work_item_id = "test-work-item-id" + + return_value = { + "result": { + "type": "NOTEBOOK", + "execution_id": None, + "error": { + "name": "Skyline.ExecutionFailed", + "code": -251050, + "message": "Notebook execution failed due to invalid parameters.", + "args": [], + "innerErrors": [], + }, + }, + "error": None, + } + + responses.add( + responses.POST, + f"{BASE_URL}/niworkitem/v1/workitems/{work_item_id}/execute", + json=return_value, + status=200, + ) + + execute_response = client.execute_work_item( + work_item_id=work_item_id, action="RUN_NOTEBOOK" + ) + + assert execute_response is not None assert execute_response.error is None + assert execute_response.result is not None + assert execute_response.result.type == "NOTEBOOK" + assert execute_response.result.execution_id is None + assert execute_response.result.error is not None + assert execute_response.result.error.code == -251050 + assert execute_response.result.error.message is not None + assert "execution failed" in execute_response.result.error.message.lower() def test__create_work_item_template__returns_created_work_item_template( self, create_work_item_templates From 5b4014a67b1aa1e556878abca1c75e5115dafdae Mon Sep 17 00:00:00 2001 From: Priyadarshini Piramanayagam Date: Mon, 16 Feb 2026 12:51:54 +0530 Subject: [PATCH 10/12] lint --- examples/work_item/work_items.py | 6 ++++-- nisystemlink/clients/work_item/_work_item_client.py | 7 +++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/examples/work_item/work_items.py b/examples/work_item/work_items.py index df84bba7..af8a2c8d 100644 --- a/examples/work_item/work_items.py +++ b/examples/work_item/work_items.py @@ -213,7 +213,7 @@ print(f"Execution failed: {execute_response.error.message}") elif execute_response.result is not None: print(f"Executed action successfully. Type: {execute_response.result.type}") - + # Check for result-level error if execute_response.result.error is not None: print(f"Execution error: {execute_response.result.error.message}") @@ -221,7 +221,9 @@ # Handle type-specific fields if execute_response.result.type == "NOTEBOOK": if execute_response.result.execution_id: - print(f"Notebook execution ID: {execute_response.result.execution_id}") + print( + f"Notebook execution ID: {execute_response.result.execution_id}" + ) elif execute_response.result.type == "JOB": if execute_response.result.job_ids: print(f"Job IDs: {', '.join(execute_response.result.job_ids)}") diff --git a/nisystemlink/clients/work_item/_work_item_client.py b/nisystemlink/clients/work_item/_work_item_client.py index fe76b11a..c21865ec 100644 --- a/nisystemlink/clients/work_item/_work_item_client.py +++ b/nisystemlink/clients/work_item/_work_item_client.py @@ -5,7 +5,7 @@ from nisystemlink.clients.core._uplink._base_client import BaseClient from nisystemlink.clients.core._uplink._methods import get, post from nisystemlink.clients.work_item import models -from uplink import Body, Field, Path, retry +from uplink import Field, Path, retry @retry( @@ -132,7 +132,10 @@ def delete_work_items( """ ... - @post("workitems/{workItemId}/execute", args=[Path(name="workItemId"), Field("action")]) + @post( + "workitems/{workItemId}/execute", + args=[Path(name="workItemId"), Field("action")], + ) def execute_work_item( self, work_item_id: str, action: str ) -> models.ExecuteWorkItemResponse: From 8ac5cf128cddd893cc2bdc246d215842efa406ed Mon Sep 17 00:00:00 2001 From: Priyadarshini Piramanayagam Date: Mon, 16 Feb 2026 14:32:42 +0530 Subject: [PATCH 11/12] add invalid test --- .../work_item/test_work_item_client.py | 38 +++---------------- 1 file changed, 6 insertions(+), 32 deletions(-) diff --git a/tests/integration/work_item/test_work_item_client.py b/tests/integration/work_item/test_work_item_client.py index 6e9c6d9b..fb4e4d14 100644 --- a/tests/integration/work_item/test_work_item_client.py +++ b/tests/integration/work_item/test_work_item_client.py @@ -4,6 +4,7 @@ import pytest import responses +from nisystemlink.clients.core import ApiException from nisystemlink.clients.core._http_configuration import HttpConfiguration from nisystemlink.clients.work_item import WorkItemClient from nisystemlink.clients.work_item.models import ( @@ -484,42 +485,15 @@ def test__execute_work_item_with_action__returns_execution_result( for field, expected_value in expected_values.items(): assert getattr(execute_response.result, field) == expected_value - @responses.activate - def test__execute_work_item_with_response_level_error__returns_error_no_result( + def test__execute_work_item_with_invalid_id__raises_ApiException_NotFound( self, client: WorkItemClient ): - work_item_id = "invalid-work-item-id" + invalid_work_item_id = "invalid-work-item-id" - return_value = { - "result": None, - "error": { - "name": "Skyline.ResourceNotFound", - "code": -251040, - "message": "The requested work item was not found.", - "resourceType": "WorkItem", - "resourceId": work_item_id, - "args": [], - "innerErrors": [], - }, - } + with pytest.raises(ApiException) as exception_info: + client.execute_work_item(work_item_id=invalid_work_item_id, action="START") - responses.add( - responses.POST, - f"{BASE_URL}/niworkitem/v1/workitems/{work_item_id}/execute", - json=return_value, - status=200, - ) - - execute_response = client.execute_work_item( - work_item_id=work_item_id, action="START" - ) - - assert execute_response is not None - assert execute_response.result is None - assert execute_response.error is not None - assert execute_response.error.code == -251040 - assert execute_response.error.message is not None - assert "not found" in execute_response.error.message.lower() + assert exception_info.value.http_status_code == 404 @responses.activate def test__execute_work_item_with_result_level_error__returns_result_with_error( From 759661a32685e943f3d8ede8be981f42beb8a859 Mon Sep 17 00:00:00 2001 From: Priyadarshini Piramanayagam Date: Mon, 16 Feb 2026 15:20:56 +0530 Subject: [PATCH 12/12] nit change --- tests/integration/work_item/test_work_item_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/work_item/test_work_item_client.py b/tests/integration/work_item/test_work_item_client.py index fb4e4d14..b7e67dd0 100644 --- a/tests/integration/work_item/test_work_item_client.py +++ b/tests/integration/work_item/test_work_item_client.py @@ -535,7 +535,7 @@ def test__execute_work_item_with_result_level_error__returns_result_with_error( assert execute_response.result.error is not None assert execute_response.result.error.code == -251050 assert execute_response.result.error.message is not None - assert "execution failed" in execute_response.result.error.message.lower() + assert "execution failed" in execute_response.result.error.message def test__create_work_item_template__returns_created_work_item_template( self, create_work_item_templates