watchfiles icon indicating copy to clipboard operation
watchfiles copied to clipboard

Possible to watch single file for modifications?

Open Wallboy opened this issue 2 years ago • 5 comments

I'm wanting to watch a single txt file for modifications, but when using the default example:

import asyncio
from watchfiles import awatch

async def main():
    async for changes in awatch('/home/user/test_file.txt'):
        print(changes)

asyncio.run(main())

When I modify test_file.txt, it will print the changes. However, subsequent changes no longer print any changes. This is likely to do with the file being deleted/re-added when one modifies the file and awatch probably thinks the file is now gone.

I have to put the test_file in it's own directory, then watch that directory for the changes which works and I can deal with that, but I'm just wondering if there is a way to continuously just watch a single file for modifications like in my above example?

Wallboy avatar Apr 26 '22 04:04 Wallboy

Fundamentally there's not much more watchfiles can do if notify/the OS's fs events don't show anything.

But try debug=True and see what's happening, maybe there's something we can fix here.

Also worth saying what OS you're on.

samuelcolvin avatar Apr 26 '22 06:04 samuelcolvin

Linux Python 3.10

Here is the output with debug=True:

watcher: INotifyWatcher { channel: Sender { .. }, waker: Waker { inner: Waker { fd: File { fd: 8, path: "anon_inode:[eventfd]", read: true, write: true } } } }
raw-event: Event { kind: Access(Close(Write)), paths: ["/home/xxxx/test.txt"], attr:tracker: None, attr:flag: None, attr:info: None, attr:source: None }
raw-event: Event { kind: Modify(Metadata(Any)), paths: ["/home/xxxx/test.txt"], attr:tracker: None, attr:flag: None, attr:info: None, attr:source: None }
raw-event: Event { kind: Remove(File), paths: ["/home/xxxx/test.txt"], attr:tracker: None, attr:flag: None, attr:info: None, attr:source: None }
{(<Change.modified: 2>, '/home/xxxx/test.txt'), (<Change.deleted: 3>, '/home/xxxx/test.txt')}

It only prints the changes when I modify/save text.txt the first time. Subsequent modifications and saves does not print anything else.

EDIT: It seems it depends on what application I'm modifying the file with. When using the mousepad text editor within XFCE, this problem happens. If I simply use nano, it continues to show the changes.

Wallboy avatar Apr 26 '22 07:04 Wallboy

Is there a particular raw event which you see with XFCE which is being logged but not prompting a change?

samuelcolvin avatar Apr 30 '22 11:04 samuelcolvin

It's very likely that your editor is using atomic file saving (i.e, create new temp file, delete old file, then move the temp file to old file's path), due to this line:

raw-event: Event { kind: Remove(File), paths: ["/home/xxxx/test.txt"], attr:tracker: None, attr:flag: None, attr:info: None, attr:source: None }

My educated guest is that inotify tracks changes to an inode, not file path. So when a file was atomically saved, the original inode will be replaced with a new inode. And since inotify only subscribes to changes of the original inode (since you ask watchfiles to watch only one file), you won't see any more changes after the first save.

You can either:

  • Watch the parent directory and subclass watchfiles.DefaultFilter to watch only the desired file path.
  • Turn off atomic save in your editor (can cause lost of data if interrupted mid-save)

lephuongbg avatar May 23 '22 23:05 lephuongbg

The other option might be to use the force_polling=True kwarg (added recently) which will force notify to use file polling which might detect the change more reliably.

samuelcolvin avatar May 24 '22 00:05 samuelcolvin

Closing since we have force_polling, any further issue will probably be a problem with notify.

samuelcolvin avatar Aug 16 '22 20:08 samuelcolvin