diff --git a/src/ansys/sherlock/core/errors.py b/src/ansys/sherlock/core/errors.py index 976459eab..9dc511ac9 100644 --- a/src/ansys/sherlock/core/errors.py +++ b/src/ansys/sherlock/core/errors.py @@ -984,3 +984,20 @@ def __init__(self, message): def __str__(self): """Format error message.""" return f"Import zipped project archive error: {self.message}" + + +class SherlockUpdatePartsListPropertiesError(Exception): + """Contains the errors raised when a parts list properties cannot be updated.""" + + def __init__(self, message=None, error_array=None): + """Initialize error message.""" + self.message = message + self.error_array = error_array + + def str_itr(self): + """Format error message.""" + if self.message is None: + return [f"Update parts list properties error: {error}" for error in self.error_array] + + assert self.error_array is None + return [f"Update parts list properties error: {self.message}"] diff --git a/src/ansys/sherlock/core/parts.py b/src/ansys/sherlock/core/parts.py index e9913b943..520372475 100644 --- a/src/ansys/sherlock/core/parts.py +++ b/src/ansys/sherlock/core/parts.py @@ -17,6 +17,7 @@ SherlockImportPartsListError, SherlockUpdatePartsFromAVLError, SherlockUpdatePartsListError, + SherlockUpdatePartsListPropertiesError, SherlockUpdatePartsLocationsByFileError, SherlockUpdatePartsLocationsError, ) @@ -804,3 +805,127 @@ def update_parts_from_AVL( except SherlockUpdatePartsFromAVLError as e: LOG.error(str(e)) raise e + + def update_parts_list_properties(self, project, cca_name, part_properties): + """ + Update one or more properties of one or more parts in a parts list. + + Parameters + ---------- + project : str + Name of the Sherlock project. + cca_name : str + Name of the CCA. + part_properties : list + List of part properties consisting of these properties: + + - reference_designators : List of str, optional + List of the reference designator for each part to be updated. If not included, + update properties for all parts in the CCA. + - properties : list + List of properties consisting of these properties: + + - name : str + Name of property to be updated. + - value : str + Value to be applied to the chosen part property. + + Returns + ------- + int + Status code of the response. 0 for success. + + Examples + -------- + >>> from ansys.sherlock.core.launcher import launch_sherlock + >>> sherlock = launch_sherlock() + >>> sherlock.parts.update_parts_list_properties( + "Test", + "Card", + [ + { + "reference_designators": ["C1"], + "properties": [ + {"name": "partType", "value": "RESISTOR"} + ] + }, + { + "reference_designators": ["C2"], + "properties": [ + {"name": "locX", "value": "1"} + ] + } + ] + ) + """ + try: + if project == "": + raise SherlockUpdatePartsListPropertiesError(message="Project name is invalid.") + if cca_name == "": + raise SherlockUpdatePartsListPropertiesError(message="CCA name is invalid.") + if len(part_properties) == 0: + raise SherlockUpdatePartsListPropertiesError(message="Part properties are missing.") + + for i, part_property in enumerate(part_properties): + if len(part_property) < 1 or len(part_property) > 2: + raise SherlockUpdatePartsListPropertiesError( + f"Number of elements ({len(part_property)}) " + f"is wrong for part list property {i}." + ) + elif not isinstance(part_property["reference_designators"], list): + raise SherlockUpdatePartsListPropertiesError( + f"reference_designators is not a list " f"for parts list property {i}." + ) + + properties = part_property["properties"] + for j, property in enumerate(properties): + if len(property) < 1 or len(property) > 2: + raise SherlockUpdatePartsListPropertiesError( + f"Number of elements ({len(property)}) " f"is wrong for property {j}." + ) + elif not isinstance(property["name"], str) or property["name"] == "": + raise SherlockUpdatePartsListPropertiesError( + f"Name is required " f"for property {j}." + ) + elif not isinstance(property["value"], str): + raise SherlockUpdatePartsListPropertiesError(message="Value is invalid.") + + if not self._is_connection_up(): + LOG.error("There is no connection to a gRPC service.") + return + + request = SherlockPartsService_pb2.UpdatePartsListPropertiesRequest( + project=project, ccaName=cca_name + ) + + # Add part properties to the request + for part_prop in part_properties: + prop = request.partProperties.add() + reference_designators = part_prop["reference_designators"] + if reference_designators is not None: + for ref_des in reference_designators: + prop.refDes.append(ref_des) + + props = part_prop["properties"] + if props is not None: + for prop_dict in props: + property_obj = prop.properties.add() + property_obj.name = prop_dict["name"] + property_obj.value = prop_dict["value"] + + response = self.stub.updatePartsListProperties(request) + + return_code = response.returnCode + + if return_code.value == -1: + if return_code.message == "": + raise SherlockUpdatePartsListPropertiesError(error_array=response.errors) + + raise SherlockUpdatePartsListPropertiesError(message=return_code.message) + + return return_code.value + + except SherlockUpdatePartsListPropertiesError as e: + for error in e.str_itr(): + LOG.error(error) + raise e diff --git a/tests/test_parts.py b/tests/test_parts.py index 020912a77..66046e8f3 100644 --- a/tests/test_parts.py +++ b/tests/test_parts.py @@ -14,6 +14,7 @@ SherlockImportPartsListError, SherlockUpdatePartsFromAVLError, SherlockUpdatePartsListError, + SherlockUpdatePartsListPropertiesError, SherlockUpdatePartsLocationsByFileError, SherlockUpdatePartsLocationsError, ) @@ -34,6 +35,8 @@ def test_all(): helper_test_update_parts_list(parts) time.sleep(1) + helper_test_update_parts_list_properties(parts) + time.sleep(1) helper_test_update_parts_from_AVL(parts) helper_test_update_parts_locations(parts) helper_test_update_parts_locations_by_file(parts) @@ -672,5 +675,160 @@ def helper_test_get_part_location(parts): assert str(e) == "Get part location error: Location unit is invalid." +def helper_test_update_parts_list_properties(parts): + """Test update_parts_list_properties API""" + try: + parts.update_parts_list_properties( + "", + "CCA_Name", + [ + { + "reference_designators": ["C1"], + "properties": [{"name": "partType", "value": "RESISTOR"}], + } + ], + ) + pytest.fail("No exception raised when using an invalid parameter") + except SherlockUpdatePartsListPropertiesError as e: + assert str(e.str_itr()) == ( + "['Update parts list properties error: Project name is invalid.']" + ) + + try: + parts.update_parts_list_properties( + "Test", + "", + [ + { + "reference_designators": ["C1"], + "properties": [{"name": "partType", "value": "RESISTOR"}], + } + ], + ) + pytest.fail("No exception raised when using an invalid parameter") + except SherlockUpdatePartsListPropertiesError as e: + assert str(e.str_itr()) == "['Update parts list properties error: CCA name is invalid.']" + + try: + parts.update_parts_list_properties("Test", "CCA_Name", []) + pytest.fail("No exception raised when using an invalid parameter") + except SherlockUpdatePartsListPropertiesError as e: + assert str(e.str_itr()) == ( + "['Update parts list properties error: Part properties are missing.']" + ) + + try: + parts.update_parts_list_properties( + "Test", + "CCA_name", + [ + { + "reference_designators": ["C1"], + "properties": [{"name": "partType", "value": "RESISTOR"}], + "test": "test", + } + ], + ) + pytest.fail("No exception raised when using an invalid parameter") + except SherlockUpdatePartsListPropertiesError as e: + assert str(e.str_itr()) == ( + "['Update parts list properties error: Number of elements (3) " + "is wrong for part list property 0.']" + ) + + try: + parts.update_parts_list_properties( + "Test", + "CCA_name", + [ + { + "reference_designators": "C1", + "properties": [{"name": "partType", "value": "RESISTOR"}], + } + ], + ) + pytest.fail("No exception raised when using an invalid parameter") + except SherlockUpdatePartsListPropertiesError as e: + assert str(e.str_itr()) == ( + "['Update parts list properties error: reference_designators is not a list " + "for parts list property 0.']" + ) + + try: + parts.update_parts_list_properties( + "Test", + "CCA_name", + [ + { + "reference_designators": ["C1"], + "properties": [{"name": "partType", "value": "RESISTOR", "test": "test"}], + } + ], + ) + pytest.fail("No exception raised when using an invalid parameter") + except SherlockUpdatePartsListPropertiesError as e: + assert str(e.str_itr()) == ( + "['Update parts list properties error: Number of elements (3) " + "is wrong for property 0.']" + ) + + try: + parts.update_parts_list_properties( + "Test", + "CCA_name", + [{"reference_designators": ["C1"], "properties": [{"name": "", "value": "RESISTOR"}]}], + ) + pytest.fail("No exception raised when using an invalid parameter") + except SherlockUpdatePartsListPropertiesError as e: + assert str(e.str_itr()) == ( + "['Update parts list properties error: Name is required for property 0.']" + ) + + try: + parts.update_parts_list_properties( + "Test", + "CCA_name", + [{"reference_designators": ["C1"], "properties": [{"name": "partType", "value": 0}]}], + ) + pytest.fail("No exception raised when using an invalid parameter") + except SherlockUpdatePartsListPropertiesError as e: + assert str(e.str_itr()) == ("['Update parts list properties error: Value is invalid.']") + + if not parts._is_connection_up(): + return + + try: + parts.update_parts_list_properties( + "Invalid project", + "CCA_name", + [ + { + "reference_designators": ["C1"], + "properties": [{"name": "partType", "value": "RESISTOR"}], + } + ], + ) + pytest.fail("No exception raised when using an invalid parameter") + except Exception as e: + assert type(e) == SherlockUpdatePartsListPropertiesError + + try: + result = parts.update_parts_list_properties( + "Tutorial Project", + "Main Board", + [ + { + "reference_designators": ["C1"], + "properties": [{"name": "partType", "value": "RESISTOR"}], + } + ], + ) + + assert result == 0 + + except Exception as e: + pytest.fail(e.message) + + if __name__ == "__main__": test_all()