File: _path.py

package info (click to toggle)
python-cyclopts 3.12.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,288 kB
  • sloc: python: 11,445; makefile: 24
file content (121 lines) | stat: -rw-r--r-- 4,641 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import pathlib
from typing import Any, Iterable, Sequence, Union

from attrs import field

from cyclopts.utils import frozen, to_tuple_converter


def ext_converter(value: Union[None, Any, Iterable[Any]]) -> tuple[str, ...]:
    return tuple(e.lower().lstrip(".") for e in to_tuple_converter(value))


@frozen(kw_only=True)
class Path:
    """Assertions on properties of :class:`pathlib.Path`.

    Example Usage:

    .. code-block:: python

        from cyclopts import App, Parameter, validators
        from pathlib import Path
        from typing import Annotated

        app = App()


        @app.default
        def main(
            # ``src`` must be a file that exists.
            src: Annotated[Path, Parameter(validator=validators.Path(exists=True, dir_okay=False))],
            # ``dst`` must be a path that does **not** exist.
            dst: Annotated[Path, Parameter(validator=validators.Path(dir_okay=False, file_okay=False))],
        ):
            "Copies src->dst."
            dst.write_bytes(src.read_bytes())


        app()

    .. code-block:: console

        $ my-script foo.bin bar.bin  # if foo.bin does not exist
        ╭─ Error ───────────────────────────────────────────────────────╮
        │ Invalid value "foo.bin" for "SRC". "foo.bin" does not exist.  │
        ╰───────────────────────────────────────────────────────────────╯

        $ my-script foo.bin bar.bin  # if bar.bin exists
        ╭─ Error ───────────────────────────────────────────────────────╮
        │ Invalid value "bar.bin" for "DST". "bar.bin" already exists.  │
        ╰───────────────────────────────────────────────────────────────╯
    """

    exists: bool = False
    """If :obj:`True`, specified path **must** exist. Defaults to :obj:`False`."""

    file_okay: bool = True
    """
    If path exists, check it's type:

    * If :obj:`True`, specified path may be an **existing** file.

    * If :obj:`False`, then **existing** files are not allowed.

    Defaults to :obj:`True`.
    """

    dir_okay: bool = True
    """
    If path exists, check it's type:

    * If :obj:`True`, specified path may be an **existing** directory.

    * If :obj:`False`, then **existing** directories are not allowed.

    Defaults to :obj:`True`.
    """

    # Can only ever really be a tuple[str, ...]
    ext: Union[str, Sequence[str]] = field(default=None, converter=ext_converter)
    """
    Supplied path must have this extension (case insensitive).
    May or may not include the ".".
    """

    def __attrs_post_init__(self):
        if self.exists and not self.file_okay and not self.dir_okay:
            raise ValueError("(exists=True, file_okay=False, dir_okay=False) is an invalid configuration.")

    def __call__(self, type_: Any, path: Any):
        if isinstance(path, Sequence):
            if isinstance(path, str):
                raise TypeError

            for p in path:
                self(type_, p)
        else:
            if not isinstance(path, pathlib.Path):
                return

            if self.ext and path.suffix.lower().lstrip(".") not in self.ext:
                if len(self.ext) == 1:
                    raise ValueError(f'"{path}" must have extension "{self.ext[0]}".')
                else:
                    pretty_ext = "{" + ", ".join(f'"{x}"' for x in self.ext) + "}"
                    raise ValueError(f'"{path}" does not match one of supported extensions {pretty_ext}.')

            if path.exists():
                if not self.file_okay and path.is_file():
                    if self.dir_okay:
                        raise ValueError(f'Only directory is allowed, but "{path}" is a file.')
                    else:
                        raise ValueError(f'"{path}" already exists.')

                if not self.dir_okay and path.is_dir():
                    if self.file_okay:
                        raise ValueError(f'Only file is allowed, but "{path}" is a directory.')
                    else:
                        raise ValueError(f'"{path}" already exists.')
            elif self.exists:
                raise ValueError(f'"{path}" does not exist.')