Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pathspec/util.py: 35%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2This module provides utility methods for dealing with path-specs.
3"""
5import os
6import os.path
7import pathlib
8import posixpath
9import stat
10from collections.abc import (
11 Collection,
12 Iterable,
13 Iterator,
14 Sequence)
15from dataclasses import (
16 dataclass)
17from typing import (
18 Any,
19 Callable, # Replaced by `collections.abc.Callable` in 3.9.2.
20 Generic,
21 Optional, # Replaced by `X | None` in 3.10.
22 TypeVar,
23 Union) # Replaced by `X | Y` in 3.10.
25from .pattern import (
26 Pattern)
27from ._typing import (
28 AnyStr, # Removed in 3.18.
29 deprecated) # Added in 3.13.
31StrPath = Union[str, os.PathLike[str]]
33TStrPath = TypeVar("TStrPath", bound=StrPath)
34"""
35Type variable for :class:`str` or :class:`os.PathLike`.
36"""
38NORMALIZE_PATH_SEPS = [
39 __sep
40 for __sep in [os.sep, os.altsep]
41 if __sep and __sep != posixpath.sep
42]
43"""
44*NORMALIZE_PATH_SEPS* (:class:`list` of :class:`str`) contains the path
45separators that need to be normalized to the POSIX separator for the current
46operating system. The separators are determined by examining :data:`os.sep` and
47:data:`os.altsep`.
48"""
50_registered_patterns = {}
51"""
52*_registered_patterns* (:class:`dict`) maps a name (:class:`str`) to the
53registered pattern factory (:class:`~collections.abc.Callable`).
54"""
57def append_dir_sep(path: pathlib.Path) -> str:
58 """
59 Appends the path separator to the path if the path is a directory. This can be
60 used to aid in distinguishing between directories and files on the file-system
61 by relying on the presence of a trailing path separator.
63 *path* (:class:`pathlib.Path`) is the path to use.
65 Returns the path (:class:`str`).
66 """
67 str_path = str(path)
68 if path.is_dir():
69 str_path += os.sep
71 return str_path
74def check_match_file(
75 patterns: Iterable[tuple[int, Pattern]],
76 file: str,
77 is_reversed: Optional[bool] = None,
78) -> tuple[Optional[bool], Optional[int]]:
79 """
80 Check the file against the patterns.
82 *patterns* (:class:`~collections.abc.Iterable`) yields each indexed pattern
83 (:class:`tuple`) which contains the pattern index (:class:`int`) and actua
84 pattern (:class:`.Pattern`).
86 *file* (:class:`str`) is the normalized file path to be matched against
87 *patterns*.
89 *is_reversed* (:class:`bool` or :data:`None`) is whether the order of the
90 patterns has been reversed. Default is :data:`None` for :data:`False`.
91 Reversing the order of the patterns is an optimization.
93 Returns a :class:`tuple` containing whether to include *file* (:class:`bool`
94 or :data:`None`), and the index of the last matched pattern (:class:`int` or
95 :data:`None`).
96 """
97 if is_reversed:
98 # Check patterns in reverse order. The first pattern that matches takes
99 # precedence.
100 for index, pattern in patterns:
101 if pattern.include is not None and pattern.match_file(file) is not None:
102 return pattern.include, index
104 return None, None
106 else:
107 # Check all patterns. The last pattern that matches takes precedence.
108 out_include: Optional[bool] = None
109 out_index: Optional[int] = None
110 for index, pattern in patterns:
111 if pattern.include is not None and pattern.match_file(file) is not None:
112 out_include = pattern.include
113 out_index = index
115 return out_include, out_index
118def detailed_match_files(
119 patterns: Iterable[Pattern],
120 files: Iterable[str],
121 all_matches: Optional[bool] = None,
122) -> dict[str, 'MatchDetail']:
123 """
124 Matches the files to the patterns, and returns which patterns matched the
125 files.
127 *patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains
128 the patterns to use.
130 *files* (:class:`~collections.abc.Iterable` of :class:`str`) contains the
131 normalized file paths to be matched against *patterns*.
133 *all_matches* (:class:`bool` or :data:`None`) is whether to return all matches
134 patterns (:data:`True`), or only the last matched pattern (:data:`False`).
135 Default is :data:`None` for :data:`False`.
137 Returns the matched files (:class:`dict`) which maps each matched file
138 (:class:`str`) to the patterns that matched in order (:class:`.MatchDetail`).
139 """
140 all_files = files if isinstance(files, Collection) else list(files)
141 return_files = {}
142 for pattern in patterns:
143 if pattern.include is not None:
144 result_files = pattern.match(all_files) # TODO: Replace with `.match_file()`.
145 if pattern.include:
146 # Add files and record pattern.
147 for result_file in result_files:
148 if result_file in return_files:
149 if all_matches:
150 return_files[result_file].patterns.append(pattern)
151 else:
152 return_files[result_file].patterns[0] = pattern
153 else:
154 return_files[result_file] = MatchDetail([pattern])
156 else:
157 # Remove files.
158 for file in result_files:
159 del return_files[file]
161 return return_files
164def _filter_check_patterns(
165 patterns: Iterable[Pattern],
166) -> list[tuple[int, Pattern]]:
167 """
168 Filters out null-patterns.
170 *patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains
171 the patterns.
173 Returns a :class:`list` containing each indexed pattern (:class:`tuple`) which
174 contains the pattern index (:class:`int`) and the actual pattern
175 (:class:`.Pattern`).
176 """
177 return [
178 (__index, __pat)
179 for __index, __pat in enumerate(patterns)
180 if __pat.include is not None
181 ]
184def _is_iterable(value: Any) -> bool:
185 """
186 Check whether the value is an iterable (excludes strings).
188 *value* is the value to check,
190 Returns whether *value* is an iterable (:class:`bool`).
191 """
192 return isinstance(value, Iterable) and not isinstance(value, (str, bytes))
195@deprecated((
196 "pathspec.util.iter_tree() is deprecated. Use iter_tree_files() instead."
197))
198def iter_tree(root, on_error=None, follow_links=None):
199 """
200 .. version-deprecated:: 0.10.0
201 This is an alias for the :func:`.iter_tree_files` function.
202 """
203 return iter_tree_files(root, on_error=on_error, follow_links=follow_links)
206def iter_tree_entries(
207 root: StrPath,
208 on_error: Optional[Callable[[OSError], None]] = None,
209 follow_links: Optional[bool] = None,
210) -> Iterator['TreeEntry']:
211 """
212 Walks the specified directory for all files and directories.
214 *root* (:class:`str` or :class:`os.PathLike`) is the root directory to search.
216 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally is
217 the error handler for file-system exceptions. It will be called with the
218 exception (:exc:`OSError`). Reraise the exception to abort the walk. Default
219 is :data:`None` to ignore file-system exceptions.
221 *follow_links* (:class:`bool` or :data:`None`) optionally is whether to walk
222 symbolic links that resolve to directories. Default is :data:`None` for
223 :data:`True`.
225 Raises :exc:`.RecursionError` if recursion is detected.
227 Returns an :class:`~collections.abc.Iterator` yielding each file or directory
228 entry (:class:`.TreeEntry`) relative to *root*.
229 """
230 if on_error is not None and not callable(on_error):
231 raise TypeError(f"on_error:{on_error!r} is not callable.")
233 if follow_links is None:
234 follow_links = True
236 yield from _iter_tree_entries_next(os.path.abspath(root), '', {}, on_error, follow_links)
239def _iter_tree_entries_next(
240 root_full: str,
241 dir_rel: str,
242 memo: dict[str, str],
243 on_error: Callable[[OSError], None],
244 follow_links: bool,
245) -> Iterator['TreeEntry']:
246 """
247 Scan the directory for all descendant files.
249 *root_full* (:class:`str`) the absolute path to the root directory.
251 *dir_rel* (:class:`str`) the path to the directory to scan relative to
252 *root_full*.
254 *memo* (:class:`dict`) keeps track of ancestor directories encountered. Maps
255 each ancestor real path (:class:`str`) to relative path (:class:`str`).
257 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally is
258 the error handler for file-system exceptions.
260 *follow_links* (:class:`bool`) is whether to walk symbolic links that resolve
261 to directories.
263 Yields each entry (:class:`.TreeEntry`).
264 """
265 dir_full = os.path.join(root_full, dir_rel)
266 dir_real = os.path.realpath(dir_full)
268 # Remember each encountered ancestor directory and its canonical (real) path.
269 # If a canonical path is encountered more than once, recursion has occurred.
270 if dir_real not in memo:
271 memo[dir_real] = dir_rel
272 else:
273 raise RecursionError(real_path=dir_real, first_path=memo[dir_real], second_path=dir_rel)
275 with os.scandir(dir_full) as scan_iter:
276 node_ent: os.DirEntry
277 for node_ent in scan_iter:
278 node_rel = os.path.join(dir_rel, node_ent.name)
280 # Inspect child node.
281 try:
282 node_lstat = node_ent.stat(follow_symlinks=False)
283 except OSError as e:
284 if on_error is not None:
285 on_error(e)
286 continue
288 if node_ent.is_symlink():
289 # Child node is a link, inspect the target node.
290 try:
291 node_stat = node_ent.stat()
292 except OSError as e:
293 if on_error is not None:
294 on_error(e)
295 continue
296 else:
297 node_stat = node_lstat
299 if node_ent.is_dir(follow_symlinks=follow_links):
300 # Child node is a directory, recurse into it and yield its descendant
301 # files.
302 yield TreeEntry(node_ent.name, node_rel, node_lstat, node_stat)
304 yield from _iter_tree_entries_next(root_full, node_rel, memo, on_error, follow_links)
306 elif node_ent.is_file() or node_ent.is_symlink():
307 # Child node is either a file or an unfollowed link, yield it.
308 yield TreeEntry(node_ent.name, node_rel, node_lstat, node_stat)
310 # NOTE: Make sure to remove the canonical (real) path of the directory from
311 # the ancestors memo once we are done with it. This allows the same directory
312 # to appear multiple times. If this is not done, the second occurrence of the
313 # directory will be incorrectly interpreted as a recursion. See
314 # <https://github.com/cpburnz/python-path-specification/pull/7>.
315 del memo[dir_real]
318def iter_tree_files(
319 root: StrPath,
320 on_error: Optional[Callable[[OSError], None]] = None,
321 follow_links: Optional[bool] = None,
322) -> Iterator[str]:
323 """
324 Walks the specified directory for all files.
326 *root* (:class:`str` or :class:`os.PathLike`) is the root directory to search
327 for files.
329 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally is
330 the error handler for file-system exceptions. It will be called with the
331 exception (:exc:`OSError`). Reraise the exception to abort the walk. Default
332 is :data:`None` to ignore file-system exceptions.
334 *follow_links* (:class:`bool` or :data:`None`) optionally is whether to walk
335 symbolic links that resolve to directories. Default is :data:`None` for
336 :data:`True`.
338 Raises :exc:`.RecursionError` if recursion is detected.
340 Returns an :class:`~collections.abc.Iterator` yielding the path to each file
341 (:class:`str`) relative to *root*.
342 """
343 if on_error is not None and not callable(on_error):
344 raise TypeError(f"on_error:{on_error!r} is not callable.")
346 if follow_links is None:
347 follow_links = True
349 yield from _iter_tree_files_next(os.path.abspath(root), '', {}, on_error, follow_links)
352def _iter_tree_files_next(
353 root_full: str,
354 dir_rel: str,
355 memo: dict[str, str],
356 on_error: Callable[[OSError], None],
357 follow_links: bool,
358) -> Iterator[str]:
359 """
360 Scan the directory for all descendant files.
362 *root_full* (:class:`str`) the absolute path to the root directory.
364 *dir_rel* (:class:`str`) the path to the directory to scan relative to
365 *root_full*.
367 *memo* (:class:`dict`) keeps track of ancestor directories encountered. Maps
368 each ancestor real path (:class:`str`) to relative path (:class:`str`).
370 *on_error* (:class:`~collections.abc.Callable` or :data:`None`) optionally is
371 the error handler for file-system exceptions.
373 *follow_links* (:class:`bool`) is whether to walk symbolic links that resolve
374 to directories.
376 Yields each file path (:class:`str`).
377 """
378 dir_full = os.path.join(root_full, dir_rel)
379 dir_real = os.path.realpath(dir_full)
381 # Remember each encountered ancestor directory and its canonical (real) path.
382 # If a canonical path is encountered more than once, recursion has occurred.
383 if dir_real not in memo:
384 memo[dir_real] = dir_rel
385 else:
386 raise RecursionError(real_path=dir_real, first_path=memo[dir_real], second_path=dir_rel)
388 with os.scandir(dir_full) as scan_iter:
389 node_ent: os.DirEntry
390 for node_ent in scan_iter:
391 node_rel = os.path.join(dir_rel, node_ent.name)
393 if node_ent.is_dir(follow_symlinks=follow_links):
394 # Child node is a directory, recurse into it and yield its descendant
395 # files.
396 yield from _iter_tree_files_next(root_full, node_rel, memo, on_error, follow_links)
398 elif node_ent.is_file():
399 # Child node is a file, yield it.
400 yield node_rel
402 elif not follow_links and node_ent.is_symlink():
403 # Child node is an unfollowed link, yield it.
404 yield node_rel
406 # NOTE: Make sure to remove the canonical (real) path of the directory from
407 # the ancestors memo once we are done with it. This allows the same directory
408 # to appear multiple times. If this is not done, the second occurrence of the
409 # directory will be incorrectly interpreted as a recursion. See
410 # <https://github.com/cpburnz/python-path-specification/pull/7>.
411 del memo[dir_real]
414def lookup_pattern(name: str) -> Callable[[AnyStr], Pattern]:
415 """
416 Lookups a registered pattern factory by name.
418 *name* (:class:`str`) is the name of the pattern factory.
420 Returns the registered pattern factory (:class:`~collections.abc.Callable`).
421 If no pattern factory is registered, raises :exc:`KeyError`.
422 """
423 return _registered_patterns[name]
426def match_file(patterns: Iterable[Pattern], file: str) -> bool:
427 """
428 Matches the file to the patterns.
430 *patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains
431 the patterns to use.
433 *file* (:class:`str`) is the normalized file path to be matched against
434 *patterns*.
436 Returns :data:`True` if *file* matched; otherwise, :data:`False`.
437 """
438 matched = False
439 for pattern in patterns:
440 if pattern.include is not None and pattern.match_file(file) is not None:
441 matched = pattern.include
443 return matched
446@deprecated((
447 "pathspec.util.match_files() is deprecated. Use match_file() with a loop for "
448 "better results."
449))
450def match_files(
451 patterns: Iterable[Pattern],
452 files: Iterable[str],
453) -> set[str]:
454 """
455 .. version-deprecated:: 0.10.0
456 This function is no longer used. Use the :func:`.match_file` function with a
457 loop for better results.
459 Matches the files to the patterns.
461 *patterns* (:class:`~collections.abc.Iterable` of :class:`.Pattern`) contains
462 the patterns to use.
464 *files* (:class:`~collections.abc.Iterable` of :class:`str`) contains the
465 normalized file paths to be matched against *patterns*.
467 Returns the matched files (:class:`set` of :class:`str`).
468 """
469 use_patterns = [__pat for __pat in patterns if __pat.include is not None]
471 return_files = set()
472 for file in files:
473 if match_file(use_patterns, file):
474 return_files.add(file)
476 return return_files
479def normalize_file(
480 file: StrPath,
481 separators: Optional[Collection[str]] = None,
482) -> str:
483 """
484 Normalizes the file path to use the POSIX path separator (i.e., ``"/"``), and
485 make the paths relative (remove leading ``"/"``).
487 *file* (:class:`str` or :class:`os.PathLike`) is the file path.
489 *separators* (:class:`~collections.abc.Collection` of :class:`str`; or
490 :data:`None`) optionally contains the path separators to normalize. This does
491 not need to include the POSIX path separator (``"/"``), but including it will
492 not affect the results. Default is ``None`` for :data:`.NORMALIZE_PATH_SEPS`.
493 To prevent normalization, pass an empty container (e.g., an empty tuple
494 ``()``).
496 Returns the normalized file path (:class:`str`).
497 """
498 # Normalize path separators.
499 if separators is None:
500 separators = NORMALIZE_PATH_SEPS
502 # Convert path object to string.
503 norm_file: str = os.fspath(file)
505 for sep in separators:
506 norm_file = norm_file.replace(sep, posixpath.sep)
508 if norm_file.startswith('/'):
509 # Make path relative.
510 norm_file = norm_file[1:]
512 elif norm_file.startswith('./'):
513 # Remove current directory prefix.
514 norm_file = norm_file[2:]
516 return norm_file
519@deprecated((
520 "pathspec.util.normalize_files() is deprecated. Use normalize_file() with a "
521 "loop for better results."
522))
523def normalize_files(
524 files: Iterable[StrPath],
525 separators: Optional[Collection[str]] = None,
526) -> dict[str, list[StrPath]]:
527 """
528 .. version-deprecated:: 0.10.0
529 This function is no longer used. Use the :func:`.normalize_file` function
530 with a loop for better results.
532 Normalizes the file paths to use the POSIX path separator.
534 *files* (:class:`~collections.abc.Iterable` of :class:`str` or
535 :class:`os.PathLike`) contains the file paths to be normalized.
537 *separators* (:class:`~collections.abc.Collection` of :class:`str`; or
538 :data:`None`) optionally contains the path separators to normalize. See
539 :func:`.normalize_file` for more information.
541 Returns a :class:`dict` mapping each normalized file path (:class:`str`) to
542 the original file paths (:class:`list` of :class:`str` or
543 :class:`os.PathLike`).
544 """
545 norm_files = {}
546 for path in files:
547 norm_file = normalize_file(path, separators=separators)
548 if norm_file in norm_files:
549 norm_files[norm_file].append(path)
550 else:
551 norm_files[norm_file] = [path]
553 return norm_files
556def register_pattern(
557 name: str,
558 pattern_factory: Callable[[AnyStr], Pattern],
559 override: Optional[bool] = None,
560) -> None:
561 """
562 Registers the specified pattern factory.
564 *name* (:class:`str`) is the name to register the pattern factory under.
566 *pattern_factory* (:class:`~collections.abc.Callable`) is used to compile
567 patterns. It must accept an uncompiled pattern (:class:`str`) and return the
568 compiled pattern (:class:`.Pattern`).
570 *override* (:class:`bool` or :data:`None`) optionally is whether to allow
571 overriding an already registered pattern under the same name (:data:`True`),
572 instead of raising an :exc:`.AlreadyRegisteredError` (:data:`False`). Default
573 is :data:`None` for :data:`False`.
574 """
575 if not isinstance(name, str):
576 raise TypeError(f"name:{name!r} is not a string.")
578 if not callable(pattern_factory):
579 raise TypeError(f"pattern_factory:{pattern_factory!r} is not callable.")
581 if name in _registered_patterns and not override:
582 raise AlreadyRegisteredError(name, _registered_patterns[name])
584 _registered_patterns[name] = pattern_factory
587class AlreadyRegisteredError(Exception):
588 """
589 The :exc:`AlreadyRegisteredError` exception is raised when a pattern factory
590 is registered under a name already in use.
591 """
593 def __init__(
594 self,
595 name: str,
596 pattern_factory: Callable[[AnyStr], Pattern],
597 ) -> None:
598 """
599 Initializes the :exc:`AlreadyRegisteredError` instance.
601 *name* (:class:`str`) is the name of the registered pattern.
603 *pattern_factory* (:class:`~collections.abc.Callable`) is the registered
604 pattern factory.
605 """
606 super().__init__(name, pattern_factory)
608 @property
609 def message(self) -> str:
610 """
611 *message* (:class:`str`) is the error message.
612 """
613 return (
614 f"{self.name!r} is already registered for pattern factory="
615 f"{self.pattern_factory!r}."
616 )
618 @property
619 def name(self) -> str:
620 """
621 *name* (:class:`str`) is the name of the registered pattern.
622 """
623 return self.args[0]
625 @property
626 def pattern_factory(self) -> Callable[[AnyStr], Pattern]:
627 """
628 *pattern_factory* (:class:`~collections.abc.Callable`) is the registered
629 pattern factory.
630 """
631 return self.args[1]
634class RecursionError(Exception):
635 """
636 The :exc:`RecursionError` exception is raised when recursion is detected.
637 """
639 def __init__(
640 self,
641 real_path: str,
642 first_path: str,
643 second_path: str,
644 ) -> None:
645 """
646 Initializes the :exc:`RecursionError` instance.
648 *real_path* (:class:`str`) is the real path that recursion was encountered
649 on.
651 *first_path* (:class:`str`) is the first path encountered for *real_path*.
653 *second_path* (:class:`str`) is the second path encountered for *real_path*.
654 """
655 super().__init__(real_path, first_path, second_path)
657 @property
658 def first_path(self) -> str:
659 """
660 *first_path* (:class:`str`) is the first path encountered for
661 :attr:`self.real_path <RecursionError.real_path>`.
662 """
663 return self.args[1]
665 @property
666 def message(self) -> str:
667 """
668 *message* (:class:`str`) is the error message.
669 """
670 return (
671 f"Real path {self.real_path!r} was encountered at {self.first_path!r} "
672 f"and then {self.second_path!r}."
673 )
675 @property
676 def real_path(self) -> str:
677 """
678 *real_path* (:class:`str`) is the real path that recursion was
679 encountered on.
680 """
681 return self.args[0]
683 @property
684 def second_path(self) -> str:
685 """
686 *second_path* (:class:`str`) is the second path encountered for
687 :attr:`self.real_path <RecursionError.real_path>`.
688 """
689 return self.args[2]
692@dataclass(frozen=True)
693class CheckResult(Generic[TStrPath]):
694 """
695 The :class:`CheckResult` class contains information about the file and which
696 pattern matched it.
697 """
699 # Make the class dict-less.
700 __slots__ = (
701 'file',
702 'include',
703 'index',
704 )
706 file: TStrPath
707 """
708 *file* (:class:`str` or :class:`os.PathLike`) is the file path.
709 """
711 include: Optional[bool]
712 """
713 *include* (:class:`bool` or :data:`None`) is whether to include or exclude the
714 file. If :data:`None`, no pattern matched.
715 """
717 index: Optional[int]
718 """
719 *index* (:class:`int` or :data:`None`) is the index of the last pattern that
720 matched. If :data:`None`, no pattern matched.
721 """
724class MatchDetail(object):
725 """
726 The :class:`.MatchDetail` class contains information about
727 """
729 # Make the class dict-less.
730 __slots__ = ('patterns',)
732 def __init__(self, patterns: Sequence[Pattern]) -> None:
733 """
734 Initialize the :class:`.MatchDetail` instance.
736 *patterns* (:class:`~collections.abc.Sequence` of :class:`.Pattern`)
737 contains the patterns that matched the file in the order they were encountered.
738 """
740 self.patterns = patterns
741 """
742 *patterns* (:class:`~collections.abc.Sequence` of :class:`.Pattern`)
743 contains the patterns that matched the file in the order they were
744 encountered.
745 """
748class TreeEntry(object):
749 """
750 The :class:`TreeEntry` class contains information about a file-system entry.
751 """
753 # Make the class dict-less.
754 __slots__ = ('_lstat', 'name', 'path', '_stat')
756 def __init__(
757 self,
758 name: str,
759 path: str,
760 lstat: os.stat_result,
761 stat: os.stat_result,
762 ) -> None:
763 """
764 Initialize the :class:`TreeEntry` instance.
766 *name* (:class:`str`) is the base name of the entry.
768 *path* (:class:`str`) is the relative path of the entry.
770 *lstat* (:class:`os.stat_result`) is the stat result of the direct entry.
772 *stat* (:class:`os.stat_result`) is the stat result of the entry,
773 potentially linked.
774 """
776 self._lstat: os.stat_result = lstat
777 """
778 *_lstat* (:class:`os.stat_result`) is the stat result of the direct entry.
779 """
781 self.name: str = name
782 """
783 *name* (:class:`str`) is the base name of the entry.
784 """
786 self.path: str = path
787 """
788 *path* (:class:`str`) is the path of the entry.
789 """
791 self._stat: os.stat_result = stat
792 """
793 *_stat* (:class:`os.stat_result`) is the stat result of the linked entry.
794 """
796 def is_dir(self, follow_links: Optional[bool] = None) -> bool:
797 """
798 Get whether the entry is a directory.
800 *follow_links* (:class:`bool` or :data:`None`) is whether to follow symbolic
801 links. If this is :data:`True`, a symlink to a directory will result in
802 :data:`True`. Default is :data:`None` for :data:`True`.
804 Returns whether the entry is a directory (:class:`bool`).
805 """
806 if follow_links is None:
807 follow_links = True
809 node_stat = self._stat if follow_links else self._lstat
810 return stat.S_ISDIR(node_stat.st_mode)
812 def is_file(self, follow_links: Optional[bool] = None) -> bool:
813 """
814 Get whether the entry is a regular file.
816 *follow_links* (:class:`bool` or :data:`None`) is whether to follow symbolic
817 links. If this is :data:`True`, a symlink to a regular file will result in
818 :data:`True`. Default is :data:`None` for :data:`True`.
820 Returns whether the entry is a regular file (:class:`bool`).
821 """
822 if follow_links is None:
823 follow_links = True
825 node_stat = self._stat if follow_links else self._lstat
826 return stat.S_ISREG(node_stat.st_mode)
828 def is_symlink(self) -> bool:
829 """
830 Returns whether the entry is a symbolic link (:class:`bool`).
831 """
832 return stat.S_ISLNK(self._lstat.st_mode)
834 def stat(self, follow_links: Optional[bool] = None) -> os.stat_result:
835 """
836 Get the cached stat result for the entry.
838 *follow_links* (:class:`bool` or :data:`None`) is whether to follow symbolic
839 links. If this is :data:`True`, the stat result of the linked file will be
840 returned. Default is :data:`None` for :data:`True`.
842 Returns that stat result (:class:`os.stat_result`).
843 """
844 if follow_links is None:
845 follow_links = True
847 return self._stat if follow_links else self._lstat