Source code for rics.paths

"""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)  # Would be nice to allow Any or a bound
"""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 isinstance(result, cls): converted_path = result elif result is None or result is True: pass # postprocessor was a validation function elif result is False: from rics.misc import tname msg = f"Validator {tname(postprocessor, prefix_classname=True)}({converted_path!r}) returned False." raise ValueError(msg) 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 cls(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 if __doc__: _set_path_func_docstring( parse_any_path, cls="of type `cls`", tail="""Raises: TypeError: If the `postprocessor` returns an invalid type. ValueError: If the `postprocessor` returns ``False``. Postprocessor return types: * ``None``: Validator. Should raise if there's an issue. * ``bool``: Validator, e.g. :meth:`~pathlib.Path.is_dir`. Raises a ``ValueError`` if the function returns ``False``. * ``PathT``: Postprocessor, e.g. :meth:`~pathlib.Path.absolute`. Result is returned as-is. 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)