diff --git a/reloadr.py b/reloadr.py index 4f9554c..0658ed0 100644 --- a/reloadr.py +++ b/reloadr.py @@ -1,21 +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): @@ -43,53 +42,50 @@ def reload_target(target, kind, filepath=None): assert kind in ('class', 'def') source = get_new_source(target, kind, filepath) + 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): - "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): - "Reload the target based on file changes in the directory" + """Reload the target based on file changes in the directory""" observer = Observer() filepath = inspect.getsourcefile(self._target) filedir = dirname(abspath(filepath)) - this = 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() @@ -102,18 +98,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 @@ -121,8 +117,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): @@ -133,31 +130,32 @@ 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: - print('ParsingError', 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: return ClassReloadr(target) -def autoreload(target): - "Decorator that immediately starts watching the source file in a thread." +def autoreload(target=None): + """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