"""File paths and output directory configuration."""
from pathlib import Path
from typing import Optional
from config.env import get_env
# Map common format variants to canonical extensions (for _normalize_plot_format)
_PLOT_FORMAT_MAPPING: dict[str, str] = {
"jpg": "jpg",
"jpeg": "jpg",
"png": "png",
"pdf": "pdf",
}
def _normalize_plot_format(value: str) -> str:
"""
Normalize a plot format string to a supported extension.
Accepts common variants like ``'jpeg'`` and coerces them to one of the
supported values ``'png'``, ``'jpg'`` or ``'pdf'``. Any unknown value
falls back to ``'png'``.
Args:
value: Raw plot format string (e.g. ``'PNG'``, ``'jpeg'``).
Returns:
Normalized extension without leading dot.
Examples:
>>> _normalize_plot_format('PNG')
'png'
>>> _normalize_plot_format('jpeg')
'jpg'
>>> _normalize_plot_format('unknown')
'png'
"""
normalized = (value or "png").strip().lower()
return _PLOT_FORMAT_MAPPING.get(normalized, "png")
FILE_CONFIG = {
"input_dir": get_env("FILE_INPUT_DIR", "input"),
"output_dir": get_env("FILE_OUTPUT_DIR", "output"),
"filename_template": get_env("FILE_FILENAME_TEMPLATE", "fit_{}"),
"plot_format": _normalize_plot_format(get_env("FILE_PLOT_FORMAT", "png")),
}
[docs]
def get_project_root() -> Path:
"""
Get the project root directory (parent of ``src/``).
The function resolves the path based on the current file location, so it
works even when the package is installed or executed from another folder.
Returns:
Absolute :class:`pathlib.Path` to the project root.
"""
# __file__ is src/config/paths.py -> parent=config, parent.parent=src, parent.parent.parent=project root
return Path(__file__).resolve().parent.parent.parent
[docs]
def ensure_output_directory(output_dir: Optional[str] = None) -> str:
"""
Ensure that the output directory exists and return its absolute path.
If ``output_dir`` is ``None``, the value from :data:`FILE_CONFIG` is used.
The directory is created recursively when missing.
Args:
output_dir: Relative output directory name, usually from configuration.
Returns:
Absolute path to the output directory as a string.
Raises:
OSError: If the directory cannot be created.
"""
if output_dir is None:
output_dir = FILE_CONFIG["output_dir"]
project_root = get_project_root()
full_path = project_root / output_dir
try:
if not full_path.exists():
full_path.mkdir(parents=True, exist_ok=True)
except OSError as e:
raise OSError(f"Could not create output directory: {e!s}") from e
return str(full_path)
[docs]
def get_output_path(fit_name: str, output_dir: Optional[str] = None) -> str:
"""
Build the full output file path for a plot image.
The final filename is created from ``FILE_CONFIG['filename_template']``
and the normalized plot format, ensuring a consistent extension.
Args:
fit_name: Base name for the plot (usually the fit or dataset name).
output_dir: Optional relative output directory; if ``None``, the
default from :data:`FILE_CONFIG` is used.
Returns:
Absolute path to the image file as a string.
Examples:
>>> get_output_path("linear_fit")
'.../output/fit_linear_fit.png'
"""
if output_dir is None:
output_dir = FILE_CONFIG["output_dir"]
output_path = ensure_output_directory(output_dir)
filename = FILE_CONFIG["filename_template"].format(fit_name)
base = Path(filename).stem
fmt = FILE_CONFIG["plot_format"]
return str(Path(output_path) / f"{base}.{fmt}")