Source code for ams.utils.paths

"""
Utility functions for loading ams stock test cases,
mainly revised from ``andes.utils.paths``.
"""
import logging
import os
import pathlib
import tempfile

logger = logging.getLogger(__name__)


[docs] class DisplayablePath: display_filename_prefix_middle = '├──' display_filename_prefix_last = '└──' display_parent_prefix_middle = ' ' display_parent_prefix_last = '│ '
[docs] def __init__(self, path, parent_path, is_last): self.path = pathlib.Path(str(path)) self.parent = parent_path self.is_last = is_last if self.parent: self.depth = self.parent.depth + 1 else: self.depth = 0
@property def displayname(self): if self.path.is_dir(): return self.path.name + '/' return self.path.name
[docs] @classmethod def make_tree(cls, root, parent=None, is_last=False, criteria=None): root = pathlib.Path(str(root)) criteria = criteria or cls._default_criteria displayable_root = cls(root, parent, is_last) yield displayable_root children = sorted(list(path for path in root.iterdir() if criteria(path)), key=lambda s: str(s).lower()) count = 1 for path in children: is_last = count == len(children) if path.is_dir(): yield from cls.make_tree(path, parent=displayable_root, is_last=is_last, criteria=criteria) else: yield cls(path, displayable_root, is_last) count += 1
@classmethod def _default_criteria(cls, path): return True
[docs] def displayable(self): if self.parent is None: return self.displayname _filename_prefix = (self.display_filename_prefix_last if self.is_last else self.display_filename_prefix_middle) parts = ['{!s} {!s}'.format(_filename_prefix, self.displayname)] parent = self.parent while parent and parent.parent is not None: parts.append(self.display_parent_prefix_middle if parent.is_last else self.display_parent_prefix_last) parent = parent.parent return ''.join(reversed(parts))
[docs] def ams_root(): """ Return the root path to the ams source code. """ dir_name = os.path.dirname(os.path.abspath(__file__)) return os.path.normpath(os.path.join(dir_name, '..'))
[docs] def cases_root(): """ Return the root path to the stock cases """ dir_name = os.path.dirname(os.path.abspath(__file__)) return os.path.normpath(os.path.join(dir_name, '..', 'cases'))
[docs] def tests_root(): """Return the root path to the stock cases""" dir_name = os.path.dirname(os.path.abspath(__file__)) return os.path.normpath(os.path.join(dir_name, '..', '..', 'tests'))
[docs] def get_case(rpath, check=True): """ Return the path to a stock case for a given path relative to ``ams/cases``. To list all cases, use ``ams.list_cases()``. Parameters ---------- check : bool True to check if file exists Examples -------- To get the path to the case `kundur_full.xlsx` under folder `kundur`, do :: ams.get_case('kundur/kundur_full.xlsx') """ case_path = os.path.join(cases_root(), rpath) case_path = os.path.normpath(case_path) if check is True and (not os.path.isfile(case_path)): raise FileNotFoundError(f'"{rpath}" is not a valid relative path to a stock case.') return case_path
[docs] def list_cases(rpath='.', no_print=False): """ List stock cases under a given folder relative to ``ams/cases`` """ case_path = os.path.join(cases_root(), rpath) case_path = os.path.normpath(case_path) tree = DisplayablePath.make_tree(pathlib.Path(case_path)) out = [] for path in tree: out.append(path.displayable()) out = '\n'.join(out) if no_print is False: print(out) else: return out
[docs] def get_config_path(file_name='ams.rc'): """ Return the path of the config file to be loaded. Search Priority: 1. current directory; 2. home directory. Parameters ---------- file_name : str, optional Config file name with the default as ``ams.rc``. Returns ------- Config path in string if found; None otherwise. """ conf_path = None home_dir = os.path.expanduser('~') # test ./ams.conf if os.path.isfile(file_name): conf_path = file_name # test ~/ams.conf elif os.path.isfile(os.path.join(home_dir, '.ams', file_name)): conf_path = os.path.join(home_dir, '.ams', file_name) return conf_path
[docs] def get_pycode_path(pycode_path=None, mkdir=False): """ Get the path to the ``pycode`` folder. """ if pycode_path is None: pycode_path = os.path.join(get_dot_ams_path(), 'pycode') if mkdir is True: os.makedirs(pycode_path, exist_ok=True) return pycode_path
[docs] def get_pkl_path(): """ Get the path to the picked/dilled function calls. Returns ------- str Path to the calls.pkl file """ pkl_name = 'calls.pkl' ams_path = get_dot_ams_path() if not os.path.exists(ams_path): os.makedirs(ams_path) pkl_path = os.path.join(ams_path, pkl_name) return pkl_path
[docs] def get_dot_ams_path(): """ Return the path to ``$HOME/.ams`` """ return os.path.join(str(pathlib.Path.home()), '.ams')
[docs] def get_log_dir(): """ Get the directory for log file. The default is ``<tempdir>/ams``, where ``<tempdir>`` is provided by ``tempfile.gettempdir()``. Returns ------- str The path to the temporary logging directory """ tempdir = tempfile.gettempdir() path = tempfile.mkdtemp(prefix='ams-', dir=tempdir) return path
[docs] def confirm_overwrite(outfile, overwrite=None): """ Confirm overwriting a file. """ try: if os.path.isfile(outfile): if overwrite is None: choice = input(f'File "{outfile}" already exist. Overwrite? [y/N]').lower() if len(choice) == 0 or choice[0] != 'y': logger.warning(f'File "{outfile}" not overwritten.') return False elif overwrite is False: return False except TypeError: pass return True
def _config_numpy(seed='None', divide='warn', invalid='warn'): """ Configure NumPy error handling and random seed. Notes ----- Adapted from ``andes.system._config_numpy``. Original author: Hantao Cui. License: GPL-3.0. """ import numpy as np if isinstance(seed, int): np.random.seed(seed) logger.debug("Random seed set to <%d>.", seed) np.seterr(divide=divide, invalid=invalid)
[docs] def load_config_rc(conf_path=None): """ Load config from an rc-formatted file. Parameters ---------- conf_path : None or str Path to the config file. If ``None``, the function returns ``None`` without reading. Returns ------- configparser.ConfigParser or None Notes ----- Adapted from ``andes.system.load_config_rc``. Original author: Hantao Cui. License: GPL-3.0. """ import configparser if conf_path is None: return None conf = configparser.ConfigParser() conf.read(conf_path) logger.info('> Loaded config from file "%s"', conf_path) return conf
[docs] def get_export_path(system, fname, path=None, fmt='csv'): """ Get the absolute export path and the derived file name for an AMS system export. This function is not intended to be used directly by users. Parameters ---------- system : ams.system.System The AMS system to export. (Mocked in example) fname : str The descriptive file name, e.g., 'PTDF', or 'DCOPF'. path : str, optional The desired output path. For a directory, the file name will be generated; For a full file path, the base name will be used with the specified format; For ``None``, falls back to ``system.files.output_path`` when set, otherwise the current working directory. fmt : str, optional The file format to export, e.g., 'csv', 'json'. Default is 'csv'. Returns ------- tuple : (str, str) - The absolute export path (e.g., '/home/user/project/data_Routine.csv'). - The export file name (e.g., 'data_Routine.csv'), including the format extension. Notes ----- Default-path precedence (when ``path is None``): explicit ``path`` arg > ``system.files.output_path`` > current working directory. An empty-string ``output_path`` (the ``FileMan`` default when no output dir is specified) is treated as "not set". .. versionchanged:: 1.3.0 ``system.files.output_path`` is now used as the default fallback, previously the current working directory was always used. """ # Determine the base name from system.files.fullname or default to "Untitled" if system.files.fullname is None: logger.info("Input file name not detected. Using `Untitled`.") base_name_prefix = f'Untitled_{fname}' else: base_name_prefix = os.path.splitext(os.path.basename(system.files.fullname))[0] base_name_prefix += f'_{fname}' target_extension = fmt.lower() # Ensure consistent extension casing if path: abs_path = os.path.abspath(path) # Resolve to absolute path early # Check if the provided path is likely intended as a directory if not os.path.splitext(abs_path)[1]: # No extension implies it's a directory dir_path = abs_path final_file_name = f"{base_name_prefix}.{target_extension}" full_export_path = os.path.join(dir_path, final_file_name) else: # Path includes a filename. Use its directory, and its base name. dir_path = os.path.dirname(abs_path) # Use the provided filename's base, but enforce the target_extension provided_base_filename = os.path.splitext(os.path.basename(abs_path))[0] final_file_name = f"{provided_base_filename}.{target_extension}" full_export_path = os.path.join(dir_path, final_file_name) else: # No path provided. Prefer system.files.output_path when set, else CWD. output_path = getattr(system.files, 'output_path', None) dir_path = os.path.abspath(output_path) if output_path else os.getcwd() final_file_name = f"{base_name_prefix}.{target_extension}" full_export_path = os.path.join(dir_path, final_file_name) return full_export_path, final_file_name