Python library to watch for file interaction
Find a file
2025-10-27 17:10:54 +09:00
tests Improve close lock (if callback is hanging) 2025-10-27 16:32:22 +09:00
watchinotify Improve close lock (if callback is hanging) 2025-10-27 16:32:22 +09:00
.gitignore Add build/publish in makefile 2025-10-27 17:10:54 +09:00
LICENSE Initial commit 2025-08-18 17:19:50 +09:00
Makefile Add build/publish in makefile 2025-10-27 17:10:54 +09:00
pyproject.toml Improve close lock (if callback is hanging) 2025-10-27 16:32:22 +09:00
README.md Improve close lock (if callback is hanging) 2025-10-27 16:32:22 +09:00
setup.cfg Improve close lock (if callback is hanging) 2025-10-27 16:32:22 +09:00

FileWatch

FileWatch is a python library to easily use the libc's inotify functions to watch for any file interaction.

The library aims at being simple yet powerfull with easily auditable code.

Limitations

If you watch a file or a folder for a move it will be unwatched after being moved (renaming is considered a move). This is a limitation of the inotify system which desn't give any way to know the new name/path. Any move/rename within watched folders are still being tracked, this also works with the recursive option.

When using the recursive option the folder SUB_CREATED and SUB_MOVED events are enforced. Otherwise the watcher would not be able to keep track of the file tree after adding or moving folders.

Install

Use pip or equivalent to install package name watchinotify:

pip install watchinotify

Usage

Create a Watcher and assign its callback to your custom function, then with the Watcher's watch function to add a list (or any iterable) or path to file/folder to watch. The watcher is going to run on a separated thread waiting for any event then calling the callback. You can run your code after calling watch or wait for an event using the threading.Event for instance.

The following example explicits the parameters types of the callback function:

from pathlib import Path
import threading

from watchinotify import Watcher

event_received = threading.Event()

def callback(path: Path | None, event, name: bytes):
    print(f'New event {event} from path {path} with additionnal name {name.decode()}')
    event_received.set()

watcher = Watcher() as watcher:
watcher.watch([Path('/path/to/watch')])
watcher.callback = callback
# Any of your code from here
event_received.wait()

# Do not forget to close the watcher once done (or use a 'with' statement)
watcher.close()

Using a with statement is easier and safer so the close will be automatically called.

with Watcher(event_type, exclude_patterns, recursive) as watcher:  # will automatically close
    watcher.watch([Path('/path/to/watch')])

The Watcher has 3 arguments:

  • event_type : what type of event to watch see the *Event Type section below. If set to None it is using default value of FileEvent.MODIFIED | FileEvent.DELETED | FileEvent.MOVED for files and FolderEvent.DELETED | FolderEvent.MOVED | FolderEvent.SUB_CREATED | FolderEvent.SUB_DELETED | FolderEvent.SUB_MOVED for folders. You can use the parent Event type to set all required events, if you want specific events separated it is better to create multiple watchers. Default: None
  • exclude_patterns : iterable (list, tuple, etc) of pattern to ignore,following glob conventions, example : ['*/sub_folder', __pycache__]. Default: None
  • recursive : boolean to watch recursively all sub folders/files when adding a folder to watch. This will force the events FolderEvent.SUB_CREATED and FolderEvent.SUB_MOVED to be watched in order to properly work, you'll have to filter out those event in you callback if unwanted. Default: True

Event Types

All possible events are un the Event flag. You can cumulate events by using the or or | operator. 2 additionnal types FileEvent and FolderEvent inherits from the Event values but only contains the event that can be trigger respectively from files and folders. The 2 sub types also have an ALL value as a handy shortcut.

The events are defined as:

from enum import IntFlag, auto

class Event(IntFlag):
    OPENED = auto()
    MODIFIED = auto()
    DELETED = auto()
    MOVED = auto()
    CLOSED = auto()
    SUB_CREATED = auto()
    SUB_DELETED = auto()
    SUB_MOVED = auto()


class FileEvent(IntFlag):
    OPENED = Event.OPENED.value
    MODIFIED = Event.MODIFIED.value
    DELETED = Event.DELETED.value
    MOVED = Event.MOVED.value
    CLOSED = Event.CLOSED.value

    ALL = OPENED | MODIFIED | DELETED | MOVED | CLOSED


class FolderEvent(IntFlag):
    DELETED = Event.DELETED.value
    MOVED = Event.MOVED.value
    SUB_CREATED = Event.SUB_CREATED.value
    SUB_DELETED = Event.SUB_DELETED.value
    SUB_MOVED = Event.SUB_MOVED.value

    ALL = DELETED | MOVED | SUB_CREATED | SUB_DELETED | SUB_MOVED

Development

Run test

pytest

Run coverage

python tests/run_coverage.py

LICENSE

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.