From 15367685e4338d9d6327f724339bdd3742917648 Mon Sep 17 00:00:00 2001 From: Nayane Fernandes <143632290+ansnfernand@users.noreply.github.com> Date: Tue, 16 Sep 2025 14:04:43 +0200 Subject: [PATCH 1/3] feat: Added APIs delete_event() and delete_phase() --- src/ansys/sherlock/core/errors.py | 12 ++ src/ansys/sherlock/core/lifecycle.py | 86 +++++++++++ .../sherlock/core/types/lifecycle_types.py | 50 +++++++ tests/test_lifecycle.py | 134 ++++++++++++++++++ 4 files changed, 282 insertions(+) diff --git a/src/ansys/sherlock/core/errors.py b/src/ansys/sherlock/core/errors.py index dceff4aa1..2e8f0d11c 100644 --- a/src/ansys/sherlock/core/errors.py +++ b/src/ansys/sherlock/core/errors.py @@ -1304,3 +1304,15 @@ def __init__(self, message): def __str__(self): """Format error message.""" return f"Save profile error: {self.message}" + + +class SherlockDeleteError(Exception): + """Contains the errors raised when an event or a phase cannot be deleted.""" + + def __init__(self, message): + """Initialize error message.""" + self.message = message + + def __str__(self): + """Format error message.""" + return f"Delete error: {self.message}" diff --git a/src/ansys/sherlock/core/lifecycle.py b/src/ansys/sherlock/core/lifecycle.py index a4bfa1c68..360cc64ca 100644 --- a/src/ansys/sherlock/core/lifecycle.py +++ b/src/ansys/sherlock/core/lifecycle.py @@ -23,6 +23,7 @@ SherlockAddThermalEventError, SherlockAddThermalProfilesError, SherlockCreateLifePhaseError, + SherlockDeleteError, SherlockInvalidHarmonicProfileEntriesError, SherlockInvalidLoadDirectionError, SherlockInvalidOrientationError, @@ -39,6 +40,8 @@ ) from ansys.sherlock.core.grpc_stub import GrpcStub from ansys.sherlock.core.types.lifecycle_types import ( + DeleteEventRequest, + DeletePhaseRequest, ImportThermalSignalRequest, SaveHarmonicProfileRequest, SaveRandomVibeProfileRequest, @@ -2353,3 +2356,86 @@ def save_thermal_profile( # Raise error if save failed if response.value != 0: raise SherlockSaveProfileError(response.message) + + @require_version(261) + def delete_event(self, request: DeleteEventRequest) -> SherlockCommonService_pb2.ReturnCode: + """Delete a life cycle event from a given phase in a project. + + Available Since: 2026R1 + + Parameters + ---------- + request : DeleteEventRequest + Request object containing project, phase name, and event name. + + Returns + ------- + SherlockCommonService_pb2.ReturnCode + Status code of the response. 0 for success. + + Examples + -------- + >>> from ansys.sherlock.core.types.lifecycle_types import DeleteEventRequest + >>> from ansys.sherlock.core.launcher import launch_sherlock + >>> sherlock = launch_sherlock() + >>> response = sherlock.lifecycle.delete_event( + >>> DeleteEventRequest( + >>> project="MyProject", + >>> phase_name="ThermalPhase", + >>> event_name="ThermalCycle_A", + >>> ) + >>> ) + >>> assert response.value == 0 + """ + grpc_request = request._convert_to_grpc() + + if not self._is_connection_up(): + raise SherlockNoGrpcConnectionException() + + response = self.stub.deleteEvent(grpc_request) + + if response.value != 0: + raise SherlockDeleteError(response.message) + + return response + + @require_version(261) + def delete_phase(self, request: DeletePhaseRequest) -> SherlockCommonService_pb2.ReturnCode: + """Delete a life cycle phase from a project. + + Available Since: 2026R1 + + Parameters + ---------- + request : DeletePhaseRequest + Request object containing project and phase name. + + Returns + ------- + SherlockCommonService_pb2.ReturnCode + Status code of the response. 0 for success. + + Examples + -------- + >>> from ansys.sherlock.core.types.lifecycle_types import DeletePhaseRequest + >>> from ansys.sherlock.core.launcher import launch_sherlock + >>> sherlock = launch_sherlock() + >>> response = sherlock.lifecycle.delete_phase( + >>> DeletePhaseRequest( + >>> project="MyProject", + >>> phase_name="ThermalPhase", + >>> ) + >>> ) + >>> assert response.value == 0 + """ + grpc_request = request._convert_to_grpc() + + if not self._is_connection_up(): + raise SherlockNoGrpcConnectionException() + + response = self.stub.deletePhase(grpc_request) + + if response.value != 0: + raise SherlockDeleteError(response.message) + + return response diff --git a/src/ansys/sherlock/core/types/lifecycle_types.py b/src/ansys/sherlock/core/types/lifecycle_types.py index 4aefc4776..4734b73e4 100644 --- a/src/ansys/sherlock/core/types/lifecycle_types.py +++ b/src/ansys/sherlock/core/types/lifecycle_types.py @@ -252,3 +252,53 @@ def _convert_to_grpc(self) -> SherlockLifeCycleService_pb2.SaveThermalProfileReq eventName=self.event_name, filePath=self.file_path, ) + + +class DeleteEventRequest(BaseModel): + """Request to delete a life cycle event from a project phase.""" + + project: str + """Sherlock project name.""" + + phase_name: str + """The name of the life cycle phase from which to delete this event.""" + + event_name: str + """Name of the event to be deleted.""" + + @field_validator("project", "phase_name", "event_name") + @classmethod + def str_validation(cls, value: str, info: ValidationInfo): + """Validate string fields listed.""" + return basic_str_validator(value, info.field_name) + + def _convert_to_grpc(self) -> SherlockLifeCycleService_pb2.DeleteEventRequest: + """Convert to gRPC DeleteEventRequest.""" + return SherlockLifeCycleService_pb2.DeleteEventRequest( + project=self.project, + phaseName=self.phase_name, + eventName=self.event_name, + ) + + +class DeletePhaseRequest(BaseModel): + """Request to delete a life cycle phase from a project.""" + + project: str + """Sherlock project name.""" + + phase_name: str + """Name of the life cycle phase to be deleted.""" + + @field_validator("project", "phase_name") + @classmethod + def str_validation(cls, value: str, info: ValidationInfo): + """Validate string fields listed.""" + return basic_str_validator(value, info.field_name) + + def _convert_to_grpc(self) -> SherlockLifeCycleService_pb2.DeletePhaseRequest: + """Convert to gRPC DeletePhaseRequest.""" + return SherlockLifeCycleService_pb2.DeletePhaseRequest( + project=self.project, + phaseName=self.phase_name, + ) diff --git a/tests/test_lifecycle.py b/tests/test_lifecycle.py index 822d1c432..46b2ef43a 100644 --- a/tests/test_lifecycle.py +++ b/tests/test_lifecycle.py @@ -16,6 +16,7 @@ SherlockAddThermalEventError, SherlockAddThermalProfilesError, SherlockCreateLifePhaseError, + SherlockDeleteError, SherlockLoadHarmonicProfileError, SherlockLoadRandomVibeProfileError, SherlockLoadShockProfileDatasetError, @@ -25,6 +26,8 @@ ) from ansys.sherlock.core.lifecycle import Lifecycle from ansys.sherlock.core.types.lifecycle_types import ( + DeleteEventRequest, + DeletePhaseRequest, ImportThermalSignalRequest, SaveHarmonicProfileRequest, SaveRandomVibeProfileRequest, @@ -62,6 +65,9 @@ def test_all(): helper_test_save_shock_pulse_profile(lifecycle) helper_test_save_thermal_profile(lifecycle) + helper_test_delete_event(lifecycle, shock_event_name, phase_name) + helper_test_delete_phase(lifecycle, phase_name) + def helper_test_create_life_phase(lifecycle: Lifecycle): """Test create_life_phase API""" @@ -2618,5 +2624,133 @@ def helper_test_save_thermal_profile(lifecycle: Lifecycle): assert type(e) == SherlockSaveProfileError +def helper_test_delete_event(lifecycle: Lifecycle, event_name: str, phase_name: str): + # project missing + try: + lifecycle.delete_event( + DeleteEventRequest( + project="", + phase_name="On The Road", + event_name="ThermalCycle_A", + ) + ) + pytest.fail("No exception raised when using a missing project parameter") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert str(e.errors()[0]["msg"]) == ( + "Value error, project is invalid because it is None or empty." + ) + + # phase_name missing + try: + lifecycle.delete_event( + DeleteEventRequest( + project="Tutorial Project", + phase_name="", + event_name="ThermalCycle_A", + ) + ) + pytest.fail("No exception raised when using a missing phase_name parameter") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert str(e.errors()[0]["msg"]) == ( + "Value error, phase_name is invalid because it is None or empty." + ) + + # event_name missing + try: + lifecycle.delete_event( + DeleteEventRequest( + project="Tutorial Project", + phase_name="On The Road", + event_name="", + ) + ) + pytest.fail("No exception raised when using a missing event_name parameter") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert str(e.errors()[0]["msg"]) == ( + "Value error, event_name is invalid because it is None or empty." + ) + + if lifecycle._is_connection_up(): + # valid request but false event_name + try: + lifecycle.delete_event( + DeleteEventRequest( + project="Tutorial Project", + phase_name="On The Road", + event_name="NonExistingEvent", + ) + ) + pytest.fail("No exception raised for server error response") + except Exception as e: + assert isinstance(e, SherlockDeleteError) + + # valid request with actual event + response = lifecycle.delete_event( + DeleteEventRequest( + project="Tutorial Project", + phase_name=phase_name, + event_name=event_name, + ) + ) + assert response.value == 0 + + +def helper_test_delete_phase(lifecycle: Lifecycle, phase_name: str): + # project missing + try: + lifecycle.delete_phase( + DeletePhaseRequest( + project="", + phase_name="SomePhase", + ) + ) + pytest.fail("No exception raised when using a missing project parameter") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert str(e.errors()[0]["msg"]) == ( + "Value error, project is invalid because it is None or empty." + ) + + # phase_name missing + try: + lifecycle.delete_phase( + DeletePhaseRequest( + project="Tutorial Project", + phase_name="", + ) + ) + pytest.fail("No exception raised when using a missing phase_name parameter") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert str(e.errors()[0]["msg"]) == ( + "Value error, phase_name is invalid because it is None or empty." + ) + + if lifecycle._is_connection_up(): + # valid request but false phase_name + try: + lifecycle.delete_phase( + DeletePhaseRequest( + project="Tutorial Project", + phase_name="NonExistingPhase", + ) + ) + pytest.fail("No exception raised for server error response") + except Exception as e: + assert isinstance(e, SherlockDeleteError) + + # valid request with actual phase + response = lifecycle.delete_phase( + DeletePhaseRequest( + project="Tutorial Project", + phase_name=phase_name, + ) + ) + assert response.value == 0 + + if __name__ == "__main__": test_all() From 754e632dbd71a7dc60860762c50ad0a6fb727e01 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Tue, 16 Sep 2025 12:09:58 +0000 Subject: [PATCH 2/3] chore: adding changelog file 648.miscellaneous.md [dependabot-skip] --- doc/changelog.d/648.miscellaneous.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/648.miscellaneous.md diff --git a/doc/changelog.d/648.miscellaneous.md b/doc/changelog.d/648.miscellaneous.md new file mode 100644 index 000000000..d2b0e7b77 --- /dev/null +++ b/doc/changelog.d/648.miscellaneous.md @@ -0,0 +1 @@ +Feat: Added APIs delete_event() and delete_phase() From 237d98b95642a4c891a6beaeb2474d49d843827a Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Tue, 16 Sep 2025 12:11:04 +0000 Subject: [PATCH 3/3] chore: adding changelog file 648.added.md [dependabot-skip] --- doc/changelog.d/{648.miscellaneous.md => 648.added.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename doc/changelog.d/{648.miscellaneous.md => 648.added.md} (100%) diff --git a/doc/changelog.d/648.miscellaneous.md b/doc/changelog.d/648.added.md similarity index 100% rename from doc/changelog.d/648.miscellaneous.md rename to doc/changelog.d/648.added.md