diff --git a/doc/changelog.d/667.miscellaneous.md b/doc/changelog.d/667.miscellaneous.md new file mode 100644 index 000000000..99bbabb9d --- /dev/null +++ b/doc/changelog.d/667.miscellaneous.md @@ -0,0 +1 @@ +Fix: Unable to load CSV files into life cycle profiles diff --git a/doc/source/api/lifecycle_types.rst b/doc/source/api/lifecycle_types.rst index f8861ebc4..6ec78a336 100644 --- a/doc/source/api/lifecycle_types.rst +++ b/doc/source/api/lifecycle_types.rst @@ -7,6 +7,16 @@ LifeCycle Types Constants --------- +.. autoclass:: HarmonicVibeProfileCsvFileProperties + :members: +.. autoclass:: RandomVibeProfileCsvFileProperties + :members: +.. autoclass:: ShockProfileDatasetCsvFileProperties + :members: +.. autoclass:: ShockProfilePulsesCsvFileProperties + :members: +.. autoclass:: ThermalProfileCsvFileProperties + :members: .. autoclass:: ThermalSignalFileProperties :members: .. autoclass:: ImportThermalSignalRequest diff --git a/src/ansys/sherlock/core/lifecycle.py b/src/ansys/sherlock/core/lifecycle.py index 8d34a41da..f0755e549 100644 --- a/src/ansys/sherlock/core/lifecycle.py +++ b/src/ansys/sherlock/core/lifecycle.py @@ -42,11 +42,16 @@ from ansys.sherlock.core.types.lifecycle_types import ( DeleteEventRequest, DeletePhaseRequest, + HarmonicVibeProfileCsvFileProperties, ImportThermalSignalRequest, + RandomVibeProfileCsvFileProperties, SaveHarmonicProfileRequest, SaveRandomVibeProfileRequest, SaveShockPulseProfileRequest, SaveThermalProfileRequest, + ShockProfileDatasetCsvFileProperties, + ShockProfilePulsesCsvFileProperties, + ThermalProfileCsvFileProperties, ) from ansys.sherlock.core.utils.version_check import require_version @@ -1750,7 +1755,12 @@ def add_shock_profiles( @require_version() def load_random_vibe_profile( - self, project: str, phase_name: str, event_name: str, file_path: str + self, + project: str, + phase_name: str, + event_name: str, + file_path: str, + csv_file_properties: RandomVibeProfileCsvFileProperties = None, ) -> int: """Load random vibe profile from .csv or .dat file. @@ -1765,7 +1775,9 @@ def load_random_vibe_profile( event_name: str Name of the random vibe event. file_path: str - File path for thermal profile .dat or .csv file + File path for thermal profile .csv or .dat file + csv_file_properties: RandomVibeProfileCsvFileProperties + Properties of the random vibe profile CSV file, required if the file is in CSV format. Returns ------- @@ -1782,15 +1794,25 @@ def load_random_vibe_profile( True, True, True, - project="Test", + project="Test Project", cca_name="Card" ) >>> sherlock.lifecycle.load_random_vibe_profile( - project="Tutorial", + project="Test Project", phase_name="Phase 1", event_name="Random Event", - file_path="TestProfile.dat" + file_path="TestProfile.csv", + csv_file_properties=RandomVibeProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + numeric_format="English", + column_delimiter=",", + frequency_column="Frequency", + frequency_units="HZ", + amplitude_column="Amplitude", + amplitude_units="G2/Hz" + ) ) """ try: @@ -1802,6 +1824,17 @@ def load_random_vibe_profile( raise SherlockLoadRandomVibeProfileError(message="Event name is invalid.") if file_path == "": raise SherlockLoadRandomVibeProfileError(message="File path is invalid.") + if file_path.lower().endswith(".csv"): + if csv_file_properties is None: + raise SherlockLoadRandomVibeProfileError( + "CSV file properties must be provided for CSV random vibe profile files." + ) + else: + if csv_file_properties is not None: + raise SherlockLoadRandomVibeProfileError( + "CSV file properties are not used for non-CSV random vibe profile files." + ) + if not self._is_connection_up(): raise SherlockNoGrpcConnectionException() @@ -1810,6 +1843,9 @@ def load_random_vibe_profile( phaseName=phase_name, eventName=event_name, filePath=file_path, + randomVibeCsvProps=( + csv_file_properties._convert_to_grpc() if csv_file_properties else None + ), ) response = self.stub.loadRandomVibeProfile(request) return_code = response.returnCode @@ -1824,9 +1860,14 @@ def load_random_vibe_profile( @require_version() def load_thermal_profile( - self, project: str, phase_name: str, event_name: str, file_path: str + self, + project: str, + phase_name: str, + event_name: str, + file_path: str, + csv_file_properties: ThermalProfileCsvFileProperties = None, ) -> int: - """Load a thermal profile from a .dat or .csv file. + """Load a thermal profile from a .csv or .dat file. Available Since: 2021R1 @@ -1839,7 +1880,9 @@ def load_thermal_profile( event_name: str Name of the random vibe event. file_path: str - File path for thermal profile .dat or .csv file + File path for thermal profile .csv or .dat file + csv_file_properties: ThermalProfileCsvFileProperties + Properties of the thermal profile CSV file, required if the file is in CSV format. Returns ------- @@ -1856,14 +1899,26 @@ def load_thermal_profile( True, True, True, - project="Test", + project="Test Project", cca_name="Card", ) - >>>loaded = sherlock.lifecycle.load_thermal_profile( - project="Tutorial", + >>> sherlock.lifecycle.load_thermal_profile( + project="Test Project", phase_name="Phase 1", event_name="Thermal Event", - file_path="Tutorial_Profile.dat" + file_path="Tutorial_Profile.csv", + csv_file_properties=ThermalProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + numeric_format="English", + column_delimiter=",", + step_column="Step", + type_column="Type", + time_column="Time (min)", + time_units="min", + temp_column="Temp (C)", + temp_units="C" + ) ) """ try: @@ -1875,6 +1930,17 @@ def load_thermal_profile( raise SherlockLoadThermalProfileError(message="Event name is invalid.") if file_path == "": raise SherlockLoadThermalProfileError(message="File path is invalid.") + if file_path.lower().endswith(".csv"): + if csv_file_properties is None: + raise SherlockLoadThermalProfileError( + "CSV file properties must be provided for CSV thermal profile files." + ) + else: + if csv_file_properties is not None: + raise SherlockLoadThermalProfileError( + "CSV file properties are not used for non-CSV thermal profile files." + ) + if not self._is_connection_up(): raise SherlockNoGrpcConnectionException() @@ -1883,6 +1949,7 @@ def load_thermal_profile( phaseName=phase_name, eventName=event_name, filePath=file_path, + csvProps=csv_file_properties._convert_to_grpc() if csv_file_properties else None, ) response = self.stub.loadThermalProfile(request) return_code = response.returnCode @@ -1902,9 +1969,15 @@ def load_thermal_profile( @require_version() def load_harmonic_profile( - self, project: str, phase_name: str, event_name: str, file_path: str + self, + project: str, + phase_name: str, + event_name: str, + file_path: str, + triaxial_axis: str, + csv_file_properties: HarmonicVibeProfileCsvFileProperties = None, ) -> int: - """Load a harmonic profile from a DAT or CSV file to a life cycle phase. + """Load a harmonic profile from a .csv or .dat file to a life cycle phase. Available Since: 2021R1 @@ -1917,7 +1990,13 @@ def load_harmonic_profile( event_name: str Name of the harmonic event. file_path: str - Path for DAT or CSV file with the harmonic profile. + Path for .csv or .dat file with the harmonic profile. + triaxial_axis: str + Axis that this profile should be assigned to if the harmonic + profile type is ``"Triaxial"``. Options are: ``"x"``, ``"y"``, + and ``"z"``. + csv_file_properties: HarmonicProfileCsvFileProperties + Properties of the harmonic profile CSV file, required if the file is in CSV format. Returns ------- @@ -1934,15 +2013,26 @@ def load_harmonic_profile( True, True, True, - project="Test", + project="Test Project", cca_name="Card" ) - >>> loaded = sherlock.lifecycle.load_harmonic_profile( - project="Tutorial", + >>> sherlock.lifecycle.load_harmonic_profile( + project="Test Project", phase_name="Phase 1", event_name="Harmonic Event", - file_path="Test_Profile.dat" + file_path="Test_Profile.csv", + triaxial_axis="x", + csv_file_properties=HarmonicVibeProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + numeric_format="English", + column_delimiter=",", + frequency_column="Frequency", + frequency_units="HZ", + load_column="Load", + load_units="G" + ) ) """ try: @@ -1954,6 +2044,17 @@ def load_harmonic_profile( raise SherlockLoadHarmonicProfileError(message="Event name is invalid.") if file_path == "": raise SherlockLoadHarmonicProfileError(message="File name is invalid.") + if file_path.lower().endswith(".csv"): + if csv_file_properties is None: + raise SherlockLoadHarmonicProfileError( + "CSV file properties must be provided for CSV harmonic profile files." + ) + else: + if csv_file_properties is not None: + raise SherlockLoadHarmonicProfileError( + "CSV file properties are not used for non-CSV harmonic profile files." + ) + if not self._is_connection_up(): raise SherlockNoGrpcConnectionException() @@ -1962,6 +2063,10 @@ def load_harmonic_profile( phaseName=phase_name, eventName=event_name, filePath=file_path, + harmonicCsvProps=( + csv_file_properties._convert_to_grpc() if csv_file_properties else None + ), + triaxialAxis=triaxial_axis, ) response = self.stub.loadHarmonicProfile(request) return_code = response.returnCode @@ -1980,7 +2085,12 @@ def load_harmonic_profile( @require_version() def load_shock_profile_dataset( - self, project: str, phase_name: str, event_name: str, file_path: str + self, + project: str, + phase_name: str, + event_name: str, + file_path: str, + csv_file_properties: ShockProfileDatasetCsvFileProperties = None, ) -> int: """Load shock profile dataset from a .csv or .dat file. @@ -1995,7 +2105,9 @@ def load_shock_profile_dataset( event_name: str Name of the random vibe event. file_path: str - File path for thermal profile .dat or .csv file + File path for thermal profile .csv or .dat file + csv_file_properties: ShockProfileDatasetCsvFileProperties + Properties of the shock profile dataset CSV file, required if the file is in CSV format. Returns ------- @@ -2012,10 +2124,26 @@ def load_shock_profile_dataset( True, True, True, - project="Test", + project="Test Project", cca_name="Card" ) + >>> sherlock.lifecycle.load_shock_profile_dataset( + project="Test Project", + phase_name="Phase 1", + event_name="Shock Event", + file_path="Test_Profile.csv", + csv_file_properties=ShockProfileDatasetCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + numeric_format="English", + column_delimiter=",", + time_column="Time", + time_units="ms", + load_column="Load", + load_units="G" + ) + ) """ try: if project == "": @@ -2026,14 +2154,28 @@ def load_shock_profile_dataset( raise SherlockLoadShockProfileDatasetError(message="Event name is invalid.") if file_path == "": raise SherlockLoadShockProfileDatasetError(message="File path is invalid.") + if file_path.lower().endswith(".csv"): + if csv_file_properties is None: + raise SherlockLoadShockProfileDatasetError( + "CSV file properties must be provided for CSV shock profile dataset files." + ) + else: + if csv_file_properties is not None: + raise SherlockLoadShockProfileDatasetError( + "CSV file properties are not used for non-CSV shock profile dataset files." + ) + if not self._is_connection_up(): raise SherlockNoGrpcConnectionException() - request = SherlockLifeCycleService_pb2.LoadShockProfilePulsesRequest( + request = SherlockLifeCycleService_pb2.LoadShockProfileDatasetRequest( project=project, phaseName=phase_name, eventName=event_name, filePath=file_path, + shockDsCsvProps=( + csv_file_properties._convert_to_grpc() if csv_file_properties else None + ), ) response = self.stub.loadShockProfileDataset(request) return_code = response.returnCode @@ -2050,7 +2192,12 @@ def load_shock_profile_dataset( @require_version() def load_shock_profile_pulses( - self, project: str, phase_name: str, event_name: str, file_path: str + self, + project: str, + phase_name: str, + event_name: str, + file_path: str, + csv_file_properties: ShockProfilePulsesCsvFileProperties = None, ) -> int: """Load shock profile pulses from a .csv .dat file. @@ -2065,7 +2212,9 @@ def load_shock_profile_pulses( event_name: str Name of the random vibe event. file_path: str - Path for thermal profile .dat or .csv file + Path for thermal profile .csv or .dat file + csv_file_properties: ShockProfilePulsesCsvFileProperties + Properties of the shock profile pulses CSV file, required if the file is in CSV format. Returns ------- @@ -2089,9 +2238,24 @@ def load_shock_profile_pulses( project="Tutorial", phase_name="Phase 1", event_name="Shock Event", - file_path="Test_Profile.dat" + file_path="Test_Profile.csv", + csv_file_properties=ShockProfilePulsesCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + numeric_format="English", + column_delimiter=",", + duration=25, + duration_units="ms", + sample_rate=0.1, + sample_rate_units="ms", + shape_column="Shape", + load_column="Load", + load_units="G", + frequency_column="Frequency", + frequency_units="HZ", + decay_column="Decay", + ) ) - """ try: if project == "": @@ -2102,6 +2266,17 @@ def load_shock_profile_pulses( raise SherlockLoadShockProfilePulsesError(message="Event name is invalid.") if file_path == "": raise SherlockLoadShockProfilePulsesError(message="File path is invalid.") + if file_path.lower().endswith(".csv"): + if csv_file_properties is None: + raise SherlockLoadShockProfilePulsesError( + "CSV file properties must be provided for CSV shock profile pulses files." + ) + else: + if csv_file_properties is not None: + raise SherlockLoadShockProfilePulsesError( + "CSV file properties are not used for non-CSV shock profile pulses files." + ) + if not self._is_connection_up(): raise SherlockNoGrpcConnectionException() @@ -2110,6 +2285,9 @@ def load_shock_profile_pulses( phaseName=phase_name, eventName=event_name, filePath=file_path, + shockPulsesCsvProps=( + csv_file_properties._convert_to_grpc() if csv_file_properties else None + ), ) response = self.stub.loadShockProfilePulses(request) return_code = response.returnCode @@ -2186,7 +2364,7 @@ def import_thermal_signal( def save_harmonic_profile( self, request: SaveHarmonicProfileRequest ) -> SherlockCommonService_pb2.ReturnCode: - """Save a harmonic life cycle event profile to a .dat or .csv file. + """Save a harmonic life cycle event profile to a .csv or .dat file. Available Since: 2026R1 @@ -2233,7 +2411,7 @@ def save_harmonic_profile( def save_random_vibe_profile( self, request: SaveRandomVibeProfileRequest ) -> SherlockCommonService_pb2.ReturnCode: - """Save a random vibe life cycle event profile to a .dat or .csv file. + """Save a random vibe life cycle event profile to a .csv or .dat file. Available Since: 2026R1 @@ -2277,7 +2455,7 @@ def save_random_vibe_profile( def save_shock_pulse_profile( self, request: SaveShockPulseProfileRequest ) -> SherlockCommonService_pb2.ReturnCode: - """Save a shock pulse life cycle event profile to a .dat or .csv file. + """Save a shock pulse life cycle event profile to a .csv or .dat file. Available Since: 2026R1 @@ -2321,7 +2499,7 @@ def save_shock_pulse_profile( def save_thermal_profile( self, request: SaveThermalProfileRequest ) -> SherlockCommonService_pb2.ReturnCode: - """Save a thermal life cycle event profile to a .dat or .csv file. + """Save a thermal life cycle event profile to a .csv or .dat file. Available Since: 2026R1 diff --git a/src/ansys/sherlock/core/types/lifecycle_types.py b/src/ansys/sherlock/core/types/lifecycle_types.py index 4734b73e4..00f4be6d0 100644 --- a/src/ansys/sherlock/core/types/lifecycle_types.py +++ b/src/ansys/sherlock/core/types/lifecycle_types.py @@ -2,9 +2,11 @@ """Module containing types for the Lifecycle Service.""" +from typing import Optional + from pydantic import BaseModel, ValidationInfo, field_validator -from ansys.sherlock.core.types.common_types import basic_str_validator +from ansys.sherlock.core.types.common_types import basic_str_validator, optional_str_validator try: import SherlockLifeCycleService_pb2 @@ -12,6 +14,340 @@ from ansys.api.sherlock.v0 import SherlockLifeCycleService_pb2 +class HarmonicVibeProfileCsvFileProperties(BaseModel): + """Properties of a harmonic vibe profile CSV file.""" + + profile_name: str + """Name of the harmonic vibe profile.""" + header_row_count: int + """Number of rows before the column header in the file.""" + column_delimiter: str = "," + """Delimiter used to separate columns in the file.""" + numeric_format: str = None + """Numeric format for the values.""" + frequency_column: str + """Name of the column containing frequency values.""" + frequency_units: str + """Units of the frequency values""" + load_column: str + """Name of the column containing load values.""" + load_units: str + """Units of the load values.""" + + def _convert_to_grpc( + self, + ) -> SherlockLifeCycleService_pb2.LoadHarmonicProfileRequest.CSVProps: + """Convert to gRPC CVSProps.""" + return SherlockLifeCycleService_pb2.LoadHarmonicProfileRequest.CSVProps( + profileName=self.profile_name, + headerRowNumber=self.header_row_count, + columnDelim=self.column_delimiter, + numericFormat=self.numeric_format, + freqColumn=self.frequency_column, + freqUnits=self.frequency_units, + loadColumn=self.load_column, + loadUnits=self.load_units, + ) + + @field_validator("header_row_count") + @classmethod + def non_negative_int_validation(cls, value: int, info: ValidationInfo): + """Validate integer fields listed contain non-negative values.""" + if value < 0: + raise ValueError(f"{info.field_name} must be greater than or equal to 0.") + return value + + @field_validator( + "profile_name", "frequency_column", "frequency_units", "load_column", "load_units" + ) + @classmethod + def str_validation(cls, value: str, info: ValidationInfo): + """Validate string fields listed.""" + return basic_str_validator(value, info.field_name) + + @field_validator("column_delimiter", "numeric_format") + @classmethod + def optional_str_validation(cls, value: Optional[str], info): + """Allow the test_point_ids to not be set, i.e., None.""" + return optional_str_validator(value, info.field_name) + + +class RandomVibeProfileCsvFileProperties(BaseModel): + """Properties of a random vibe profile CSV file.""" + + profile_name: str + """Name of the random vibe profile.""" + header_row_count: int + """Number of rows before the column header in the file.""" + column_delimiter: str = "," + """Delimiter used to separate columns in the file.""" + numeric_format: str = None + """Numeric format for the values.""" + frequency_column: str + """Name of the column containing frequency values.""" + frequency_units: str + """Units of the frequency values""" + amplitude_column: str + """Name of the column containing amplitude values.""" + amplitude_units: str + """Units of the amplitude values.""" + + def _convert_to_grpc( + self, + ) -> SherlockLifeCycleService_pb2.LoadRandomVibeProfileRequest.CSVProps: + """Convert to gRPC CVSProps.""" + return SherlockLifeCycleService_pb2.LoadRandomVibeProfileRequest.CSVProps( + profileName=self.profile_name, + headerRowNumber=self.header_row_count, + columnDelim=self.column_delimiter, + numericFormat=self.numeric_format, + freqColumn=self.frequency_column, + freqUnits=self.frequency_units, + amplColumn=self.amplitude_column, + amplUnits=self.amplitude_units, + ) + + @field_validator("header_row_count") + @classmethod + def non_negative_int_validation(cls, value: int, info: ValidationInfo): + """Validate integer fields listed contain non-negative values.""" + if value < 0: + raise ValueError(f"{info.field_name} must be greater than or equal to 0.") + return value + + @field_validator( + "profile_name", "frequency_column", "frequency_units", "amplitude_column", "amplitude_units" + ) + @classmethod + def str_validation(cls, value: str, info: ValidationInfo): + """Validate string fields listed.""" + return basic_str_validator(value, info.field_name) + + @field_validator("column_delimiter", "numeric_format") + @classmethod + def optional_str_validation(cls, value: Optional[str], info): + """Allow the test_point_ids to not be set, i.e., None.""" + return optional_str_validator(value, info.field_name) + + +class ShockProfileDatasetCsvFileProperties(BaseModel): + """Properties of a shock event profile using dataset CSV file.""" + + profile_name: str + """Name of the shock vibe profile.""" + header_row_count: int + """Number of rows before the column header in the file.""" + column_delimiter: str = "," + """Delimiter used to separate columns in the file.""" + numeric_format: str = None + """Numeric format for the values.""" + time_column: str + """Name of the column containing timeuency values.""" + time_units: str + """Units of the timeuency values""" + load_column: str + """Name of the column containing load values.""" + load_units: str + """Units of the load values.""" + + def _convert_to_grpc( + self, + ) -> SherlockLifeCycleService_pb2.LoadShockProfileDatasetRequest.CSVProps: + """Convert to gRPC CVSProps.""" + return SherlockLifeCycleService_pb2.LoadShockProfileDatasetRequest.CSVProps( + profileName=self.profile_name, + headerRowNumber=self.header_row_count, + columnDelim=self.column_delimiter, + numericFormat=self.numeric_format, + timeColumn=self.time_column, + timeUnits=self.time_units, + loadColumn=self.load_column, + loadUnits=self.load_units, + ) + + @field_validator("header_row_count") + @classmethod + def non_negative_int_validation(cls, value: int, info: ValidationInfo): + """Validate integer fields listed contain non-negative values.""" + if value < 0: + raise ValueError(f"{info.field_name} must be greater than or equal to 0.") + return value + + @field_validator("profile_name", "time_column", "time_units", "load_column", "load_units") + @classmethod + def str_validation(cls, value: str, info: ValidationInfo): + """Validate string fields listed.""" + return basic_str_validator(value, info.field_name) + + @field_validator("column_delimiter", "numeric_format") + @classmethod + def optional_str_validation(cls, value: Optional[str], info): + """Allow the test_point_ids to not be set, i.e., None.""" + return optional_str_validator(value, info.field_name) + + +class ShockProfilePulsesCsvFileProperties(BaseModel): + """Properties of a shock event profile using pulses CSV file.""" + + profile_name: str + """Name of the shock vibe profile.""" + header_row_count: int + """Number of rows before the column header in the file.""" + column_delimiter: str = "," + """Delimiter used to separate columns in the file.""" + numeric_format: str = None + """Numeric format for the values.""" + duration: float + """Pulse duration length""" + duration_units: str + """Time units of the pulse duration""" + sample_rate: float + """Sample rate""" + sample_rate_units: str + """Time units of the sample rate""" + shape_column: str + """Name of the column containing shape values.""" + load_column: str + """Name of the column containing load values.""" + load_units: str + """Units of the load values.""" + frequency_column: str + """Name of the column containing frequency values.""" + frequency_units: str + """Units of the frequency values""" + decay_column: str + """Name of the column containing decay values.""" + + def _convert_to_grpc( + self, + ) -> SherlockLifeCycleService_pb2.LoadShockProfilePulsesRequest.CSVProps: + """Convert to gRPC CVSProps.""" + return SherlockLifeCycleService_pb2.LoadShockProfilePulsesRequest.CSVProps( + profileName=self.profile_name, + headerRowNumber=self.header_row_count, + columnDelim=self.column_delimiter, + numericFormat=self.numeric_format, + duration=self.duration, + durationUnits=self.duration_units, + sampleRate=self.sample_rate, + sampleRateUnits=self.sample_rate_units, + shapeColumn=self.shape_column, + loadColumn=self.load_column, + loadUnits=self.load_units, + freqColumn=self.frequency_column, + freqUnits=self.frequency_units, + decayColumn=self.decay_column, + ) + + @field_validator("header_row_count") + @classmethod + def non_negative_int_validation(cls, value: int, info: ValidationInfo): + """Validate integer fields listed contain non-negative values.""" + if value < 0: + raise ValueError(f"{info.field_name} must be greater than or equal to 0.") + return value + + @field_validator("duration", "sample_rate") + @classmethod + def greater_than_zero_float_validation(cls, value: float, info: ValidationInfo): + """Validate float fields listed contain values greater than 0.""" + if value < 0: + raise ValueError(f"{info.field_name} must be greater than 0.") + return value + + @field_validator( + "profile_name", + "duration_units", + "sample_rate_units", + "shape_column", + "load_column", + "load_units", + "frequency_column", + "frequency_units", + "decay_column", + ) + @classmethod + def str_validation(cls, value: str, info: ValidationInfo): + """Validate string fields listed.""" + return basic_str_validator(value, info.field_name) + + @field_validator("column_delimiter", "numeric_format") + @classmethod + def optional_str_validation(cls, value: Optional[str], info): + """Allow the test_point_ids to not be set, i.e., None.""" + return optional_str_validator(value, info.field_name) + + +class ThermalProfileCsvFileProperties(BaseModel): + """Properties of a thermal profile CSV file.""" + + profile_name: str + """Name of the thermal profile.""" + header_row_count: int + """Number of rows before the column header in the file.""" + column_delimiter: str = "," + """Delimiter used to separate columns in the file.""" + numeric_format: str = None + """Numeric format for the values.""" + step_column: str + """Name of the column containing step values.""" + type_column: str + """Name of the column containing step type values.""" + time_column: str + """Name of the column containing time duration values.""" + time_units: str + """Units of the time values.""" + temperature_column: str + """Name of the column containing temperature values.""" + temperature_units: str + """Units of the temperature values.""" + + def _convert_to_grpc( + self, + ) -> SherlockLifeCycleService_pb2.LoadThermalProfileRequest.CSVProps: + """Convert to gRPC CVSProps.""" + return SherlockLifeCycleService_pb2.LoadThermalProfileRequest.CSVProps( + profileName=self.profile_name, + headerRowNumber=self.header_row_count, + columnDelim=self.column_delimiter, + numericFormat=self.numeric_format, + stepColumn=self.step_column, + typeColumn=self.type_column, + timeColumn=self.time_column, + timeUnits=self.time_units, + tempColumn=self.temperature_column, + tempUnits=self.temperature_units, + ) + + @field_validator("header_row_count") + @classmethod + def non_negative_int_validation(cls, value: int, info: ValidationInfo): + """Validate integer fields listed contain non-negative values.""" + if value < 0: + raise ValueError(f"{info.field_name} must be greater than or equal to 0.") + return value + + @field_validator( + "profile_name", + "step_column", + "type_column", + "time_column", + "time_units", + "temperature_column", + "temperature_units", + ) + @classmethod + def str_validation(cls, value: str, info: ValidationInfo): + """Validate string fields listed.""" + return basic_str_validator(value, info.field_name) + + @field_validator("column_delimiter", "numeric_format") + @classmethod + def optional_str_validation(cls, value: Optional[str], info): + """Allow the test_point_ids to not be set, i.e., None.""" + return optional_str_validator(value, info.field_name) + + class ThermalSignalFileProperties(BaseModel): """Properties of a thermal signal file.""" diff --git a/tests/test_lifecycle.py b/tests/test_lifecycle.py index 9e8d4cd64..f672e92b9 100644 --- a/tests/test_lifecycle.py +++ b/tests/test_lifecycle.py @@ -28,11 +28,16 @@ from ansys.sherlock.core.types.lifecycle_types import ( DeleteEventRequest, DeletePhaseRequest, + HarmonicVibeProfileCsvFileProperties, ImportThermalSignalRequest, + RandomVibeProfileCsvFileProperties, SaveHarmonicProfileRequest, SaveRandomVibeProfileRequest, SaveShockPulseProfileRequest, SaveThermalProfileRequest, + ShockProfileDatasetCsvFileProperties, + ShockProfilePulsesCsvFileProperties, + ThermalProfileCsvFileProperties, ThermalSignalFileProperties, ) from ansys.sherlock.core.utils.version_check import SKIP_VERSION_CHECK @@ -1802,7 +1807,7 @@ def helper_test_load_random_vibe_profile(lifecycle: Lifecycle): try: lifecycle.load_random_vibe_profile( - "Test", + "Test Project", "", "Random Event", "TestProfile.dat", @@ -1813,7 +1818,7 @@ def helper_test_load_random_vibe_profile(lifecycle: Lifecycle): try: lifecycle.load_random_vibe_profile( - "Test", + "Test Project", "Phase 1", "", "TestProfile.dat", @@ -1824,7 +1829,7 @@ def helper_test_load_random_vibe_profile(lifecycle: Lifecycle): try: lifecycle.load_random_vibe_profile( - "Test", + "Test Project", "Phase 1", "Random Event", "", @@ -1833,11 +1838,192 @@ def helper_test_load_random_vibe_profile(lifecycle: Lifecycle): except SherlockLoadRandomVibeProfileError as e: assert str(e.message) == "File path is invalid." + try: + lifecycle.load_random_vibe_profile( + "Test Project", + "Phase 1", + "Random Event", + "RandomProfile.csv", + ) + pytest.fail("No exception raised when using missing CSV properties") + except SherlockLoadRandomVibeProfileError as e: + assert ( + str(e.message) + == "CSV file properties must be provided for CSV random vibe profile files." + ) + + try: + lifecycle.load_random_vibe_profile( + "Test Project", + "Phase 1", + "Random Event", + "RandomProfile.csv", + csv_file_properties=RandomVibeProfileCsvFileProperties( + profile_name="", + header_row_count=0, + column_delimiter=",", + frequency_column="Frequency", + frequency_units="Hz", + amplitude_column="Amplitude", + amplitude_units="G2/Hz", + ), + ) + pytest.fail("No exception raised when using missing profile name") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, profile_name is invalid because it is None or empty." + ) + + try: + lifecycle.load_random_vibe_profile( + "Test Project", + "Phase 1", + "Random Event", + "RandomProfile.csv", + csv_file_properties=RandomVibeProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + column_delimiter=",", + frequency_column="", + frequency_units="Hz", + amplitude_column="Amplitude", + amplitude_units="G2/Hz", + ), + ) + pytest.fail("No exception raised when using missing frequency column") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, frequency_column is invalid because it is None or empty." + ) + + try: + lifecycle.load_random_vibe_profile( + "Test Project", + "Phase 1", + "Random Event", + "RandomProfile.csv", + csv_file_properties=RandomVibeProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + column_delimiter=",", + frequency_column="Frequency", + frequency_units="", + amplitude_column="Amplitude", + amplitude_units="G2/Hz", + ), + ) + pytest.fail("No exception raised when using missing frequency units") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, frequency_units is invalid because it is None or empty." + ) + + try: + lifecycle.load_random_vibe_profile( + "Test Project", + "Phase 1", + "Random Event", + "RandomProfile.csv", + csv_file_properties=RandomVibeProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + column_delimiter=",", + frequency_column="Frequency", + frequency_units="Hz", + amplitude_column="", + amplitude_units="G2/Hz", + ), + ) + pytest.fail("No exception raised when using missing amplitude column") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, amplitude_column is invalid because it is None or empty." + ) + + try: + lifecycle.load_random_vibe_profile( + "Test Project", + "Phase 1", + "Random Event", + "RandomProfile.csv", + csv_file_properties=RandomVibeProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + column_delimiter=",", + frequency_column="Frequency", + frequency_units="Hz", + amplitude_column="Amplitude", + amplitude_units="", + ), + ) + pytest.fail("No exception raised when using missing amplitude units") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, amplitude_units is invalid because it is None or empty." + ) + + try: + lifecycle.load_random_vibe_profile( + "Test Project", + "Phase 1", + "Random Event", + "RandomProfile.csv", + csv_file_properties=RandomVibeProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=-1, + column_delimiter=",", + frequency_column="Frequency", + frequency_units="Hz", + amplitude_column="Amplitude", + amplitude_units="G2/Hz", + ), + ) + pytest.fail("No exception raised when using negative header row count") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, header_row_count must be greater than or equal to 0." + ) + + try: + lifecycle.load_random_vibe_profile( + "Test Project", + "Phase 1", + "Random Event", + "RandomProfile.dat", + csv_file_properties=RandomVibeProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + column_delimiter=",", + frequency_column="Frequency", + frequency_units="Hz", + amplitude_column="Amplitude", + amplitude_units="G2/Hz", + ), + ) + pytest.fail("No exception raised when using csv_file_properties for non-CSV file") + except SherlockLoadRandomVibeProfileError as e: + assert ( + str(e.message) + == "CSV file properties are not used for non-CSV random vibe profile files." + ) + if lifecycle._is_connection_up(): # happy path test missing because needs valid file try: lifecycle.load_random_vibe_profile( - "Invalid Project", + "Test Project", "Phase 1", "Random Event", "TestProfile.dat", @@ -1851,34 +2037,223 @@ def helper_test_load_harmonic_profile(lifecycle: Lifecycle): """Test load_harmonic_profile API.""" try: - lifecycle.load_harmonic_profile("", "Phase 1", "Harmonic Event", "Test_Profile.dat") + lifecycle.load_harmonic_profile("", "Phase 1", "Harmonic Event", "Test_Profile.dat", "X") pytest.fail("No exception raised when using an invalid parameter") except SherlockLoadHarmonicProfileError as e: - assert str(e.str_itr()) == "['Load harmonic profile error: Project name is invalid.']" + assert str(e.message) == "Project name is invalid." try: - lifecycle.load_harmonic_profile("Test", "", "Harmonic Event", "Test_Profile.dat") + lifecycle.load_harmonic_profile( + "Test Project", "", "Harmonic Event", "Test_Profile.dat", "X" + ) pytest.fail("No exception raised when using an invalid parameter") except SherlockLoadHarmonicProfileError as e: - assert str(e.str_itr()) == "['Load harmonic profile error: Phase name is invalid.']" + assert str(e.message) == "Phase name is invalid." try: - lifecycle.load_harmonic_profile("Test", "Phase 1", "", "Test_Profile.dat") + lifecycle.load_harmonic_profile("Test Project", "Phase 1", "", "Test_Profile.dat", "X") pytest.fail("No exception raised when using an invalid parameter") except SherlockLoadHarmonicProfileError as e: - assert str(e.str_itr()) == "['Load harmonic profile error: Event name is invalid.']" + assert str(e.message) == "Event name is invalid." try: - lifecycle.load_harmonic_profile("Test", "Phase 1", "Harmonic Event", "") + lifecycle.load_harmonic_profile("Test Project", "Phase 1", "Harmonic Event", "", "X") pytest.fail("No exception raised when using an invalid parameter") except SherlockLoadHarmonicProfileError as e: - assert str(e.str_itr()) == "['Load harmonic profile error: File name is invalid.']" + assert str(e.message) == "File name is invalid." + + try: + lifecycle.load_harmonic_profile( + "Test Project", + "Phase 1", + "Harmonic Event", + "Harmonic_Profile.csv", + "X", + ) + pytest.fail("No exception raised when using missing CSV properties") + except SherlockLoadHarmonicProfileError as e: + assert ( + str(e.message) == "CSV file properties must be provided for CSV harmonic profile files." + ) + + try: + lifecycle.load_harmonic_profile( + "Test Project", + "Phase 1", + "Harmonic Event", + "Harmonic_Profile.csv", + "X", + csv_file_properties=HarmonicVibeProfileCsvFileProperties( + profile_name="", + header_row_count=0, + column_delimiter=",", + frequency_column="Frequency", + frequency_units="Hz", + load_column="Load", + load_units="G", + ), + ) + pytest.fail("No exception raised when using missing profile name") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, profile_name is invalid because it is None or empty." + ) + + try: + lifecycle.load_harmonic_profile( + "Test Project", + "Phase 1", + "Harmonic Event", + "Harmonic_Profile.csv", + "X", + csv_file_properties=HarmonicVibeProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + column_delimiter=",", + frequency_column="", + frequency_units="Hz", + load_column="Load", + load_units="G", + ), + ) + pytest.fail("No exception raised when using missing frequency column") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, frequency_column is invalid because it is None or empty." + ) + + try: + lifecycle.load_harmonic_profile( + "Test Project", + "Phase 1", + "Harmonic Event", + "Harmonic_Profile.csv", + "X", + csv_file_properties=HarmonicVibeProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + column_delimiter=",", + frequency_column="Frequency", + frequency_units="", + load_column="Load", + load_units="G", + ), + ) + pytest.fail("No exception raised when using missing frequency units") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, frequency_units is invalid because it is None or empty." + ) + + try: + lifecycle.load_harmonic_profile( + "Test Project", + "Phase 1", + "Harmonic Event", + "Harmonic_Profile.csv", + "X", + csv_file_properties=HarmonicVibeProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + column_delimiter=",", + frequency_column="Frequency", + frequency_units="Hz", + load_column="", + load_units="G", + ), + ) + pytest.fail("No exception raised when using missing load column") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, load_column is invalid because it is None or empty." + ) + + try: + lifecycle.load_harmonic_profile( + "Test Project", + "Phase 1", + "Harmonic Event", + "Harmonic_Profile.csv", + "X", + csv_file_properties=HarmonicVibeProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + column_delimiter=",", + frequency_column="Frequency", + frequency_units="Hz", + load_column="Load", + load_units="", + ), + ) + pytest.fail("No exception raised when using missing load units") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, load_units is invalid because it is None or empty." + ) + + try: + lifecycle.load_harmonic_profile( + "Test Project", + "Phase 1", + "Harmonic Event", + "Harmonic_Profile.csv", + "X", + csv_file_properties=HarmonicVibeProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=-1, + column_delimiter=",", + frequency_column="Frequency", + frequency_units="Hz", + load_column="Load", + load_units="G", + ), + ) + pytest.fail("No exception raised when using negative header row count") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, header_row_count must be greater than or equal to 0." + ) + + try: + lifecycle.load_harmonic_profile( + "Test Project", + "Phase 1", + "Harmonic Event", + "Harmonic_Profile.dat", + "X", + csv_file_properties=HarmonicVibeProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + column_delimiter=",", + frequency_column="Frequency", + frequency_units="Hz", + load_column="Load", + load_units="G", + ), + ) + pytest.fail("No exception raised when using csv_file_properties for non-CSV file") + except SherlockLoadHarmonicProfileError as e: + assert ( + str(e.message) == "CSV file properties are not used for non-CSV harmonic profile files." + ) if lifecycle._is_connection_up(): # happy path test missing because needs valid file try: lifecycle.load_harmonic_profile( - "Invalid Project", + "Test Project", "Phase 1", "Harmonic Event", "Test_Profile.dat", @@ -1900,40 +2275,285 @@ def helper_test_load_thermal_profile(lifecycle: Lifecycle): ) pytest.fail("No exception raised when using an invalid parameter") except SherlockLoadThermalProfileError as e: - assert str(e.str_itr()) == "['Load thermal profile error: Project name is invalid.']" + assert str(e.message) == "Project name is invalid." try: lifecycle.load_thermal_profile( - "Test", + "Test Project", "", "Thermal Event", "Tutorial_Profile.dat", ) pytest.fail("No exception raised when using an invalid parameter") except SherlockLoadThermalProfileError as e: - assert str(e.str_itr()) == "['Load thermal profile error: Phase name is invalid.']" + assert str(e.message) == "Phase name is invalid." try: lifecycle.load_thermal_profile( - "Test", + "Test Project", "Phase 1", "", "Tutorial_Profile.dat", ) pytest.fail("No exception raised when using an invalid parameter") except SherlockLoadThermalProfileError as e: - assert str(e.str_itr()) == "['Load thermal profile error: Event name is invalid.']" + assert str(e.message) == "Event name is invalid." try: lifecycle.load_thermal_profile( - "Test", + "Test Project", "Phase 1", "Thermal Event", "", ) pytest.fail("No exception raised when using an invalid parameter") except SherlockLoadThermalProfileError as e: - assert str(e.str_itr()) == "['Load thermal profile error: File path is invalid.']" + assert str(e.message) == "File path is invalid." + + try: + lifecycle.load_thermal_profile( + "Test Project", + "Phase 1", + "Thermal Event", + "Tutorial_Profile.csv", + ) + pytest.fail("No exception raised when using missing CSV properties") + except SherlockLoadThermalProfileError as e: + assert ( + str(e.message) == "CSV file properties must be provided for CSV thermal profile files." + ) + + try: + lifecycle.load_thermal_profile( + "Test Project", + "Phase 1", + "Thermal Event", + "Tutorial_Profile.csv", + csv_file_properties=ThermalProfileCsvFileProperties( + profile_name="", + header_row_count=0, + column_delimiter=",", + step_column="Step", + type_column="Type", + time_column="Time", + time_units="min", + temperature_column="Temp", + temperature_units="C", + ), + ) + pytest.fail("No exception raised when using missing profile name") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, profile_name is invalid because it is None or empty." + ) + + try: + lifecycle.load_thermal_profile( + "Test Project", + "Phase 1", + "Thermal Event", + "Tutorial_Profile.csv", + csv_file_properties=ThermalProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + column_delimiter=",", + step_column="", + type_column="Type", + time_column="Time", + time_units="min", + temperature_column="Temp", + temperature_units="C", + ), + ) + pytest.fail("No exception raised when using missing step column") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, step_column is invalid because it is None or empty." + ) + + try: + lifecycle.load_thermal_profile( + "Test Project", + "Phase 1", + "Thermal Event", + "Tutorial_Profile.csv", + csv_file_properties=ThermalProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + column_delimiter=",", + step_column="Step", + type_column="", + time_column="Time", + time_units="min", + temperature_column="Temp", + temperature_units="C", + ), + ) + pytest.fail("No exception raised when using missing type column") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, type_column is invalid because it is None or empty." + ) + + try: + lifecycle.load_thermal_profile( + "Test Project", + "Phase 1", + "Thermal Event", + "Tutorial_Profile.csv", + csv_file_properties=ThermalProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + column_delimiter=",", + step_column="Step", + type_column="Type", + time_column="", + time_units="min", + temperature_column="Temp", + temperature_units="C", + ), + ) + pytest.fail("No exception raised when using missing time column") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, time_column is invalid because it is None or empty." + ) + + try: + lifecycle.load_thermal_profile( + "Test Project", + "Phase 1", + "Thermal Event", + "Tutorial_Profile.csv", + csv_file_properties=ThermalProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + column_delimiter=",", + step_column="Step", + type_column="Type", + time_column="Time", + time_units="", + temperature_column="Temp", + temperature_units="C", + ), + ) + pytest.fail("No exception raised when using missing time units") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, time_units is invalid because it is None or empty." + ) + + try: + lifecycle.load_thermal_profile( + "Test Project", + "Phase 1", + "Thermal Event", + "Tutorial_Profile.csv", + csv_file_properties=ThermalProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + column_delimiter=",", + step_column="Step", + type_column="Type", + time_column="Time", + time_units="min", + temperature_column="", + temperature_units="C", + ), + ) + pytest.fail("No exception raised when using missing temperature column") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, temperature_column is invalid because it is None or empty." + ) + + try: + lifecycle.load_thermal_profile( + "Test Project", + "Phase 1", + "Thermal Event", + "Tutorial_Profile.csv", + csv_file_properties=ThermalProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + column_delimiter=",", + step_column="Step", + type_column="Type", + time_column="Time", + time_units="min", + temperature_column="Temp", + temperature_units="", + ), + ) + pytest.fail("No exception raised when using missing temperature units") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, temperature_units is invalid because it is None or empty." + ) + + try: + lifecycle.load_thermal_profile( + "Test Project", + "Phase 1", + "Thermal Event", + "Tutorial_Profile.csv", + csv_file_properties=ThermalProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=-1, + column_delimiter=",", + step_column="Step", + type_column="Type", + time_column="Time", + time_units="min", + temperature_column="Temp", + temperature_units="C", + ), + ) + pytest.fail("No exception raised when using invalid header_row_count") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, header_row_count must be greater than or equal to 0." + ) + + try: + lifecycle.load_thermal_profile( + "Test Project", + "Phase 1", + "Thermal Event", + "Tutorial_Profile.dat", + csv_file_properties=ThermalProfileCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + column_delimiter=",", + step_column="Step", + type_column="Type", + time_column="Time", + time_units="min", + temperature_column="Temp", + temperature_units="C", + ), + ) + pytest.fail("No exception raised when using csv_file_properties for non-CSV file") + except SherlockLoadThermalProfileError as e: + assert ( + str(e.message) == "CSV file properties are not used for non-CSV thermal profile files." + ) if lifecycle._is_connection_up(): # happy path test missing because needs valid file @@ -1961,46 +2581,227 @@ def helper_test_load_shock_profile_dataset(lifecycle: Lifecycle): ) pytest.fail("No exception raised when using an invalid parameter") except SherlockLoadShockProfileDatasetError as e: - assert str(e.str_itr()) == "['Load shock profile dataset error: Project name is invalid.']" + assert str(e.message) == "Project name is invalid." try: lifecycle.load_shock_profile_dataset( - "Test", + "Test Project", "", "Shock Event", "Test_Profile.dat", ) pytest.fail("No exception raised when using an invalid parameter") except SherlockLoadShockProfileDatasetError as e: - assert str(e.str_itr()) == "['Load shock profile dataset error: Phase name is invalid.']" + assert str(e.message) == "Phase name is invalid." try: lifecycle.load_shock_profile_dataset( - "Test", + "Test Project", "Phase 1", "", "Test_Profile.dat", ) pytest.fail("No exception raised when using an invalid parameter") except SherlockLoadShockProfileDatasetError as e: - assert str(e.str_itr()) == "['Load shock profile dataset error: Event name is invalid.']" + assert str(e.message) == "Event name is invalid." try: lifecycle.load_shock_profile_dataset( - "Test", + "Test Project", "Phase 1", "Shock Event", "", ) pytest.fail("No exception raised when using an invalid parameter") except SherlockLoadShockProfileDatasetError as e: - assert str(e.str_itr()) == "['Load shock profile dataset error: File path is invalid.']" + assert str(e.message) == "File path is invalid." + + try: + lifecycle.load_shock_profile_dataset( + "Test Project", + "Phase 1", + "Shock Event", + "Test_Profile.csv", + ) + pytest.fail("No exception raised when using missing CSV properties") + except SherlockLoadShockProfileDatasetError as e: + assert ( + str(e.message) + == "CSV file properties must be provided for CSV shock profile dataset files." + ) + + try: + lifecycle.load_shock_profile_dataset( + "Test Project", + "Phase 1", + "Shock Event", + "Test_Profile.csv", + csv_file_properties=ShockProfileDatasetCsvFileProperties( + profile_name="", + header_row_count=0, + column_delimiter=",", + time_column="Time", + time_units="ms", + load_column="Load", + load_units="G", + ), + ) + pytest.fail("No exception raised when using missing profile name") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, profile_name is invalid because it is None or empty." + ) + + try: + lifecycle.load_shock_profile_dataset( + "Test Project", + "Phase 1", + "Shock Event", + "Test_Profile.csv", + csv_file_properties=ShockProfileDatasetCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + column_delimiter=",", + time_column="", + time_units="ms", + load_column="Load", + load_units="G", + ), + ) + pytest.fail("No exception raised when using missing time column") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, time_column is invalid because it is None or empty." + ) + + try: + lifecycle.load_shock_profile_dataset( + "Test Project", + "Phase 1", + "Shock Event", + "Test_Profile.csv", + csv_file_properties=ShockProfileDatasetCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + column_delimiter=",", + time_column="Time", + time_units="", + load_column="Load", + load_units="G", + ), + ) + pytest.fail("No exception raised when using missing time units") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, time_units is invalid because it is None or empty." + ) + + try: + lifecycle.load_shock_profile_dataset( + "Test Project", + "Phase 1", + "Shock Event", + "Test_Profile.csv", + csv_file_properties=ShockProfileDatasetCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + column_delimiter=",", + time_column="Time", + time_units="ms", + load_column="", + load_units="G", + ), + ) + pytest.fail("No exception raised when using missing load column") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, load_column is invalid because it is None or empty." + ) + + try: + lifecycle.load_shock_profile_dataset( + "Test Project", + "Phase 1", + "Shock Event", + "Test_Profile.csv", + csv_file_properties=ShockProfileDatasetCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + column_delimiter=",", + time_column="Time", + time_units="ms", + load_column="Load", + load_units="", + ), + ) + pytest.fail("No exception raised when using missing load units") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, load_units is invalid because it is None or empty." + ) + + try: + lifecycle.load_shock_profile_dataset( + "Test Project", + "Phase 1", + "Shock Event", + "Test_Profile.csv", + csv_file_properties=ShockProfileDatasetCsvFileProperties( + profile_name="Test Profile", + header_row_count=-1, + column_delimiter=",", + time_column="Time", + time_units="ms", + load_column="Load", + load_units="G", + ), + ) + pytest.fail("No exception raised when using invalid header_row_count") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, header_row_count must be greater than or equal to 0." + ) + + try: + lifecycle.load_shock_profile_dataset( + "Test Project", + "Phase 1", + "Shock Event", + "Test_Profile.dat", + csv_file_properties=ShockProfileDatasetCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + column_delimiter=",", + time_column="Time", + time_units="ms", + load_column="Load", + load_units="G", + ), + ) + pytest.fail("No exception raised when using csv_file_properties for non-CSV file") + except SherlockLoadShockProfileDatasetError as e: + assert ( + str(e.message) + == "CSV file properties are not used for non-CSV shock profile dataset files." + ) if lifecycle._is_connection_up(): # happy path test missing because needs valid file try: lifecycle.load_shock_profile_dataset( - "Tutorial Project", + "Test Project", "Phase 1", "Shock Event", "Test_Profile.dat", @@ -2021,46 +2822,456 @@ def helper_test_load_shock_profile_pulses(lifecycle: Lifecycle): ) pytest.fail("No exception raised when using an invalid parameter") except SherlockLoadShockProfilePulsesError as e: - assert str(e.str_itr()) == "['Load shock profile pulses error: Project name is invalid.']" + assert str(e.message) == "Project name is invalid." try: lifecycle.load_shock_profile_pulses( - "Test", + "Test Project", "", "Shock Event", "Test_Profile.dat", ) pytest.fail("No exception raised when using an invalid parameter") except SherlockLoadShockProfilePulsesError as e: - assert str(e.str_itr()) == "['Load shock profile pulses error: Phase name is invalid.']" + assert str(e.message) == "Phase name is invalid." try: lifecycle.load_shock_profile_pulses( - "Test", + "Test Project", "Phase 1", "", "Test_Profile.dat", ) pytest.fail("No exception raised when using an invalid parameter") except SherlockLoadShockProfilePulsesError as e: - assert str(e.str_itr()) == "['Load shock profile pulses error: Event name is invalid.']" + assert str(e.message) == "Event name is invalid." try: lifecycle.load_shock_profile_pulses( - "Test", + "Test Project", "Phase 1", "Shock Event", "", ) pytest.fail("No exception raised when using an invalid parameter") except SherlockLoadShockProfilePulsesError as e: - assert str(e.str_itr()) == "['Load shock profile pulses error: File path is invalid.']" + assert str(e.message) == "File path is invalid." + + try: + lifecycle.load_shock_profile_pulses( + "Test Project", + "Phase 1", + "Shock Event", + "Test_Profile.csv", + ) + pytest.fail("No exception raised when using missing CSV properties") + except SherlockLoadShockProfilePulsesError as e: + assert ( + str(e.message) + == "CSV file properties must be provided for CSV shock profile pulses files." + ) + + try: + lifecycle.load_shock_profile_pulses( + "Test Project", + "Phase 1", + "Shock Event", + "Test_Profile.csv", + csv_file_properties=ShockProfilePulsesCsvFileProperties( + profile_name="", + header_row_count=0, + numeric_format="English", + column_delimiter=",", + duration=25, + duration_units="ms", + sample_rate=0.1, + sample_rate_units="ms", + shape_column="Shape", + load_column="Load", + load_units="G", + frequency_column="Frequency", + frequency_units="HZ", + decay_column="Decay", + ), + ) + pytest.fail("No exception raised when using missing profile name") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, profile_name is invalid because it is None or empty." + ) + + try: + lifecycle.load_shock_profile_pulses( + "Test Project", + "Phase 1", + "Shock Event", + "Test_Profile.csv", + csv_file_properties=ShockProfilePulsesCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + numeric_format="English", + column_delimiter=",", + duration=25, + duration_units="ms", + sample_rate=0.1, + sample_rate_units="ms", + shape_column="", + load_column="Load", + load_units="G", + frequency_column="Frequency", + frequency_units="HZ", + decay_column="Decay", + ), + ) + pytest.fail("No exception raised when using missing shape column") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, shape_column is invalid because it is None or empty." + ) + + try: + lifecycle.load_shock_profile_pulses( + "Test Project", + "Phase 1", + "Shock Event", + "Test_Profile.csv", + csv_file_properties=ShockProfilePulsesCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + numeric_format="English", + column_delimiter=",", + duration=25, + duration_units="ms", + sample_rate=0.1, + sample_rate_units="ms", + shape_column="Shape", + load_column="", + load_units="G", + frequency_column="Frequency", + frequency_units="HZ", + decay_column="Decay", + ), + ) + pytest.fail("No exception raised when using missing load column") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, load_column is invalid because it is None or empty." + ) + + try: + lifecycle.load_shock_profile_pulses( + "Test Project", + "Phase 1", + "Shock Event", + "Test_Profile.csv", + csv_file_properties=ShockProfilePulsesCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + numeric_format="English", + column_delimiter=",", + duration=25, + duration_units="ms", + sample_rate=0.1, + sample_rate_units="ms", + shape_column="Shape", + load_column="Load", + load_units="", + frequency_column="Frequency", + frequency_units="HZ", + decay_column="Decay", + ), + ) + pytest.fail("No exception raised when using missing load units") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, load_units is invalid because it is None or empty." + ) + + try: + lifecycle.load_shock_profile_pulses( + "Test Project", + "Phase 1", + "Shock Event", + "Test_Profile.csv", + csv_file_properties=ShockProfilePulsesCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + numeric_format="English", + column_delimiter=",", + duration=25, + duration_units="ms", + sample_rate=0.1, + sample_rate_units="ms", + shape_column="Shape", + load_column="Load", + load_units="G", + frequency_column="", + frequency_units="HZ", + decay_column="Decay", + ), + ) + pytest.fail("No exception raised when using missing frequency column") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, frequency_column is invalid because it is None or empty." + ) + + try: + lifecycle.load_shock_profile_pulses( + "Test Project", + "Phase 1", + "Shock Event", + "Test_Profile.csv", + csv_file_properties=ShockProfilePulsesCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + numeric_format="English", + column_delimiter=",", + duration=25, + duration_units="ms", + sample_rate=0.1, + sample_rate_units="ms", + shape_column="Shape", + load_column="Load", + load_units="G", + frequency_column="Frequency", + frequency_units="", + decay_column="Decay", + ), + ) + pytest.fail("No exception raised when using missing frequency units") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, frequency_units is invalid because it is None or empty." + ) + + try: + lifecycle.load_shock_profile_pulses( + "Test Project", + "Phase 1", + "Shock Event", + "Test_Profile.csv", + csv_file_properties=ShockProfilePulsesCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + numeric_format="English", + column_delimiter=",", + duration=25, + duration_units="ms", + sample_rate=0.1, + sample_rate_units="ms", + shape_column="Shape", + load_column="Load", + load_units="G", + frequency_column="Frequency", + frequency_units="HZ", + decay_column="", + ), + ) + pytest.fail("No exception raised when using missing decay column") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, decay_column is invalid because it is None or empty." + ) + + try: + lifecycle.load_shock_profile_pulses( + "Test Project", + "Phase 1", + "Shock Event", + "Test_Profile.csv", + csv_file_properties=ShockProfilePulsesCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + numeric_format="English", + column_delimiter=",", + duration=25, + duration_units="", + sample_rate=0.1, + sample_rate_units="ms", + shape_column="Shape", + load_column="Load", + load_units="G", + frequency_column="Frequency", + frequency_units="HZ", + decay_column="Decay", + ), + ) + pytest.fail("No exception raised when using missing duration units") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, duration_units is invalid because it is None or empty." + ) + + try: + lifecycle.load_shock_profile_pulses( + "Test Project", + "Phase 1", + "Shock Event", + "Test_Profile.csv", + csv_file_properties=ShockProfilePulsesCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + numeric_format="English", + column_delimiter=",", + duration=25, + duration_units="ms", + sample_rate=0.1, + sample_rate_units="", + shape_column="Shape", + load_column="Load", + load_units="G", + frequency_column="Frequency", + frequency_units="HZ", + decay_column="Decay", + ), + ) + pytest.fail("No exception raised when using missing sample rate units") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, sample_rate_units is invalid because it is None or empty." + ) + + try: + lifecycle.load_shock_profile_pulses( + "Test Project", + "Phase 1", + "Shock Event", + "Test_Profile.csv", + csv_file_properties=ShockProfilePulsesCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + numeric_format="English", + column_delimiter=",", + duration=-25, + duration_units="ms", + sample_rate=0.1, + sample_rate_units="ms", + shape_column="Shape", + load_column="Load", + load_units="G", + frequency_column="Frequency", + frequency_units="HZ", + decay_column="Decay", + ), + ) + pytest.fail("No exception raised when using negative duration") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert str(e.errors()[0]["msg"]) == "Value error, duration must be greater than 0." + + try: + lifecycle.load_shock_profile_pulses( + "Test Project", + "Phase 1", + "Shock Event", + "Test_Profile.csv", + csv_file_properties=ShockProfilePulsesCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + numeric_format="English", + column_delimiter=",", + duration=25, + duration_units="ms", + sample_rate=-0.1, + sample_rate_units="ms", + shape_column="Shape", + load_column="Load", + load_units="G", + frequency_column="Frequency", + frequency_units="HZ", + decay_column="Decay", + ), + ) + pytest.fail("No exception raised when using negative sample rate") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert str(e.errors()[0]["msg"]) == "Value error, sample_rate must be greater than 0." + + try: + lifecycle.load_shock_profile_pulses( + "Test Project", + "Phase 1", + "Shock Event", + "Test_Profile.csv", + csv_file_properties=ShockProfilePulsesCsvFileProperties( + profile_name="Test Profile", + header_row_count=-1, + numeric_format="English", + column_delimiter=",", + duration=25, + duration_units="ms", + sample_rate=0.1, + sample_rate_units="ms", + shape_column="Shape", + load_column="Load", + load_units="G", + frequency_column="Frequency", + frequency_units="HZ", + decay_column="Decay", + ), + ) + pytest.fail("No exception raised when using invalid header_row_count") + except Exception as e: + assert isinstance(e, pydantic.ValidationError) + assert ( + str(e.errors()[0]["msg"]) + == "Value error, header_row_count must be greater than or equal to 0." + ) + + try: + lifecycle.load_shock_profile_pulses( + "Test Project", + "Phase 1", + "Shock Event", + "Test_Profile.dat", + csv_file_properties=ShockProfilePulsesCsvFileProperties( + profile_name="Test Profile", + header_row_count=0, + numeric_format="English", + column_delimiter=",", + duration=25, + duration_units="ms", + sample_rate=0.1, + sample_rate_units="ms", + shape_column="Shape", + load_column="Load", + load_units="G", + frequency_column="Frequency", + frequency_units="HZ", + decay_column="Decay", + ), + ) + pytest.fail("No exception raised when using csv_file_properties for non-CSV file") + except SherlockLoadShockProfilePulsesError as e: + assert ( + str(e.message) + == "CSV file properties are not used for non-CSV shock profile pulses files." + ) if lifecycle._is_connection_up(): # happy path test missing because needs valid file try: lifecycle.load_shock_profile_pulses( - "Tutorial Project", + "Test Project", "Phase 1", "Shock Event", "Test_Profile.dat",