"""Utility methods for working with paths and filesystems."""
import typing as _t
from pathlib import Path as _Path
from rics.types import AnyPath as _AnyPath
AnyPath = _AnyPath
"""Reexport of :attr:`rics.types.AnyPath`."""
PathT = _t.TypeVar("PathT", str, _Path)
"""A specific path-like type."""
PathPostprocessor = _t.Callable[[PathT], PathT | bool | None]
[docs]
def any_path_to_str(
path: _AnyPath,
*,
postprocessor: PathPostprocessor[str] | None = None,
) -> str:
"""Docstring is generated."""
return parse_any_path(path, cls=str, postprocessor=postprocessor)
[docs]
def any_path_to_path(
path: _AnyPath,
*,
postprocessor: PathPostprocessor[_Path] | None = None,
) -> _Path:
"""Docstring is generated."""
return parse_any_path(path, cls=_Path, postprocessor=postprocessor)
[docs]
def parse_any_path(
path: _AnyPath,
*,
cls: type[PathT],
postprocessor: PathPostprocessor[PathT] | None = None,
) -> PathT:
"""Docstring is generated."""
converted_path = _convert_path(path, cls=cls)
if postprocessor is not None:
result = postprocessor(converted_path)
if result is None or isinstance(result, bool):
pass # postprocessor was a validation function
elif isinstance(result, cls):
converted_path = result
else:
from rics.misc import tname
pretty_actual = f"`{tname(result)}`"
pretty_expected = f"({tname(cls)} | bool | None)"
pretty_pp = f"{tname(postprocessor)}({converted_path!r})"
msg = f"Postprocessor {pretty_pp} returned {result=} of type {pretty_actual}; expected={pretty_expected}."
raise TypeError(msg)
return converted_path
def _convert_path(path: _AnyPath, *, cls: type[PathT]) -> PathT:
if isinstance(path, cls):
return path
if issubclass(cls, str):
return cls(path)
if issubclass(cls, _Path) and isinstance(path, str):
return _Path(path)
try:
return cls(str(path))
except Exception as e:
msg = f"Cannot convert {path=} from type(path)=`{type(path).__name__}` to cls=`{cls.__name__}`."
raise TypeError(msg) from e
def _set_path_func_docstring(func: _t.Any, cls: str | type[_t.Any], tail: str = "") -> None:
args = {"path": "Any :class:`path-like <rics.paths.AnyPath>` type."}
if isinstance(cls, type): # Specialization
cls = cls.__name__
equivalent_to = f"""Equivalent to :func:`parse_any_path(path, cls={cls}) <.parse_any_path>`."""
args["postprocessor"] = f"A callable ``({cls}) -> {cls} | None``."
cls = f"``{cls}``"
else: # Base function
equivalent_to = ""
args["cls"] = "Desired output type; typically one of :py:class:`str` | :py:class:`pathlib.Path`."
args["postprocessor"] = "A callable ``(PathT) -> PathT | bool | None``."
args["postprocessor"] += " Returns the post-processed value if `postprocessor` does not return ``None | bool``."
args_str = "\n ".join(f"{arg}: {docstring}" for arg, docstring in args.items())
docstring = f"""Convert a path of any type to {cls}.
{equivalent_to}
Args:
{args_str}
Returns:
A `path` of type {cls}.
{tail}
"""
func.__doc__ = docstring
_set_path_func_docstring(
parse_any_path,
cls="of type `cls`",
tail="""Examples:
>>> from pathlib import Path
>>> parse_any_path("/home/dev/", cls=Path)
PosixPath('/home/dev')
>>> parse_any_path(Path("/home/dev/"), cls=Path)
PosixPath('/home/dev')
See Also:
* :func:`any_path_to_str`: Alias of ``parse_any_path(cls=str)``
* :func:`any_path_to_path`: Alias of ``parse_any_path(cls=pathlib.Path)``
""",
)
_set_path_func_docstring(any_path_to_str, cls=str)
_set_path_func_docstring(any_path_to_path, cls=_Path)