Conversation
nisystemlink/clients/work_item/models/_execute_work_item_request.py
Outdated
Show resolved
Hide resolved
nisystemlink/clients/work_item/models/_execute_work_item_response.py
Outdated
Show resolved
Hide resolved
| """Error information if the action failed.""" | ||
|
|
||
| result: ExecutionResult | None = None | ||
| """Result of the action execution.""" |
There was a problem hiding this comment.
I want to know if we can clean up this response model or if its useful to have these extra layers. Best I can tell the error will only be set when the response is an error status code, which we handle in the base client to convert into an ApiError already, so this part of the model will never be used. That leaves just the result, so can we elevate that and return it directly from the client instead?
Conversely, if we do need to return both the error and the execution result together in the error case then we need to override the base client's status code handler so it doesn't throw so we can have the behavior we'd want.
@darrenbiel or @kaviarasu-ni can you comment on the way we intend the API to behave?
There was a problem hiding this comment.
The service may return both an error and job IDs in a partial failure case. It will cancel the jobs it successfully queued and return their IDs along with the error. The error is duplicated at the base response and in the Result.Error.
I think we would need to override the base client as you suggested.
There was a problem hiding this comment.
@rbell517 So as to have the error as well as the execution result for certain status codes (as documented in the swagger page), I've added a new field response_data in the ApiException itself. Then in the respective function in the client, I've handled the exception and interpreted the response.
try:
return self._execute_work_item(work_item_id=work_item_id, action=action)
except core.ApiException as e:
if e.http_status_code in _EXECUTE_RESPONSE_STATUS_CODES and e.response_data:
return models.ExecuteWorkItemResponse.model_validate(e.response_data)
raiseLet me know if this approach looks fine.
There was a problem hiding this comment.
Sorry after Darren's message I had intended to come back with more detailed instructions but I slipped. What I was thinking when suggesting we override the base client was that for the execute route we would define our own response_handler hook on the execute route. I believe that will run prior to the base class response handler and allow us to extract the execute specific error model and let us raise our own exception type.
@execute_error_response_handler
@post(
"workitems/{workItemId}/execute",
args=[Path(name="workItemId"), Field("action")],
)
def execute_work_item(
self, work_item_id: str, action: str
) -> models.ExecuteWorkItemResponse:
...
@response_handler
def execute_error_response_handler(response: Response):
if 200 <= response.status_code < 300 or response.status_code > 499:
# let the base class handle these
return response
# details...
raise WorkItemExecuteApiException( ... ) class WorkItemExecuteApiException(ApiException):
...
@property
def result(self) -> ExecutionResult | None:
return self._resultThis gives full control over the behavior and we stick to the existing semantics of errors raising exceptions, but with a new well typed exception.
nisystemlink/clients/work_item/models/_execute_work_item_response.py
Outdated
Show resolved
Hide resolved
| """Error information if the action failed.""" | ||
|
|
||
| result: ExecutionResult | None = None | ||
| """Result of the action execution.""" |
There was a problem hiding this comment.
Sorry after Darren's message I had intended to come back with more detailed instructions but I slipped. What I was thinking when suggesting we override the base client was that for the execute route we would define our own response_handler hook on the execute route. I believe that will run prior to the base class response handler and allow us to extract the execute specific error model and let us raise our own exception type.
@execute_error_response_handler
@post(
"workitems/{workItemId}/execute",
args=[Path(name="workItemId"), Field("action")],
)
def execute_work_item(
self, work_item_id: str, action: str
) -> models.ExecuteWorkItemResponse:
...
@response_handler
def execute_error_response_handler(response: Response):
if 200 <= response.status_code < 300 or response.status_code > 499:
# let the base class handle these
return response
# details...
raise WorkItemExecuteApiException( ... ) class WorkItemExecuteApiException(ApiException):
...
@property
def result(self) -> ExecutionResult | None:
return self._resultThis gives full control over the behavior and we stick to the existing semantics of errors raising exceptions, but with a new well typed exception.
| # Status codes for which the execute work item API returns ExecuteWorkItemResponse | ||
| # in the body even on failure, allowing callers to recover partial results | ||
| # (e.g. cancelled job IDs). See the API spec for details. | ||
| _EXECUTE_RESPONSE_STATUS_CODES = frozenset({403, 404, 500}) |
There was a problem hiding this comment.
It strikes me as odd that we would expect to get this different error behavior for 403, 404, and 500, but not 400 or 401, etc.
When it comes to the implementation of the response handler, we should probably assume it could be from any error status code and determine if we need the execute specific model based on whether the response body has the fields we expect. That could be explicitly checking fields exist or a validate call on the Pydantic model in a try
What does this Pull Request accomplish?
This PR includes client method for
niworkitem/v1/{workitemid}/executeWhy should this Pull Request be merged?
This API endpoint can be used to execute the workflow attached to a work item
What testing has been done?
Automated testing has been included