Skip to content

Commit

Permalink
adding callback argument to (a)run_process
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelcolvin committed Oct 17, 2017
1 parent dd158c2 commit d748326
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 13 deletions.
22 changes: 16 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,15 @@ To run a function and restart it when code changes:
run_process('./path/to/dir', foobar, process_args=(1, 2, 3))
(``run_process`` uses ``PythonWatcher`` so only changes to python files will prompt a
reload, see *custom watchers* below)
``run_process`` uses ``PythonWatcher`` so only changes to python files will prompt a
reload, see *custom watchers* below.

If you need notifications about change events as well as to restart a process you can
use the ``callback`` argument to pass a function will will be called on every file change
with one argument: the set of file changes.

Asynchronous Methods
....................

*watchgod* comes with an asynchronous equivalents of ``watch``: ``awatch`` which uses
a ``ThreadPoolExecutor`` to iterate over files.
Expand Down Expand Up @@ -65,11 +72,14 @@ uses ``awatch``:
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
(``arun_process`` uses ``PythonWatcher`` so only changes to python files will prompt a
reload, see *custom watchers* below)
``arun_process`` uses ``PythonWatcher`` so only changes to python files will prompt a
reload, see *custom watchers* below.

The signature of ``arun_process`` is almost identical to ``run_process`` except that
the ``callback`` argument if provide must be a coroutine, not a function.

Custom watchers
---------------
Custom Watchers
...............

*watchgod* comes with the following watcher classes which can be used via the ``watcher_cls``
keyword argument to any of the methods above.
Expand Down
16 changes: 13 additions & 3 deletions tests/test_run_process.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from asyncio import Future

from watchgod import arun_process, run_process
from watchgod.main import _start_process

Expand Down Expand Up @@ -42,14 +44,17 @@ def test_alive_terminates(mocker):
assert mock_kill.call_count == 1


def test_dead(mocker):
def test_dead_callback(mocker):
mock_start_process = mocker.patch('watchgod.main._start_process')
mock_start_process.return_value = FakeProcess(is_alive=False)
mock_kill = mocker.patch('watchgod.main.os.kill')
c = mocker.MagicMock()

assert run_process('/x/y/z', object(), watcher_cls=FakeWatcher, debounce=5, min_sleep=1) == 1
assert run_process('/x/y/z', object(), watcher_cls=FakeWatcher, callback=c, debounce=5, min_sleep=1) == 1
assert mock_start_process.call_count == 2
assert mock_kill.call_count == 0
assert c.call_count == 1
c.assert_called_with({'x'})


def test_alive_doesnt_terminate(mocker):
Expand All @@ -74,8 +79,13 @@ async def test_async_alive_terminates(mocker):
mock_start_process = mocker.patch('watchgod.main._start_process')
mock_start_process.return_value = FakeProcess()
mock_kill = mocker.patch('watchgod.main.os.kill')
f = Future()
f.set_result(1)
c = mocker.MagicMock(return_value=f)

reloads = await arun_process('/x/y/async', object(), watcher_cls=FakeWatcher, debounce=5, min_sleep=1)
reloads = await arun_process('/x/y/async', object(), watcher_cls=FakeWatcher, callback=c, debounce=5, min_sleep=1)
assert reloads == 1
assert mock_start_process.call_count == 2
assert mock_kill.call_count == 1
assert c.call_count == 1
c.assert_called_with({'x'})
12 changes: 8 additions & 4 deletions watchgod/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
from multiprocessing import Process
from pathlib import Path
from time import sleep, time
from typing import Any, Callable, Dict, Tuple, Type, Union
from typing import Any, Awaitable, Callable, Dict, Set, Tuple, Type, Union

from .watcher import AllWatcher, DefaultWatcher, PythonWatcher
from .watcher import AllWatcher, Change, DefaultWatcher, PythonWatcher

__all__ = 'watch', 'awatch', 'run_process', 'arun_process'
logger = logging.getLogger('watchgod.main')
Expand Down Expand Up @@ -116,6 +116,7 @@ def _stop_process(process):
def run_process(path: Union[Path, str], target: Callable, *,
args: Tuple[Any]=(),
kwargs: Dict[str, Any]=None,
callback: Callable[[Set[Tuple[Change, str]]], None]=None,
watcher_cls: Type[AllWatcher]=PythonWatcher,
debounce=400,
min_sleep=100):
Expand All @@ -126,7 +127,8 @@ def run_process(path: Union[Path, str], target: Callable, *,
process = _start_process(target=target, args=args, kwargs=kwargs)
reloads = 0

for _ in watch(path, watcher_cls=watcher_cls, debounce=debounce, min_sleep=min_sleep):
for changes in watch(path, watcher_cls=watcher_cls, debounce=debounce, min_sleep=min_sleep):
callback and callback(changes)
_stop_process(process)
process = _start_process(target=target, args=args, kwargs=kwargs)
reloads += 1
Expand All @@ -136,6 +138,7 @@ def run_process(path: Union[Path, str], target: Callable, *,
async def arun_process(path: Union[Path, str], target: Callable, *,
args: Tuple[Any]=(),
kwargs: Dict[str, Any]=None,
callback: Callable[[Set[Tuple[Change, str]]], Awaitable]=None,
watcher_cls: Type[AllWatcher]=PythonWatcher,
debounce=400,
min_sleep=100):
Expand All @@ -148,7 +151,8 @@ async def arun_process(path: Union[Path, str], target: Callable, *,
process = await loop.run_in_executor(None, start_process)
reloads = 0

async for _ in awatch(path, watcher_cls=watcher_cls, debounce=debounce, min_sleep=min_sleep): # noqa: F841
async for changes in awatch(path, watcher_cls=watcher_cls, debounce=debounce, min_sleep=min_sleep): # noqa: F841
callback and await callback(changes)
await loop.run_in_executor(None, _stop_process, process)
process = await loop.run_in_executor(None, start_process)
reloads += 1
Expand Down

0 comments on commit d748326

Please sign in to comment.