Skip to content

Commit

Permalink
[inotify] Add support for the follow_symlink keyword argument (#1086)
Browse files Browse the repository at this point in the history
* Add follow_symlink argument to Inotify observer

* Fix FSEventObserver (add follow_symlink kwarg)

* Improve link test in inotify_c.py

---------

Co-authored-by: Corentin <[email protected]>
  • Loading branch information
Corentin-pro and Corentin authored Nov 12, 2024
1 parent 439a4d7 commit 861293a
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 8 deletions.
18 changes: 16 additions & 2 deletions src/watchdog/observers/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"""
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 3 additions & 1 deletion src/watchdog/observers/fsevents.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,11 +329,13 @@ 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
# string. https://github.com/gorakhargosh/watchdog/issues#issue/26
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)
4 changes: 3 additions & 1 deletion src/watchdog/observers/inotify.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
6 changes: 4 additions & 2 deletions src/watchdog/observers/inotify_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 7 additions & 2 deletions src/watchdog/observers/inotify_c.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand Down

0 comments on commit 861293a

Please sign in to comment.