1010import time
1111from abc import ABC , abstractmethod
1212from dataclasses import dataclass
13- from typing import Dict , Union
13+ from typing import Dict , List , Union
1414
1515import pandas as pd
1616from loguru import logger
1717
1818from disruption_py .config import config
19+ from disruption_py .core .physics_method .errors import FetchDataError
1920from disruption_py .core .utils .enums import map_string_to_enum
2021from disruption_py .core .utils .misc import get_temporary_folder
2122from disruption_py .inout .base import DataConnection
23+ from disruption_py .inout .mds import mdsExceptions
2224from disruption_py .inout .sql import ShotDatabase
2325from disruption_py .machine .tokamak import Tokamak
2426
2527NicknameSettingType = Union [
26- "NicknameSettingType" , str , Dict [Tokamak , "NicknameSettingType" ]
28+ "NicknameSetting" ,
29+ str ,
30+ List [Union [str , "NicknameSetting" ]],
31+ Dict [Tokamak , "NicknameSettingType" ],
2732]
2833
2934
@@ -232,6 +237,91 @@ def _get_tree_name(self, params: NicknameSettingParams) -> str:
232237 return self .tree_name
233238
234239
240+ class NicknameSettingList (NicknameSetting ):
241+ """
242+ Cascade nickname setting. Tries each item in order via ``open_tree``,
243+ returning the first that opens successfully. Falls back to the next on
244+ ``mdsExceptions.TreeFOPENR``.
245+
246+ Parameters
247+ ----------
248+ items : list
249+ Cascade of tree-name strings or NicknameSetting instances. Tried in
250+ order; the first item that opens wins.
251+ """
252+
253+ def __init__ (self , items : list ):
254+ """
255+ Initialize with a cascade of items to try in order.
256+
257+ Parameters
258+ ----------
259+ items : list
260+ Non-empty cascade. Each item is a tree-name string or a
261+ NicknameSetting instance.
262+ """
263+ if not items :
264+ raise ValueError ("NicknameSettingList requires at least one item." )
265+ self .items = items
266+ self .resolved_items = [self ._resolve_item (item ) for item in items ]
267+
268+ @staticmethod
269+ def _resolve_item (item ):
270+ """Validate a cascade item: must be a string or NicknameSetting."""
271+ if isinstance (item , NicknameSetting ):
272+ return item
273+ if isinstance (item , str ):
274+ return item
275+ raise ValueError (
276+ "NicknameSettingList items must be str or NicknameSetting, "
277+ f"got { type (item ).__name__ } : { item !r} "
278+ )
279+
280+ def _get_tree_name (self , params : NicknameSettingParams ) -> str :
281+ """
282+ Try each cascade item via ``open_tree``; return the first that opens.
283+
284+ Parameters
285+ ----------
286+ params : NicknameSettingParams
287+ Parameters needed to determine the nickname.
288+
289+ Returns
290+ -------
291+ str
292+ The tree name of the first cascade item that opened successfully.
293+
294+ Raises
295+ ------
296+ FetchDataError
297+ If no item in the cascade could be opened.
298+ """
299+ attempts : List [str ] = []
300+ for item in self .resolved_items :
301+ candidate = item if isinstance (item , str ) else type (item ).__name__
302+ try :
303+ if isinstance (item , NicknameSetting ):
304+ candidate = item .get_tree_name (params )
305+ params .data_conn .open_tree (candidate )
306+ except (mdsExceptions .TreeFOPENR , FetchDataError ):
307+ # TreeFOPENR = tree missing; FetchDataError = nested cascade exhausted.
308+ attempts .append (candidate )
309+ continue
310+ if attempts :
311+ logger .verbose (
312+ "Nickname cascade for shot {shot}: selected '{name}' "
313+ "after failed: {prior}" ,
314+ shot = params .shot_id ,
315+ name = candidate ,
316+ prior = ", " .join (attempts ),
317+ )
318+ return candidate
319+ raise FetchDataError (
320+ f"No tree in nickname cascade could be opened for shot "
321+ f"{ params .shot_id } . Tried: { attempts } "
322+ )
323+
324+
235325class DefaultNicknameSetting (NicknameSetting ):
236326 """
237327 Nickname setting to resolve the '_efit_tree' nickname to the default EFIT tree.
@@ -417,9 +507,7 @@ def _cmod_nickname(self, params: NicknameSettingParams) -> str:
417507 tree = config (params .tokamak ).efit .tree
418508 if "pytest" in sys .modules :
419509 tree = "efit18"
420- if tree == "efit18" and params .disruption_time is None :
421- return DefaultNicknameSetting ().get_tree_name (params )
422- return tree
510+ return NicknameSettingList ([tree , "analysis" ]).get_tree_name (params )
423511
424512 def _get_tree_name (self , params : NicknameSettingParams ) -> str :
425513 """
@@ -453,7 +541,9 @@ def resolve_nickname_setting(nickname_setting: NicknameSettingType) -> NicknameS
453541 Parameters
454542 ----------
455543 nickname_setting : NicknameSettingType
456- The nickname setting, which can be a string, a dictionary, or a NicknameSetting instance.
544+ The nickname setting: a NicknameSetting instance, a string (single tree
545+ name, registered key like "disruption", or comma-separated cascade), a
546+ list (cascade), or a dictionary (per-tokamak dispatch).
457547
458548 Returns
459549 -------
@@ -462,11 +552,21 @@ def resolve_nickname_setting(nickname_setting: NicknameSettingType) -> NicknameS
462552 """
463553 if isinstance (nickname_setting , NicknameSetting ):
464554 return nickname_setting
555+ if isinstance (nickname_setting , list ):
556+ return NicknameSettingList (nickname_setting )
465557 if isinstance (nickname_setting , dict ):
466558 return NicknameSettingDict (nickname_setting )
467559 if isinstance (nickname_setting , str ):
468560 if nickname_setting in _nickname_setting_mappings :
469561 return _nickname_setting_mappings [nickname_setting ]
562+ if "," in nickname_setting :
563+ tokens = [s .strip () for s in nickname_setting .split ("," ) if s .strip ()]
564+ # Registered keys map to their class; other tokens are literal tree names.
565+ items = [
566+ _nickname_setting_mappings [s ] if s in _nickname_setting_mappings else s
567+ for s in tokens
568+ ]
569+ return NicknameSettingList (items )
470570 return StaticNicknameSetting (nickname_setting )
471571
472572 raise ValueError (f"Invalid nickname setting type { type (nickname_setting )} ." )
0 commit comments