Repository: gorakhargosh/watchdog
Pull request #822 (2021-07-21 21:09:04)

Fix watching non-recursive files in a symlinked directory (821)
See #821

It appears that in `fsevents`, the events will contain the "real" file paths. This will break if fsevents end up generating events with the not-"real" path instead, for which I don't know if it could happen or not. The way around this would be to also use `os.path.realpath()` for the `src_path` in `_is_recursive_event()`.

NiklasRosenstein: @gorakhargosh I can't reproduce the `test_watcher_deletion_while_receiving_events_2` test failing on my machine in Python 3.7. I've looked a bit at the test case and it seems it is looking to produce a failure that occurs randomly. Sounds to me like there could be a chance that this test just failed out of pure chance and not because the PR breaks something.

I don't have permission to re-run the tests. Could you kick it off once more? (Or provide some input on what may be going on here?)
BoboTiG: It seems good for me. How do you feel it @CCP-Aporia?
CCP-Aporia: The change itself looks good to me. Thanks for fixing this @NiklasRosenstein !

The test failure should be unrelated to this change as well, though I am surprised to see that one being back. I thought we fixed all those issues around the watcher being `None` previously. 🤔 

Patch:
diff --git a/changelog.rst b/changelog.rst
index 776d8bf..5e7ea21 100644
--- a/changelog.rst
+++ b/changelog.rst
@@ -10,7 +10,8 @@ Changelog
 
 - [watchmedo] Fix usage of ``os.setsid()`` and ``os.killpg()`` Unix-only functions.  (`#809 <https://github.com/gorakhargosh/watchdog/pull/809>`_)
 - [mac] Fix missing ``FileModifiedEvent`` on permission or ownership changes of a file.  (`#809 <https://github.com/gorakhargosh/watchdog/pull/814>`_)
-- Thanks to our beloved contributors: @replabrobin, @BoboTiG, @SamSchott
+- [mac] Fix a possible ``AttributeError`` in ``SkipRepeatsQueue._put()``.  (`#818 <https://github.com/gorakhargosh/watchdog/pull/818>`_)
+- Thanks to our beloved contributors: @replabrobin, @BoboTiG, @SamSchott, @AndreiB97
 
 2.1.3
 ~~~~~
diff --git a/src/watchdog/observers/fsevents.py b/src/watchdog/observers/fsevents.py
index 21ba95c..f1020e8 100644
--- a/src/watchdog/observers/fsevents.py
+++ b/src/watchdog/observers/fsevents.py
@@ -84,7 +84,7 @@ def __init__(self, event_queue, watch, timeout=DEFAULT_EMITTER_TIMEOUT, suppress
         self._start_time = 0.0
         self._starting_state = None
         self._lock = threading.Lock()
-        self._absolute_watch_path = os.path.abspath(os.path.expanduser(self.watch.path))
+        self._absolute_watch_path = os.path.realpath(os.path.abspath(os.path.expanduser(self.watch.path)))
 
     def on_thread_stop(self):
         _fsevents.remove_watch(self.watch)
diff --git a/src/watchdog/utils/bricks.py b/src/watchdog/utils/bricks.py
index 35426b7..0a7e1cd 100644
--- a/src/watchdog/utils/bricks.py
+++ b/src/watchdog/utils/bricks.py
@@ -87,7 +87,7 @@ def _init(self, maxsize):
         self._last_item = None
 
     def _put(self, item):
-        if item != self._last_item:
+        if self._last_item is None or item != self._last_item:
             super()._put(item)
             self._last_item = item
         else:


Test patch:
diff --git a/tests/test_skip_repeats_queue.py b/tests/test_skip_repeats_queue.py
index be03a80..9954a4a 100644
--- a/tests/test_skip_repeats_queue.py
+++ b/tests/test_skip_repeats_queue.py
@@ -16,6 +16,7 @@
 # limitations under the License.
 
 import pytest
+import watchdog.events as events
 from watchdog.utils.bricks import SkipRepeatsQueue
 
 from .markers import cpython_only
@@ -58,6 +59,18 @@ def test_allow_nonconsecutive():
     assert q.empty()
 
 
+def test_put_with_watchdog_events():
+    # FileSystemEvent.__ne__() uses the key property without
+    # doing any type checking. Since _last_item is set to
+    # None in __init__(), an AttributeError is raised when
+    # FileSystemEvent.__ne__() tries to use None.key
+    queue = SkipRepeatsQueue()
+    dummy_file = 'dummy.txt'
+    event = events.FileCreatedEvent(dummy_file)
+    queue.put(event)
+    assert queue.get() is event
+
+
 def test_prevent_consecutive():
     q = SkipRepeatsQueue()