diff --git a/changelog.rst b/changelog.rst index f21da117e..61572ee72 100644 --- a/changelog.rst +++ b/changelog.rst @@ -8,8 +8,8 @@ Changelog 2021-0x-xx • `full history `__ -- -- Thanks to our beloved contributors: @ +- [mac] Add support for non-recursive watches in FSEventsEmitter (`#779 `_) +- Thanks to our beloved contributors: @CCP-Aporia, @ 2.0.3 diff --git a/src/watchdog/observers/fsevents.py b/src/watchdog/observers/fsevents.py index e1ec74a1e..01b6bf72c 100644 --- a/src/watchdog/observers/fsevents.py +++ b/src/watchdog/observers/fsevents.py @@ -92,8 +92,31 @@ def on_thread_stop(self): self._watch = None def queue_event(self, event): - logger.debug("queue_event %s", event) - EventEmitter.queue_event(self, event) + # fsevents defaults to be recursive, so if the watch was meant to be non-recursive then we need to drop + # all the events here which do not have a src_path / dest_path that matches the watched path + if self._watch.is_recursive: + logger.debug("queue_event %s", event) + EventEmitter.queue_event(self, event) + else: + if not self._is_recursive_event(event): + logger.debug("queue_event %s", event) + EventEmitter.queue_event(self, event) + else: + logger.debug("drop event %s", event) + + def _is_recursive_event(self, event): + src_path = event.src_path if event.is_directory else os.path.dirname(event.src_path) + if src_path == self._watch.path: + return False + + if isinstance(event, (FileMovedEvent, DirMovedEvent)): + # when moving something into the watch path we must always take the dirname, + # otherwise we miss out on `DirMovedEvent`s + dest_path = os.path.dirname(event.dest_path) + if dest_path == self._watch.path: + return False + + return True def _queue_created_event(self, event, src_path, dirname): cls = DirCreatedEvent if event.is_directory else FileCreatedEvent diff --git a/tests/test_emitter.py b/tests/test_emitter.py index 4983a7be1..708a0378b 100644 --- a/tests/test_emitter.py +++ b/tests/test_emitter.py @@ -419,7 +419,6 @@ def test_recursive_on(): @pytest.mark.flaky(max_runs=5, min_passes=1, rerun_filter=rerun_filter) -@pytest.mark.skipif(platform.is_darwin(), reason="macOS watches are always recursive") def test_recursive_off(): mkdir(p('dir1')) start_watching(recursive=False) @@ -428,6 +427,34 @@ def test_recursive_off(): with pytest.raises(Empty): event_queue.get(timeout=5) + mkfile(p('b')) + expect_event(FileCreatedEvent(p('b'))) + if not platform.is_windows(): + expect_event(DirModifiedEvent(p())) + + if platform.is_linux(): + expect_event(FileClosedEvent(p('b'))) + + # currently limiting these additional events to macOS only, see https://github.com/gorakhargosh/watchdog/pull/779 + if platform.is_darwin(): + mkdir(p('dir1', 'dir2')) + with pytest.raises(Empty): + event_queue.get(timeout=5) + mkfile(p('dir1', 'dir2', 'somefile')) + with pytest.raises(Empty): + event_queue.get(timeout=5) + + mkdir(p('dir3')) + expect_event(DirModifiedEvent(p())) # the contents of the parent directory changed + + mv(p('dir1', 'dir2', 'somefile'), p('somefile')) + expect_event(FileMovedEvent(p('dir1', 'dir2', 'somefile'), p('somefile'))) + expect_event(DirModifiedEvent(p())) + + mv(p('dir1', 'dir2'), p('dir2')) + expect_event(DirMovedEvent(p('dir1', 'dir2'), p('dir2'))) + expect_event(DirModifiedEvent(p())) + @pytest.mark.skipif(platform.is_windows(), reason="Windows create another set of events for this test")