Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Improve typing references for events #1058

Merged
merged 1 commit into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Changelog
- [core] Enable ``disallow_untyped_calls`` Mypy rule (`#1055 <https://github.com/gorakhargosh/watchdog/pull/1055>`__)
- [core] Enforced usage of proper keyword-arguments (`#1057 <https://github.com/gorakhargosh/watchdog/pull/1057>`__)
- [core] Deleted the ``BaseObserverSubclassCallable`` class. Use ``type[BaseObserver]`` directly (`#1055 <https://github.com/gorakhargosh/watchdog/pull/1055>`__)
- [core] Improve typing references for events (`#1040 <https://github.com/gorakhargosh/watchdog/issues/1040>`__)
- [inotify] Renamed the ``inotify_event_struct`` class to ``InotifyEventStruct`` (`#1055 <https://github.com/gorakhargosh/watchdog/pull/1055>`__)
- [inotify] Renamed the ``UnsupportedLibc`` exception to ``UnsupportedLibcError`` (`#1057 <https://github.com/gorakhargosh/watchdog/pull/1057>`__)
- [watchmedo] Renamed the ``LogLevelException`` exception to ``LogLevelError`` (`#1057 <https://github.com/gorakhargosh/watchdog/pull/1057>`__)
Expand Down
4 changes: 2 additions & 2 deletions docs/source/examples/patterns.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import sys
import time

from watchdog.events import PatternMatchingEventHandler
from watchdog.events import FileSystemEvent, PatternMatchingEventHandler
from watchdog.observers import Observer

import logging
Expand All @@ -10,7 +10,7 @@


class MyEventHandler(PatternMatchingEventHandler):
def on_any_event(self, event):
def on_any_event(self, event: FileSystemEvent):
logging.debug(event)


Expand Down
43 changes: 23 additions & 20 deletions src/watchdog/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
import os.path
import re
from dataclasses import dataclass, field
from typing import ClassVar

from watchdog.utils.patterns import match_any_paths

Expand Down Expand Up @@ -205,6 +206,15 @@ class DirMovedEvent(FileSystemMovedEvent):
class FileSystemEventHandler:
"""Base file system event handler that you can override methods from."""

dispatch_table: ClassVar = {
EVENT_TYPE_CREATED: "on_created",
EVENT_TYPE_DELETED: "on_deleted",
EVENT_TYPE_MODIFIED: "on_modified",
EVENT_TYPE_MOVED: "on_moved",
EVENT_TYPE_CLOSED: "on_closed",
EVENT_TYPE_OPENED: "on_opened",
}

def dispatch(self, event: FileSystemEvent) -> None:
"""Dispatches events to the appropriate methods.

Expand All @@ -214,14 +224,7 @@ def dispatch(self, event: FileSystemEvent) -> None:
:class:`FileSystemEvent`
"""
self.on_any_event(event)
{
EVENT_TYPE_CREATED: self.on_created,
EVENT_TYPE_DELETED: self.on_deleted,
EVENT_TYPE_MODIFIED: self.on_modified,
EVENT_TYPE_MOVED: self.on_moved,
EVENT_TYPE_CLOSED: self.on_closed,
EVENT_TYPE_OPENED: self.on_opened,
}[event.event_type](event)
getattr(self, self.dispatch_table[event.event_type])(event)

def on_any_event(self, event: FileSystemEvent) -> None:
"""Catch-all event handler.
Expand All @@ -232,7 +235,7 @@ def on_any_event(self, event: FileSystemEvent) -> None:
:class:`FileSystemEvent`
"""

def on_moved(self, event: FileSystemEvent) -> None:
def on_moved(self, event: DirMovedEvent | FileMovedEvent) -> None:
"""Called when a file or a directory is moved or renamed.

:param event:
Expand All @@ -241,7 +244,7 @@ def on_moved(self, event: FileSystemEvent) -> None:
:class:`DirMovedEvent` or :class:`FileMovedEvent`
"""

def on_created(self, event: FileSystemEvent) -> None:
def on_created(self, event: DirCreatedEvent | FileCreatedEvent) -> None:
"""Called when a file or directory is created.

:param event:
Expand All @@ -250,7 +253,7 @@ def on_created(self, event: FileSystemEvent) -> None:
:class:`DirCreatedEvent` or :class:`FileCreatedEvent`
"""

def on_deleted(self, event: FileSystemEvent) -> None:
def on_deleted(self, event: DirDeletedEvent | FileDeletedEvent) -> None:
"""Called when a file or directory is deleted.

:param event:
Expand All @@ -259,7 +262,7 @@ def on_deleted(self, event: FileSystemEvent) -> None:
:class:`DirDeletedEvent` or :class:`FileDeletedEvent`
"""

def on_modified(self, event: FileSystemEvent) -> None:
def on_modified(self, event: DirModifiedEvent | FileModifiedEvent) -> None:
"""Called when a file or directory is modified.

:param event:
Expand All @@ -268,7 +271,7 @@ def on_modified(self, event: FileSystemEvent) -> None:
:class:`DirModifiedEvent` or :class:`FileModifiedEvent`
"""

def on_closed(self, event: FileSystemEvent) -> None:
def on_closed(self, event: FileClosedEvent) -> None:
"""Called when a file opened for writing is closed.

:param event:
Expand All @@ -277,7 +280,7 @@ def on_closed(self, event: FileSystemEvent) -> None:
:class:`FileClosedEvent`
"""

def on_opened(self, event: FileSystemEvent) -> None:
def on_opened(self, event: FileOpenedEvent) -> None:
"""Called when a file is opened.

:param event:
Expand Down Expand Up @@ -453,36 +456,36 @@ def __init__(self, logger: logging.Logger | None = None) -> None:
super().__init__()
self.logger = logger or logging.root

def on_moved(self, event: FileSystemEvent) -> None:
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: FileSystemEvent) -> None:
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: FileSystemEvent) -> None:
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: FileSystemEvent) -> None:
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: FileSystemEvent) -> None:
def on_closed(self, event: FileClosedEvent) -> None:
super().on_closed(event)

self.logger.info("Closed file: %s", event.src_path)

def on_opened(self, event: FileSystemEvent) -> None:
def on_opened(self, event: FileOpenedEvent) -> None:
super().on_opened(event)

self.logger.info("Opened file: %s", event.src_path)
Expand Down
24 changes: 12 additions & 12 deletions src/watchdog/tricks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def __init__(
self.process = None
self._process_watchers = set()

def on_any_event(self, event):
def on_any_event(self, event: FileSystemEvent) -> None:
if event.event_type == EVENT_TYPE_OPENED:
# FIXME: see issue #949, and find a way to better handle that scenario
return
Expand Down Expand Up @@ -152,8 +152,8 @@ def on_any_event(self, event):
)
process_watcher.start()

def is_process_running(self):
return self._process_watchers or (self.process is not None and self.process.poll() is None)
def is_process_running(self) -> bool:
return bool(self._process_watchers or (self.process is not None and self.process.poll() is None))


class AutoRestartTrick(Trick):
Expand Down Expand Up @@ -233,7 +233,7 @@ def stop(self):
if process_watcher is not None:
process_watcher.join()

def _start_process(self):
def _start_process(self) -> None:
if self._is_trick_stopping:
return

Expand All @@ -243,7 +243,7 @@ def _start_process(self):
self.process_watcher = ProcessWatcher(self.process, self._restart_process)
self.process_watcher.start()

def _stop_process(self):
def _stop_process(self) -> None:
# Ensure the body of the function is not run in parallel in different threads.
with self._stopping_lock:
if self._is_process_stopping:
Expand Down Expand Up @@ -276,7 +276,7 @@ def _stop_process(self):
self._is_process_stopping = False

@echo_events
def on_any_event(self, event):
def on_any_event(self, event: FileSystemEvent) -> None:
if event.event_type == EVENT_TYPE_OPENED:
# FIXME: see issue #949, and find a way to better handle that scenario
return
Expand All @@ -286,20 +286,20 @@ def on_any_event(self, event):
else:
self._restart_process()

def _restart_process(self):
def _restart_process(self) -> None:
if self._is_trick_stopping:
return
self._stop_process()
self._start_process()
self.restart_count += 1


if not platform.is_windows():
if platform.is_windows():

def kill_process(pid, stop_signal):
os.killpg(os.getpgid(pid), stop_signal)
def kill_process(pid: int, stop_signal: int) -> None:
os.kill(pid, stop_signal)

else:

def kill_process(pid, stop_signal):
os.kill(pid, stop_signal)
def kill_process(pid: int, stop_signal: int) -> None:
os.killpg(os.getpgid(pid), stop_signal) # type: ignore[attr-defined]
6 changes: 3 additions & 3 deletions src/watchdog/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class WatchdogShutdownError(Exception):
class BaseThread(threading.Thread):
"""Convenience class for creating stoppable threads."""

def __init__(self):
def __init__(self) -> None:
threading.Thread.__init__(self)
if hasattr(self, "daemon"):
self.daemon = True
Expand Down Expand Up @@ -73,15 +73,15 @@ def stop(self):
self._stopped_event.set()
self.on_thread_stop()

def on_thread_start(self):
def on_thread_start(self) -> None:
"""Override this method instead of :meth:`start()`. :meth:`start()`
calls this method.

This method is called right before this thread is started and this
object's run() method is invoked.
"""

def start(self):
def start(self) -> None:
self.on_thread_start()
threading.Thread.start(self)

Expand Down
15 changes: 9 additions & 6 deletions src/watchdog/utils/process_watcher.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
from __future__ import annotations

import logging
from typing import TYPE_CHECKING

from watchdog.utils import BaseThread

if TYPE_CHECKING:
import subprocess
from typing import Callable

logger = logging.getLogger(__name__)


class ProcessWatcher(BaseThread):
def __init__(self, popen_obj, process_termination_callback):
def __init__(self, popen_obj: subprocess.Popen, process_termination_callback: Callable | None) -> None:
super().__init__()
self.popen_obj = popen_obj
self.process_termination_callback = process_termination_callback

def run(self):
while True:
if self.popen_obj.poll() is not None:
break
def run(self) -> None:
while self.popen_obj.poll() is None:
if self.stopped_event.wait(timeout=0.1):
return

try:
if not self.stopped_event.is_set():
if not self.stopped_event.is_set() and self.process_termination_callback:
self.process_termination_callback()
except Exception:
logger.exception("Error calling process termination callback")