"""Utility functions for io plugin."""
from __future__ import annotations
import os
from collections.abc import Callable
from functools import partial
from functools import wraps
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any
from typing import TypeVar
from typing import cast
DecoratedFunc = TypeVar("DecoratedFunc", bound=Callable[..., Any]) # decorated function
if TYPE_CHECKING:
from collections.abc import Iterable
from collections.abc import Iterator
from glotaran.typing import StrOrPath
[docs]
def not_implemented_to_value_error(func: DecoratedFunc) -> DecoratedFunc:
"""Decorate a function to raise ValueError instead of NotImplementedError.
This decorator is supposed to be used on functions which call functions
that might raise a NotImplementedError, but raise ValueError instead with
the same error text.
Parameters
----------
func : DecoratedFunc
Function to be decorated.
Returns
-------
DecoratedFunc
Wrapped function.
"""
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
try:
return func(*args, **kwargs)
except NotImplementedError as error:
raise ValueError(error.args) from error
return cast(DecoratedFunc, wrapper)
[docs]
def protect_from_overwrite(path: str | os.PathLike[str], *, allow_overwrite: bool = False) -> None:
"""Raise FileExistsError if files already exists and allow_overwrite isn't True.
As a side effect this also creates the parent directory of a file if it does not exist.
Parameters
----------
path : str
Path to a file or folder.
allow_overwrite : bool
Whether or not to allow overwriting existing files, by default False
Raises
------
FileExistsError
If path points to an existing file.
FileExistsError
If path points to an existing folder which is not empty.
"""
user_info = (
"To protect users overwriting existing files is deactivated by default. "
"If you are absolutely sure this is what you want and need to do you can "
"use the argument 'allow_overwrite=True'."
)
path = Path(path).resolve()
if path.parent.is_file() is False:
path.parent.mkdir(parents=True, exist_ok=True)
if allow_overwrite:
return
elif path.is_file():
raise FileExistsError(f"The file {path!r} already exists. \n{user_info}")
elif path.is_dir() and os.listdir(str(path)):
raise FileExistsError(
f"The folder {path.as_posix()!r} already exists and is not empty. \n{user_info}"
)
[docs]
def bool_str_repr(value: Any, true_repr: str = "*", false_repr: str = "/") -> Any:
"""Replace boolean value with string repr.
This function is a helper for table representation (e.g. with tabulate)
of boolean values.
Parameters
----------
value : Any
Arbitrary value
true_repr : str
Desired repr for ``True``, by default "*"
false_repr : str
Desired repr for ``False``, by default "/"
Returns
-------
Any
Original value or desired repr for bool
Examples
--------
>>> table_data = [["foo", True, False], ["bar", False, True]]
>>> print(tabulate(map(lambda x: map(bool_table_repr, x), table_data)))
--- - -
foo * /
bar / *
--- - -
"""
if value is True:
return true_repr
elif value is False:
return false_repr
else:
return value
[docs]
def bool_table_repr(
table_data: Iterable[Iterable[Any]], true_repr: str = "*", false_repr: str = "/"
) -> Iterator[Iterator[Any]]:
"""Replace boolean value with string repr for all table values.
This function is an implementation of :func:`bool_str_repr` for a
2D table, for easy usage with tabulate.
Parameters
----------
table_data : Iterable[Iterable[Any]]
Data of the table e.g. a list of lists.
true_repr : str
Desired repr for ``True``, by default "*"
false_repr : str
Desired repr for ``False``, by default "/"
Returns
-------
Iterator[Iterator[Any]]
``table_data`` with original values or desired repr for bool
See Also
--------
bool_str_repr
Examples
--------
>>> table_data = [["foo", True, False], ["bar", False, True]]
>>> print(tabulate(bool_table_repr(table_data))
--- - -
foo * /
bar / *
--- - -
"""
bool_repr = partial(bool_str_repr, true_repr=true_repr, false_repr=false_repr)
return ((bool_repr(value) for value in values) for values in table_data)