@@ -27,6 +27,7 @@ class JPLSpecClass(BaseQuery):
2727
2828 # use the Configuration Items imported from __init__.py
2929 URL = conf .server
30+ FTP_CAT_URL = conf .ftp_cat_server
3031 TIMEOUT = conf .timeout
3132
3233 def __init__ (self ):
@@ -142,6 +143,7 @@ def query_lines(self, min_frequency, max_frequency, *,
142143 parse_name_locally = False ,
143144 get_query_payload = False ,
144145 fallback_to_getmolecule = True ,
146+ use_getmolecule = True ,
145147 cache = True ):
146148 """
147149 Query the JPLSpec service for spectral lines.
@@ -153,7 +155,20 @@ def query_lines(self, min_frequency, max_frequency, *,
153155 governs whether `get_molecule` will be used when no results are returned
154156 by the query service. This workaround is needed while JPLSpec's query
155157 tool is broken.
158+
159+ use_getmolecule is an option to skip the query service entirely and
160+ retrieve full molecule catalogs via `get_molecule`. It is needed when
161+ the JPL query server is unresponsive. Frequency and strength limits
162+ are not applied in this mode.
156163 """
164+ if use_getmolecule :
165+ if get_query_payload :
166+ return [('Mol' , tuple (self ._resolve_molecules (molecule , flags = flags ,
167+ parse_name_locally = parse_name_locally )))]
168+ mols = self ._resolve_molecules (molecule , flags = flags ,
169+ parse_name_locally = parse_name_locally )
170+ return self ._build_table_from_get_molecule (mols )
171+
157172 response = self .query_lines_async (min_frequency = min_frequency ,
158173 max_frequency = max_frequency ,
159174 min_strength = min_strength ,
@@ -170,6 +185,45 @@ def query_lines(self, min_frequency, max_frequency, *,
170185
171186 query_lines .__doc__ = process_asyncs .async_to_sync_docstr (query_lines_async .__doc__ )
172187
188+ def _resolve_molecules (self , molecule , * , flags = 0 , parse_name_locally = False ):
189+ """Return a list of molecule identifiers to feed to get_molecule."""
190+ if molecule is None or molecule == 'All' :
191+ raise InvalidQueryError ("use_getmolecule requires an explicit molecule "
192+ "or regex; 'All' is not supported." )
193+ if parse_name_locally :
194+ self .lookup_ids = build_lookup ()
195+ mols = list (self .lookup_ids .find (molecule , flags ).values ())
196+ if len (mols ) == 0 :
197+ raise InvalidQueryError ('No matching species found.' )
198+ return mols
199+ if isinstance (molecule , (list , tuple )):
200+ return list (molecule )
201+ return [molecule ]
202+
203+ def _build_table_from_get_molecule (self , mols ):
204+ """
205+ Fetch full catalog tables for each molecule and combine them.
206+
207+ ``mols`` should be passed through ``_resolve_molecules`` before being
208+ sent to this function if it is user-specified, but if it comes directly
209+ from a query, it should be trusted as-is.
210+ """
211+ self .lookup_ids = build_lookup ()
212+ tbs = [self .get_molecule (mol ) for mol in mols ]
213+ if len (tbs ) > 1 :
214+ for tb , mol in zip (tbs , mols ):
215+ tb ['Name' ] = self .lookup_ids .find (str (mol ), flags = 0 )
216+ for key in list (tb .meta .keys ()):
217+ tb .meta [f'{ mol } _{ key } ' ] = tb .meta .pop (key )
218+ tb = table .vstack (tbs )
219+ tb .meta ['molecule_list' ] = list (mols )
220+ return tb
221+ else :
222+ tb = tbs [0 ]
223+ tb .meta ['molecule_id' ] = mols [0 ]
224+ tb .meta ['molecule_name' ] = self .lookup_ids .find (str (mols [0 ]), flags = 0 )
225+ return tb
226+
173227 def _parse_result (self , response , * , verbose = False , fallback_to_getmolecule = False ):
174228 """
175229 Parse a response into an `~astropy.table.Table`
@@ -203,26 +257,12 @@ def _parse_result(self, response, *, verbose=False, fallback_to_getmolecule=Fals
203257
204258 if 'Zero lines were found' in response .text :
205259 if fallback_to_getmolecule :
206- self .lookup_ids = build_lookup ()
207260 payload = parse_qs (response .request .body )
208- tbs = [self .get_molecule (mol ) for mol in payload ['Mol' ]]
209- if len (tbs ) > 1 :
210- mols = []
211- for tb , mol in zip (tbs , payload ['Mol' ]):
212- tb ['Name' ] = self .lookup_ids .find (mol , flags = 0 )
213- for key in list (tb .meta .keys ()):
214- tb .meta [f'{ mol } _{ key } ' ] = tb .meta .pop (key )
215- mols .append (mol )
216- tb = table .vstack (tbs )
217- tb .meta ['molecule_list' ] = mols
218- else :
219- tb = tbs [0 ]
220- tb .meta ['molecule_id' ] = payload ['Mol' ][0 ]
221- tb .meta ['molecule_name' ] = self .lookup_ids .find (payload ['Mol' ][0 ], flags = 0 )
222-
223- return tb
224- else :
225- raise EmptyResponseError (f"Response was empty; message was '{ response .text } '." )
261+ return self ._build_table_from_get_molecule (
262+ [payload ['Mol' ]]
263+ if isinstance (payload ['Mol' ], str )
264+ else payload ['Mol' ])
265+ raise EmptyResponseError (f"Response was empty; message was '{ response .text } '." )
226266
227267 # data starts at 0 since regex was applied
228268 # Warning for a result with more than 1000 lines:
@@ -320,7 +360,7 @@ def get_molecule(self, molecule_id, *, cache=True):
320360 molecule_str = parse_molid (molecule_id )
321361
322362 # Construct the URL to the catalog file
323- url = f'https://spec.jpl.nasa.gov/ftp/pub/catalog /c{ molecule_str } .cat'
363+ url = f'{ self . FTP_CAT_URL } /c{ molecule_str } .cat'
324364
325365 # Request the catalog file
326366 response = self ._request (method = 'GET' , url = url ,
0 commit comments