Namespace

You can access everything from 'pyinotify':

  • Events codes (also accessible as class attributes of EventsCodes).
      import pyinotify
      print pyinotify.ALL_EVENTS

      # is equivalent to
      from pyinotify import *
      print ALL_EVENTS
  • You also can access inotify's variables max_queued_events, max_user_instances and max_user_watches, like this:
      # Read
      mqe = max_queued_events.value
      # Modify
      max_queued_events.value = mqe + 42

The EventsCodes Class

Event NameIs an EventDescription
IN_ACCESSYesfile was accessed.
IN_ATTRIBYesmetadata changed.
IN_CLOSE_NOWRITEYesunwrittable file was closed.
IN_CLOSE_WRITEYeswrittable 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 on watched item removing. Probably useless for you, prefer instead IN_DELETE*.
IN_ISDIRNoevent occurred against directory. It is always piggybacked to an event. The Event structure automatically provide this information (via .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 traced 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 (.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 .add_watch.
IN_OPENYesfile was opened.
IN_Q_OVERFLOWYesevent's queue overflow. This event doesn't belongs to any particular watch.
IN_UNMOUNTYesbacking fs was unmounted. Notified to all watches located on this fs.

The Event Class

Each instance contains all the useful informations about the observed event. However, the presence of each field depends on the type of event. In effect, some fields are irrelevant for some kind of event (for example cookie is meaningless for IN_CREATE whereas it is useful to IN_MOVE_TO).

Consider this example:

<event dir=False mask=0x100 maskname=IN_CREATE name=foo path=/tmp pathname=/tmp/foo wd=1 >

Each raised event will be dispatched to one appropriate processing method (according to its type), in which it can takes actions in response to this event. The possible fields are:

  • wd (int): is the Watch Descriptor, it is an unique identifier who represents the watched item through which this event could be observed.
  • path (str): is the complete path of the watched item as given in parameter to the method .add_watch.
  • name (str): is not None only if the watched item is a directory, and if the current event has occurred against an element included in that directory.
  • pathname (str): concatenation of path and name strings, but if name is None pathname has the same value than path.
  • mask (int): is a bitmask of events, it carries all the types of events watched on wd.
  • maskname (str): readable event name.
  • dir (bool): is a boolean flag set to True if the event has occurred against a directory.
  • cookie (int): is a unique identifier permitting to tie together two related 'moved to' and 'moved from' events.

Subclassing ProcessEvent

We can subclass ProcessEvent and provide you own processing implementation by defining appropriate methods. See the commented example below:

from pyinotify import *

class MyProcessing(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__() are delegated to my_init().
        """
        self._file_object = file_object

    def process_IN_DELETE(event):
        """
        This method process a specific kind 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 for these events: IN_CLOSE_WRITE,
        IN_CLOSE_NOWRITE.
        """
        self._file_object.write('closing: %s\n' event.pathname)

    def process_default(event):
        """
        Ultimately, this method is called for all others kind of events.
        This method can be usuful when similar processing can be applied
        to various events.
        """
        self._file_object.write('default processing\n')

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

Explanations and details:

  • IN_DELETE have its own method providing a specific treatment. We associate an individual processing method by providing a method whose the name is written with the specific syntax: process_EVENT_NAME where EVENT_NAME is the name of the handled event to process. For the sake of simplicity, our two methods are very basics they only print messages on standart output.
  • There are related events which needs most of the time the same treatment. It would be annoying to have to implement two times the same code. In those cases there are common methods. For example for those two following related events:
      mask = IN_CLOSE_WRITE | IN_CLOSE_NOWRITE

There is a method named process_IN_CLOSE according to the general syntax process_IN_FAMILYBASENAME. The two previous events would be processed by this method. In this case, beware to not implement process_IN_CLOSE_WRITE or process_IN_CLOSE_NOWRITE, because these methods have an higher precedence (see below), thereby are looked first and would have been called instead of process_IN_CLOSE.

  • It only makes sense to define process_IN_Q_OVERFLOW when its class' instance is given to Notifier, indeed it could never be called from a processed object associated to a watch, because this event isn't associated to any watch.
  • ALL_EVENTS isn't an event by itself, that means that you don't have to implement the method process_ALL_EVENTS (even worst it would be wrong to define this method), this is just an alias to tell the kernel we want to be notified for all kind of events on a given watch. The kernel raises individual events (along with the IN_ISDIR flag if necessary). Instead, if we need to apply the same actions whatever the kind of event, we should implement a process_default method.
  • Processing methods lookup's order (ordered by increasing order of priority): specialized method (ex: process_IN_CLOSE_WRITE) first, then family method (ex: process_IN_CLOSE), then 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, so we could easily imagine the kind of endless situation: call process_IN_OPEN, open /etc/mtab, call process_IN_OPEN, open /etc/mtab ... loop forever.

Whenever possible you should process your notifications this way, with a single processing object. It is easy to imagine the benefits to have to deal with only one instance (efficiency, data sharing,...):

notifier = Notifier(wm, MyProcessing())

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

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

The Notifier Class

Notifier(watch_manager, default_proc_fun=ProcessEvent(), read_freq=0, treshold=0, timeout=None)
Read notifications, process events.
  • watch_manager (object): instance of WatchManager.
  • default_proc_fun (object): instance of ProcessEvent or one of its subclasses.
  • read_freq (int): if read_freq == 0, events are read asap, if read_freq is > 0, this thread sleeps max(0, read_freq - timeout) seconds. But if timeout is None it can be different because poll is blocking waiting for something to read.
  • treshold (int): File descriptor will be read only if its size to read is >= treshold. If != 0, you likely want to use it in combination with read_freq because without that you keep looping without really reading anything and that until the amount to read is >= treshold. At least with read_freq you may sleep.
  • timeout (int): timeout value passed on to select.select().
check_events() => None
(Don't call this method directly unless you know what you are doing) Check for new events available to read.
process_events() => None
(Don't call this method directly unless you know what you are doing) Routine for processing events from queue by calling their associated processing function (instance of ProcessEvent or one of its subclasses).
read_events() => None
(Don't call this method directly unless you know what you are doing) Read events from device and enqueue them, waiting to be processed.
loop(callback=None, daemonize=False, **args) => None
Read and process events forever (or until receiving sigint signal).
  • callback: functor called after each event processing.
  • daemonize: this thread is daemonized if set to True.
  • args: is a dict passed to the daemonize function which can contains the following keywords:
    • pid_file: file to which pid will be written.
    • force_kill: if True kill the process associated to pid_file
    • stdin, stdout, stderr: files associated to common streams.
stop() => None
Stop the notifications.

The ThreadedNotifier Class

ThreadedNotifier(watch_manager, default_proc_fun=ProcessEvent(), read_freq=0, treshold=0, timeout=10000):
This notifier inherits from threading.Thread and from Notifier, instantiating a separate thread, and providing standart Notifier functionalities. This is a threaded version of Notifier.
  • watch_manager is an instance of WatchManager.
  • default_proc_fun is an instance of ProcessEvent or one of its subclasses. inherits all the methods of Notifier but override the stop() method.
start() => None
Start the new thread, start events notifications.
stop() => None
Stop the thread, stop the notifications.

The Watch Class

Watch(wd, path, mask, proc_fun, auto_add)
Represent a watch, i.e. a file or directory being watched.
  • wd (int): Watch Descriptor.
  • path (str): Path of the file or directory being watched.
  • mask (int): Mask.
  • proc_fun (ProcessEvent): Processing object.
  • auto_add (bool): Automatically add watches on creation of directories.

The WatchManager Class

WatchManager()
The Watch Manager let the client add a new watch, store the active watches, and provide operations on these watches.
add_watch(path, mask, proc_fun=None, rec=False, auto_add=False, do_glob=False, quiet=True) => dict
Add watch(es) on given path(s) with the specified mask.
  • path (str or list of str): Path(s) to watch, the path can either be a file or a directory.
  • mask (int): Bitmask of events.
  • proc_fun (ProcessEvent): Processing object (must be callable). Will be called if provided, otherwise, notifier.default_proc_fun will be called.
  • rec (bool): Recursively add watches on the given path and on all its subdirectories.
  • auto_add (bool): Automatically add watches on newly created directories in the watch's path.
  • do_glob (bool): Authorizes globbing on pathname.
  • quiet (bool): if True raise an WatchManagerError exception on error. See example not_quiet.py.
update_watch(wd, mask=None, proc_fun=None, rec=False, auto_add=False, quiet=True) => dict
Update existing watch(es). All those parameters are updatable.
rm_watch(wd, rec=False, quiet=True) => dict
Remove watch(es).
watch_transient_file(filename, mask, proc_class) => dict
Watch a transient file (filename), which will be created and deleted frequently over time (e.g. pid file).
get_wd(path) => int
Return the watch descriptor associated to path, path is unknown None is returned.
get_path(wd) => str
Return the path associated to wd, if wd is unknown None is returned.

Operations on WatchManager: add_watch, update_watch, rm_watch

The following table specifies the kind of parameter accepted and the kind of result returned by each method. Remark the description of the return value is only really true in the case where quiet=True, because if quiet=False, an exception could be raised on error.

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"

The methods updating or removing a watch only take watch descriptors and return a dictionary notifying the success or failure of operations.

In extreme case if your parameter doesn't fit the expected format, which can happens if you lost previous returned values, you can use the methods get_wd(a_path) and its counterpart get_path(a_wd). The former takes a path and returns its 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. Whenever it is possible avoid this method.

Logging messages

pyinotify use the logging module of the standart library, you can access it and for example set its level of logging with 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.

# cat  .../foo.py
log.debug('debug msg1')
log.setLevel(10)
log.debug('debug msg2')

Gives as result:

# python .../foo.py
DEBUG: debug msg2