Source code for gemseo.utils.run_folder_manager
# Copyright 2021 IRT Saint Exupéry, https://www.irt-saintexupery.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""Tools for unique run folder name generation."""
from __future__ import annotations
from ast import literal_eval
from multiprocessing import Lock
from multiprocessing import Value
from os import listdir
from pathlib import Path
from uuid import uuid1
from strenum import StrEnum
from gemseo.utils.portable_path import PLATFORM_IS_WINDOWS
FoldersIter = StrEnum("FoldersIter", "NUMBERED UUID")
[docs]class RunFolderManager:
"""Class generating unique names for run folders."""
__counter: Value
"""The number of existing run folders."""
__output_folder_basepath: Path
"""The path of the root of run folders."""
__lock: Lock
"""The non-recursive lock object."""
__use_shell: bool
"""Whether to run the command using the default shell."""
_folders_iter: FoldersIter
"""The type of unique identifiers for the output folders."""
def __init__(
self,
output_folder_basepath: str,
folders_iter: FoldersIter = FoldersIter.NUMBERED,
use_shell: bool = True,
) -> None:
"""
Args:
folders_iter: The type of unique identifiers for the output folders.
If :attr:`~.FoldersIter.NUMBERED`,
the generated output folders will be ``f"output_folder_basepath{i+1}"``,
where ``i`` is the maximum value
of the already existing ``f"output_folder_basepath{i}"`` folders.
Otherwise, a unique number based on the UUID function is
generated. This last option shall be used if multiple MDO
processes are run in the same work folder.
use_shell: If ``True``, run the command using the default shell.
Otherwise, run directly the command.
output_folder_basepath: The base path of the execution folders.
""" # noqa:D205 D212 D415
self.__use_shell = use_shell
self.__lock = Lock()
self._folders_iter = folders_iter
self.__output_folder_basepath = Path(output_folder_basepath)
self.__check_base_path_on_windows()
self.__counter = Value("i", self.__get_max_outdir())
[docs] def get_unique_run_folder_path(self) -> Path:
"""Generate a unique folder path for the run folder.
Returns: A unique Path to be used as run folder.
"""
return self.__output_folder_basepath / self.__generate_uid()
def __generate_uid(self) -> str:
"""Generate a unique identifier for the execution folder.
Generate a unique identifier for the current execution.
If the _folders_iter strategy is :attr:`~.FoldersIter.NUMBERED`,
the successive iterations are named by an integer 1, 2, 3 etc.
This is multiprocess safe.
Otherwise, a unique number based on the UUID function is generated.
This last option shall be used if multiple MDO processes are run
in the same workdir.
Returns:
An unique string identifier (either a number or a UUID).
Raises:
ValueError: If ``_folders_iter`` is not a :class:`.FoldersIter` object.
"""
if self._folders_iter == FoldersIter.NUMBERED:
with self.__lock:
self.__counter.value += 1
return str(self.__counter.value)
elif self._folders_iter == FoldersIter.UUID:
return str(uuid1()).split("-")[-1]
raise ValueError(
f"{self._folders_iter} is not a valid method "
"for creating the execution folders."
)
def __get_max_outdir(self) -> int:
"""Get the maximum current index of output folders.
Returns:
The maximum index in the output folders.
"""
outs = listdir(self.__output_folder_basepath)
if not outs:
return 0
return max(literal_eval(n) for n in outs)
def __check_base_path_on_windows(self) -> None:
"""Check that the base path can be used.
Raises:
ValueError: When the users use the shell under Windows
and the base path is located on a network location.
"""
if PLATFORM_IS_WINDOWS and self.__use_shell:
output_folder_base_path = Path(self.__output_folder_basepath).resolve()
if not output_folder_base_path.parts[0].startswith("\\\\"):
return
raise ValueError(
"A network base path and use_shell cannot be used together"
" under Windows, as cmd.exe cannot change the current folder"
" to a UNC path."
" Please try use_shell=False or use a local base path."
)