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

allow overriding the default monitor via env var #29

Merged
merged 2 commits into from
May 2, 2018
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion src/hupper/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# public api
# flake8: noqa

from .compat import is_watchdog_supported
from .utils import is_watchdog_supported

from .reloader import (
start_reloader,
Expand Down
9 changes: 0 additions & 9 deletions src/hupper/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,6 @@
import pickle


def is_watchdog_supported():
""" Return ``True`` if watchdog is available."""
try:
import watchdog
except ImportError:
return False
return True


################################################
# cross-compatible metaclass implementation
# Copyright (c) 2010-2012 Benjamin Peterson
Expand Down
2 changes: 1 addition & 1 deletion src/hupper/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def trigger_reload(self):


class IFileMonitorFactory(with_metaclass(abc.ABCMeta)):
def __call__(self, callback):
def __call__(self, callback, **kw):
""" Return an :class:`.IFileMonitor` instance.

``callback`` is a callable to be invoked by the ``IFileMonitor``
Expand Down
9 changes: 1 addition & 8 deletions src/hupper/ipc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import io
import imp
import importlib
import os
import struct
import sys
Expand All @@ -10,13 +9,7 @@
from .compat import WIN
from .compat import pickle
from .compat import queue


def resolve_spec(spec):
modname, funcname = spec.rsplit('.', 1)
module = importlib.import_module(modname)
func = getattr(module, funcname)
return func
from .utils import resolve_spec


if WIN: # pragma: no cover
Expand Down
4 changes: 2 additions & 2 deletions src/hupper/polling.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ class PollingFileMonitor(threading.Thread, IFileMonitor):
disk. Do not set this too low or it will eat your CPU and kill your drive.

"""
def __init__(self, callback, poll_interval=1):
def __init__(self, callback, interval=1, **kw):
super(PollingFileMonitor, self).__init__()
self.callback = callback
self.poll_interval = poll_interval
self.poll_interval = interval
self.paths = set()
self.mtimes = {}
self.lock = threading.Lock()
Expand Down
62 changes: 41 additions & 21 deletions src/hupper/reloader.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
from __future__ import print_function

from glob import glob
import os
import signal
import sys
import threading
import time

from .compat import is_watchdog_supported
from .compat import queue
from .ipc import ProcessGroup
from .utils import resolve_spec
from .utils import is_watchdog_supported
from .worker import (
Worker,
is_active,
Expand All @@ -23,8 +25,9 @@ class FileMonitorProxy(object):
when it should reload.

"""
def __init__(self, monitor_factory, verbose=1):
self.monitor = monitor_factory(self.file_changed)
monitor = None

def __init__(self, verbose=1):
self.verbose = verbose
self.change_event = threading.Event()
self.changed_paths = set()
Expand Down Expand Up @@ -197,7 +200,12 @@ def _wait_for_changes(self):
self.monitor.clear_changes()

def _start_monitor(self):
self.monitor = FileMonitorProxy(self.monitor_factory, self.verbose)
proxy = FileMonitorProxy(self.verbose)
proxy.monitor = self.monitor_factory(
proxy.file_changed,
interval=self.reload_interval,
)
self.monitor = proxy
self.monitor.start()

def _stop_monitor(self):
Expand All @@ -219,6 +227,29 @@ def _restore_signals(self):
signal.signal(signal.SIGHUP, signal.SIG_DFL)


def find_default_monitor_factory(verbose):
spec = os.environ.get('HUPPER_DEFAULT_MONITOR')
if spec:
monitor_factory = resolve_spec(spec)

if verbose > 1:
print('File monitor backend: ' + spec)

elif is_watchdog_supported():
from .watchdog import WatchdogFileMonitor as monitor_factory

if verbose > 1:
print('File monitor backend: watchdog')

else:
from .polling import PollingFileMonitor as monitor_factory

if verbose > 1:
print('File monitor backend: polling')

return monitor_factory


def start_reloader(
worker_path,
reload_interval=1,
Expand Down Expand Up @@ -254,28 +285,17 @@ def start_reloader(
fallback to the less efficient
:class:`hupper.polling.PollingFileMonitor` otherwise.

If ``monitor_factory`` is ``None`` it can be overridden by the
``HUPPER_DEFAULT_MONITOR`` environment variable. It should be a dotted
python path pointing at an object implementing
:class:`hupper.interfaces.IFileMonitorFactory`.

"""
if is_active():
return get_reloader()

if monitor_factory is None:
if is_watchdog_supported():
from .watchdog import WatchdogFileMonitor

def monitor_factory(callback):
return WatchdogFileMonitor(callback)

if verbose > 1:
print('File monitor backend: watchdog')

else:
from .polling import PollingFileMonitor

def monitor_factory(callback):
return PollingFileMonitor(callback, reload_interval)

if verbose > 1:
print('File monitor backend: polling')
monitor_factory = find_default_monitor_factory(verbose)

reloader = Reloader(
worker_path=worker_path,
Expand Down
17 changes: 17 additions & 0 deletions src/hupper/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import importlib


def resolve_spec(spec):
modname, funcname = spec.rsplit('.', 1)
module = importlib.import_module(modname)
func = getattr(module, funcname)
return func


def is_watchdog_supported():
""" Return ``True`` if watchdog is available."""
try:
import watchdog # noqa: F401
except ImportError:
return False
return True
2 changes: 1 addition & 1 deletion src/hupper/watchdog.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class WatchdogFileMonitor(FileSystemEventHandler, Observer, IFileMonitor):
``callback`` is a callable that accepts a path to a changed file.

"""
def __init__(self, callback):
def __init__(self, callback, **kw):
super(WatchdogFileMonitor, self).__init__()
self.callback = callback
self.paths = set()
Expand Down
12 changes: 7 additions & 5 deletions tests/test_reloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@

here = os.path.abspath(os.path.dirname(__file__))

def make_proxy(*args, **kwargs):
def make_proxy(monitor_factory):
from hupper.reloader import FileMonitorProxy
return FileMonitorProxy(*args, **kwargs)
proxy = FileMonitorProxy()
proxy.monitor = monitor_factory(proxy.file_changed)
return proxy

def test_proxy_proxies():
class DummyMonitor(object):
started = stopped = joined = False

def __call__(self, cb):
def __call__(self, cb, **kw):
self.cb = cb
return self

Expand All @@ -34,7 +36,7 @@ def join(self):

def test_proxy_expands_paths(tmpdir):
class DummyMonitor(object):
def __call__(self, cb):
def __call__(self, cb, **kw):
self.cb = cb
self.paths = []
return self
Expand All @@ -59,7 +61,7 @@ def add_path(self, path):

def test_proxy_tracks_changes(capsys):
class DummyMonitor(object):
def __call__(self, cb):
def __call__(self, cb, **kw):
self.cb = cb
return self

Expand Down