13

I'm trying to use watchdog to run a sync script whenever anything changes in a dir (except for one specific file). I simply copied the code from the readme (pasted below), which does what it says; log which file has changed.

import sys
import time
import logging
from watchdog.observers import Observer
from watchdog.events import LoggingEventHandler

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO,
                        format='%(asctime)s - %(message)s',
                        datefmt='%Y-%m-%d %H:%M:%S')
    path = sys.argv[1] if len(sys.argv) > 1 else '.'
    event_handler = LoggingEventHandler()
    observer = Observer()
    observer.schedule(event_handler, path, recursive=True)
    observer.start()
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()

I now want to run a function (which syncs the whole folder to a remote machine) whenever anything changes. So I just replaced event_handler with my own function. But that gives me the following error:

Traceback (most recent call last):
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.py", line 810, in __bootstrap_inner
    self.run()
  File "/Library/Python/2.7/site-packages/watchdog/observers/api.py", line 199, in run
    self.dispatch_events(self.event_queue, self.timeout)
  File "/Library/Python/2.7/site-packages/watchdog/observers/api.py", line 368, in dispatch_events
    handler.dispatch(event)
AttributeError: 'function' object has no attribute 'dispatch'

Does anybody know what I'm doing wrong here? All tips are welcome!

ps. I also want to exclude one file in the folder from being watched. Any ideas how I should do that?

kramer65
  • 39,074
  • 90
  • 255
  • 436

2 Answers2

26

You need to subclass and do whatever you want in dispatch:

import sys
import time
import logging
from watchdog.observers import Observer
from watchdog.events import LoggingEventHandler

class Event(LoggingEventHandler):
    def dispatch(self, event):
        print("Foobar")

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO,
                        format='%(asctime)s - %(message)s',
                        datefmt='%Y-%m-%d %H:%M:%S')
    path = sys.argv[1] if len(sys.argv) > 1 else '.'
    event_handler = Event()
    observer = Observer()
    observer.schedule(event_handler, path, recursive=True)
    observer.start()
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()

If you run the code you will see Foobar outputted whenever a change is detected, to ignore files you may need to use [events.PatternMatchingEventHandler][1]. There are various methods in each

To do something when a something is modified case we can override on_modified:

class Event(LoggingEventHandler):
    def on_modified(self, event):
        print("Doh")

And running the code using the class above with event_handler = Event() and changing a file will output something like:

Doh
Doh
Doh
Doh
Doh
Doh
Doh
2015-10-03 15:33:55 - Created file: ./test.txt___jb_bak___
2015-10-03 15:33:55 - Moved file: from ./test.txt to ./test.txt___jb_old___
2015-10-03 15:33:55 - Moved file: from ./test.txt___jb_bak___ to ./test.txt
2015-10-03 15:33:55 - Deleted file: ./test.txt___jb_old___
Doh

[1]: http://pythonhosted.org/watchdog/api.html#watchdog.events.PatternMatchingEventHandler EventHandler class you can override, it all depends on what it is you want to do. The LoggingEventHandler class itslef is a subclass of watchdog.events.FileSystemEventHandler:

class watchdog.events.FileSystemEventHandler Bases: object

Base file system event handler that you can override methods from.

dispatch(event) Dispatches events to the appropriate methods.

Parameters: event (FileSystemEvent) – The event object representing the file system event.

on_any_event(event) Catch-all event handler.

Parameters: event (FileSystemEvent) – The event object representing the file system event.

on_created(event) Called when a file or directory is created.

Parameters: event (DirCreatedEvent or FileCreatedEvent) – Event representing file/directory creation.

on_deleted(event) Called when a file or directory is deleted.

Parameters: event (DirDeletedEvent or FileDeletedEvent) – Event representing file/directory deletion.

on_modified(event) Called when a file or directory is modified.

Parameters: event (DirModifiedEvent or FileModifiedEvent) – Event representing file/directory modification.

on_moved(event) Called when a file or a directory is moved or renamed.

Parameters: event (DirMovedEvent or FileMovedEvent) – Event representing file/directory movement.
Padraic Cunningham
  • 160,756
  • 20
  • 201
  • 286
  • Why is `LoggingEventHandler` being passed in definition `class Event(LoggingEventHandler)`? I see it works either ways, please explain. – CᴴᴀZ Oct 25 '18 at 09:53
  • @CᴴᴀZ the `LoggingEventHandler` in `class Event(LoggingEventHandler)` is making it so that the `Event` class inherits from the `LoggingEventHandler` class. It works either way in this case probably because you are only using the `dispatch` method. However, if you wanted to use other parts of the `LoggingEventHandler` class they wouldn't work unless it is included – colelemonz Jan 20 '20 at 17:46
1

There are many ways in python to follow changes made in a directory. One such way is to use the watchdog module.

  • Modules needed
    • To install watchdog run this command in the terminal.
pip install watchdog

If you want to make changes (currently in the root directory - ".") at the time a file/directory is created or modified, you can do so by using the following code:

import time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler


class Watcher:
    def __init__(self, path):
        self.observer = Observer()
        self.path = path

    def run(self):
        event_handler = Handler()
        self.observer.schedule(event_handler, self.path, recursive=True)
        self.observer.start()
        try:
            while True:
                time.sleep(1)
        except:
            self.observer.stop()
            print("Error")

        self.observer.join()


class Handler(FileSystemEventHandler):
    @staticmethod
    def on_any_event(event):
        # if event.is_directory:
        #     return None
        print(
            "[{}] noticed: [{}] on: [{}] ".format(
                time.asctime(), event.event_type, event.src_path
            )
        )


if __name__ == "__main__":
    w = Watcher(".")
    w.run()
  • event.src_path will be the full file path
  • event.event_type will be created, moved, etc.

If you want to ignore directory changes just remove the comment.

output:

[Tue Feb  9 00:16:02 2021] noticed: [created] on: [/Users/mt/Documents/stackoverflow/test.txt] 
[Tue Feb  9 00:16:02 2021] noticed: [modified] on: [/Users/mt/Documents/stackoverflow] 
[Tue Feb  9 00:16:19 2021] noticed: [created] on: [/Users/mt/Documents/stackoverflow/download.jpg] 
[Tue Feb  9 00:16:19 2021] noticed: [modified] on: [/Users/mt/Documents/stackoverflow] 
[Tue Feb  9 00:16:30 2021] noticed: [created] on: [/Users/mt/Documents/stackoverflow/new_folder] 
[Tue Feb  9 00:16:30 2021] noticed: [modified] on: [/Users/mt/Documents/stackoverflow] 
[Tue Feb  9 00:16:46 2021] noticed: [deleted] on: [/Users/mt/Documents/stackoverflow/new_folder] 
[Tue Feb  9 00:16:46 2021] noticed: [modified] on: [/Users/mt/Documents/stackoverflow] 
[Tue Feb  9 00:16:52 2021] noticed: [deleted] on: [/Users/mt/Documents/stackoverflow/download.jpg] 
[Tue Feb  9 00:16:52 2021] noticed: [modified] on: [/Users/mt/Documents/stackoverflow] 
[Tue Feb  9 00:17:00 2021] noticed: [deleted] on: [/Users/mt/Documents/stackoverflow/test.txt] 
[Tue Feb  9 00:17:00 2021] noticed: [modified] on: [/Users/mt/Documents/stackoverflow] 

The Observer is the class that watches for any file system change and then dispatches the event to the event handler. It monitors the file system and look for any changes.

The EventHandler is an object that will be notified when something happens to the file system. In general a script is written to watch over any type of new files created or modified like csv, txt, xml, jpg etc.

For example, in the code below the PatternMatchingEventHandler inherits from the FileSystemEventHandler class and is used to do just that. Some useful methods of this class are:

  • on_any_event: will be executed for any event.
  • on_created: Executed when a file or a directory is created.
  • on_modified: Executed when a file is modified or a directory renamed.
  • on_deleted: Executed when a file or directory is deleted.
  • on_moved: Executed when a file or directory is moved.

The below script is used to observe only .csv files using the PatternMAtchingEventHandler. You can further extend the patterns list if you want to observe more than one type of file.

import watchdog.events
import watchdog.observers
import time
import sys


class Handler(watchdog.events.PatternMatchingEventHandler):
    def __init__(self):
        # Set the patterns for PatternMatchingEventHandler
        watchdog.events.PatternMatchingEventHandler.__init__(
            self,
            patterns=["*.csv"],
            ignore_directories=True,
            case_sensitive=False,
        )

    def on_any_event(self, event):
        print(
            "[{}] noticed: [{}] on: [{}] ".format(
                time.asctime(), event.event_type, event.src_path
            )
        )


if __name__ == "__main__":
    path = sys.argv[1] if len(sys.argv) > 1 else "."
    event_handler = Handler()
    observer = watchdog.observers.Observer()
    observer.schedule(event_handler, path=path, recursive=True)
    observer.start()
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()
  • Here with the help of PatternMAtchingEventHandler we can take advantage of processing just events related with files with the .csv extension.

output:

[Tue Feb  9 00:18:51 2021] noticed: [created] on: [/Users/mt/Documents/stackoverflow/test.csv] 
[Tue Feb  9 00:18:59 2021] noticed: [modified] on: [/Users/mt/Documents/stackoverflow/test.csv] 
[Tue Feb  9 00:19:12 2021] noticed: [deleted] on: [/Users/mt/Documents/stackoverflow/test.csv] 
Milovan Tomašević
  • 1,206
  • 1
  • 7
  • 13