Source code for poulet_py.tools.organizational
try:
from datetime import datetime, timezone
from inspect import stack
from os import chdir, makedirs
from pathlib import Path
from re import sub
from deprecated import deprecated
from poulet_py.config.logging import LOGGER
except ImportError as e:
msg = """
Missing 'tools' module. Install options:
- Module: pip install poulet_py[tools]
- Full: pip install poulet_py[all]
"""
raise ImportError(msg) from e
[docs]
@deprecated(
"This function is deprecated and will be removed in the next release."
"Use os.makedirs(path, exists_ok=True) instead.",
version="0.0.2",
)
def check_or_create(path):
"""
Function to check whether a folder exists.
If it does NOT exist, it is created
"""
makedirs(path, exist_ok=True)
[docs]
@deprecated(
"This function is deprecated and will be removed in the next release."
"Use sanitize_path instead.",
version="0.0.2",
)
def define_folder_name(name: str, *, add_date: bool = True) -> str:
sanitized_name = sub(r"[^\w]", "_", name)
if add_date:
return f"{datetime.now(timezone.utc).strftime('%Y%m%d')}_{sanitized_name}"
return sanitized_name
[docs]
def sanitize_path(path: Path | str, *, add_timestamp: bool = False) -> Path:
"""
Sanitize a full path by making all components filesystem-friendly and
optionally adding a timestamp to the last component (file or folder).
Parameters
----------
path : str
The input path to sanitize (can include folders and filename)
add_timestamp : bool, optional
If True, prepend timestamp in YYYYMMDD format to the last component
Returns
-------
str
Sanitized path with all special characters replaced by underscores and
optional timestamp on the last component
Examples
--------
>>> sanitize_path("/s$ss!on1/d%ta/345.pkl", add_timestamp=True)
'/s_ss_on1/d_ta/20250503_345.pkl'
>>> sanitize_path("my@project/data#files")
'my_project/data_files'
"""
path_obj = str(path).lower()
path_obj = path_obj.replace("\\", "/")
path_obj = Path(path_obj)
parts = path_obj.parts
# Sanitize each component
sanitized_parts = []
for part in parts:
# Skip root parts like '/' or 'C:\'
if part in ("/") or (len(part) == 2 and part[1] == ":"):
sanitized_parts.append(part)
else:
sanitized_parts.append(sub(r"[^\w\.\-]", "_", part).lower())
# Add timestamp if requested
if add_timestamp:
sanitized_parts[-1] = (
f"{datetime.now(timezone.utc).strftime('%Y%m%d')}_{sanitized_parts[-1]}"
)
return Path(*sanitized_parts)
[docs]
def go_to(key: str, *, path: Path | str | None = None) -> bool:
"""
Change the current working directory to the level containing a specified key in a path.
Parameters
----------
key : str
The directory name to search for in the path (e.g., "neuropixels").
path : Path or str, optional
The input path to analyze. Defaults to the caller script's location (`__file__`).
Returns
-------
bool
True if the directory was changed successfully, False otherwise.
Examples
--------
>>> change_dir_to_key("neuropixels", path="/project/neuropixels/src/file.py")
True # Changes CWD to "/project/neuropixels"
"""
if path is None:
frame = stack()[1]
path = frame.filename
LOGGER.debug(f"Using caller's path: {path}")
path = Path(path).absolute()
parts = list(path.parts)
try:
key_index = len(parts) - 1 - parts[::-1].index(key)
except ValueError:
LOGGER.warning(f"Key '{key}' not found in path.")
return False
new_path = Path(*parts[: key_index + 1])
try:
chdir(new_path)
LOGGER.info(f"Changed directory to: {new_path}")
return True
except Exception as e:
LOGGER.warning(f"Error changing directory: {e}")
return False