Skip to content

argparse UPath typed path args becomes pathlib.Path instead of upath.UPath

I am using Universal Pathlib (UPath) as unified pathlib-compatible interface for local and remote filesystems (e.g. S3). When trying to use it with argparser cli instead of getting a UPath the arg paths were Path type.

Delving into the issue, it seems to be a result of UPath being a subclass of Path and typed settings code assuming it should be Path.

Potential fix

This is how I am currently handling this problem in our code:

def handle_path(type_: type[Path], default: Default, is_optional: bool) -> StrDict:
    """Patched ts.cli_argparse.handle_path to fix handling UPath type."""
    kwargs = {"type": type_, "metavar": "PATH"}
    if isinstance(default, (type_, str)):
        kwargs["default"] = str(default)
    elif is_optional:
        kwargs["default"] = None

    return kwargs

def to_path(value: Path | str, cls: type) -> Path:
    """Patched ts.convertors.to_path to use cls arg"""
    return cls(value)


def to_resolved_path(value: Path | str, cls: type) -> Path:
    """Patched ts.convertors.to_path to use cls arg"""
    return cls(value).resolve()


def apply_upath_fixes() -> None:
    """Patch typed settings Path type functions to support UPath."""
    # Patch DEFAULT_TYPES since UPath is a subclass of Path and incorrectly cast as Path
    ts.cli_argparse.DEFAULT_TYPES[Path] = handle_path
    # Replace converters with UPath type aware versions
    ts.converters.to_path = to_path
    ts.converters.to_resolved_path = to_resolved_path

Would you be open to a PR to fix this?