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 (lk 2.6.15).
IN_IGNOREDYesraised when a watch is removed. Probably useless for you, prefer instead IN_DELETE*.
IN_ISDIRNoevent occurred against a directory. It is always piggybacked with an event. The Event structure automatically provides this information via attribute event.is_dir
IN_MASK_ADDNoto update a mask without overwriting the previous value (lk 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 (lk 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 name is written with specific syntax: process_EVENT_NAME where EVENT_NAME is the name of the handled event to process.
  • There are related events which need most of the time the same treatment. It would be annoying to have to implement multiple times the same code. In those cases common methods can be defined. For example for those two following related events:
      mask = pyinotify.IN_CLOSE_WRITE | pyinotify.IN_CLOSE_NOWRITE

There is a method named process_IN_CLOSE according to syntax pattern process_IN_FAMILYBASENAME. In this case, the two previous events would be processed by this method. Beware not to also implement process_IN_CLOSE_WRITE or process_IN_CLOSE_NOWRITE, because these methods would have an higher precedence (see below), and would be called instead of process_IN_CLOSE.

  • 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 the same actions whatever the kind of event or if we need to have a fall back method for unexpected events, we just need to implement process_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 occurred in /etc. Because, one particularity of os.ismount is to check in /etc/mtab if the partition is mounted, this would lead to an endless situation: call process_IN_OPEN, open /etc/mtab, call process_IN_OPEN, open /etc/mtab ... 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 accepted parameters and returned results of these methods. Note that a return value is always returned when quiet=True, otherwise if quiet=False an exception can 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 extreme case if your parameter doesn't fit the expected format, which could happen if you lost returned values of previous operations, you still can use method get_wd(a_path) and its counterpart get_path(a_wd) from 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')