Source code for dendrotweaks.path_manager

# SPDX-FileCopyrightText: 2025 Poirazi Lab <dendrotweaks@dendrites.gr>
# SPDX-License-Identifier: MPL-2.0

import os
from typing import List, Dict
import shutil

[docs] class PathManager: """ A manager class for handling file and directory paths related to models data. Parameters ---------- path_to_model : str The path to the model directory. Attributes ---------- path_to_model : str The path to the model directory. paths : Dict[str, str] A dictionary of paths for different file types. """ def __init__(self, path_to_model: str): if not os.path.isdir(path_to_model): raise FileNotFoundError(f"Directory {path_to_model} does not exist.") self.path_to_model = path_to_model self.paths = { 'default_mod': os.path.join(self.path_to_data, 'Default'), 'templates': os.path.join(self.path_to_data, 'Templates'), 'morphology': os.path.join(self.path_to_model, 'morphology'), 'biophys': os.path.join(self.path_to_model, 'biophys'), 'mod': os.path.join(self.path_to_model, 'biophys', 'mod'), 'python': os.path.join(self.path_to_model, 'biophys', 'python'), 'stimuli': os.path.join(self.path_to_model, 'stimuli'), } self._ensure_paths_exist() def _ensure_paths_exist(self): """ Ensure all necessary paths exist. """ os.makedirs(self.path_to_model, exist_ok=True) for path in self.paths.values(): os.makedirs(path, exist_ok=True) # if empty, copy default mod files if not os.listdir(self.paths['default_mod']): self.copy_default_mod_files() if not os.listdir(self.paths['templates']): self.copy_template_files() @property def path_to_data(self): """ The path to the data directory, which is always the parent directory of path_to_model. """ return os.path.abspath(os.path.join(self.path_to_model, os.pardir)) def __repr__(self): return f"PathManager({self.path_to_model})"
[docs] def copy_default_mod_files(self): """ Copy default mod files to the data directory. """ DEFAULT_MOD_DIR = os.path.join(os.path.dirname(__file__), 'biophys', 'default_mod') for file_name in os.listdir(DEFAULT_MOD_DIR): source = os.path.join(DEFAULT_MOD_DIR, file_name) destination = os.path.join(self.paths['default_mod'], file_name) shutil.copyfile(source, destination)
[docs] def copy_template_files(self): """ Copy template files to the data directory. """ TEMPLATES_DIR = os.path.join(os.path.dirname(__file__), 'biophys', 'default_templates') for file_name in os.listdir(TEMPLATES_DIR): source = os.path.join(TEMPLATES_DIR, file_name) destination = os.path.join(self.paths['templates'], file_name) shutil.copyfile(source, destination)
[docs] def remove_folder(self, relative_path: str) -> None: """ Remove a folder and all its contents. Parameters ---------- relative_path : str The absolute path to the folder to be removed. """ folder_path = self.get_abs_path(relative_path) if os.path.isdir(folder_path): shutil.rmtree(folder_path, ignore_errors=True)
def _resolve_root(self, relative_path: str) -> tuple[str, str]: """ Given a relative path like 'stimuli/depolarizing/protocol.json', resolve the absolute root and the remainder. """ relative_path = os.path.normpath(relative_path.strip()) parts = relative_path.split(os.sep) root_key = parts[0] base_root = self.paths.get(root_key) if base_root is None: raise KeyError( f"Unknown top-level folder '{root_key}'. " f"Known roots: {list(self.paths.keys())}" ) remainder = os.path.join(*parts[1:]) if len(parts) > 1 else "" return base_root, remainder
[docs] def get_abs_path(self, relative_path: str, create_dirs: bool = False) -> str: """ Get the absolute path to a file or directory based on a relative path. Parameters ---------- relative_path : str Path relative to one of the registered roots. create_dirs : bool, default False If True, create the parent directories if they don't exist. Returns ------- str Absolute path in OS-native format. Examples -------- >>> pm = PathManager('/path/to/model') >>> pm.get_abs_path('stimuli/depolarizing/protocol.json') '/path/to/model/stimuli/depolarizing/protocol.json' """ base_root, remainder = self._resolve_root(relative_path) abs_path = os.path.join(base_root, remainder) if create_dirs and remainder: os.makedirs(os.path.dirname(abs_path), exist_ok=True) return abs_path
[docs] def list_folders(self, relative_path: str) -> List[str]: """ List all folders in a given directory. Parameters ---------- relative_path : str Path relative to one of the registered roots. Returns ------- List[str] A list of folder names. Examples -------- >>> pm = PathManager('/path/to/model') >>> pm.list_folders('stimuli') ['depolarizing_current', 'hyperpolarizing_current'] """ abs_path = self.get_abs_path(relative_path) if not os.path.isdir(abs_path): return [] return [f for f in os.listdir(abs_path) if os.path.isdir(os.path.join(abs_path, f))]
[docs] def list_files(self, relative_path: str, extension: str | None = None) -> List[str]: """ List all files in a given directory with an optional extension filter. If the extension is None, su Parameters ---------- relative_path : str Path relative to one of the registered roots. extension : str The file extension to filter by (e.g., 'mod', 'swc'). Returns ------- List[str] A list of file names. """ abs_path = self.get_abs_path(relative_path) if extension and not extension.startswith('.'): extension = f".{extension}" if not os.path.isdir(abs_path): return [] return [f.replace(extension, '') for f in os.listdir(abs_path) if f.endswith(extension)]
[docs] def list_morphologies(self, extension: str = '.swc') -> List[str]: """ List all SWC files. Returns ------- List[str] A list of SWC file names. """ return self.list_files('morphology', extension=extension)
[docs] def list_stimuli(self) -> List[str]: """ List all JSON files. Returns ------- List[str] A list of JSON file names. """ return self.list_folders('stimuli')
[docs] def list_biophys(self): """ List all biophysics files. Returns ------- List[str] A list of biophysics file names. """ return self.list_files('biophys', extension='.json')
[docs] def print_directory_tree(self, subfolder=None) -> None: """ Print a directory tree for a given file type. Parameters ---------- file_type : str The type of file (e.g., 'mod', 'swc'). """ base_path = self.paths.get('model') if not subfolder else self.paths.get(subfolder) if not base_path or not os.path.isdir(base_path): print(f"Directory for {file_type} does not exist.") return def print_tree(path, prefix=""): items = os.listdir(path) for idx, item in enumerate(sorted(items)): is_last = idx == len(items) - 1 connector = "└──" if is_last else "├──" item_path = os.path.join(path, item) print(f"{prefix}{connector} {item}") if os.path.isdir(item_path) and not item.startswith('x86_64'): extension = "│ " if not is_last else " " print_tree(item_path, prefix + extension) print_tree(base_path)
[docs] def get_channel_paths(self, mechanism_name: str, python_template_name: str = None) -> Dict[str, str]: """ Get all necessary paths for creating a channel. Parameters ---------- mechanism_name : str The name of the mechanism. python_template_name : str, optional The name of the Python template file. Returns ------- Dict[str, str] A dictionary of paths. """ python_template_name = python_template_name or "default" return { 'path_to_mod_file': self.get_abs_path(f'mod/{mechanism_name}.mod'), 'path_to_python_file': self.get_abs_path(f'python/{mechanism_name}.py'), 'path_to_python_template': self.get_abs_path(f'templates/{python_template_name}.py'), }
[docs] def get_standard_channel_paths(self, mechanism_name: str, python_template_name: str = None, mod_template_name: str = None) -> Dict[str, str]: """ Get all necessary paths for creating a standard channel. Parameters ---------- mechanism_name : str The name of the mechanism. python_template_name : str, optional The name of the Python template file. mod_template_name : str, optional The name of the MOD template file. Returns ------- Dict[str, str] A dictionary of paths. """ python_template_name = python_template_name or "default" mod_template_name = mod_template_name or "standard_channel" return { # **self.get_channel_paths(mechanism_name, python_template_name), 'path_to_mod_template': self.get_abs_path(f'templates/{mod_template_name}.mod'), 'path_to_standard_mod_file': self.get_abs_path(f'mod/std{mechanism_name}.mod'), }