Spaces:
Sleeping
Sleeping
""":module: watchdog.events | |
:synopsis: File system events and event handlers. | |
:author: [email protected] (Yesudeep Mangalapilly) | |
:author: [email protected] (Mickaël Schoentgen) | |
Event Classes | |
------------- | |
.. autoclass:: FileSystemEvent | |
:members: | |
:show-inheritance: | |
:inherited-members: | |
.. autoclass:: FileSystemMovedEvent | |
:members: | |
:show-inheritance: | |
.. autoclass:: FileMovedEvent | |
:members: | |
:show-inheritance: | |
.. autoclass:: DirMovedEvent | |
:members: | |
:show-inheritance: | |
.. autoclass:: FileModifiedEvent | |
:members: | |
:show-inheritance: | |
.. autoclass:: DirModifiedEvent | |
:members: | |
:show-inheritance: | |
.. autoclass:: FileCreatedEvent | |
:members: | |
:show-inheritance: | |
.. autoclass:: FileClosedEvent | |
:members: | |
:show-inheritance: | |
.. autoclass:: FileClosedNoWriteEvent | |
:members: | |
:show-inheritance: | |
.. autoclass:: FileOpenedEvent | |
:members: | |
:show-inheritance: | |
.. autoclass:: DirCreatedEvent | |
:members: | |
:show-inheritance: | |
.. autoclass:: FileDeletedEvent | |
:members: | |
:show-inheritance: | |
.. autoclass:: DirDeletedEvent | |
:members: | |
:show-inheritance: | |
Event Handler Classes | |
--------------------- | |
.. autoclass:: FileSystemEventHandler | |
:members: | |
:show-inheritance: | |
.. autoclass:: PatternMatchingEventHandler | |
:members: | |
:show-inheritance: | |
.. autoclass:: RegexMatchingEventHandler | |
:members: | |
:show-inheritance: | |
.. autoclass:: LoggingEventHandler | |
:members: | |
:show-inheritance: | |
""" | |
from __future__ import annotations | |
import logging | |
import os.path | |
import re | |
from dataclasses import dataclass, field | |
from typing import TYPE_CHECKING | |
from watchdog.utils.patterns import match_any_paths | |
if TYPE_CHECKING: | |
from collections.abc import Generator | |
EVENT_TYPE_MOVED = "moved" | |
EVENT_TYPE_DELETED = "deleted" | |
EVENT_TYPE_CREATED = "created" | |
EVENT_TYPE_MODIFIED = "modified" | |
EVENT_TYPE_CLOSED = "closed" | |
EVENT_TYPE_CLOSED_NO_WRITE = "closed_no_write" | |
EVENT_TYPE_OPENED = "opened" | |
class FileSystemEvent: | |
"""Immutable type that represents a file system event that is triggered | |
when a change occurs on the monitored file system. | |
All FileSystemEvent objects are required to be immutable and hence | |
can be used as keys in dictionaries or be added to sets. | |
""" | |
src_path: bytes | str | |
dest_path: bytes | str = "" | |
event_type: str = field(default="", init=False) | |
is_directory: bool = field(default=False, init=False) | |
""" | |
True if event was synthesized; False otherwise. | |
These are events that weren't actually broadcast by the OS, but | |
are presumed to have happened based on other, actual events. | |
""" | |
is_synthetic: bool = field(default=False) | |
class FileSystemMovedEvent(FileSystemEvent): | |
"""File system event representing any kind of file system movement.""" | |
event_type = EVENT_TYPE_MOVED | |
# File events. | |
class FileDeletedEvent(FileSystemEvent): | |
"""File system event representing file deletion on the file system.""" | |
event_type = EVENT_TYPE_DELETED | |
class FileModifiedEvent(FileSystemEvent): | |
"""File system event representing file modification on the file system.""" | |
event_type = EVENT_TYPE_MODIFIED | |
class FileCreatedEvent(FileSystemEvent): | |
"""File system event representing file creation on the file system.""" | |
event_type = EVENT_TYPE_CREATED | |
class FileMovedEvent(FileSystemMovedEvent): | |
"""File system event representing file movement on the file system.""" | |
class FileClosedEvent(FileSystemEvent): | |
"""File system event representing file close on the file system.""" | |
event_type = EVENT_TYPE_CLOSED | |
class FileClosedNoWriteEvent(FileSystemEvent): | |
"""File system event representing an unmodified file close on the file system.""" | |
event_type = EVENT_TYPE_CLOSED_NO_WRITE | |
class FileOpenedEvent(FileSystemEvent): | |
"""File system event representing file close on the file system.""" | |
event_type = EVENT_TYPE_OPENED | |
# Directory events. | |
class DirDeletedEvent(FileSystemEvent): | |
"""File system event representing directory deletion on the file system.""" | |
event_type = EVENT_TYPE_DELETED | |
is_directory = True | |
class DirModifiedEvent(FileSystemEvent): | |
"""File system event representing directory modification on the file system.""" | |
event_type = EVENT_TYPE_MODIFIED | |
is_directory = True | |
class DirCreatedEvent(FileSystemEvent): | |
"""File system event representing directory creation on the file system.""" | |
event_type = EVENT_TYPE_CREATED | |
is_directory = True | |
class DirMovedEvent(FileSystemMovedEvent): | |
"""File system event representing directory movement on the file system.""" | |
is_directory = True | |
class FileSystemEventHandler: | |
"""Base file system event handler that you can override methods from.""" | |
def dispatch(self, event: FileSystemEvent) -> None: | |
"""Dispatches events to the appropriate methods. | |
:param event: | |
The event object representing the file system event. | |
:type event: | |
:class:`FileSystemEvent` | |
""" | |
self.on_any_event(event) | |
getattr(self, f"on_{event.event_type}")(event) | |
def on_any_event(self, event: FileSystemEvent) -> None: | |
"""Catch-all event handler. | |
:param event: | |
The event object representing the file system event. | |
:type event: | |
:class:`FileSystemEvent` | |
""" | |
def on_moved(self, event: DirMovedEvent | FileMovedEvent) -> None: | |
"""Called when a file or a directory is moved or renamed. | |
:param event: | |
Event representing file/directory movement. | |
:type event: | |
:class:`DirMovedEvent` or :class:`FileMovedEvent` | |
""" | |
def on_created(self, event: DirCreatedEvent | FileCreatedEvent) -> None: | |
"""Called when a file or directory is created. | |
:param event: | |
Event representing file/directory creation. | |
:type event: | |
:class:`DirCreatedEvent` or :class:`FileCreatedEvent` | |
""" | |
def on_deleted(self, event: DirDeletedEvent | FileDeletedEvent) -> None: | |
"""Called when a file or directory is deleted. | |
:param event: | |
Event representing file/directory deletion. | |
:type event: | |
:class:`DirDeletedEvent` or :class:`FileDeletedEvent` | |
""" | |
def on_modified(self, event: DirModifiedEvent | FileModifiedEvent) -> None: | |
"""Called when a file or directory is modified. | |
:param event: | |
Event representing file/directory modification. | |
:type event: | |
:class:`DirModifiedEvent` or :class:`FileModifiedEvent` | |
""" | |
def on_closed(self, event: FileClosedEvent) -> None: | |
"""Called when a file opened for writing is closed. | |
:param event: | |
Event representing file closing. | |
:type event: | |
:class:`FileClosedEvent` | |
""" | |
def on_closed_no_write(self, event: FileClosedNoWriteEvent) -> None: | |
"""Called when a file opened for reading is closed. | |
:param event: | |
Event representing file closing. | |
:type event: | |
:class:`FileClosedNoWriteEvent` | |
""" | |
def on_opened(self, event: FileOpenedEvent) -> None: | |
"""Called when a file is opened. | |
:param event: | |
Event representing file opening. | |
:type event: | |
:class:`FileOpenedEvent` | |
""" | |
class PatternMatchingEventHandler(FileSystemEventHandler): | |
"""Matches given patterns with file paths associated with occurring events. | |
Uses pathlib's `PurePath.match()` method. `patterns` and `ignore_patterns` | |
are expected to be a list of strings. | |
""" | |
def __init__( | |
self, | |
*, | |
patterns: list[str] | None = None, | |
ignore_patterns: list[str] | None = None, | |
ignore_directories: bool = False, | |
case_sensitive: bool = False, | |
): | |
super().__init__() | |
self._patterns = patterns | |
self._ignore_patterns = ignore_patterns | |
self._ignore_directories = ignore_directories | |
self._case_sensitive = case_sensitive | |
def patterns(self) -> list[str] | None: | |
"""(Read-only) | |
Patterns to allow matching event paths. | |
""" | |
return self._patterns | |
def ignore_patterns(self) -> list[str] | None: | |
"""(Read-only) | |
Patterns to ignore matching event paths. | |
""" | |
return self._ignore_patterns | |
def ignore_directories(self) -> bool: | |
"""(Read-only) | |
``True`` if directories should be ignored; ``False`` otherwise. | |
""" | |
return self._ignore_directories | |
def case_sensitive(self) -> bool: | |
"""(Read-only) | |
``True`` if path names should be matched sensitive to case; ``False`` | |
otherwise. | |
""" | |
return self._case_sensitive | |
def dispatch(self, event: FileSystemEvent) -> None: | |
"""Dispatches events to the appropriate methods. | |
:param event: | |
The event object representing the file system event. | |
:type event: | |
:class:`FileSystemEvent` | |
""" | |
if self.ignore_directories and event.is_directory: | |
return | |
paths = [] | |
if hasattr(event, "dest_path"): | |
paths.append(os.fsdecode(event.dest_path)) | |
if event.src_path: | |
paths.append(os.fsdecode(event.src_path)) | |
if match_any_paths( | |
paths, | |
included_patterns=self.patterns, | |
excluded_patterns=self.ignore_patterns, | |
case_sensitive=self.case_sensitive, | |
): | |
super().dispatch(event) | |
class RegexMatchingEventHandler(FileSystemEventHandler): | |
"""Matches given regexes with file paths associated with occurring events. | |
Uses the `re` module. | |
""" | |
def __init__( | |
self, | |
*, | |
regexes: list[str] | None = None, | |
ignore_regexes: list[str] | None = None, | |
ignore_directories: bool = False, | |
case_sensitive: bool = False, | |
): | |
super().__init__() | |
if regexes is None: | |
regexes = [r".*"] | |
elif isinstance(regexes, str): | |
regexes = [regexes] | |
if ignore_regexes is None: | |
ignore_regexes = [] | |
if case_sensitive: | |
self._regexes = [re.compile(r) for r in regexes] | |
self._ignore_regexes = [re.compile(r) for r in ignore_regexes] | |
else: | |
self._regexes = [re.compile(r, re.IGNORECASE) for r in regexes] | |
self._ignore_regexes = [re.compile(r, re.IGNORECASE) for r in ignore_regexes] | |
self._ignore_directories = ignore_directories | |
self._case_sensitive = case_sensitive | |
def regexes(self) -> list[re.Pattern[str]]: | |
"""(Read-only) | |
Regexes to allow matching event paths. | |
""" | |
return self._regexes | |
def ignore_regexes(self) -> list[re.Pattern[str]]: | |
"""(Read-only) | |
Regexes to ignore matching event paths. | |
""" | |
return self._ignore_regexes | |
def ignore_directories(self) -> bool: | |
"""(Read-only) | |
``True`` if directories should be ignored; ``False`` otherwise. | |
""" | |
return self._ignore_directories | |
def case_sensitive(self) -> bool: | |
"""(Read-only) | |
``True`` if path names should be matched sensitive to case; ``False`` | |
otherwise. | |
""" | |
return self._case_sensitive | |
def dispatch(self, event: FileSystemEvent) -> None: | |
"""Dispatches events to the appropriate methods. | |
:param event: | |
The event object representing the file system event. | |
:type event: | |
:class:`FileSystemEvent` | |
""" | |
if self.ignore_directories and event.is_directory: | |
return | |
paths = [] | |
if hasattr(event, "dest_path"): | |
paths.append(os.fsdecode(event.dest_path)) | |
if event.src_path: | |
paths.append(os.fsdecode(event.src_path)) | |
if any(r.match(p) for r in self.ignore_regexes for p in paths): | |
return | |
if any(r.match(p) for r in self.regexes for p in paths): | |
super().dispatch(event) | |
class LoggingEventHandler(FileSystemEventHandler): | |
"""Logs all the events captured.""" | |
def __init__(self, *, logger: logging.Logger | None = None) -> None: | |
super().__init__() | |
self.logger = logger or logging.root | |
def on_moved(self, event: DirMovedEvent | FileMovedEvent) -> None: | |
super().on_moved(event) | |
what = "directory" if event.is_directory else "file" | |
self.logger.info("Moved %s: from %s to %s", what, event.src_path, event.dest_path) | |
def on_created(self, event: DirCreatedEvent | FileCreatedEvent) -> None: | |
super().on_created(event) | |
what = "directory" if event.is_directory else "file" | |
self.logger.info("Created %s: %s", what, event.src_path) | |
def on_deleted(self, event: DirDeletedEvent | FileDeletedEvent) -> None: | |
super().on_deleted(event) | |
what = "directory" if event.is_directory else "file" | |
self.logger.info("Deleted %s: %s", what, event.src_path) | |
def on_modified(self, event: DirModifiedEvent | FileModifiedEvent) -> None: | |
super().on_modified(event) | |
what = "directory" if event.is_directory else "file" | |
self.logger.info("Modified %s: %s", what, event.src_path) | |
def on_closed(self, event: FileClosedEvent) -> None: | |
super().on_closed(event) | |
self.logger.info("Closed modified file: %s", event.src_path) | |
def on_closed_no_write(self, event: FileClosedNoWriteEvent) -> None: | |
super().on_closed_no_write(event) | |
self.logger.info("Closed read file: %s", event.src_path) | |
def on_opened(self, event: FileOpenedEvent) -> None: | |
super().on_opened(event) | |
self.logger.info("Opened file: %s", event.src_path) | |
def generate_sub_moved_events( | |
src_dir_path: bytes | str, | |
dest_dir_path: bytes | str, | |
) -> Generator[DirMovedEvent | FileMovedEvent]: | |
"""Generates an event list of :class:`DirMovedEvent` and | |
:class:`FileMovedEvent` objects for all the files and directories within | |
the given moved directory that were moved along with the directory. | |
:param src_dir_path: | |
The source path of the moved directory. | |
:param dest_dir_path: | |
The destination path of the moved directory. | |
:returns: | |
An iterable of file system events of type :class:`DirMovedEvent` and | |
:class:`FileMovedEvent`. | |
""" | |
for root, directories, filenames in os.walk(dest_dir_path): # type: ignore[type-var] | |
for directory in directories: | |
full_path = os.path.join(root, directory) # type: ignore[call-overload] | |
renamed_path = full_path.replace(dest_dir_path, src_dir_path) if src_dir_path else "" | |
yield DirMovedEvent(renamed_path, full_path, is_synthetic=True) | |
for filename in filenames: | |
full_path = os.path.join(root, filename) # type: ignore[call-overload] | |
renamed_path = full_path.replace(dest_dir_path, src_dir_path) if src_dir_path else "" | |
yield FileMovedEvent(renamed_path, full_path, is_synthetic=True) | |
def generate_sub_created_events(src_dir_path: bytes | str) -> Generator[DirCreatedEvent | FileCreatedEvent]: | |
"""Generates an event list of :class:`DirCreatedEvent` and | |
:class:`FileCreatedEvent` objects for all the files and directories within | |
the given moved directory that were moved along with the directory. | |
:param src_dir_path: | |
The source path of the created directory. | |
:returns: | |
An iterable of file system events of type :class:`DirCreatedEvent` and | |
:class:`FileCreatedEvent`. | |
""" | |
for root, directories, filenames in os.walk(src_dir_path): # type: ignore[type-var] | |
for directory in directories: | |
full_path = os.path.join(root, directory) # type: ignore[call-overload] | |
yield DirCreatedEvent(full_path, is_synthetic=True) | |
for filename in filenames: | |
full_path = os.path.join(root, filename) # type: ignore[call-overload] | |
yield FileCreatedEvent(full_path, is_synthetic=True) | |