File: utilpathtest.py

package info (click to toggle)
python-beartype 0.22.9-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 9,504 kB
  • sloc: python: 85,502; sh: 328; makefile: 30; javascript: 18
file content (321 lines) | stat: -rw-r--r-- 11,700 bytes parent folder | download
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
#!/usr/bin/env python3
# --------------------( LICENSE                            )--------------------
# Copyright (c) 2014-2025 Beartype authors.
# See "LICENSE" for further details.

'''
Project-wide **path testers** (i.e., low-level callables testing various aspects
of on-disk files and directories and raising exceptions when those files and
directories fail to satisfy various constraints).

This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS                            }....................
from beartype.roar._roarexc import _BeartypeUtilPathException
from beartype._data.typing.datatyping import (
    PathnameLike,
    PathnameLikeTuple,
    TypeException,
)
from os import (
    X_OK,
    access as is_path_permissions,
)
from pathlib import Path

# ....................{ RAISERS ~ path                     }....................
#FIXME: Unit test us up, please.
def die_if_subpath(
    # Mandatory parameters.
    parent_pathname: PathnameLike,
    child_pathname: PathnameLike,

    # Optional parameters.
    exception_cls: TypeException = _BeartypeUtilPathException,
    exception_prefix: str = '',
) -> None:
    '''
    Raise an exception of the passed type if the child path with the passed
    pathname is a **child** (i.e., either a subdirectory *or* a file in a
    subdirectory) of the parent path with the passed pathname.

    Parameters
    ----------
    parent_dirname: PathnameLike
        Absolute or relative dirname of the parent path to be validated.
    child_dirname: PathnameLike
        Absolute or relative dirname of the child path to be validated.
    exception_cls : Type[Exception], default: _BeartypeUtilPathException
        Type of exception to be raised in the event of a fatal error. Defaults
        to :exc:`._BeartypeUtilPathException`.
    exception_prefix : str, default: ''
        Human-readable substring prefixed raised exceptions messages. Defaults
        to the empty string.

    Raises
    ------
    exception_cls
        If this child path is a child of this parent path.
    '''

    # If this child path is a child of this parent path...
    if is_subpath(parent_pathname, child_pathname):
        assert isinstance(exception_cls, type), (
            f'{repr(exception_cls)} not exception type.')
        assert isinstance(exception_prefix, str), (
            f'{repr(exception_prefix)} not string.')

        # Raise an exception.
        raise exception_cls(
            f'{exception_prefix}'
            f'child path "{child_pathname}" is relative to '
            f'parent path "{parent_pathname}".'
        )
    # Else, this child path is *NOT* a child of this parent path.

# ....................{ RAISERS ~ dir                      }....................
#FIXME: Unit test us up, please.
def die_if_dir(
    # Mandatory parameters.
    dirname: PathnameLike,

    # Optional parameters.
    exception_cls: TypeException = _BeartypeUtilPathException,
    exception_prefix: str = '',
) -> None:
    '''
    Raise an exception of the passed type if a directory with the passed
    dirname already exists.

    Parameters
    ----------
    dirname : PathnameLike
        Dirname to be validated.
    exception_cls : Type[Exception], default: _BeartypeUtilPathException
        Type of exception to be raised in the event of a fatal error. Defaults
        to :exc:`._BeartypeUtilPathException`.
    exception_prefix : str, default: ''
        Human-readable substring prefixed raised exceptions messages. Defaults
        to the empty string.

    Raises
    ------
    exception_cls
        If a directory with the passed dirname already exists.
    '''
    assert isinstance(dirname, PathnameLikeTuple), (
        f'{repr(dirname)} neither string nor "Path" object.')

    # High-level "Path" object encapsulating this dirname.
    dirname_path = Path(dirname)

    # If a directory with this dirname already exists...
    if dirname_path.is_dir():
        assert isinstance(exception_cls, type), (
            f'{repr(exception_cls)} not exception type.')
        assert isinstance(exception_prefix, str), (
            f'{repr(exception_prefix)} not string.')

        # Raise an exception.
        raise exception_cls(
            f'{exception_prefix}path "{dirname_path}" already directory.')
    # Else, *NO* directory with this dirname exists.


#FIXME: Unit test us up, please.
def die_unless_dir(
    # Mandatory parameters.
    dirname: PathnameLike,

    # Optional parameters.
    exception_cls: TypeException = _BeartypeUtilPathException,
    exception_prefix: str = '',
) -> None:
    '''
    Raise an exception of the passed type if *no* directory with the passed
    dirname exists.

    Parameters
    ----------
    dirname : PathnameLike
        Dirname to be validated.
    exception_cls : Type[Exception], default: _BeartypeUtilPathException
        Type of exception to be raised in the event of a fatal error. Defaults
        to :exc:`._BeartypeUtilPathException`.
    exception_prefix : str, default: ''
        Human-readable substring prefixed raised exceptions messages. Defaults
        to the empty string.

    Raises
    ------
    exception_cls
        If *no* directory with the passed dirname exists.
    '''
    assert isinstance(dirname, PathnameLikeTuple), (
        f'{repr(dirname)} neither string nor "Path" object.')

    # High-level "Path" object encapsulating this dirname.
    dirname_path = Path(dirname)

    # If either no path with this pathname exists *OR* a path with this pathname
    # exists but this path is not a directory...
    if not dirname_path.is_dir():
        assert isinstance(exception_cls, type), (
            f'{repr(exception_cls)} not exception type.')
        assert isinstance(exception_prefix, str), (
            f'{repr(exception_prefix)} not string.')

        # If no path with this pathname exists, raise an appropriate exception.
        if not dirname_path.exists():
            raise exception_cls(
                f'{exception_prefix}directory "{dirname_path}" not found.')
        # Else, a path with this pathname exists.

        # By elimination, a path with this pathname exists but this path is not
        # a directory. In this case, raise an appropriate exception.
        raise exception_cls(
            f'{exception_prefix}path "{dirname_path}" not directory.')
    # Else, a directory with this dirname exists.

# ....................{ RAISERS ~ file                     }....................
#FIXME: Unit test us up, please.
def die_unless_file(
    # Mandatory parameters.
    filename: PathnameLike,

    # Optional parameters.
    exception_cls: TypeException = _BeartypeUtilPathException,
) -> None:
    '''
    Raise an exception of the passed type if *no* file with the passed filename
    exists.

    Parameters
    ----------
    filename : PathnameLike
        Dirname to be validated.
    exception_cls : Type[Exception], optional
        Type of exception to be raised in the event of a fatal error. Defaults
        to :exc:`._BeartypeUtilPathException`.

    Raises
    ------
    exception_cls
        If *no* file with the passed filename exists.
    '''

    # High-level "Path" object encapsulating this filename.
    filename_path = Path(filename)

    # If either no path with this pathname exists *OR* a path with this pathname
    # exists but this path is not a file...
    if not filename_path.is_file():
        assert isinstance(exception_cls, type), (
            f'{repr(exception_cls)} not type.')

        # If no path with this pathname exists, raise an appropriate exception.
        if not filename_path.exists():
            raise exception_cls(f'File "{filename_path}" not found.')
        # Else, a path with this pathname exists.

        # By elimination, a path with this pathname exists but this path is not
        # a file. In this case, raise an appropriate exception.
        raise exception_cls(f'Path "{filename_path}" not file.')
    # Else, a file with this filename exists.


#FIXME: Unit test us up, please.
def die_unless_file_executable(
    # Mandatory parameters.
    filename: PathnameLike,

    # Optional parameters.
    exception_cls: TypeException = _BeartypeUtilPathException,
) -> None:
    '''
    Raise an exception of the passed type if either no file with the passed
    filename exists *or* this file exists but is not **executable** (i.e., the
    current user lacks sufficient permissions to execute this file).

    Parameters
    ----------
    filename : PathnameLike
        Dirname to be validated.
    exception_cls : Type[Exception], optional
        Type of exception to be raised in the event of a fatal error. Defaults
        to :exc:`._BeartypeUtilPathException`.

    Raises
    ------
    :exception_cls
        If either:

        * No file with the passed filename exists.
        * This file exists but is not executable by the current user.
    '''

    # If *NO* file with this filename exists, raise an exception.
    die_unless_file(filename=filename, exception_cls=exception_cls)
    # Else, a file with this filename exists.

    # Note that this logic necessarily leverages the low-level "os.path"
    # submodule rather than the object-oriented "pathlib.Path" class, which
    # currently lacks *ANY* public facilities for introspecting permissions
    # (including executability) as of Python 3.12. This is why we sigh.

    # Reduce this possible high-level "Path" object to a low-level filename.
    filename_str = str(filename)

    # If the current user has *NO* permission to execute this file...
    if not is_path_permissions(filename_str, X_OK):
        assert isinstance(exception_cls, type), (
            f'{repr(exception_cls)} not type.')

        # Raise an appropriate exception.
        raise exception_cls(f'File "{filename_str}" not executable.')
    # Else, the current user has permission to execute this file. Ergo, this
    # file is an executable file with respect to this user.

# ....................{ TESTERS ~ path                     }....................
#FIXME: Unit test us up, please.
def is_subpath(
    parent_pathname: PathnameLike, child_pathname: PathnameLike) -> bool:
    '''
    :data:`True` only if the child path with the passed pathname is a **child**
    (i.e., either a subdirectory *or* a file in a subdirectory) of the parent
    path with the passed pathname.

    Parameters
    -----------
    parent_dirname: PathnameLike
        Absolute or relative dirname of the parent path to be tested.
    child_dirname: PathnameLike
        Absolute or relative dirname of the child path to be tested.

    Returns
    -------
    bool
        :data:`True` only if this child path is a child of this parent path.

    See Also
    --------
    https://stackoverflow.com/a/66626684/2809027
        StackOverflow answer strongly inspiring this implementation.
    '''
    assert isinstance(parent_pathname, PathnameLikeTuple), (
        f'{repr(parent_pathname)} neither string nor "Path" object.')
    assert isinstance(child_pathname, PathnameLikeTuple), (
        f'{repr(child_pathname)} neither string nor "Path" object.')

    # High-level "Path" objects encapsulating these pathnames.
    parent_path = Path(parent_pathname)
    child_path = Path(child_pathname)

    # Canonicalize these possibly relative pathnames into absolute pathnames,
    # silently resolving both relative pathnames and symbolic links as needed.
    parent_path = parent_path.resolve()
    child_path = child_path.resolve()

    # Return true only if this child path is a child of this parent path.
    return child_path.is_relative_to(parent_path)