55import xml .etree .ElementTree as ET
66from abc import ABC , abstractmethod
77from functools import cached_property
8- from os import path
8+ from os import path , remove
99
1010import numpy as np
1111import requests
12-
12+ import logging
1313from ..mathutils .function import Function , funcify_method
1414from ..plots .motor_plots import _MotorPlots
1515from ..prints .motor_prints import _MotorPrints
1616from ..tools import parallel_axis_theorem_from_com , tuple_handler
1717
18+ logger = logging .getLogger (__name__ )
19+
1820
1921# pylint: disable=too-many-public-methods
2022class Motor (ABC ):
@@ -1916,7 +1918,7 @@ def load_from_rse_file(
19161918 interpolation_method = interpolation_method ,
19171919 coordinate_system_orientation = coordinate_system_orientation ,
19181920 )
1919-
1921+
19201922 @staticmethod
19211923 def load_from_thrustcurve_api (name : str , ** kwargs ):
19221924 """
@@ -1926,16 +1928,25 @@ def load_from_thrustcurve_api(name: str, **kwargs):
19261928 Parameters
19271929 ----------
19281930 name : str
1929- The motor name according to the API (e.g., "Cesaroni_M1670").
1931+ The motor name according to the API (e.g., "Cesaroni_M1670" or "M1670").
1932+ Both manufacturer-prefixed and shorthand names are commonly used; if multiple
1933+ motors match the search, the first result is used.
19301934 **kwargs :
1931- Additional arguments passed to the Motor constructor, such as dry_mass, nozzle_radius, etc.
1935+ Additional arguments passed to the Motor constructor or loader, such as
1936+ dry_mass, nozzle_radius, etc.
19321937
19331938 Returns
19341939 -------
1935- instance : cls
1936- A new Motor instance initialized using the downloaded .eng file.
1940+ instance : GenericMotor
1941+ A new GenericMotor instance initialized using the downloaded .eng file.
1942+
1943+ Raises
1944+ ------
1945+ ValueError
1946+ If no motor is found or if the downloaded .eng data is missing.
1947+ requests.exceptions.RequestException
1948+ If a network or HTTP error occurs during the API call.
19371949 """
1938-
19391950 base_url = "https://www.thrustcurve.org/api/v1"
19401951
19411952 # Step 1. Search motor
@@ -1944,34 +1955,55 @@ def load_from_thrustcurve_api(name: str, **kwargs):
19441955 data = response .json ()
19451956
19461957 if not data .get ("results" ):
1947- print ("No motor found." )
1948- return None
1958+ raise ValueError (
1959+ f"No motor found for name '{ name } '. "
1960+ "Please verify the motor name format (e.g., 'Cesaroni_M1670' or 'M1670') and try again."
1961+ )
19491962
1950- motor = data ["results" ][0 ]
1951- motor_id = motor ["motorId" ]
1952- designation = motor ["designation" ].replace ("/" , "-" )
1953- print (f"Motor found: { designation } ({ motor ['manufacturer' ]} )" )
1963+ motor_info = data ["results" ][0 ]
1964+ motor_id = motor_info .get ("motorId" )
1965+ designation = motor_info .get ("designation" , "" ).replace ("/" , "-" )
1966+ manufacturer = motor_info .get ("manufacturer" , "" )
1967+ # Logging the fact that the motor was found
1968+ logger .info (f"Motor found: { designation } ({ manufacturer } )" )
19541969
19551970 # Step 2. Download the .eng file
19561971 dl_response = requests .get (
19571972 f"{ base_url } /download.json" ,
19581973 params = {"motorIds" : motor_id , "format" : "RASP" , "data" : "file" },
19591974 )
19601975 dl_response .raise_for_status ()
1961- data = dl_response .json ()
1976+ dl_data = dl_response .json ()
19621977
1963- data_base64 = data ["results" ][0 ]["data" ]
1964- data_bytes = base64 .b64decode (data_base64 )
1965-
1966- # Step 3. Create the motor from the .eng file
1978+ if not dl_data .get ("results" ):
1979+ raise ValueError (f"No .eng file found for motor '{ name } ' in the ThrustCurve API." )
19671980
1968- with tempfile . NamedTemporaryFile ( suffix = ".eng" , delete = True ) as tmp_file :
1969- tmp_file . write ( data_bytes )
1970- tmp_file . flush ( )
1981+ data_base64 = dl_data [ "results" ][ 0 ]. get ( "data" )
1982+ if not data_base64 :
1983+ raise ValueError ( f"Downloaded .eng data for motor ' { name } ' is empty or invalid." )
19711984
1972- motor = GenericMotor . load_from_eng_file ( tmp_file . name , ** kwargs )
1985+ data_bytes = base64 . b64decode ( data_base64 )
19731986
1974- return motor
1987+ # Step 3. Create the motor from the .eng file
1988+ tmp_path = None
1989+ try :
1990+ # create a temporary file that persists until we explicitly remove it
1991+ with tempfile .NamedTemporaryFile (suffix = ".eng" , delete = False ) as tmp_file :
1992+ tmp_file .write (data_bytes )
1993+ tmp_file .flush ()
1994+ tmp_path = tmp_file .name
1995+
1996+
1997+ motor_instance = GenericMotor .load_from_eng_file (tmp_path , ** kwargs )
1998+ return motor_instance
1999+ finally :
2000+ # Ensuring the temporary file is removed
2001+ if tmp_path and path .exists (tmp_path ):
2002+ try :
2003+ remove (tmp_path )
2004+ except OSError :
2005+ # If cleanup fails, don't raise: we don't want to mask prior exceptions.
2006+ pass
19752007
19762008 def all_info (self ):
19772009 """Prints out all data and graphs available about the Motor."""
0 commit comments