Repository: gorakhargosh/watchdog

Issue:
watchmedo - argparse for --kill-after needs to be a float
If you set --kill-after argument on watchmedo then it will fail as the argparse configuration is not set to a float.

It needs to be: 

```
          argument('--kill-after',
                   dest='kill_after',
                   default=10.0,
                   type=float,
                   help='When stopping, kill the subprocess after the specified timeout '
                   'in seconds (default 10.0).')])
```

Discussion:
BoboTiG: Thanks @taleinat !

Do you mind adding a line,  and your nickname, in the changelog?

Patch:
diff --git a/changelog.rst b/changelog.rst
index ffc8fb0..900a55d 100644
--- a/changelog.rst
+++ b/changelog.rst
@@ -8,8 +8,8 @@ Changelog
 
 2022-xx-xx • `full history <https://github.com/gorakhargosh/watchdog/compare/v2.1.7...master>`__
 
-- 
-- Thanks to our beloved contributors: @
+- [watchmedo] Fix broken parsing of ``--kill-after`` argument for the ``auto-restart`` command. (`#870 <https://github.com/gorakhargosh/watchdog/issues/870>`_)
+- Thanks to our beloved contributors: @taleinat
 
 2.1.7
 ~~~~~
diff --git a/src/watchdog/observers/fsevents2.py b/src/watchdog/observers/fsevents2.py
index 365c976..1c0a894 100644
--- a/src/watchdog/observers/fsevents2.py
+++ b/src/watchdog/observers/fsevents2.py
@@ -205,7 +205,7 @@ def queue_events(self, timeout):
                 # Internal moves appears to always be consecutive in the same
                 # buffer and have IDs differ by exactly one (while others
                 # don't) making it possible to pair up the two events coming
-                # from a singe move operation. (None of this is documented!)
+                # from a single move operation. (None of this is documented!)
                 # Otherwise, guess whether file was moved in or out.
                 # TODO: handle id wrapping
                 if (i + 1 < len(events) and events[i + 1].is_renamed
diff --git a/src/watchdog/watchmedo.py b/src/watchdog/watchmedo.py
index 0841c9a..a3d1fd8 100755
--- a/src/watchdog/watchmedo.py
+++ b/src/watchdog/watchmedo.py
@@ -579,6 +579,7 @@ def shell_command(args):
           argument('--kill-after',
                    dest='kill_after',
                    default=10.0,
+                   type=float,
                    help='When stopping, kill the subprocess after the specified timeout '
                    'in seconds (default 10.0).')])
 def auto_restart(args):
diff --git a/src/watchdog_fsevents.c b/src/watchdog_fsevents.c
index c2c7626..5b18884 100644
--- a/src/watchdog_fsevents.c
+++ b/src/watchdog_fsevents.c
@@ -210,14 +210,14 @@ static void NativeEventDealloc(NativeEventObject *self) {
 }
 
 static PyGetSetDef NativeEventProperties[] = {
-    {"flags", NativeEventTypeFlags, NULL, "The raw mask of flags as returend by FSEvents", NULL},
+    {"flags", NativeEventTypeFlags, NULL, "The raw mask of flags as returned by FSEvents", NULL},
     {"path", NativeEventTypePath, NULL, "The path for which this event was generated", NULL},
     {"inode", NativeEventTypeInode, NULL, "The inode for which this event was generated", NULL},
     {"event_id", NativeEventTypeID, NULL, "The id of the generated event", NULL},
     {"is_coalesced", NativeEventTypeIsCoalesced, NULL, "True if multiple ambiguous changes to the monitored path happened", NULL},
     {"must_scan_subdirs", NativeEventTypeIsMustScanSubDirs, NULL, "True if application must rescan all subdirectories", NULL},
-    {"is_user_dropped", NativeEventTypeIsUserDropped, NULL, "True if a failure during event buffering occured", NULL},
-    {"is_kernel_dropped", NativeEventTypeIsKernelDropped, NULL, "True if a failure during event buffering occured", NULL},
+    {"is_user_dropped", NativeEventTypeIsUserDropped, NULL, "True if a failure during event buffering occurred", NULL},
+    {"is_kernel_dropped", NativeEventTypeIsKernelDropped, NULL, "True if a failure during event buffering occurred", NULL},
     {"is_event_ids_wrapped", NativeEventTypeIsEventIdsWrapped, NULL, "True if event_id wrapped around", NULL},
     {"is_history_done", NativeEventTypeIsHistoryDone, NULL, "True if all historical events are done", NULL},
     {"is_root_changed", NativeEventTypeIsRootChanged, NULL, "True if a change to one of the directories along the path to one of the directories you watch occurred", NULL},


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 2e84a02..bb526d3 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -52,4 +52,5 @@ def no_warnings(recwarn):
         ):
             continue
         warnings.append("{w.filename}:{w.lineno} {w.message}".format(w=warning))
+    print(warnings)
     assert not warnings
diff --git a/tests/test_0_watchmedo.py b/tests/test_0_watchmedo.py
index 312ba5a..5da4314 100644
--- a/tests/test_0_watchmedo.py
+++ b/tests/test_0_watchmedo.py
@@ -74,6 +74,21 @@ def test_kill_auto_restart(tmpdir, capfd):
     # assert 'KeyboardInterrupt' in cap.err
 
 
+def test_auto_restart_arg_parsing_basic():
+    args = watchmedo.cli.parse_args(["auto-restart", "-d", ".", "cmd"])
+    assert args.func is watchmedo.auto_restart
+    assert args.command == "cmd"
+    assert args.directories == ["."]
+
+
+def test_auto_restart_arg_parsing_kill_after():
+    args = watchmedo.cli.parse_args(["auto-restart", "-d", ".", "--kill-after", "12.5", "cmd"])
+    assert args.func is watchmedo.auto_restart
+    assert args.command == "cmd"
+    assert args.directories == ["."]
+    assert args.kill_after == pytest.approx(12.5)
+
+
 @pytest.mark.parametrize("command", ["tricks-from", "tricks"])
 def test_tricks_from_file(command, tmp_path):
     tricks_file = tmp_path / "tricks.yaml"
diff --git a/tests/test_emitter.py b/tests/test_emitter.py
index bec052c..6b71e1a 100644
--- a/tests/test_emitter.py
+++ b/tests/test_emitter.py
@@ -143,7 +143,7 @@ def test_close():
     assert os.path.normpath(event.src_path) == os.path.normpath(p(''))
     assert isinstance(event, DirModifiedEvent)
 
-    # After read-only, only IN_CLOSE_NOWRITE is emitted but not catched for now #747
+    # After read-only, only IN_CLOSE_NOWRITE is emitted but not caught for now #747
     open(p('a'), 'r').close()
 
     assert event_queue.empty()