wiki:DocumentationPage

The EventsCodes Class

Event NameIs an EventDescription
IN_ACCESSYesfile was accessed.
IN_ATTRIBYesmetadata changed.
IN_CLOSE_NOWRITEYesunwritable file was closed.
IN_CLOSE_WRITEYeswritable file was closed.
IN_CREATEYesfile/dir was created in watched directory.
IN_DELETEYesfile/dir was deleted in watched directory.
IN_DELETE_SELFYeswatched item itself was deleted.
IN_DONT_FOLLOWNodon't follow a symlink (since kernel 2.6.15).
IN_IGNOREDYesraised when a watch is removed. Probably useless for you, prefer instead IN_DELETE*.
IN_ISDIRNoAlways associated with an event triggered on a directory. The Event structure automatically provides this information via attribute event.is_dir
IN_MASK_ADDNoto update a mask without overwriting the previous value (since kernel 2.6.14). Useful when updating a watch.
IN_MODIFYYesfile was modified.
IN_MOVE_SELFYeswatched item itself was moved, currently its full pathname destination can only be known if its source directory and destination directory are both watched. Otherwise, the file is still being watched but you cannot rely anymore on the given path attribute event.path
IN_MOVED_FROMYesfile/dir in a watched dir was moved from X. Can trace the full move of an item when IN_MOVED_TO is available too, in this case if the moved item is itself watched, its path will be updated (see IN_MOVE_SELF).
IN_MOVED_TOYesfile/dir was moved to Y in a watched dir (see IN_MOVE_FROM).
IN_ONLYDIRNoonly watch the path if it is a directory (since kernel 2.6.15). Usable when calling watch_manager.add_watch().
IN_OPENYesfile was opened.
IN_Q_OVERFLOWYesevent's queue overflown. This event doesn't is not associated with a watch.
IN_UNMOUNTYesbacking fs was unmounted. Notified to each watch of this fs.

Subclassing ProcessEvent

We can subclass ProcessEvent and provide our own event handler by defining appropriate methods. See the commented example below:

import pyinotify

class MyEventHandler(pyinotify.ProcessEvent):
    def my_init(self, file_object=sys.stdout):
        """
        This is your constructor it is automatically called from ProcessEvent.__init__(),
        And extra arguments passed to __init__() would be delegated automatically to 
        my_init().
        """
        self._file_object = file_object

    def process_IN_DELETE(event):
        """
        This method processes a specific type of event: IN_DELETE. event
        is an instance of Event.
        """
        self._file_object.write('deleting: %s\n' event.pathname)

    def process_IN_CLOSE(event):
        """
        This method is called on these events: IN_CLOSE_WRITE and
        IN_CLOSE_NOWRITE.
        """
        self._file_object.write('closing: %s\n' event.pathname)

    def process_default(event):
        """
        Eventually, this method is called for all others types of events.
        This method can be useful when an action fits all events.
        """
        self._file_object.write('default processing\n')

# A way to instantiate this class could be:
p = MyEventHandler(file('/tmp/output', 'w'))

Explanations and details:

  • IN_DELETE has a specific method. An individual processing method is provided by a method whose the name is written with a specific syntax: process_EVENT_NAME where EVENT_NAME is the name of the current event to process.
  • Related events most of the time need the same treatment. In those cases shared methods can be defined avoiding duplicating chunks of codes. Like for instance for those two following related events:
      mask = pyinotify.IN_CLOSE_WRITE | pyinotify.IN_CLOSE_NOWRITE

It is enough to implement a method named process_IN_CLOSE (according to pattern process_IN_FAMILYBASENAME) to handle both types of events. Beware though, not to also implement process_IN_CLOSE_WRITE or process_IN_CLOSE_NOWRITE, because if defined these methods would have an higher precedence (see below) over process_IN_CLOSE and the latter would not be called.

  • ALL_EVENTS isn't an event by itself, it is the aggregation of all events codes, that means that you can't implement the method process_ALL_EVENTS.
  • If we need to apply a single treatment whatever the kind of event received or if we need to have a fall back method for unexpected events, we just need to implement process_default which is the default method.
  • Processing methods lookup's order (by increasing order of priority): specialized methods (eg: process_IN_CLOSE_WRITE) first, then family methods (eg: process_IN_CLOSE), finally default method (process_default).
  • One more thing: say you redefine the method process_default which contains the instruction os.ismount(my-mount-point), it would be for example a mistake having this method called for every event IN_OPEN occurring on /etc. Because, one particularity of os.ismount is to check in /etc/mtab if the partition is mounted, in this case it would lead to an endless situation: process_IN_OPEN called, /etc/mtab opened, process_IN_OPEN called, /etc/mtab opened... looping forever.

Whenever possible you should process your notifications this way:

notifier = pyinotify.Notifier(wm, MyEventHandler())

But, some watches may need a different kind of processing, you can attach them a specialized instance which will be called only on their associated watch:

wm.add_watch('/one/path', pyinotify.ALL_EVENTS, proc_fun=MyEventHandler())

Operations on WatchManager: add_watch, update_watch, rm_watch

The following table details what parameters are accepted and what types of results are returned by few important methods. Note that a value is always returned when quiet=True, otherwise if quiet=False an exception may be triggered if an error happens.

MethodParameterReturned resultExample
add_watchpath (or list of paths){path1: wd1, path2: wd2, ...} where wdx is the watch descriptor associated to pathx, and is positive on success.ra = notifier.add_watch('/a-dir', mask); if ra['/a-dir'] > 0: print "added"
update_watchwd (or list of wds){wd1: success, wd2: success, ...} where success is True if the op on wdx succeeded, False otherwise.ru = notifier.update_watch(ra['/a-dir'], new_mask); if ru['/a-dir']: print "updated"
rm_watchwd (or list of wds){wd1: success, wd2: success, ...} where success is True if the op on wdx succeeded, False otherwise.rr = notifier.rm_watch(ra['/a-dir']); if rr['/a-dir']: print "deleted"

Update and remove methods take watch descriptors as arguments and return a dictionary notifying the success or failure of the operations.

In last resort if you lose the watch descriptor associated to a path you might retrieve this descriptor by calling get_wd(a_path). Likewise this method has a counterpart get_path(a_wd) also located in class WatchManager?. The former method takes a path and returns its associated wd, the latter takes a wd and returns its path. Caution: in worst case get_wd(a_path) will have to iterate the whole list of watches, thus the cost can be high.

Logging messages

pyinotify use the logging module of the standard library, you can access it through 'pyinotify.log' attibute and for example set its level to log.setLevel(level) (by default the level is set to 20 when pyinotify is imported and is set to 10 when it is executed from command line.

import pyinotify
# [...]
# Modify logging level
pyinotify.log.setLevel(10)
# output debug message
pyinotify.log.debug('debug msg2')