diff --git a/src/watchdog/events.py b/src/watchdog/events.py index 3beff35b..db430235 100644 --- a/src/watchdog/events.py +++ b/src/watchdog/events.py @@ -451,7 +451,7 @@ def dispatch(self, event: FileSystemEvent) -> None: class LoggingEventHandler(FileSystemEventHandler): """Logs all the events captured.""" - def __init__(self, logger: logging.Logger | None = None) -> None: + def __init__(self, *, logger: logging.Logger | None = None) -> None: super().__init__() self.logger = logger or logging.root diff --git a/src/watchdog/observers/__init__.py b/src/watchdog/observers/__init__.py index 36677fda..ce395741 100644 --- a/src/watchdog/observers/__init__.py +++ b/src/watchdog/observers/__init__.py @@ -47,7 +47,7 @@ class ObserverType(Protocol): - def __call__(self, *, timeout: int = ...) -> BaseObserver: ... + def __call__(self, *, timeout: float = ...) -> BaseObserver: ... def _get_observer_cls() -> ObserverType: diff --git a/src/watchdog/observers/api.py b/src/watchdog/observers/api.py index 1dc42c6e..30cb21ca 100644 --- a/src/watchdog/observers/api.py +++ b/src/watchdog/observers/api.py @@ -3,6 +3,7 @@ import contextlib import queue import threading +from collections import defaultdict from pathlib import Path from typing import TYPE_CHECKING @@ -12,8 +13,8 @@ if TYPE_CHECKING: from watchdog.events import FileSystemEvent, FileSystemEventHandler -DEFAULT_EMITTER_TIMEOUT = 1 # in seconds. -DEFAULT_OBSERVER_TIMEOUT = 1 # in seconds. +DEFAULT_EMITTER_TIMEOUT = 1.0 # in seconds +DEFAULT_OBSERVER_TIMEOUT = 1.0 # in seconds class EventQueue(SkipRepeatsQueue): @@ -110,7 +111,7 @@ def __init__( event_queue: EventQueue, watch: ObservedWatch, *, - timeout: int = DEFAULT_EMITTER_TIMEOUT, + timeout: float = DEFAULT_EMITTER_TIMEOUT, event_filter: list[type[FileSystemEvent]] | None = None, ) -> None: super().__init__() @@ -120,7 +121,7 @@ def __init__( self._event_filter = frozenset(event_filter) if event_filter is not None else None @property - def timeout(self) -> int: + def timeout(self) -> float: """Blocking timeout for reading events.""" return self._timeout @@ -141,7 +142,7 @@ def queue_event(self, event: FileSystemEvent) -> None: if self._event_filter is None or any(isinstance(event, cls) for cls in self._event_filter): self._event_queue.put((event, self.watch)) - def queue_events(self, timeout: int) -> None: + def queue_events(self, timeout: float) -> None: """Override this method to populate the event queue with events per interval period. @@ -171,13 +172,13 @@ class EventDispatcher(BaseThread): stop_event = object() """Event inserted into the queue to signal a requested stop.""" - def __init__(self, *, timeout: int = DEFAULT_OBSERVER_TIMEOUT) -> None: + def __init__(self, *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None: super().__init__() self._event_queue = EventQueue() self._timeout = timeout @property - def timeout(self) -> int: + def timeout(self) -> float: """Timeout value to construct emitters with.""" return self._timeout @@ -217,12 +218,12 @@ def run(self) -> None: class BaseObserver(EventDispatcher): """Base observer.""" - def __init__(self, emitter_class: type[EventEmitter], *, timeout: int = DEFAULT_OBSERVER_TIMEOUT) -> None: + def __init__(self, emitter_class: type[EventEmitter], *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None: super().__init__(timeout=timeout) self._emitter_class = emitter_class self._lock = threading.RLock() self._watches: set[ObservedWatch] = set() - self._handlers: dict[ObservedWatch, set[FileSystemEventHandler]] = {} + self._handlers: defaultdict[ObservedWatch, set[FileSystemEventHandler]] = defaultdict(set) self._emitters: set[EventEmitter] = set() self._emitter_for_watch: dict[ObservedWatch, EventEmitter] = {} @@ -247,8 +248,6 @@ def _clear_emitters(self) -> None: self._emitter_for_watch.clear() def _add_handler_for_watch(self, event_handler: FileSystemEventHandler, watch: ObservedWatch) -> None: - if watch not in self._handlers: - self._handlers[watch] = set() self._handlers[watch].add(event_handler) def _remove_handlers_for_watch(self, watch: ObservedWatch) -> None: @@ -307,7 +306,7 @@ def schedule( self._add_handler_for_watch(event_handler, watch) # If we don't have an emitter for this watch already, create it. - if self._emitter_for_watch.get(watch) is None: + if watch not in self._emitter_for_watch: emitter = self._emitter_class(self.event_queue, watch, timeout=self.timeout, event_filter=event_filter) if self.is_alive(): emitter.start() @@ -367,9 +366,7 @@ def unschedule(self, watch: ObservedWatch) -> None: self._watches.remove(watch) def unschedule_all(self) -> None: - """Unschedules all watches and detaches all associated event - handlers. - """ + """Unschedules all watches and detaches all associated event handlers.""" with self._lock: self._handlers.clear() self._clear_emitters() @@ -382,13 +379,14 @@ def dispatch_events(self, event_queue: EventQueue) -> None: entry = event_queue.get(block=True) if entry is EventDispatcher.stop_event: return + event, watch = entry with self._lock: # To allow unschedule/stop and safe removal of event handlers # within event handlers itself, check if the handler is still # registered after every dispatch. - for handler in list(self._handlers.get(watch, [])): - if handler in self._handlers.get(watch, []): + for handler in self._handlers[watch].copy(): + if handler in self._handlers[watch]: handler.dispatch(event) event_queue.task_done() diff --git a/src/watchdog/observers/fsevents.py b/src/watchdog/observers/fsevents.py index 0812e430..257e16e7 100644 --- a/src/watchdog/observers/fsevents.py +++ b/src/watchdog/observers/fsevents.py @@ -67,7 +67,7 @@ def __init__( event_queue: EventQueue, watch: ObservedWatch, *, - timeout: int = DEFAULT_EMITTER_TIMEOUT, + timeout: float = DEFAULT_EMITTER_TIMEOUT, event_filter: list[type[FileSystemEvent]] | None = None, suppress_history: bool = False, ) -> None: @@ -156,7 +156,7 @@ def _is_meta_mod(event: _fsevents.NativeEvent) -> bool: """Returns True if the event indicates a change in metadata.""" return event.is_inode_meta_mod or event.is_xattr_mod or event.is_owner_change - def queue_events(self, timeout: int, events: list[_fsevents.NativeEvent]) -> None: # type: ignore[override] + def queue_events(self, timeout: float, events: list[_fsevents.NativeEvent]) -> None: # type: ignore[override] if logger.getEffectiveLevel() <= logging.DEBUG: for event in events: flags = ", ".join(attr for attr in dir(event) if getattr(event, attr) is True) @@ -320,7 +320,7 @@ def _encode_path(self, path: bytes | str) -> bytes | str: class FSEventsObserver(BaseObserver): - def __init__(self, *, timeout: int = DEFAULT_OBSERVER_TIMEOUT) -> None: + def __init__(self, *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None: super().__init__(FSEventsEmitter, timeout=timeout) def schedule( diff --git a/src/watchdog/observers/fsevents2.py b/src/watchdog/observers/fsevents2.py index cca7edba..3d1f7e9b 100644 --- a/src/watchdog/observers/fsevents2.py +++ b/src/watchdog/observers/fsevents2.py @@ -187,7 +187,7 @@ def __init__( event_queue: EventQueue, watch: ObservedWatch, *, - timeout: int = DEFAULT_EMITTER_TIMEOUT, + timeout: float = DEFAULT_EMITTER_TIMEOUT, event_filter: list[type[FileSystemEvent]] | None = None, ): super().__init__(event_queue, watch, timeout=timeout, event_filter=event_filter) @@ -197,7 +197,7 @@ def __init__( def on_thread_stop(self) -> None: self._fsevents.stop() - def queue_events(self, timeout: int) -> None: + def queue_events(self, timeout: float) -> None: events = self._fsevents.read_events() if events is None: return @@ -249,5 +249,5 @@ def queue_events(self, timeout: int) -> None: class FSEventsObserver2(BaseObserver): - def __init__(self, *, timeout: int = DEFAULT_OBSERVER_TIMEOUT) -> None: + def __init__(self, *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None: super().__init__(FSEventsEmitter, timeout=timeout) diff --git a/src/watchdog/observers/inotify.py b/src/watchdog/observers/inotify.py index 36dae47b..a07aee5c 100644 --- a/src/watchdog/observers/inotify.py +++ b/src/watchdog/observers/inotify.py @@ -106,7 +106,7 @@ def __init__( event_queue: EventQueue, watch: ObservedWatch, *, - timeout: int = DEFAULT_EMITTER_TIMEOUT, + timeout: float = DEFAULT_EMITTER_TIMEOUT, event_filter: list[type[FileSystemEvent]] | None = None, ) -> None: super().__init__(event_queue, watch, timeout=timeout, event_filter=event_filter) @@ -123,7 +123,7 @@ def on_thread_stop(self) -> None: self._inotify.close() self._inotify = None - def queue_events(self, timeout: int, *, full_events: bool = False) -> None: + def queue_events(self, timeout: float, *, full_events: bool = False) -> None: # If "full_events" is true, then the method will report unmatched move events as separate events # This behavior is by default only called by a InotifyFullEmitter if self._inotify is None: @@ -238,7 +238,7 @@ class InotifyFullEmitter(InotifyEmitter): Such move events will have a ``None`` value for the unmatched part. """ - def queue_events(self, timeout: int, *, events: bool = True) -> None: # type: ignore[override] + def queue_events(self, timeout: float, *, events: bool = True) -> None: # type: ignore[override] super().queue_events(timeout, full_events=events) @@ -247,6 +247,6 @@ class InotifyObserver(BaseObserver): calls to event handlers. """ - def __init__(self, *, timeout: int = DEFAULT_OBSERVER_TIMEOUT, generate_full_events: bool = False) -> None: + def __init__(self, *, timeout: float = DEFAULT_OBSERVER_TIMEOUT, generate_full_events: bool = False) -> None: cls = InotifyFullEmitter if generate_full_events else InotifyEmitter super().__init__(cls, timeout=timeout) diff --git a/src/watchdog/observers/kqueue.py b/src/watchdog/observers/kqueue.py index cd2e7d6f..1648fca5 100644 --- a/src/watchdog/observers/kqueue.py +++ b/src/watchdog/observers/kqueue.py @@ -402,9 +402,9 @@ def __init__( event_queue: EventQueue, watch: ObservedWatch, *, - timeout: int = DEFAULT_EMITTER_TIMEOUT, + timeout: float = DEFAULT_EMITTER_TIMEOUT, event_filter: list[type[FileSystemEvent]] | None = None, - stat: Callable = os.stat, + stat: Callable[[str], os.stat_result] = os.stat, ) -> None: super().__init__(event_queue, watch, timeout=timeout, event_filter=event_filter) @@ -590,7 +590,7 @@ def _gen_renamed_events( yield FileDeletedEvent(src_path) yield self._parent_dir_modified(src_path) - def _read_events(self, timeout: float | None = None) -> list[select.kevent]: + def _read_events(self, timeout: float) -> list[select.kevent]: """Reads events from a call to the blocking :meth:`select.kqueue.control()` method. @@ -601,7 +601,7 @@ def _read_events(self, timeout: float | None = None) -> list[select.kevent]: """ return self._kq.control(self._descriptors.kevents, MAX_EVENTS, timeout=timeout) - def queue_events(self, timeout: int) -> None: + def queue_events(self, timeout: float) -> None: """Queues events by reading them from a call to the blocking :meth:`select.kqueue.control()` method. @@ -651,5 +651,5 @@ class KqueueObserver(BaseObserver): calls to event handlers. """ - def __init__(self, *, timeout: int = DEFAULT_OBSERVER_TIMEOUT) -> None: + def __init__(self, *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None: super().__init__(KqueueEmitter, timeout=timeout) diff --git a/src/watchdog/observers/polling.py b/src/watchdog/observers/polling.py index 853d849e..5c94e525 100644 --- a/src/watchdog/observers/polling.py +++ b/src/watchdog/observers/polling.py @@ -36,6 +36,7 @@ from watchdog.utils.dirsnapshot import DirectorySnapshot, DirectorySnapshotDiff, EmptyDirectorySnapshot if TYPE_CHECKING: + from collections.abc import Iterator from typing import Callable from watchdog.events import FileSystemEvent @@ -52,10 +53,10 @@ def __init__( event_queue: EventQueue, watch: ObservedWatch, *, - timeout: int = DEFAULT_EMITTER_TIMEOUT, + timeout: float = DEFAULT_EMITTER_TIMEOUT, event_filter: list[type[FileSystemEvent]] | None = None, - stat: Callable = os.stat, - listdir: Callable = os.scandir, + stat: Callable[[str], os.stat_result] = os.stat, + listdir: Callable[[str | None], Iterator[os.DirEntry]] = os.scandir, ) -> None: super().__init__(event_queue, watch, timeout=timeout, event_filter=event_filter) self._snapshot: DirectorySnapshot = EmptyDirectorySnapshot() @@ -70,7 +71,7 @@ def __init__( def on_thread_start(self) -> None: self._snapshot = self._take_snapshot() - def queue_events(self, timeout: int) -> None: + def queue_events(self, timeout: float) -> None: # We don't want to hit the disk continuously. # timeout behaves like an interval for polling emitters. if self.stopped_event.wait(timeout): @@ -118,17 +119,23 @@ class PollingObserver(BaseObserver): system changes. """ - def __init__(self, *, timeout: int = DEFAULT_OBSERVER_TIMEOUT) -> None: + def __init__(self, *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None: super().__init__(PollingEmitter, timeout=timeout) class PollingObserverVFS(BaseObserver): """File system independent observer that polls a directory to detect changes.""" - def __init__(self, stat: Callable, listdir: Callable, *, polling_interval: int = 1) -> None: + def __init__( + self, + stat: Callable[[str], os.stat_result], + listdir: Callable[[str | None], Iterator[os.DirEntry]], + *, + polling_interval: int = 1, + ) -> None: """:param stat: stat function. See ``os.stat`` for details. :param listdir: listdir function. See ``os.scandir`` for details. - :type polling_interval: float + :type polling_interval: int :param polling_interval: interval in seconds between polling the file system. """ emitter_cls = partial(PollingEmitter, stat=stat, listdir=listdir) diff --git a/src/watchdog/observers/read_directory_changes.py b/src/watchdog/observers/read_directory_changes.py index 660255a0..3b816353 100644 --- a/src/watchdog/observers/read_directory_changes.py +++ b/src/watchdog/observers/read_directory_changes.py @@ -38,7 +38,7 @@ def __init__( event_queue: EventQueue, watch: ObservedWatch, *, - timeout: int = DEFAULT_EMITTER_TIMEOUT, + timeout: float = DEFAULT_EMITTER_TIMEOUT, event_filter: list[type[FileSystemEvent]] | None = None, ) -> None: super().__init__(event_queue, watch, timeout=timeout, event_filter=event_filter) @@ -66,7 +66,7 @@ def _read_events(self) -> list[WinAPINativeEvent]: return [] return read_events(self._whandle, self.watch.path, recursive=self.watch.is_recursive) - def queue_events(self, timeout: int) -> None: + def queue_events(self, timeout: float) -> None: winapi_events = self._read_events() with self._lock: last_renamed_src_path = "" @@ -107,5 +107,5 @@ class WindowsApiObserver(BaseObserver): calls to event handlers. """ - def __init__(self, *, timeout: int = DEFAULT_OBSERVER_TIMEOUT) -> None: + def __init__(self, *, timeout: float = DEFAULT_OBSERVER_TIMEOUT) -> None: super().__init__(WindowsApiEmitter, timeout=timeout) diff --git a/src/watchdog/observers/winapi.py b/src/watchdog/observers/winapi.py index d39b2870..2e929bb7 100644 --- a/src/watchdog/observers/winapi.py +++ b/src/watchdog/observers/winapi.py @@ -12,7 +12,8 @@ from __future__ import annotations import contextlib -import ctypes.wintypes +import ctypes +from ctypes.wintypes import BOOL, DWORD, HANDLE, LPCWSTR, LPVOID, LPWSTR from dataclasses import dataclass from functools import reduce from typing import TYPE_CHECKING @@ -20,8 +21,6 @@ if TYPE_CHECKING: from typing import Any -LPVOID = ctypes.wintypes.LPVOID - # Invalid handle value. INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value @@ -75,10 +74,10 @@ class OVERLAPPED(ctypes.Structure): _fields_ = ( ("Internal", LPVOID), ("InternalHigh", LPVOID), - ("Offset", ctypes.wintypes.DWORD), - ("OffsetHigh", ctypes.wintypes.DWORD), + ("Offset", DWORD), + ("OffsetHigh", DWORD), ("Pointer", LPVOID), - ("hEvent", ctypes.wintypes.HANDLE), + ("hEvent", HANDLE), ) @@ -105,117 +104,116 @@ def _errcheck_dword(value: Any | None, func: Any, args: Any) -> Any: kernel32 = ctypes.WinDLL("kernel32") ReadDirectoryChangesW = kernel32.ReadDirectoryChangesW -ReadDirectoryChangesW.restype = ctypes.wintypes.BOOL +ReadDirectoryChangesW.restype = BOOL ReadDirectoryChangesW.errcheck = _errcheck_bool ReadDirectoryChangesW.argtypes = ( - ctypes.wintypes.HANDLE, # hDirectory + HANDLE, # hDirectory LPVOID, # lpBuffer - ctypes.wintypes.DWORD, # nBufferLength - ctypes.wintypes.BOOL, # bWatchSubtree - ctypes.wintypes.DWORD, # dwNotifyFilter - ctypes.POINTER(ctypes.wintypes.DWORD), # lpBytesReturned + DWORD, # nBufferLength + BOOL, # bWatchSubtree + DWORD, # dwNotifyFilter + ctypes.POINTER(DWORD), # lpBytesReturned ctypes.POINTER(OVERLAPPED), # lpOverlapped LPVOID, # FileIOCompletionRoutine # lpCompletionRoutine ) CreateFileW = kernel32.CreateFileW -CreateFileW.restype = ctypes.wintypes.HANDLE +CreateFileW.restype = HANDLE CreateFileW.errcheck = _errcheck_handle CreateFileW.argtypes = ( - ctypes.wintypes.LPCWSTR, # lpFileName - ctypes.wintypes.DWORD, # dwDesiredAccess - ctypes.wintypes.DWORD, # dwShareMode + LPCWSTR, # lpFileName + DWORD, # dwDesiredAccess + DWORD, # dwShareMode LPVOID, # lpSecurityAttributes - ctypes.wintypes.DWORD, # dwCreationDisposition - ctypes.wintypes.DWORD, # dwFlagsAndAttributes - ctypes.wintypes.HANDLE, # hTemplateFile + DWORD, # dwCreationDisposition + DWORD, # dwFlagsAndAttributes + HANDLE, # hTemplateFile ) CloseHandle = kernel32.CloseHandle -CloseHandle.restype = ctypes.wintypes.BOOL -CloseHandle.argtypes = (ctypes.wintypes.HANDLE,) # hObject +CloseHandle.restype = BOOL +CloseHandle.argtypes = (HANDLE,) # hObject CancelIoEx = kernel32.CancelIoEx -CancelIoEx.restype = ctypes.wintypes.BOOL +CancelIoEx.restype = BOOL CancelIoEx.errcheck = _errcheck_bool CancelIoEx.argtypes = ( - ctypes.wintypes.HANDLE, # hObject + HANDLE, # hObject ctypes.POINTER(OVERLAPPED), # lpOverlapped ) CreateEvent = kernel32.CreateEventW -CreateEvent.restype = ctypes.wintypes.HANDLE +CreateEvent.restype = HANDLE CreateEvent.errcheck = _errcheck_handle CreateEvent.argtypes = ( LPVOID, # lpEventAttributes - ctypes.wintypes.BOOL, # bManualReset - ctypes.wintypes.BOOL, # bInitialState - ctypes.wintypes.LPCWSTR, # lpName + BOOL, # bManualReset + BOOL, # bInitialState + LPCWSTR, # lpName ) SetEvent = kernel32.SetEvent -SetEvent.restype = ctypes.wintypes.BOOL +SetEvent.restype = BOOL SetEvent.errcheck = _errcheck_bool -SetEvent.argtypes = (ctypes.wintypes.HANDLE,) # hEvent +SetEvent.argtypes = (HANDLE,) # hEvent WaitForSingleObjectEx = kernel32.WaitForSingleObjectEx -WaitForSingleObjectEx.restype = ctypes.wintypes.DWORD +WaitForSingleObjectEx.restype = DWORD WaitForSingleObjectEx.errcheck = _errcheck_dword WaitForSingleObjectEx.argtypes = ( - ctypes.wintypes.HANDLE, # hObject - ctypes.wintypes.DWORD, # dwMilliseconds - ctypes.wintypes.BOOL, # bAlertable + HANDLE, # hObject + DWORD, # dwMilliseconds + BOOL, # bAlertable ) CreateIoCompletionPort = kernel32.CreateIoCompletionPort -CreateIoCompletionPort.restype = ctypes.wintypes.HANDLE +CreateIoCompletionPort.restype = HANDLE CreateIoCompletionPort.errcheck = _errcheck_handle CreateIoCompletionPort.argtypes = ( - ctypes.wintypes.HANDLE, # FileHandle - ctypes.wintypes.HANDLE, # ExistingCompletionPort + HANDLE, # FileHandle + HANDLE, # ExistingCompletionPort LPVOID, # CompletionKey - ctypes.wintypes.DWORD, # NumberOfConcurrentThreads + DWORD, # NumberOfConcurrentThreads ) GetQueuedCompletionStatus = kernel32.GetQueuedCompletionStatus -GetQueuedCompletionStatus.restype = ctypes.wintypes.BOOL +GetQueuedCompletionStatus.restype = BOOL GetQueuedCompletionStatus.errcheck = _errcheck_bool GetQueuedCompletionStatus.argtypes = ( - ctypes.wintypes.HANDLE, # CompletionPort + HANDLE, # CompletionPort LPVOID, # lpNumberOfBytesTransferred LPVOID, # lpCompletionKey ctypes.POINTER(OVERLAPPED), # lpOverlapped - ctypes.wintypes.DWORD, # dwMilliseconds + DWORD, # dwMilliseconds ) PostQueuedCompletionStatus = kernel32.PostQueuedCompletionStatus -PostQueuedCompletionStatus.restype = ctypes.wintypes.BOOL +PostQueuedCompletionStatus.restype = BOOL PostQueuedCompletionStatus.errcheck = _errcheck_bool PostQueuedCompletionStatus.argtypes = ( - ctypes.wintypes.HANDLE, # CompletionPort - ctypes.wintypes.DWORD, # lpNumberOfBytesTransferred - ctypes.wintypes.DWORD, # lpCompletionKey + HANDLE, # CompletionPort + DWORD, # lpNumberOfBytesTransferred + DWORD, # lpCompletionKey ctypes.POINTER(OVERLAPPED), # lpOverlapped ) GetFinalPathNameByHandleW = kernel32.GetFinalPathNameByHandleW -GetFinalPathNameByHandleW.restype = ctypes.wintypes.DWORD +GetFinalPathNameByHandleW.restype = DWORD GetFinalPathNameByHandleW.errcheck = _errcheck_dword GetFinalPathNameByHandleW.argtypes = ( - ctypes.wintypes.HANDLE, # hFile - ctypes.wintypes.LPWSTR, # lpszFilePath - ctypes.wintypes.DWORD, # cchFilePath - ctypes.wintypes.DWORD, # DWORD + HANDLE, # hFile + LPWSTR, # lpszFilePath + DWORD, # cchFilePath + DWORD, # DWORD ) class FileNotifyInformation(ctypes.Structure): _fields_ = ( - ("NextEntryOffset", ctypes.wintypes.DWORD), - ("Action", ctypes.wintypes.DWORD), - ("FileNameLength", ctypes.wintypes.DWORD), - # ("FileName", (ctypes.wintypes.WCHAR * 1))] + ("NextEntryOffset", DWORD), + ("Action", DWORD), + ("FileNameLength", DWORD), ("FileName", (ctypes.c_char * 1)), ) @@ -276,7 +274,7 @@ def _parse_event_buffer(read_buffer: bytes, n_bytes: int) -> list[tuple[int, str return results -def _is_observed_path_deleted(handle: ctypes.wintypes.HANDLE, path: str) -> bool: +def _is_observed_path_deleted(handle: HANDLE, path: str) -> bool: # Comparison of observed path and actual path, returned by # GetFinalPathNameByHandleW. If directory moved to the trash bin, or # deleted, actual path will not be equal to observed path. @@ -295,7 +293,7 @@ def _generate_observed_path_deleted_event() -> tuple[bytes, int]: return buff.raw, event_size -def get_directory_handle(path: str) -> ctypes.wintypes.HANDLE: +def get_directory_handle(path: str) -> HANDLE: """Returns a Windows handle to the specified directory path.""" return CreateFileW( path, @@ -308,7 +306,7 @@ def get_directory_handle(path: str) -> ctypes.wintypes.HANDLE: ) -def close_directory_handle(handle: ctypes.wintypes.HANDLE) -> None: +def close_directory_handle(handle: HANDLE) -> None: try: CancelIoEx(handle, None) # force ReadDirectoryChangesW to return CloseHandle(handle) @@ -317,13 +315,13 @@ def close_directory_handle(handle: ctypes.wintypes.HANDLE) -> None: CloseHandle(handle) -def read_directory_changes(handle: ctypes.wintypes.HANDLE, path: str, *, recursive: bool) -> tuple[bytes, int]: +def read_directory_changes(handle: HANDLE, path: str, *, recursive: bool) -> tuple[bytes, int]: """Read changes to the directory using the specified directory handle. https://timgolden.me.uk/pywin32-docs/win32file__ReadDirectoryChangesW_meth.html """ event_buffer = ctypes.create_string_buffer(BUFFER_SIZE) - nbytes = ctypes.wintypes.DWORD() + nbytes = DWORD() try: ReadDirectoryChangesW( handle, @@ -378,7 +376,7 @@ def is_removed_self(self) -> bool: return self.action == FILE_ACTION_REMOVED_SELF -def read_events(handle: ctypes.wintypes.HANDLE, path: str, *, recursive: bool) -> list[WinAPINativeEvent]: +def read_events(handle: HANDLE, path: str, *, recursive: bool) -> list[WinAPINativeEvent]: buf, nbytes = read_directory_changes(handle, path, recursive=recursive) events = _parse_event_buffer(buf, nbytes) return [WinAPINativeEvent(action, src_path) for action, src_path in events] diff --git a/src/watchdog/utils/echo.py b/src/watchdog/utils/echo.py index d3bc3111..86cc58fe 100644 --- a/src/watchdog/utils/echo.py +++ b/src/watchdog/utils/echo.py @@ -86,7 +86,7 @@ def format_arg_value(arg_val: tuple[str, tuple[Any, ...]]) -> str: return f"{arg}={val!r}" -def echo(fn: Callable, write: Callable = sys.stdout.write) -> Callable: +def echo(fn: Callable, write: Callable[[str], int | None] = sys.stdout.write) -> Callable: """Echo calls to a function. Returns a decorated version of the input function which "echoes" calls @@ -117,7 +117,7 @@ def wrapped(*v: Any, **k: Any) -> Callable: return wrapped -def echo_instancemethod(klass: type, method: MethodType, write: Callable = sys.stdout.write) -> None: +def echo_instancemethod(klass: type, method: MethodType, write: Callable[[str], int | None] = sys.stdout.write) -> None: """Change an instancemethod so that calls to it are echoed. Replacing a classmethod is a little more tricky. @@ -135,7 +135,7 @@ def echo_instancemethod(klass: type, method: MethodType, write: Callable = sys.s setattr(klass, mname, echo(method, write)) -def echo_class(klass: type, write: Callable = sys.stdout.write) -> None: +def echo_class(klass: type, write: Callable[[str], int | None] = sys.stdout.write) -> None: """Echo calls to class methods and static functions""" for _, method in inspect.getmembers(klass, inspect.ismethod): # In python 3 only class methods are returned here diff --git a/src/watchdog/utils/event_debouncer.py b/src/watchdog/utils/event_debouncer.py index 0692a9b9..d9569733 100644 --- a/src/watchdog/utils/event_debouncer.py +++ b/src/watchdog/utils/event_debouncer.py @@ -24,7 +24,11 @@ class EventDebouncer(BaseThread): events in the order in which they were received. """ - def __init__(self, debounce_interval_seconds: int, events_callback: Callable) -> None: + def __init__( + self, + debounce_interval_seconds: int, + events_callback: Callable[[list[FileSystemEvent]], None], + ) -> None: super().__init__() self.debounce_interval_seconds = debounce_interval_seconds self.events_callback = events_callback diff --git a/src/watchdog/utils/process_watcher.py b/src/watchdog/utils/process_watcher.py index 1e9a525f..f500266a 100644 --- a/src/watchdog/utils/process_watcher.py +++ b/src/watchdog/utils/process_watcher.py @@ -13,7 +13,7 @@ class ProcessWatcher(BaseThread): - def __init__(self, popen_obj: subprocess.Popen, process_termination_callback: Callable | None) -> None: + def __init__(self, popen_obj: subprocess.Popen, process_termination_callback: Callable[[], None] | None) -> None: super().__init__() self.popen_obj = popen_obj self.process_termination_callback = process_termination_callback diff --git a/src/watchdog/watchmedo.py b/src/watchdog/watchmedo.py index 2456c567..4aab0872 100644 --- a/src/watchdog/watchmedo.py +++ b/src/watchdog/watchmedo.py @@ -81,6 +81,7 @@ def argument(*name_or_flags: str, **kwargs: Any) -> Argument: def command( args: list[Argument], + *, parent: _SubParsersAction[ArgumentParser] = subparsers, cmd_aliases: list[str] | None = None, ) -> Callable: diff --git a/tests/test_echo.py b/tests/test_echo.py index ea1801c0..36d85775 100644 --- a/tests/test_echo.py +++ b/tests/test_echo.py @@ -1,7 +1,7 @@ from typing import Any -from watchdog.utils import echo import pytest +from watchdog.utils import echo @pytest.mark.parametrize(