From b8d073b1421a3940886306df295ec9ca54aa358f Mon Sep 17 00:00:00 2001 From: Fred Glass Date: Mon, 14 Oct 2019 17:50:00 +0100 Subject: [PATCH 1/5] Prevent file reloads on any directory change --- reloadr.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/reloadr.py b/reloadr.py index 4f9554c..bfc29b6 100644 --- a/reloadr.py +++ b/reloadr.py @@ -86,10 +86,9 @@ def _start_watch_reload(self): class EventHandler(FileSystemEventHandler): def on_modified(self, event): - this._reload() + if not event.is_directory and event.src_path == filepath: + this._reload() - # Sadly, watchdog only operates on directories and not on a file - # level, so any change within the directory will trigger a reload. observer.schedule(EventHandler(), filedir, recursive=False) observer.start() From 50de7ce29aa884f22bc5421164d5f0a8fd87a113 Mon Sep 17 00:00:00 2001 From: Fred Glass Date: Mon, 14 Oct 2019 17:54:55 +0100 Subject: [PATCH 2/5] Prevent multiple watchers to reduce redundant reloads --- reloadr.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/reloadr.py b/reloadr.py index bfc29b6..7c81ed9 100644 --- a/reloadr.py +++ b/reloadr.py @@ -43,6 +43,8 @@ def reload_target(target, kind, filepath=None): assert kind in ('class', 'def') source = get_new_source(target, kind, filepath) + decorator = '@autoreload' + source = source.replace(decorator, '{}(original=False)'.format(decorator)) module = inspect.getmodule(target) # We will populate these locals using exec() locals_ = {} @@ -76,12 +78,14 @@ def _start_timer_reload(self, interval=1): thread = threading.Thread(target=self._timer_reload) thread.start() - def _start_watch_reload(self): + def _start_watch_reload(self, original): "Reload the target based on file changes in the directory" + if not original: # Prevent multiple watchers + return + observer = Observer() filepath = inspect.getsourcefile(self._target) filedir = dirname(abspath(filepath)) - this = self class EventHandler(FileSystemEventHandler): @@ -155,8 +159,17 @@ def reloadr(target): return ClassReloadr(target) -def autoreload(target): +def autoreload(target, original=True): "Decorator that immediately starts watching the source file in a thread." - result = reloadr(target) - result._start_watch_reload() - return result \ No newline at end of file + def execute(target): + result = reloadr(target) + result._start_watch_reload(original) + return result + + if target: + return execute(target) + else: + def wrapper(target): + return execute(target) + + return wrapper \ No newline at end of file From 2c983230754086e70fbe5e09ef759ab13ffdc97b Mon Sep 17 00:00:00 2001 From: Fred Glass Date: Mon, 14 Oct 2019 17:56:51 +0100 Subject: [PATCH 3/5] Add logging --- reloadr.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/reloadr.py b/reloadr.py index 7c81ed9..1f37910 100644 --- a/reloadr.py +++ b/reloadr.py @@ -4,6 +4,7 @@ from os.path import dirname, abspath import inspect +import logging import redbaron from baron.parser import ParsingError import threading @@ -124,8 +125,9 @@ def _reload(self): instance = ref() # We keep weak references to objects if instance: instance.__class__ = self._target + logging.info('Reloaded {}'.format(self._target.__name__)) except ParsingError as error: - print('ParsingError', error) + logging.error('Parsing error: {}'.format(error)) class FuncReloadr(GenericReloadr): @@ -148,7 +150,7 @@ def _reload(self): try: self._target = reload_function(self._target, self._filepath) except ParsingError as error: - print('ParsingError', error) + logging.error('Parsing error: {}'.format(error)) def reloadr(target): From 2f62777064db7f2a97f93b1af104e69dcd8c3d24 Mon Sep 17 00:00:00 2001 From: Fred Glass Date: Mon, 14 Oct 2019 17:59:53 +0100 Subject: [PATCH 4/5] Increment version and use triple quotes for docstrings --- reloadr.py | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/reloadr.py b/reloadr.py index 1f37910..39347b6 100644 --- a/reloadr.py +++ b/reloadr.py @@ -1,22 +1,20 @@ """Reloadr - Python library for hot code reloading -(c) 2015-2017 Hugo Herter +(c) 2015-2019 Hugo Herter """ - -from os.path import dirname, abspath import inspect import logging import redbaron -from baron.parser import ParsingError import threading import types -from time import sleep import weakref - +from baron.parser import ParsingError +from os.path import dirname, abspath +from time import sleep from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler __author__ = "Hugo Herter" -__version__ = '0.3.3' +__version__ = '0.3.4' def get_new_source(target, kind, filepath=None): @@ -57,30 +55,30 @@ def reload_target(target, kind, filepath=None): def reload_class(target): - "Get the new class object corresponding to the target class." + """Get the new class object corresponding to the target class.""" return reload_target(target, 'class') def reload_function(target, filepath: str): - "Get the new function object corresponding to the target function." + """Get the new function object corresponding to the target function.""" return reload_target(target, 'def', filepath) class GenericReloadr: def _timer_reload(self, interval=1): - "Reload the target every `interval` seconds." + """Reload the target every `interval` seconds.""" while True: self._reload() sleep(interval) def _start_timer_reload(self, interval=1): - "Start a thread that reloads the target every `interval` seconds." + """Start a thread that reloads the target every `interval` seconds.""" thread = threading.Thread(target=self._timer_reload) thread.start() def _start_watch_reload(self, original): - "Reload the target based on file changes in the directory" + """Reload the target based on file changes in the directory""" if not original: # Prevent multiple watchers return @@ -106,18 +104,18 @@ def __init__(self, target): self._instances = [] # For classes, keep a reference to all instances def __call__(self, *args, **kwargs): - "Override instantiation in order to register a reference to the instance" + """Override instantiation in order to register a reference to the instance""" instance = self._target.__call__(*args, **kwargs) # Register a reference to the instance self._instances.append(weakref.ref(instance)) return instance def __getattr__(self, name): - "Proxy inspection to the target" + """Proxy inspection to the target""" return self._target.__getattr__(name) def _reload(self): - "Manually reload the class with its new code." + """Manually reload the class with its new code.""" try: self._target = reload_class(self._target) # Replace the class reference of all instances with the new class @@ -138,23 +136,24 @@ def __init__(self, target): self._filepath = inspect.getsourcefile(target) def __call__(self, *args, **kwargs): - "Proxy function call to the target" + """Proxy function call to the target""" return self._target.__call__(*args, **kwargs) def __getattr__(self, name): - "Proxy inspection to the target" + """Proxy inspection to the target""" return self._target.__getattr__(name) def _reload(self): - "Manually reload the function with its new code." + """Manually reload the function with its new code.""" try: self._target = reload_function(self._target, self._filepath) + logging.info('Reloaded {}'.format(self._target.__name__)) except ParsingError as error: logging.error('Parsing error: {}'.format(error)) def reloadr(target): - "Main decorator, forwards the target to the appropriate class." + """Main decorator, forwards the target to the appropriate class.""" if isinstance(target, types.FunctionType): return FuncReloadr(target) else: @@ -162,7 +161,7 @@ def reloadr(target): def autoreload(target, original=True): - "Decorator that immediately starts watching the source file in a thread." + """Decorator that immediately starts watching the source file in a thread.""" def execute(target): result = reloadr(target) result._start_watch_reload(original) @@ -173,5 +172,4 @@ def execute(target): else: def wrapper(target): return execute(target) - return wrapper \ No newline at end of file From 2ecee74b20f96ac65a71fa32383bc1905026ad16 Mon Sep 17 00:00:00 2001 From: Fred Glass Date: Tue, 15 Oct 2019 11:34:45 +0100 Subject: [PATCH 5/5] Remove decorator on reload instead of maintaining original --- reloadr.py | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/reloadr.py b/reloadr.py index 39347b6..0658ed0 100644 --- a/reloadr.py +++ b/reloadr.py @@ -42,16 +42,13 @@ def reload_target(target, kind, filepath=None): assert kind in ('class', 'def') source = get_new_source(target, kind, filepath) - decorator = '@autoreload' - source = source.replace(decorator, '{}(original=False)'.format(decorator)) + source = source.replace('@{}'.format(autoreload.__name__), '') # Remove decorator module = inspect.getmodule(target) # We will populate these locals using exec() locals_ = {} # module.__dict__ is the namespace of the module exec(source, module.__dict__, locals_) - # The result is expected to be decorated with @reloadr, so we return - # ._target, which corresponds to the class itself and not the Reloadr class - return locals_[target.__name__]._target + return locals_[target.__name__] def reload_class(target): @@ -77,11 +74,8 @@ def _start_timer_reload(self, interval=1): thread = threading.Thread(target=self._timer_reload) thread.start() - def _start_watch_reload(self, original): + def _start_watch_reload(self): """Reload the target based on file changes in the directory""" - if not original: # Prevent multiple watchers - return - observer = Observer() filepath = inspect.getsourcefile(self._target) filedir = dirname(abspath(filepath)) @@ -160,16 +154,8 @@ def reloadr(target): return ClassReloadr(target) -def autoreload(target, original=True): +def autoreload(target=None): """Decorator that immediately starts watching the source file in a thread.""" - def execute(target): - result = reloadr(target) - result._start_watch_reload(original) - return result - - if target: - return execute(target) - else: - def wrapper(target): - return execute(target) - return wrapper \ No newline at end of file + result = reloadr(target) + result._start_watch_reload() + return result \ No newline at end of file