Skip to content

Commit 9c28c61

Browse files
authored
mypy check_untyped_defs (gorakhargosh#966)
* mypy check_untyped_defs * maybe fix macos * flake8 * isort * flake8 * mypy * just another approach * isort * remove unneeded mypy exception * cleanup
1 parent 764a234 commit 9c28c61

11 files changed

+62
-35
lines changed

mypy.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ disallow_subclassing_any = True
88
;disallow_untyped_calls = True
99
;disallow_untyped_defs = True
1010
disallow_incomplete_defs = True
11-
;check_untyped_defs = True
11+
check_untyped_defs = True
1212
disallow_untyped_decorators = True
1313
no_implicit_optional = True
1414
warn_return_any = True

src/watchdog/events.py

+12-12
Original file line numberDiff line numberDiff line change
@@ -564,17 +564,17 @@ def generate_sub_moved_events(src_dir_path, dest_dir_path):
564564
renamed_path = (
565565
full_path.replace(dest_dir_path, src_dir_path) if src_dir_path else None
566566
)
567-
event = DirMovedEvent(renamed_path, full_path)
568-
event.is_synthetic = True
569-
yield event
567+
dir_moved_event = DirMovedEvent(renamed_path, full_path)
568+
dir_moved_event.is_synthetic = True
569+
yield dir_moved_event
570570
for filename in filenames:
571571
full_path = os.path.join(root, filename)
572572
renamed_path = (
573573
full_path.replace(dest_dir_path, src_dir_path) if src_dir_path else None
574574
)
575-
event = FileMovedEvent(renamed_path, full_path)
576-
event.is_synthetic = True
577-
yield event
575+
file_moved_event = FileMovedEvent(renamed_path, full_path)
576+
file_moved_event.is_synthetic = True
577+
yield file_moved_event
578578

579579

580580
def generate_sub_created_events(src_dir_path):
@@ -590,10 +590,10 @@ def generate_sub_created_events(src_dir_path):
590590
"""
591591
for root, directories, filenames in os.walk(src_dir_path):
592592
for directory in directories:
593-
event = DirCreatedEvent(os.path.join(root, directory))
594-
event.is_synthetic = True
595-
yield event
593+
dir_created_event = DirCreatedEvent(os.path.join(root, directory))
594+
dir_created_event.is_synthetic = True
595+
yield dir_created_event
596596
for filename in filenames:
597-
event = FileCreatedEvent(os.path.join(root, filename))
598-
event.is_synthetic = True
599-
yield event
597+
file_created_event = FileCreatedEvent(os.path.join(root, filename))
598+
file_created_event.is_synthetic = True
599+
yield file_created_event

src/watchdog/observers/fsevents2.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import unicodedata
2727
import warnings
2828
from threading import Thread
29+
from typing import List, Optional, Type
2930

3031
# pyobjc
3132
import AppKit # type: ignore[import]
@@ -65,6 +66,7 @@
6566
FileDeletedEvent,
6667
FileModifiedEvent,
6768
FileMovedEvent,
69+
FileSystemEvent,
6870
)
6971
from watchdog.observers.api import DEFAULT_EMITTER_TIMEOUT, DEFAULT_OBSERVER_TIMEOUT, BaseObserver, EventEmitter
7072

@@ -80,7 +82,7 @@ class FSEventsQueue(Thread):
8082

8183
def __init__(self, path):
8284
Thread.__init__(self)
83-
self._queue = queue.Queue()
85+
self._queue: queue.Queue[Optional[List[NativeEvent]]] = queue.Queue()
8486
self._run_loop = None
8587

8688
if isinstance(path, bytes):
@@ -206,6 +208,7 @@ def queue_events(self, timeout):
206208
while i < len(events):
207209
event = events[i]
208210

211+
cls: Type[FileSystemEvent]
209212
# For some reason the create and remove flags are sometimes also
210213
# set for rename and modify type events, so let those take
211214
# precedence.

src/watchdog/observers/inotify.py

+7
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
import logging
7070
import os
7171
import threading
72+
from typing import Type
7273

7374
from watchdog.events import (
7475
DirCreatedEvent,
@@ -81,6 +82,7 @@
8182
FileModifiedEvent,
8283
FileMovedEvent,
8384
FileOpenedEvent,
85+
FileSystemEvent,
8486
generate_sub_created_events,
8587
generate_sub_moved_events,
8688
)
@@ -128,9 +130,14 @@ def queue_events(self, timeout, full_events=False):
128130
logger.error("InotifyEmitter.queue_events() called when the thread is inactive")
129131
return
130132
with self._lock:
133+
if self._inotify is None:
134+
logger.error("InotifyEmitter.queue_events() called when the thread is inactive")
135+
return
131136
event = self._inotify.read_event()
132137
if event is None:
133138
return
139+
140+
cls: Type[FileSystemEvent]
134141
if isinstance(event, tuple):
135142
move_from, move_to = event
136143
src_path = self._decode_path(move_from.src_path)

src/watchdog/observers/inotify_buffer.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
from __future__ import annotations
1616

1717
import logging
18+
from typing import TYPE_CHECKING, List, Tuple, Union
1819

19-
from watchdog.observers.inotify_c import Inotify
20+
from watchdog.observers.inotify_c import Inotify, InotifyEvent
2021
from watchdog.utils import BaseThread
2122
from watchdog.utils.delayed_queue import DelayedQueue
2223

@@ -32,7 +33,7 @@ class InotifyBuffer(BaseThread):
3233

3334
def __init__(self, path, recursive=False):
3435
super().__init__()
35-
self._queue = DelayedQueue(self.delay)
36+
self._queue = DelayedQueue[InotifyEvent](self.delay)
3637
self._inotify = Inotify(path, recursive)
3738
self.start()
3839

@@ -53,7 +54,7 @@ def close(self):
5354

5455
def _group_events(self, event_list):
5556
"""Group any matching move events"""
56-
grouped = []
57+
grouped: List[Union[InotifyEvent, Tuple[InotifyEvent, InotifyEvent]]] = []
5758
for inotify_event in event_list:
5859
logger.debug("in-event %s", inotify_event)
5960

@@ -68,6 +69,9 @@ def matching_from_event(event):
6869
# Check if move_from is already in the buffer
6970
for index, event in enumerate(grouped):
7071
if matching_from_event(event):
72+
if TYPE_CHECKING:
73+
# this check is hidden from mypy inside matching_from_event()
74+
assert not isinstance(event, tuple)
7175
grouped[index] = (event, inotify_event)
7276
break
7377
else:

src/watchdog/tricks/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import os
4747
import signal
4848
import subprocess
49+
import sys
4950
import threading
5051
import time
5152

@@ -322,7 +323,7 @@ def _restart_process(self):
322323
self.restart_count += 1
323324

324325

325-
if hasattr(os, "getpgid") and hasattr(os, "killpg"):
326+
if not sys.platform.startswith("win"):
326327

327328
def kill_process(pid, stop_signal):
328329
os.killpg(os.getpgid(pid), stop_signal)

src/watchdog/utils/delayed_queue.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,20 @@
1717
import threading
1818
import time
1919
from collections import deque
20+
from typing import Callable, Deque, Generic, Optional, Tuple, TypeVar
2021

22+
T = TypeVar("T")
2123

22-
class DelayedQueue:
24+
25+
class DelayedQueue(Generic[T]):
2326
def __init__(self, delay):
2427
self.delay_sec = delay
2528
self._lock = threading.Lock()
2629
self._not_empty = threading.Condition(self._lock)
27-
self._queue = deque()
30+
self._queue: Deque[Tuple[T, float, bool]] = deque()
2831
self._closed = False
2932

30-
def put(self, element, delay=False):
33+
def put(self, element: T, delay: bool = False) -> None:
3134
"""Add element to queue."""
3235
self._lock.acquire()
3336
self._queue.append((element, time.time(), delay))
@@ -42,7 +45,7 @@ def close(self):
4245
self._not_empty.notify()
4346
self._not_empty.release()
4447

45-
def get(self):
48+
def get(self) -> Optional[T]:
4649
"""Remove and return an element from the queue, or this queue has been
4750
closed raise the Closed exception.
4851
"""
@@ -71,7 +74,7 @@ def get(self):
7174
self._queue.popleft()
7275
return head
7376

74-
def remove(self, predicate):
77+
def remove(self, predicate: Callable[[T], bool]) -> Optional[T]:
7578
"""Remove and return the first items for which predicate is True,
7679
ignoring delay."""
7780
with self._lock:

src/watchdog/watchmedo.py

+11-4
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
from argparse import ArgumentParser, RawDescriptionHelpFormatter
3333
from io import StringIO
3434
from textwrap import dedent
35+
from typing import TYPE_CHECKING
3536

37+
from watchdog.observers.api import BaseObserverSubclassCallable
3638
from watchdog.utils import WatchdogShutdown, load_class
3739
from watchdog.version import VERSION_STRING
3840

@@ -265,11 +267,12 @@ def tricks_from(args):
265267
"""
266268
Command to execute tricks from a tricks configuration file.
267269
"""
270+
Observer: BaseObserverSubclassCallable
268271
if args.debug_force_polling:
269272
from watchdog.observers.polling import PollingObserver as Observer
270273
elif args.debug_force_kqueue:
271274
from watchdog.observers.kqueue import KqueueObserver as Observer
272-
elif args.debug_force_winapi:
275+
elif (not TYPE_CHECKING and args.debug_force_winapi) or (TYPE_CHECKING and sys.platform.startswith("win")):
273276
from watchdog.observers.read_directory_changes import WindowsApiObserver as Observer
274277
elif args.debug_force_inotify:
275278
from watchdog.observers.inotify import InotifyObserver as Observer
@@ -376,8 +379,8 @@ def tricks_generate_yaml(args):
376379
else:
377380
if not os.path.exists(args.append_to_file):
378381
content = header + content
379-
with open(args.append_to_file, "a", encoding="utf-8") as output:
380-
output.write(content)
382+
with open(args.append_to_file, "a", encoding="utf-8") as file:
383+
file.write(content)
381384

382385

383386
@command(
@@ -471,11 +474,13 @@ def log(args):
471474
ignore_patterns=ignore_patterns,
472475
ignore_directories=args.ignore_directories,
473476
)
477+
478+
Observer: BaseObserverSubclassCallable
474479
if args.debug_force_polling:
475480
from watchdog.observers.polling import PollingObserver as Observer
476481
elif args.debug_force_kqueue:
477482
from watchdog.observers.kqueue import KqueueObserver as Observer
478-
elif args.debug_force_winapi:
483+
elif (not TYPE_CHECKING and args.debug_force_winapi) or (TYPE_CHECKING and sys.platform.startswith("win")):
479484
from watchdog.observers.read_directory_changes import WindowsApiObserver as Observer
480485
elif args.debug_force_inotify:
481486
from watchdog.observers.inotify import InotifyObserver as Observer
@@ -585,6 +590,7 @@ def shell_command(args):
585590
if not args.command:
586591
args.command = None
587592

593+
Observer: BaseObserverSubclassCallable
588594
if args.debug_force_polling:
589595
from watchdog.observers.polling import PollingObserver as Observer
590596
else:
@@ -704,6 +710,7 @@ def auto_restart(args):
704710
Command to start a long-running subprocess and restart it on matched events.
705711
"""
706712

713+
Observer: BaseObserverSubclassCallable
707714
if args.debug_force_polling:
708715
from watchdog.observers.polling import PollingObserver as Observer
709716
else:

tests/test_delayed_queue.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
@pytest.mark.flaky(max_runs=5, min_passes=1)
2525
def test_delayed_get():
26-
q = DelayedQueue(2)
26+
q = DelayedQueue[str](2)
2727
q.put("", True)
2828
inserted = time()
2929
q.get()
@@ -34,7 +34,7 @@ def test_delayed_get():
3434

3535
@pytest.mark.flaky(max_runs=5, min_passes=1)
3636
def test_nondelayed_get():
37-
q = DelayedQueue(2)
37+
q = DelayedQueue[str](2)
3838
q.put("", False)
3939
inserted = time()
4040
q.get()

tests/test_inotify_buffer.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,9 @@ def delayed(*args, **kwargs):
146146
class InotifyBufferDelayedRead(InotifyBuffer):
147147
def run(self, *args, **kwargs):
148148
# Introduce a delay to trigger the race condition where the file descriptor is
149-
# closed prior to a read being triggered.
150-
self._inotify.read_events = delay_call(
149+
# closed prior to a read being triggered. Ignoring type concerns since we are
150+
# intentionally doing something odd.
151+
self._inotify.read_events = delay_call( # type: ignore[method-assign]
151152
function=self._inotify.read_events, seconds=1
152153
)
153154

tests/test_snapshot_diff.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -172,14 +172,15 @@ def test_ignore_device(p):
172172

173173
inode_orig = DirectorySnapshot.inode
174174

175+
inode_times = 0
176+
175177
def inode(self, path):
176178
# This function will always return a different device_id,
177179
# even for the same file.
180+
nonlocal inode_times
178181
result = inode_orig(self, path)
179-
inode.times += 1
180-
return result[0], result[1] + inode.times
181-
182-
inode.times = 0
182+
inode_times += 1
183+
return result[0], result[1] + inode_times
183184

184185
# Set the custom inode function.
185186
with patch.object(DirectorySnapshot, "inode", new=inode):

0 commit comments

Comments
 (0)