From 066eac3c7c4b6194ce66086a61b0aff1349871f1 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Sun, 15 Dec 2024 02:56:49 -0300 Subject: [PATCH] DEV: add pragma comments to exclude specific lines from coverage --- .vscode/settings.json | 1 + rocketpy/environment/environment.py | 21 ++++---- rocketpy/environment/fetchers.py | 10 ++-- rocketpy/environment/tools.py | 2 +- rocketpy/mathutils/function.py | 10 ++-- rocketpy/mathutils/vector_matrix.py | 2 +- rocketpy/motors/fluid.py | 4 +- rocketpy/motors/motor.py | 4 +- rocketpy/plots/rocket_plots.py | 4 +- rocketpy/prints/compare_prints.py | 2 +- rocketpy/rocket/aero_surface/nose_cone.py | 15 +++--- rocketpy/rocket/rocket.py | 4 +- rocketpy/simulation/flight.py | 6 +-- rocketpy/tools.py | 19 +++---- rocketpy/utilities.py | 64 +++++++++++------------ 15 files changed, 82 insertions(+), 86 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 0af75e918..53e4c76d9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -262,6 +262,7 @@ "rtol", "rtype", "rucsoundings", + "runslow", "rwork", "savetxt", "savgol", diff --git a/rocketpy/environment/environment.py b/rocketpy/environment/environment.py index dbabeee78..e6db229a0 100644 --- a/rocketpy/environment/environment.py +++ b/rocketpy/environment/environment.py @@ -456,8 +456,7 @@ def __initialize_utm_coordinates(self): flattening=self.ellipsoid.flattening, semi_major_axis=self.ellipsoid.semi_major_axis, ) - else: - # pragma: no cover + else: # pragma: no cover warnings.warn( "UTM coordinates are not available for latitudes " "above 84 or below -80 degrees. The UTM conversions will fail." @@ -712,8 +711,8 @@ def set_location(self, latitude, longitude): if not isinstance(latitude, NUMERICAL_TYPES) and isinstance( longitude, NUMERICAL_TYPES - ): - # pragma: no cover + ): # pragma: no cover + raise TypeError("Latitude and Longitude must be numbers!") # Store latitude and longitude @@ -809,8 +808,8 @@ def max_expected_height(self): @max_expected_height.setter def max_expected_height(self, value): - if value < self.elevation: - raise ValueError( # pragma: no cover + if value < self.elevation: # pragma: no cover + raise ValueError( "Max expected height cannot be lower than the surface elevation" ) self._max_expected_height = value @@ -949,8 +948,8 @@ def get_elevation_from_topographic_profile(self, lat, lon): Elevation provided by the topographic data, in meters. """ # TODO: refactor this method. pylint: disable=too-many-statements - if self.topographic_profile_activated is False: - raise ValueError( # pragma: no cover + if self.topographic_profile_activated is False: # pragma: no cover + raise ValueError( "You must define a Topographic profile first, please use the " "Environment.set_topographic_profile() method first." ) @@ -1279,8 +1278,8 @@ def set_atmospheric_model( # pylint: disable=too-many-statements self.process_forecast_reanalysis(dataset, dictionary) else: self.process_ensemble(dataset, dictionary) - else: - raise ValueError(f"Unknown model type '{type}'.") # pragma: no cover + else: # pragma: no cover + raise ValueError(f"Unknown model type '{type}'.") if type not in ["ensemble"]: # Ensemble already computed these values @@ -2749,7 +2748,7 @@ def decimal_degrees_to_arc_seconds(angle): return degrees, arc_minutes, arc_seconds -if __name__ == "__main__": +if __name__ == "__main__": # pragma: no cover import doctest results = doctest.testmod() diff --git a/rocketpy/environment/fetchers.py b/rocketpy/environment/fetchers.py index 58537025b..b95f12118 100644 --- a/rocketpy/environment/fetchers.py +++ b/rocketpy/environment/fetchers.py @@ -79,12 +79,12 @@ def fetch_atmospheric_data_from_windy(lat, lon, model): try: response = requests.get(url).json() - if "data" not in response.keys(): + if "data" not in response.keys(): # pragma: no cover raise ValueError( f"Could not get a valid response for '{model}' from Windy. " "Check if the coordinates are set inside the model's domain." ) - except requests.exceptions.RequestException as e: + except requests.exceptions.RequestException as e: # pragma: no cover if model == "iconEu": raise ValueError( "Could not get a valid response for Icon-EU from Windy. " @@ -315,8 +315,8 @@ def fetch_wyoming_sounding(file): If the response indicates the output format is invalid. """ response = requests.get(file) - if response.status_code != 200: - raise ImportError(f"Unable to load {file}.") # pragma: no cover + if response.status_code != 200: # pragma: no cover + raise ImportError(f"Unable to load {file}.") if len(re.findall("Can't get .+ Observations at", response.text)): raise ValueError( re.findall("Can't get .+ Observations at .+", response.text)[0] @@ -330,7 +330,7 @@ def fetch_wyoming_sounding(file): @exponential_backoff(max_attempts=5, base_delay=2, max_delay=60) -def fetch_noaaruc_sounding(file): +def fetch_noaaruc_sounding(file): # pragma: no cover """Fetches sounding data from a specified file using the NOAA RUC soundings. Parameters diff --git a/rocketpy/environment/tools.py b/rocketpy/environment/tools.py index 4fc3ca7c7..571a7de47 100644 --- a/rocketpy/environment/tools.py +++ b/rocketpy/environment/tools.py @@ -590,7 +590,7 @@ def utm_to_geodesic( # pylint: disable=too-many-locals,too-many-statements return lat, lon -if __name__ == "__main__": +if __name__ == "__main__": # pragma: no cover import doctest results = doctest.testmod() diff --git a/rocketpy/mathutils/function.py b/rocketpy/mathutils/function.py index 8ae7a2100..75688ff8e 100644 --- a/rocketpy/mathutils/function.py +++ b/rocketpy/mathutils/function.py @@ -1479,7 +1479,7 @@ def plot(self, *args, **kwargs): else: print("Error: Only functions with 1D or 2D domains can be plotted.") - def plot1D(self, *args, **kwargs): + def plot1D(self, *args, **kwargs): # pragma: no cover """Deprecated method, use Function.plot_1d instead.""" warnings.warn( "The `Function.plot1D` method is set to be deprecated and fully " @@ -1579,7 +1579,7 @@ def plot_1d( # pylint: disable=too-many-statements if return_object: return fig, ax - def plot2D(self, *args, **kwargs): + def plot2D(self, *args, **kwargs): # pragma: no cover """Deprecated method, use Function.plot_2d instead.""" warnings.warn( "The `Function.plot2D` method is set to be deprecated and fully " @@ -2770,7 +2770,7 @@ def differentiate_complex_step(self, x, dx=1e-200, order=1): """ if order == 1: return float(self.get_value_opt(x + dx * 1j).imag / dx) - else: + else: # pragma: no cover raise NotImplementedError( "Only 1st order derivatives are supported yet. Set order=1." ) @@ -3262,7 +3262,7 @@ def __validate_source(self, source): # pylint: disable=too-many-statements self.__inputs__ = header[:-1] if self.__outputs__ is None: self.__outputs__ = [header[-1]] - except Exception as e: + except Exception as e: # pragma: no cover raise ValueError( "Could not read the csv or txt file to create Function source." ) from e @@ -3565,7 +3565,7 @@ def reset_funcified_methods(instance): instance.__dict__.pop(key) -if __name__ == "__main__": +if __name__ == "__main__": # pragma: no cover import doctest results = doctest.testmod() diff --git a/rocketpy/mathutils/vector_matrix.py b/rocketpy/mathutils/vector_matrix.py index 0da44935d..f47ef70e8 100644 --- a/rocketpy/mathutils/vector_matrix.py +++ b/rocketpy/mathutils/vector_matrix.py @@ -1093,7 +1093,7 @@ def transformation_euler_angles(roll, pitch, roll2): return Matrix.transformation(euler313_to_quaternions(roll, pitch, roll2)) -if __name__ == "__main__": +if __name__ == "__main__": # pragma: no cover import doctest results = doctest.testmod() diff --git a/rocketpy/motors/fluid.py b/rocketpy/motors/fluid.py index 4be124ec3..89c839f5e 100644 --- a/rocketpy/motors/fluid.py +++ b/rocketpy/motors/fluid.py @@ -30,9 +30,9 @@ def __post_init__(self): If the density is not a positive number. """ - if not isinstance(self.name, str): + if not isinstance(self.name, str): # pragma: no cover raise ValueError("The name must be a string.") - if self.density < 0: + if self.density < 0: # pragma: no cover raise ValueError("The density must be a positive number.") # Initialize plots and prints object diff --git a/rocketpy/motors/motor.py b/rocketpy/motors/motor.py index 93571b879..3d9762b98 100644 --- a/rocketpy/motors/motor.py +++ b/rocketpy/motors/motor.py @@ -247,7 +247,7 @@ class Function. Thrust units are Newtons. self._csys = 1 elif coordinate_system_orientation == "combustion_chamber_to_nozzle": self._csys = -1 - else: + else: # pragma: no cover raise ValueError( "Invalid coordinate system orientation. Options are " "'nozzle_to_combustion_chamber' and 'combustion_chamber_to_nozzle'." @@ -346,7 +346,7 @@ def burn_time(self, burn_time): else: if not callable(self.thrust.source): self._burn_time = (self.thrust.x_array[0], self.thrust.x_array[-1]) - else: + else: # pragma: no cover raise ValueError( "When using a float or callable as thrust source, a burn_time" " argument must be specified." diff --git a/rocketpy/plots/rocket_plots.py b/rocketpy/plots/rocket_plots.py index f305fcb99..6b571759b 100644 --- a/rocketpy/plots/rocket_plots.py +++ b/rocketpy/plots/rocket_plots.py @@ -385,7 +385,7 @@ def _draw_generic_surface( x_pos = position[2] # y position of the surface is the y position in the plot y_pos = position[1] - else: + else: # pragma: no cover raise ValueError("Plane must be 'xz' or 'yz'.") ax.scatter( @@ -633,7 +633,7 @@ def _draw_sensors(self, ax, sensors, plane): # y position of the sensor is the y position in the plot y_pos = pos[1] normal_y = sensor.normal_vector.y - else: + else: # pragma: no cover raise ValueError("Plane must be 'xz' or 'yz'.") # line length is 2/5 of the rocket radius diff --git a/rocketpy/prints/compare_prints.py b/rocketpy/prints/compare_prints.py index 9b94f2da4..548dd0238 100644 --- a/rocketpy/prints/compare_prints.py +++ b/rocketpy/prints/compare_prints.py @@ -1,3 +1,3 @@ -class _ComparePrints: +class _ComparePrints: # pragma: no cover def __init__(self) -> None: pass diff --git a/rocketpy/rocket/aero_surface/nose_cone.py b/rocketpy/rocket/aero_surface/nose_cone.py index c8f3a2062..c1f8d59a9 100644 --- a/rocketpy/rocket/aero_surface/nose_cone.py +++ b/rocketpy/rocket/aero_surface/nose_cone.py @@ -135,7 +135,7 @@ def __init__( # pylint: disable=too-many-statements self._base_radius = base_radius self._length = length if bluffness is not None: - if bluffness > 1 or bluffness < 0: + if bluffness > 1 or bluffness < 0: # pragma: no cover raise ValueError( f"Bluffness ratio of {bluffness} is out of range. " "It must be between 0 and 1." @@ -286,7 +286,7 @@ def theta(x): self.y_nosecone = Function( lambda x: self.base_radius * np.power(x / self.length, self.power) ) - else: + else: # pragma: no cover raise ValueError( f"Nose Cone kind '{self.kind}' not found, " + "please use one of the following Nose Cone kinds:" @@ -317,12 +317,11 @@ def bluffness(self, value): raise ValueError( "Parameter 'bluffness' must be None or 0 when using a nose cone kind 'powerseries'." ) - if value is not None: - if value > 1 or value < 0: - raise ValueError( - f"Bluffness ratio of {value} is out of range. " - "It must be between 0 and 1." - ) + if value is not None and not (0 <= value <= 1): # pragma: no cover + raise ValueError( + f"Bluffness ratio of {value} is out of range. " + "It must be between 0 and 1." + ) self._bluffness = value self.evaluate_nose_shape() diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index aacf5b4a6..73679db95 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -278,7 +278,7 @@ def __init__( # pylint: disable=too-many-statements self._csys = 1 elif coordinate_system_orientation == "nose_to_tail": self._csys = -1 - else: + else: # pragma: no cover raise TypeError( "Invalid coordinate system orientation. Please choose between " + '"tail_to_nose" and "nose_to_tail".' @@ -1173,7 +1173,7 @@ def add_nose( self.add_surfaces(nose, position) return nose - def add_fins(self, *args, **kwargs): + def add_fins(self, *args, **kwargs): # pragma: no cover """See Rocket.add_trapezoidal_fins for documentation. This method is set to be deprecated in version 1.0.0 and fully removed by version 2.0.0. Use Rocket.add_trapezoidal_fins instead. It keeps the diff --git a/rocketpy/simulation/flight.py b/rocketpy/simulation/flight.py index e4be64f5a..87ab6b33a 100644 --- a/rocketpy/simulation/flight.py +++ b/rocketpy/simulation/flight.py @@ -872,11 +872,11 @@ def __simulate(self, verbose): for t_root in t_roots if 0 < t_root.real < t1 and abs(t_root.imag) < 0.001 ] - if len(valid_t_root) > 1: + if len(valid_t_root) > 1: # pragma: no cover raise ValueError( "Multiple roots found when solving for rail exit time." ) - if len(valid_t_root) == 0: + if len(valid_t_root) == 0: # pragma: no cover raise ValueError( "No valid roots found when solving for rail exit time." ) @@ -3441,7 +3441,7 @@ def __len__(self): def __repr__(self): return str(self.list) - def display_warning(self, *messages): + def display_warning(self, *messages): # pragma: no cover """A simple function to print a warning message.""" print("WARNING:", *messages) diff --git a/rocketpy/tools.py b/rocketpy/tools.py index 46660755a..e0cd35393 100644 --- a/rocketpy/tools.py +++ b/rocketpy/tools.py @@ -27,11 +27,10 @@ def tuple_handler(value): - """Transforms the input value into a tuple that - represents a range. If the input is an int or float, - the output is a tuple from zero to the input value. If - the input is a tuple or list, the output is a tuple with - the same range. + """Transforms the input value into a tuple that represents a range. If the + input is an int or float, the output is a tuple from zero to the input + value. If the input is a tuple or list, the output is a tuple with the same + range. Parameters ---------- @@ -263,7 +262,7 @@ def get_distribution(distribution_function_name): } try: return distributions[distribution_function_name] - except KeyError as e: + except KeyError as e: # pragma: no cover raise ValueError( f"Distribution function '{distribution_function_name}' not found, " + "please use one of the following np.random distribution function:" @@ -913,7 +912,7 @@ def import_optional_dependency(name): """ try: module = importlib.import_module(name) - except ImportError as exc: + except ImportError as exc: # pragma: no cover module_name = name.split(".")[0] package_name = INSTALL_MAPPING.get(module_name, module_name) raise ImportError( @@ -977,7 +976,9 @@ def wrapper(*args, **kwargs): for i in range(max_attempts): try: return func(*args, **kwargs) - except Exception as e: # pylint: disable=broad-except + except ( + Exception + ) as e: # pragma: no cover # pylint: disable=broad-except if i == max_attempts - 1: raise e from None delay = min(delay * 2, max_delay) @@ -1167,7 +1168,7 @@ def get_matplotlib_supported_file_endings(): return filetypes -if __name__ == "__main__": +if __name__ == "__main__": # pragma: no cover import doctest res = doctest.testmod() diff --git a/rocketpy/utilities.py b/rocketpy/utilities.py index f6e68d162..4dcf7ebf5 100644 --- a/rocketpy/utilities.py +++ b/rocketpy/utilities.py @@ -14,7 +14,6 @@ from .simulation.flight import Flight -# TODO: Needs tests def compute_cd_s_from_drop_test( terminal_velocity, rocket_mass, air_density=1.225, g=9.80665 ): @@ -39,13 +38,34 @@ def compute_cd_s_from_drop_test( ------- cd_s : float Number equal to drag coefficient times reference area for parachute. + """ + return 2 * rocket_mass * g / ((terminal_velocity**2) * air_density) + +def check_constant(f, eps): """ + Check for three consecutive elements in the list that are approximately + equal within a tolerance. - return 2 * rocket_mass * g / ((terminal_velocity**2) * air_density) + Parameters + ---------- + f : list or array + A list or array of numerical values. + eps : float + The tolerance level for comparing the elements. + + Returns + ------- + int or None + The index of the first element in the first sequence of three + consecutive elements that are approximately equal within the tolerance. + Returns None if no such sequence is found. + """ + for i in range(len(f) - 2): + if abs(f[i + 2] - f[i + 1]) < eps and abs(f[i + 1] - f[i]) < eps: + return i -# TODO: Needs tests def calculate_equilibrium_altitude( rocket_mass, cd_s, @@ -90,7 +110,6 @@ def calculate_equilibrium_altitude( affect the final result if the value is not high enough. Increase the estimative in case the final solution is not founded. - Returns ------- altitude_function: Function @@ -103,30 +122,8 @@ def calculate_equilibrium_altitude( """ final_sol = {} - if v0 >= 0: - print("Please set a valid negative value for v0") - return None - - # TODO: Improve docs - def check_constant(f, eps): - """_summary_ - - Parameters - ---------- - f : array, list - - _description_ - eps : float - _description_ - - Returns - ------- - int, None - _description_ - """ - for i in range(len(f) - 2): - if abs(f[i + 2] - f[i + 1]) < eps and abs(f[i + 1] - f[i]) < eps: - return i + if v0 >= 0: # pragma: no cover + raise ValueError("Please set a valid negative value for v0") if env is None: environment = Environment( @@ -138,21 +135,20 @@ def check_constant(f, eps): else: environment = env - # TODO: Improve docs def du(z, u): - """_summary_ + """Returns the derivative of the velocity at a given altitude. Parameters ---------- z : float - _description_ + altitude, in meters, at a given time u : float velocity, in m/s, at a given z altitude Returns ------- float - _description_ + velocity at a given altitude """ return ( u[1], @@ -258,7 +254,7 @@ def fin_flutter_analysis( found_fin = True else: warnings.warn("More than one fin set found. The last one will be used.") - if not found_fin: + if not found_fin: # pragma: no cover raise AttributeError( "There is no TrapezoidalFins in the rocket, can't run Flutter Analysis." ) @@ -442,7 +438,7 @@ def _flutter_prints( # TODO: deprecate and delete this function. Never used and now we have Monte Carlo. -def create_dispersion_dictionary(filename): +def create_dispersion_dictionary(filename): # pragma: no cover """Creates a dictionary with the rocket data provided by a .csv file. File should be organized in four columns: attribute_class, parameter_name, mean_value, standard_deviation. The first row should be the header.