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

mypy check_untyped_defs #966

Merged
merged 12 commits into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
7 changes: 6 additions & 1 deletion mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@ disallow_subclassing_any = True
;disallow_untyped_calls = True
;disallow_untyped_defs = True
disallow_incomplete_defs = True
;check_untyped_defs = True
check_untyped_defs = True
disallow_untyped_decorators = True
no_implicit_optional = True
warn_return_any = True
no_implicit_reexport = True
strict_equality = True
warn_redundant_casts = True

[mypy-watchdog.watchmedo]
# unable to handle platform detection overrides
# https://github.com/python/mypy/issues/8823
warn_unused_ignores = False
24 changes: 12 additions & 12 deletions src/watchdog/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,17 +562,17 @@ def generate_sub_moved_events(src_dir_path, dest_dir_path):
renamed_path = (
full_path.replace(dest_dir_path, src_dir_path) if src_dir_path else None
)
event = DirMovedEvent(renamed_path, full_path)
event.is_synthetic = True
yield event
dir_moved_event = DirMovedEvent(renamed_path, full_path)
dir_moved_event.is_synthetic = True
yield dir_moved_event
for filename in filenames:
full_path = os.path.join(root, filename)
renamed_path = (
full_path.replace(dest_dir_path, src_dir_path) if src_dir_path else None
)
event = FileMovedEvent(renamed_path, full_path)
event.is_synthetic = True
yield event
file_moved_event = FileMovedEvent(renamed_path, full_path)
file_moved_event.is_synthetic = True
yield file_moved_event


def generate_sub_created_events(src_dir_path):
Expand All @@ -588,10 +588,10 @@ def generate_sub_created_events(src_dir_path):
"""
for root, directories, filenames in os.walk(src_dir_path):
for directory in directories:
event = DirCreatedEvent(os.path.join(root, directory))
event.is_synthetic = True
yield event
dir_created_event = DirCreatedEvent(os.path.join(root, directory))
dir_created_event.is_synthetic = True
yield dir_created_event
for filename in filenames:
event = FileCreatedEvent(os.path.join(root, filename))
event.is_synthetic = True
yield event
file_created_event = FileCreatedEvent(os.path.join(root, filename))
file_created_event.is_synthetic = True
yield file_created_event
4 changes: 2 additions & 2 deletions src/watchdog/observers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@
from typing import Type

from watchdog.utils import UnsupportedLibc
from .api import BaseObserver
from .api import BaseObserverSubclassCallable

Observer: Type[BaseObserver]
Observer: BaseObserverSubclassCallable


if sys.platform.startswith("linux"):
Expand Down
6 changes: 6 additions & 0 deletions src/watchdog/observers/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import queue
import threading
from pathlib import Path
from typing import Protocol

from watchdog.utils import BaseThread
from watchdog.utils.bricks import SkipRepeatsQueue
Expand Down Expand Up @@ -377,3 +378,8 @@ def dispatch_events(self, event_queue):
if handler in self._handlers.get(watch, []):
handler.dispatch(event)
event_queue.task_done()


class BaseObserverSubclassCallable(Protocol):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dunno, I think I might rather rework the inheritance so that the init calls are "properly" compatible. But, this is a game to play for the moment.

Copy link
Collaborator

@BoboTiG BoboTiG Mar 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "nice" was mostly for the Protocol usage :)
I've never really used that pattern, and it may be quite helpful!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely, I tend away from inheritance and Protocol certainly gives you a nice static way to describe shared interfaces.

def __call__(self, timeout: float = ...) -> BaseObserver:
...
5 changes: 4 additions & 1 deletion src/watchdog/observers/fsevents2.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@
import unicodedata
import warnings
from threading import Thread
from typing import List, Optional, Type

from watchdog.events import (
FileDeletedEvent,
FileModifiedEvent,
FileCreatedEvent,
FileMovedEvent,
FileSystemEvent,
DirDeletedEvent,
DirModifiedEvent,
DirCreatedEvent,
Expand Down Expand Up @@ -86,7 +88,7 @@ class FSEventsQueue(Thread):

def __init__(self, path):
Thread.__init__(self)
self._queue = queue.Queue()
self._queue: queue.Queue[Optional[List[NativeEvent]]] = queue.Queue()
self._run_loop = None

if isinstance(path, bytes):
Expand Down Expand Up @@ -212,6 +214,7 @@ def queue_events(self, timeout):
while i < len(events):
event = events[i]

cls: Type[FileSystemEvent]
# For some reason the create and remove flags are sometimes also
# set for rename and modify type events, so let those take
# precedence.
Expand Down
11 changes: 11 additions & 0 deletions src/watchdog/observers/inotify.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,10 @@

"""

import logging
import os
import threading
from typing import Type
from .inotify_buffer import InotifyBuffer

from watchdog.observers.api import (
Expand All @@ -86,11 +88,15 @@
FileCreatedEvent,
FileClosedEvent,
FileOpenedEvent,
FileSystemEvent,
generate_sub_moved_events,
generate_sub_created_events,
)


logger = logging.getLogger(__name__)


class InotifyEmitter(EventEmitter):
"""
inotify(7)-based event emitter.
Expand Down Expand Up @@ -125,9 +131,14 @@ def queue_events(self, timeout, full_events=False):
# 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
with self._lock:
if self._inotify is None:
logger.error("InotifyEmitter.queue_events() called when the thread is inactive")
return
event = self._inotify.read_event()
if event is None:
return

cls: Type[FileSystemEvent]
if isinstance(event, tuple):
move_from, move_to = event
src_path = self._decode_path(move_from.src_path)
Expand Down
11 changes: 8 additions & 3 deletions src/watchdog/observers/inotify_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
# limitations under the License.

import logging
from typing import List, TYPE_CHECKING, Tuple, Union

from watchdog.utils import BaseThread
from watchdog.utils.delayed_queue import DelayedQueue
from watchdog.observers.inotify_c import Inotify
from watchdog.observers.inotify_c import Inotify, InotifyEvent

logger = logging.getLogger(__name__)

Expand All @@ -29,7 +31,7 @@ class InotifyBuffer(BaseThread):

def __init__(self, path, recursive=False):
super().__init__()
self._queue = DelayedQueue(self.delay)
self._queue = DelayedQueue[InotifyEvent](self.delay)
self._inotify = Inotify(path, recursive)
self.start()

Expand All @@ -50,7 +52,7 @@ def close(self):

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

Expand All @@ -65,6 +67,9 @@ def matching_from_event(event):
# Check if move_from is already in the buffer
for index, event in enumerate(grouped):
if matching_from_event(event):
if TYPE_CHECKING:
# this check is hidden from mypy inside matching_from_event()
assert not isinstance(event, tuple)
grouped[index] = (event, inotify_event)
break
else:
Expand Down
3 changes: 2 additions & 1 deletion src/watchdog/tricks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import os
import signal
import subprocess
import sys
import threading
import time

Expand Down Expand Up @@ -320,7 +321,7 @@ def _restart_process(self):
self.restart_count += 1


if hasattr(os, "getpgid") and hasattr(os, "killpg"):
if not sys.platform.startswith("win"):

def kill_process(pid, stop_signal):
os.killpg(os.getpgid(pid), stop_signal)
Expand Down
14 changes: 9 additions & 5 deletions src/watchdog/utils/delayed_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,21 @@
import time
import threading
from collections import deque
from typing import Callable, Deque, Generic, Optional, Tuple, TypeVar


class DelayedQueue:
T = TypeVar("T")


class DelayedQueue(Generic[T]):
def __init__(self, delay):
self.delay_sec = delay
self._lock = threading.Lock()
self._not_empty = threading.Condition(self._lock)
self._queue = deque()
self._queue: Deque[Tuple[T, float, bool]] = deque()
self._closed = False

def put(self, element, delay=False):
def put(self, element: T, delay: bool = False) -> None:
"""Add element to queue."""
self._lock.acquire()
self._queue.append((element, time.time(), delay))
Expand All @@ -40,7 +44,7 @@ def close(self):
self._not_empty.notify()
self._not_empty.release()

def get(self):
def get(self) -> Optional[T]:
"""Remove and return an element from the queue, or this queue has been
closed raise the Closed exception.
"""
Expand Down Expand Up @@ -69,7 +73,7 @@ def get(self):
self._queue.popleft()
return head

def remove(self, predicate):
def remove(self, predicate: Callable[[T], bool]) -> Optional[T]:
"""Remove and return the first items for which predicate is True,
ignoring delay."""
with self._lock:
Expand Down
15 changes: 11 additions & 4 deletions src/watchdog/watchmedo.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
from argparse import ArgumentParser, RawDescriptionHelpFormatter
from io import StringIO
from textwrap import dedent
from typing import TYPE_CHECKING, Type

from watchdog.observers.api import BaseObserverSubclassCallable
from watchdog.utils import WatchdogShutdown, load_class
from watchdog.version import VERSION_STRING

Expand Down Expand Up @@ -263,12 +265,13 @@ def tricks_from(args):
"""
Command to execute tricks from a tricks configuration file.
"""
Observer: BaseObserverSubclassCallable
if args.debug_force_polling:
from watchdog.observers.polling import PollingObserver as Observer
elif args.debug_force_kqueue:
from watchdog.observers.kqueue import KqueueObserver as Observer
elif args.debug_force_winapi:
from watchdog.observers.read_directory_changes import (
from watchdog.observers.read_directory_changes import ( # type:ignore[attr-defined,no-redef]
WindowsApiObserver as Observer,
)
elif args.debug_force_inotify:
Expand Down Expand Up @@ -376,8 +379,8 @@ def tricks_generate_yaml(args):
else:
if not os.path.exists(args.append_to_file):
content = header + content
with open(args.append_to_file, "ab") as output:
output.write(content)
with open(args.append_to_file, "a", encoding="utf-8") as file:
file.write(content)


@command(
Expand Down Expand Up @@ -471,12 +474,14 @@ def log(args):
ignore_patterns=ignore_patterns,
ignore_directories=args.ignore_directories,
)

Observer: BaseObserverSubclassCallable
if args.debug_force_polling:
from watchdog.observers.polling import PollingObserver as Observer
elif args.debug_force_kqueue:
from watchdog.observers.kqueue import KqueueObserver as Observer
elif args.debug_force_winapi:
from watchdog.observers.read_directory_changes import (
from watchdog.observers.read_directory_changes import ( # type:ignore[attr-defined,no-redef]
WindowsApiObserver as Observer,
)
elif args.debug_force_inotify:
Expand Down Expand Up @@ -587,6 +592,7 @@ def shell_command(args):
if not args.command:
args.command = None

Observer: BaseObserverSubclassCallable
if args.debug_force_polling:
from watchdog.observers.polling import PollingObserver as Observer
else:
Expand Down Expand Up @@ -706,6 +712,7 @@ def auto_restart(args):
Command to start a long-running subprocess and restart it on matched events.
"""

Observer: BaseObserverSubclassCallable
if args.debug_force_polling:
from watchdog.observers.polling import PollingObserver as Observer
else:
Expand Down
4 changes: 2 additions & 2 deletions tests/test_delayed_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

@pytest.mark.flaky(max_runs=5, min_passes=1)
def test_delayed_get():
q = DelayedQueue(2)
q = DelayedQueue[str](2)
Copy link
Collaborator

@BoboTiG BoboTiG Mar 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I've learned something too today :)

q.put("", True)
inserted = time()
q.get()
Expand All @@ -31,7 +31,7 @@ def test_delayed_get():

@pytest.mark.flaky(max_runs=5, min_passes=1)
def test_nondelayed_get():
q = DelayedQueue(2)
q = DelayedQueue[str](2)
q.put("", False)
inserted = time()
q.get()
Expand Down
Loading