diff --git a/src/watchdog/observers/api.py b/src/watchdog/observers/api.py index 30cb21ca..a26322f3 100644 --- a/src/watchdog/observers/api.py +++ b/src/watchdog/observers/api.py @@ -37,9 +37,17 @@ class ObservedWatch: Optional collection of :class:`watchdog.events.FileSystemEvent` to watch """ - def __init__(self, path: str | Path, *, recursive: bool, event_filter: list[type[FileSystemEvent]] | None = None): + def __init__( + self, + path: str | Path, + *, + recursive: bool, + event_filter: list[type[FileSystemEvent]] | None = None, + follow_symlink: bool = False, + ): self._path = str(path) if isinstance(path, Path) else path self._is_recursive = recursive + self._follow_symlink = follow_symlink self._event_filter = frozenset(event_filter) if event_filter is not None else None @property @@ -52,6 +60,11 @@ def is_recursive(self) -> bool: """Determines whether subdirectories are watched for the path.""" return self._is_recursive + @property + def follow_symlink(self) -> bool: + """Determines whether symlink are followed.""" + return self._follow_symlink + @property def event_filter(self) -> frozenset[type[FileSystemEvent]] | None: """Collection of event types watched for the path""" @@ -274,6 +287,7 @@ def schedule( *, recursive: bool = False, event_filter: list[type[FileSystemEvent]] | None = None, + follow_symlink: bool = False, ) -> ObservedWatch: """Schedules watching a path and calls appropriate methods specified in the given event handler in response to file system events. @@ -302,7 +316,7 @@ def schedule( a watch. """ with self._lock: - watch = ObservedWatch(path, recursive=recursive, event_filter=event_filter) + watch = ObservedWatch(path, recursive=recursive, event_filter=event_filter, follow_symlink=follow_symlink) self._add_handler_for_watch(event_handler, watch) # If we don't have an emitter for this watch already, create it. diff --git a/src/watchdog/observers/fsevents.py b/src/watchdog/observers/fsevents.py index 257e16e7..dd6886ef 100644 --- a/src/watchdog/observers/fsevents.py +++ b/src/watchdog/observers/fsevents.py @@ -329,6 +329,7 @@ def schedule( path: str, *, recursive: bool = False, + follow_symlink: bool = False, event_filter: list[type[FileSystemEvent]] | None = None, ) -> ObservedWatch: # Fix for issue #26: Trace/BPT error when given a unicode path @@ -336,4 +337,5 @@ def schedule( if isinstance(path, str): path = unicodedata.normalize("NFC", path) - return super().schedule(event_handler, path, recursive=recursive, event_filter=event_filter) + return super().schedule(event_handler, path, recursive=recursive, follow_symlink=follow_symlink, + event_filter=event_filter) diff --git a/src/watchdog/observers/inotify.py b/src/watchdog/observers/inotify.py index a07aee5c..39858e3e 100644 --- a/src/watchdog/observers/inotify.py +++ b/src/watchdog/observers/inotify.py @@ -116,7 +116,9 @@ def __init__( def on_thread_start(self) -> None: path = os.fsencode(self.watch.path) event_mask = self.get_event_mask_from_filter() - self._inotify = InotifyBuffer(path, recursive=self.watch.is_recursive, event_mask=event_mask) + self._inotify = InotifyBuffer( + path, recursive=self.watch.is_recursive, event_mask=event_mask, follow_symlink=self.watch.follow_symlink + ) def on_thread_stop(self) -> None: if self._inotify: diff --git a/src/watchdog/observers/inotify_buffer.py b/src/watchdog/observers/inotify_buffer.py index f542974a..f8ce7ba4 100644 --- a/src/watchdog/observers/inotify_buffer.py +++ b/src/watchdog/observers/inotify_buffer.py @@ -23,11 +23,13 @@ class InotifyBuffer(BaseThread): delay = 0.5 - def __init__(self, path: bytes, *, recursive: bool = False, event_mask: int | None = None) -> None: + def __init__( + self, path: bytes, *, recursive: bool = False, event_mask: int | None = None, follow_symlink: bool = False + ) -> None: super().__init__() # XXX: Remove quotes after Python 3.9 drop self._queue = DelayedQueue["InotifyEvent | tuple[InotifyEvent, InotifyEvent]"](self.delay) - self._inotify = Inotify(path, recursive=recursive, event_mask=event_mask) + self._inotify = Inotify(path, recursive=recursive, event_mask=event_mask, follow_symlink=follow_symlink) self.start() def read_event(self) -> InotifyEvent | tuple[InotifyEvent, InotifyEvent] | None: diff --git a/src/watchdog/observers/inotify_c.py b/src/watchdog/observers/inotify_c.py index 33cbd25d..c8c641b6 100644 --- a/src/watchdog/observers/inotify_c.py +++ b/src/watchdog/observers/inotify_c.py @@ -142,7 +142,9 @@ class Inotify: ``True`` if subdirectories should be monitored; ``False`` otherwise. """ - def __init__(self, path: bytes, *, recursive: bool = False, event_mask: int | None = None) -> None: + def __init__( + self, path: bytes, *, recursive: bool = False, event_mask: int | None = None, follow_symlink: bool = False + ) -> None: # The file descriptor associated with the inotify instance. inotify_fd = inotify_init() if inotify_fd == -1: @@ -179,7 +181,10 @@ def do_select() -> bool: # Default to all events if event_mask is None: event_mask = WATCHDOG_ALL_EVENTS + if follow_symlink: + event_mask &= ~InotifyConstants.IN_DONT_FOLLOW self._event_mask = event_mask + self._follow_symlink = follow_symlink self._is_recursive = recursive if os.path.isdir(path): self._add_dir_watch(path, event_mask, recursive=recursive) @@ -406,7 +411,7 @@ def _add_dir_watch(self, path: bytes, mask: int, *, recursive: bool) -> None: for root, dirnames, _ in os.walk(path): for dirname in dirnames: full_path = os.path.join(root, dirname) - if os.path.islink(full_path): + if not self._follow_symlink and os.path.islink(full_path): continue self._add_watch(full_path, mask)