# `cement.ext.ext_watchdog`

The Watchdog Framework Extension enables applications Built on Cement (tm) to easily monitor, and react to, changes in filesystem paths based on the filesystem events monitoring library Watchdog.

On application startup, the Watchdog Observer is automatically started and then upon application close, the observer thread is properly stopped and joined with the parent process before exit.

This is a simplified wrapper around the functionality of the Watchdog library. For full usage, please see the Watchdog API Documentation.

## Requirements

> - Watchdog (`pip install watchdog`)

## Features

> - Cross platform support for Linux, OSX, Windows, etc.

## Configuration

This extension honors the following application meta-data settings:

> - watchdog_paths - A List of tuples that are passed directly as arguments to `WatchdogManager.add()` (shortcut equivalent of `app.watchdog.add(...)`.

## Hooks

This extension defines the following hooks:

### watchdog_pre_start

Run first when `CementApp.watchdog.start()` is called. The application object is passed as an argument. Nothing is expected in return.

### watchdog_post_start

Run last when `CementApp.watchdog.start()` is called. The application object is passed as an argument. Nothing is expected in return.

### watchdog_pre_stop

Run first when `CementApp.watchdog.stop()` is called. The application object is passed as an argument. Nothing is expected in return.

### watchdog_post_stop

Run last when `CementApp.watchdog.stop()` is called. The application object is passed as an argument. Nothing is expected in return.

### watchdog_pre_join

Run first when `CementApp.watchdog.join()` is called. The application object is passed as an argument. Nothing is expected in return.

### watchdog_post_join

Run last when `CementApp.watchdog.join()` is called. The application object is passed as an argument. Nothing is expected in return.

## Usage

The following example uses the default `WatchdogEventHandler` that simply logs all events to `debug`:

    from time import sleep
    from cement.core.foundation import CementApp
    from cement.core.exc import CaughtSignal
    from cement.ext.ext_watchdog import WatchdogEventHandler

    class MyApp(CementApp):
        class Meta:
            label = 'myapp'
            extensions = ['watchdog']
            watchdog_paths = [
                ('./tmp/', WatchdogEventHandler),
            ]

    with MyApp() as app:
        app.run()

        try:
            while True:
                sleep(1)
        except CaughtSignal as e:
            print(e)

In the above example, nothing is printed to console however you will see something like the following via debug logging:

    $ python myapp.py --debug 2>&1 | grep -i watchdog
    cement.core.extension : loading the 'cement.ext.ext_watchdog' framework extension
    cement.core.hook : defining hook 'watchdog_pre_start'
    cement.core.hook : defining hook 'watchdog_post_start'
    cement.core.hook : defining hook 'watchdog_pre_stop'
    cement.core.hook : defining hook 'watchdog_post_stop'
    cement.core.hook : defining hook 'watchdog_pre_join'
    cement.core.hook : defining hook 'watchdog_post_join'
    cement.core.hook : registering hook 'watchdog_extend_app' from cement.ext.ext_watchdog into hooks['post_setup']
    cement.core.hook : registering hook 'watchdog_add_paths' from cement.ext.ext_watchdog into hooks['post_setup']
    cement.core.hook : registering hook 'watchdog_start' from cement.ext.ext_watchdog into hooks['pre_run']
    cement.core.hook : registering hook 'watchdog_cleanup' from cement.ext.ext_watchdog into hooks['pre_close']
    cement.core.hook : running hook 'post_setup' (<function watchdog_extend_app at 0x103c991e0>) from cement.ext.ext_watchdog
    cement.core.foundation : extending appication with '.watchdog' (<cement.ext.ext_watchdog.WatchdogManager object at 0x103f83ef0>)
    cement.core.hook : running hook 'post_setup' (<function watchdog_add_paths at 0x103ddd6a8>) from cement.ext.ext_watchdog
    cement.ext.ext_watchdog : adding path /path/to/tmp with event handler <class 'cement.ext.ext_watchdog.WatchdogEventHandler'>
    cement.core.hook : running hook 'pre_run' (<function watchdog_start at 0x103ddd598>) from cement.ext.ext_watchdog
    cement.ext.ext_watchdog : starting watchdog observer
    myapp : Watchdog Event: <FileDeletedEvent: src_path='/path/to/tmp/test2'>
    myapp : Watchdog Event: <FileDeletedEvent: src_path='/path/to/tmp/test4'>
    myapp : Watchdog Event: <FileDeletedEvent: src_path='/path/to/tmp/test3'>
    myapp : Watchdog Event: <FileDeletedEvent: src_path='/path/to/tmp/dir1/test'>
    myapp : Watchdog Event: <FileDeletedEvent: src_path='/path/to/tmp/test'>
    myapp : Watchdog Event: <DirDeletedEvent: src_path='/path/to/tmp/dir1'>
    myapp : Watchdog Event: <DirModifiedEvent: src_path='/path/to/tmp'>
    myapp : Watchdog Event: <DirModifiedEvent: src_path='/path/to/tmp'>
    myapp : Watchdog Event: <DirCreatedEvent: src_path='/path/to/tmp/dir1'>
    myapp : Watchdog Event: <FileCreatedEvent: src_path='/path/to/tmp/dir1/test.file'>
    myapp : Watchdog Event: <DirModifiedEvent: src_path='/path/to/tmp/dir1'>
    cement.core.hook : running hook 'pre_close' (<function watchdog_cleanup at 0x10e930620>) from cement.ext.ext_watchdog
    cement.ext.ext_watchdog : stopping watchdog observer
    cement.ext.ext_watchdog : joining watchdog observer
    cement.core.foundation : closing the myapp application

To expand on the above example, we can add our own event handlers:

    class MyEventHandler(WatchdogEventHandler):
        def on_any_event(self, event):
            # do something with the ``event`` object
            print("The modified path was: %s" % event.src_path)

    class MyApp(CementApp):
        class Meta:
            label = 'myapp'
            extensions = ['watchdog']
            watchdog_paths = [
                ('./tmp/', MyEventHandler),
            ]

    $ python myapp.py
    The modified path was: /path/to/tmp/test.file

Note that the `WatchdogEventHandler` could be replaced with any other event handler classes (i.e. those available from `watchdog` directly), however to play nicely with Cement, we sub-class them first in order to pass in our application object:

    from watchdog.events import FileSystemEventHandler

    class MyEventHandler(FileSystemEventHandler):
        def __init__(self, app, *args, **kw):
            super(MyEventHandler, self).__init__(*args, **kw)
            self.app = app

For full usage of Watchdog event handlers, refer to the Watchdog API Documentation.

class `cement.ext.ext_watchdog.``WatchdogEventHandler`(app, *args, **kw)

  Bases: `watchdog.events.FileSystemEventHandler`

  Default event handler used by Cement, that logs all events to the application’s debug log. Additional `*args` and `**kwargs` are passed to the parent class.

  Parameters: | app – The application object

  `on_any_event`(event)

    Catch-all event handler.

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

class `cement.ext.ext_watchdog.``WatchdogManager`(app, *args, **kw)

  Bases: `cement.core.meta.MetaMixin`

  The manager class that is attached to the application object via `CementApp.extend()`.

  Usage:

      with MyApp() as app:
          app.watchdog.start()
          app.watchdog.stop()
          app.watchdog.join()

  `add`(path, event_handler=None, recursive=True)

    Add a directory path and event handler to the observer.

    Parameters:

    - path – A directory path to monitor (str)
    - event_handler – An event handler class used to handle events for `path` (class)
    - recursive – Whether to monitor the `path` recursively (bool)

    Returns: | Returns `True` if the path is added, `False` otherwise. (bool)

  `join`(*args, **kw)

    Join the observer with the parent process. All `*args` and `**kwargs` are passed down to the backend observer.

  `start`(*args, **kw)

    Start the observer. All `*args` and `**kwargs` are passed down to the backend observer.

  `stop`(*args, **kw)

    Stop the observer. All `*args` and `**kwargs` are passed down to the backend observer.