Skip to content

Commit 4d1510d

Browse files
authored
Add mypy type checking (gorakhargosh#908)
* Add mypy as a tox test environment * Fix all of the issues reported by mypy * Update platform-specific Observer imports so mypy understands them This allows mypy to perform platform-specific type-checking, which fixes type-checking of the example code in `README.rst`. Closes gorakhargosh#840 * Add a changelog entry * Add mypy as a CI environment * Resolve an error with mypy 1.1.1
1 parent c0bd8b5 commit 4d1510d

14 files changed

+64
-31
lines changed

.github/workflows/tests.yml

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ jobs:
2828
- name: Run flake8
2929
run: |
3030
tox -e flake8
31+
- name: Run mypy
32+
run: tox -e mypy
3133

3234
tests:
3335
name: Run tests for ${{ matrix.os }} for ${{ matrix.python }}

changelog.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ Changelog
88

99
2023-xx-xx • `full history <https://github.com/gorakhargosh/watchdog/compare/v2.3.1...HEAD>`__
1010

11-
-
12-
- Thanks to our beloved contributors: @
11+
- [testing] watchdog is now PEP 561 compatible and is tested by mypy.
12+
- Thanks to our beloved contributors: @kurtmckee
1313

1414
2.3.1
1515
~~~~~

requirements-tests.txt

+3
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@ pytest
55
pytest-cov
66
pytest-timeout
77
sphinx
8+
# mypy testing
9+
mypy
10+
types-PyYAML

src/watchdog/events.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ class FileSystemEvent:
114114
can be used as keys in dictionaries or be added to sets.
115115
"""
116116

117-
event_type = None
117+
event_type = ""
118118
"""The type of the event as a string."""
119119

120120
is_directory = False

src/watchdog/observers/__init__.py

+13-8
Original file line numberDiff line numberDiff line change
@@ -50,33 +50,38 @@
5050
5151
"""
5252

53+
import sys
5354
import warnings
54-
from watchdog.utils import platform
55+
from typing import Type
56+
5557
from watchdog.utils import UnsupportedLibc
58+
from .api import BaseObserver
59+
60+
Observer: Type[BaseObserver]
5661

57-
if platform.is_linux():
62+
63+
if sys.platform.startswith("linux"):
5864
try:
5965
from .inotify import InotifyObserver as Observer
6066
except UnsupportedLibc:
6167
from .polling import PollingObserver as Observer
6268

63-
elif platform.is_darwin():
69+
elif sys.platform.startswith("darwin"):
6470
try:
6571
from .fsevents import FSEventsObserver as Observer
6672
except Exception:
6773
try:
68-
from .kqueue import KqueueObserver as Observer
69-
74+
from .kqueue import KqueueObserver as Observer # type: ignore[attr-defined,no-redef]
7075
warnings.warn("Failed to import fsevents. Fall back to kqueue")
7176
except Exception:
7277
from .polling import PollingObserver as Observer
7378

7479
warnings.warn("Failed to import fsevents and kqueue. Fall back to polling.")
7580

76-
elif platform.is_bsd():
77-
from .kqueue import KqueueObserver as Observer
81+
elif sys.platform in ("dragonfly", "freebsd", "netbsd", "openbsd", "bsd"):
82+
from .kqueue import KqueueObserver as Observer # type: ignore[attr-defined,no-redef]
7883

79-
elif platform.is_windows():
84+
elif sys.platform.startswith("win"):
8085
try:
8186
from .read_directory_changes import WindowsApiObserver as Observer
8287
except Exception:

src/watchdog/observers/fsevents.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import os
2727
import threading
2828
import unicodedata
29-
import _watchdog_fsevents as _fsevents
29+
import _watchdog_fsevents as _fsevents # type: ignore
3030

3131
from watchdog.events import (
3232
FileDeletedEvent,

src/watchdog/observers/fsevents2.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@
4343
)
4444

4545
# pyobjc
46-
import AppKit
47-
from FSEvents import (
46+
import AppKit # type: ignore
47+
from FSEvents import ( # type: ignore
4848
FSEventStreamCreate,
4949
CFRunLoopGetCurrent,
5050
FSEventStreamScheduleWithRunLoop,

src/watchdog/observers/kqueue.py

+8
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515

16+
# The `select` module varies between platforms.
17+
# mypy may complain about missing module attributes
18+
# depending on which platform it's running on.
19+
# The comment below disables mypy's attribute check.
20+
#
21+
# mypy: disable-error-code=attr-defined
22+
#
23+
1624
"""
1725
:module: watchdog.observers.kqueue
1826
:synopsis: ``kqueue(2)`` based emitter implementation.

src/watchdog/observers/winapi.py

+11-11
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,11 @@ def _errcheck_dword(value, func, args):
119119
return args
120120

121121

122-
kernel32 = ctypes.WinDLL("kernel32")
122+
kernel32 = ctypes.WinDLL("kernel32") # type: ignore[attr-defined]
123123

124124
ReadDirectoryChangesW = kernel32.ReadDirectoryChangesW
125125
ReadDirectoryChangesW.restype = ctypes.wintypes.BOOL
126-
ReadDirectoryChangesW.errcheck = _errcheck_bool
126+
ReadDirectoryChangesW.errcheck = _errcheck_bool # type: ignore
127127
ReadDirectoryChangesW.argtypes = (
128128
ctypes.wintypes.HANDLE, # hDirectory
129129
LPVOID, # lpBuffer
@@ -137,7 +137,7 @@ def _errcheck_dword(value, func, args):
137137

138138
CreateFileW = kernel32.CreateFileW
139139
CreateFileW.restype = ctypes.wintypes.HANDLE
140-
CreateFileW.errcheck = _errcheck_handle
140+
CreateFileW.errcheck = _errcheck_handle # type: ignore
141141
CreateFileW.argtypes = (
142142
ctypes.wintypes.LPCWSTR, # lpFileName
143143
ctypes.wintypes.DWORD, # dwDesiredAccess
@@ -154,15 +154,15 @@ def _errcheck_dword(value, func, args):
154154

155155
CancelIoEx = kernel32.CancelIoEx
156156
CancelIoEx.restype = ctypes.wintypes.BOOL
157-
CancelIoEx.errcheck = _errcheck_bool
157+
CancelIoEx.errcheck = _errcheck_bool # type: ignore
158158
CancelIoEx.argtypes = (
159159
ctypes.wintypes.HANDLE, # hObject
160160
ctypes.POINTER(OVERLAPPED), # lpOverlapped
161161
)
162162

163163
CreateEvent = kernel32.CreateEventW
164164
CreateEvent.restype = ctypes.wintypes.HANDLE
165-
CreateEvent.errcheck = _errcheck_handle
165+
CreateEvent.errcheck = _errcheck_handle # type: ignore
166166
CreateEvent.argtypes = (
167167
LPVOID, # lpEventAttributes
168168
ctypes.wintypes.BOOL, # bManualReset
@@ -172,12 +172,12 @@ def _errcheck_dword(value, func, args):
172172

173173
SetEvent = kernel32.SetEvent
174174
SetEvent.restype = ctypes.wintypes.BOOL
175-
SetEvent.errcheck = _errcheck_bool
175+
SetEvent.errcheck = _errcheck_bool # type: ignore
176176
SetEvent.argtypes = (ctypes.wintypes.HANDLE,) # hEvent
177177

178178
WaitForSingleObjectEx = kernel32.WaitForSingleObjectEx
179179
WaitForSingleObjectEx.restype = ctypes.wintypes.DWORD
180-
WaitForSingleObjectEx.errcheck = _errcheck_dword
180+
WaitForSingleObjectEx.errcheck = _errcheck_dword # type: ignore
181181
WaitForSingleObjectEx.argtypes = (
182182
ctypes.wintypes.HANDLE, # hObject
183183
ctypes.wintypes.DWORD, # dwMilliseconds
@@ -186,7 +186,7 @@ def _errcheck_dword(value, func, args):
186186

187187
CreateIoCompletionPort = kernel32.CreateIoCompletionPort
188188
CreateIoCompletionPort.restype = ctypes.wintypes.HANDLE
189-
CreateIoCompletionPort.errcheck = _errcheck_handle
189+
CreateIoCompletionPort.errcheck = _errcheck_handle # type: ignore
190190
CreateIoCompletionPort.argtypes = (
191191
ctypes.wintypes.HANDLE, # FileHandle
192192
ctypes.wintypes.HANDLE, # ExistingCompletionPort
@@ -196,7 +196,7 @@ def _errcheck_dword(value, func, args):
196196

197197
GetQueuedCompletionStatus = kernel32.GetQueuedCompletionStatus
198198
GetQueuedCompletionStatus.restype = ctypes.wintypes.BOOL
199-
GetQueuedCompletionStatus.errcheck = _errcheck_bool
199+
GetQueuedCompletionStatus.errcheck = _errcheck_bool # type: ignore
200200
GetQueuedCompletionStatus.argtypes = (
201201
ctypes.wintypes.HANDLE, # CompletionPort
202202
LPVOID, # lpNumberOfBytesTransferred
@@ -207,7 +207,7 @@ def _errcheck_dword(value, func, args):
207207

208208
PostQueuedCompletionStatus = kernel32.PostQueuedCompletionStatus
209209
PostQueuedCompletionStatus.restype = ctypes.wintypes.BOOL
210-
PostQueuedCompletionStatus.errcheck = _errcheck_bool
210+
PostQueuedCompletionStatus.errcheck = _errcheck_bool # type: ignore
211211
PostQueuedCompletionStatus.argtypes = (
212212
ctypes.wintypes.HANDLE, # CompletionPort
213213
ctypes.wintypes.DWORD, # lpNumberOfBytesTransferred
@@ -218,7 +218,7 @@ def _errcheck_dword(value, func, args):
218218

219219
GetFinalPathNameByHandleW = kernel32.GetFinalPathNameByHandleW
220220
GetFinalPathNameByHandleW.restype = ctypes.wintypes.DWORD
221-
GetFinalPathNameByHandleW.errcheck = _errcheck_dword
221+
GetFinalPathNameByHandleW.errcheck = _errcheck_dword # type: ignore
222222
GetFinalPathNameByHandleW.argtypes = (
223223
ctypes.wintypes.HANDLE, # hFile
224224
ctypes.wintypes.LPWSTR, # lpszFilePath

tests/test_emitter.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import logging
2020
from functools import partial
2121
from queue import Queue, Empty
22+
from typing import Type
2223

2324
from .shell import mkfile, mkdir, touch, mv, rm
2425
from watchdog.utils import platform
@@ -34,7 +35,9 @@
3435
FileClosedEvent,
3536
FileOpenedEvent,
3637
)
37-
from watchdog.observers.api import ObservedWatch
38+
from watchdog.observers.api import EventEmitter, ObservedWatch
39+
40+
Emitter: Type[EventEmitter]
3841

3942
if platform.is_linux():
4043
from watchdog.observers.inotify import (
@@ -46,7 +49,9 @@
4649
elif platform.is_windows():
4750
from watchdog.observers.read_directory_changes import WindowsApiEmitter as Emitter
4851
elif platform.is_bsd():
49-
from watchdog.observers.kqueue import KqueueEmitter as Emitter
52+
from watchdog.observers.kqueue import ( # type: ignore[attr-defined,no-redef]
53+
KqueueEmitter as Emitter
54+
)
5055

5156
logging.basicConfig(level=logging.DEBUG)
5257
logger = logging.getLogger(__name__)

tests/test_fsevents.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from time import sleep
1616
from unittest.mock import patch
1717

18-
import _watchdog_fsevents as _fsevents
18+
import _watchdog_fsevents as _fsevents # type: ignore
1919
from watchdog.events import FileSystemEventHandler
2020
from watchdog.observers import Observer
2121
from watchdog.observers.api import ObservedWatch

tests/test_skip_repeats_queue.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def test_consecutives_allowed_across_empties():
103103
@cpython_only
104104
def test_eventlet_monkey_patching():
105105
try:
106-
import eventlet
106+
import eventlet # type: ignore
107107
except Exception:
108108
pytest.skip("eventlet not installed")
109109

tools/dump_fsevents_constants.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44
from io import StringIO
55

6-
import FSEvents
6+
import FSEvents # type: ignore
77

88
header = """ File generated by watchdog/scripts/dump_mac_constants.py
99

tox.ini

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
[tox]
2-
envlist = py{311,310,39,38,37,36,py3}, docs
2+
envlist =
3+
py{311,310,39,38,37,36,py3}
4+
docs
5+
mypy
36
skip_missing_interpreters = True
47

58
[testenv]
@@ -28,3 +31,10 @@ extras =
2831
watchmedo
2932
commands =
3033
sphinx-build -aEWb html docs/source docs/build/html
34+
35+
[testenv:mypy]
36+
usedevelop = true
37+
deps =
38+
-r requirements-tests.txt
39+
commands =
40+
mypy tools src tests

0 commit comments

Comments
 (0)