From e878b8321ee34705e0322ea95b3dfaded20f63d4 Mon Sep 17 00:00:00 2001 From: Ari Koivula Date: Mon, 20 May 2013 02:03:19 +0300 Subject: [PATCH 1/2] Unregister and unbind modules from willie.bot when they are reloaded. -Adds methods unregister and is_callable to willie.bot. -Adds code required to remove references to willie callables, in modules themselves and willie, to willie.modules.reload. --- willie/bot.py | 42 +++++++++++++++++++++++++++++++++++++++- willie/modules/reload.py | 16 ++++++++++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/willie/bot.py b/willie/bot.py index 64b06ba141..bbfc26cabc 100644 --- a/willie/bot.py +++ b/willie/bot.py @@ -152,6 +152,22 @@ def setup(self): self.bind_commands() + @staticmethod + def is_callable(obj): + """Return true if object is a willie callable. + + Object must be both be callable (function or object with __call__) and + have hashable. Furthermore, it must have either "commands" or "rule" + as attributes to mark it as a willie callable. + """ + if not hasattr(obj, '__call__') or not hasattr(obj, '__hash__'): + # There might not be any objects with __hash__ but no __call__, + # but I'm not sure so check both. __hash__ is required for set. + return False + if hasattr(obj, 'commands') or hasattr(obj, 'rule'): + return True + return False + def register(self, variables): """ With the ``__dict__`` attribute from a Willie module, update or add the @@ -159,9 +175,33 @@ def register(self, variables): """ # This is used by reload.py, hence it being methodised for obj in variables.itervalues(): - if hasattr(obj, 'commands') or hasattr(obj, 'rule'): + if self.is_callable(obj): self.callables.add(obj) + def unregister(self, callables): + """Unregister all callables and their bindings. + + When unloading a module, this ensures that the unloaded modules will + not get called and that the objects can be garbage collected. Objects + that have not been registered are ignored. + + Args: + callables -- A list of callable objects from a willie module. + """ + def remove_func(func, commands): + """Remove all traces of func from commands.""" + for func_list in commands.itervalues(): + if func in func_list: + func_list.remove(func) + + for obj in callables.itervalues(): + if not self.is_callable(obj): + continue + if obj in self.callables: + self.callables.remove(obj) + for commands in self.commands.itervalues(): + remove_func(obj, commands) + def bind_commands(self): self.commands = {'high': {}, 'medium': {}, 'low': {}} diff --git a/willie/modules/reload.py b/willie/modules/reload.py index 28306bcc36..c6566135c2 100644 --- a/willie/modules/reload.py +++ b/willie/modules/reload.py @@ -32,8 +32,22 @@ def f_reload(willie, trigger): if not name in sys.modules: return willie.reply('%s: no such module!' % name) + old_module = sys.modules[name] + + old_callables = {} + for obj_name, obj in vars(old_module).iteritems(): + if willie.is_callable(obj): + old_callables[obj_name] = obj + + willie.unregister(old_callables) + # Also remove all references to willie callables from top level of the + # module, so that they will not get loaded again if reloading the + # module does not override them. + for obj_name in old_callables.keys(): + delattr(old_module, obj_name) + # Thanks to moot for prodding me on this - path = sys.modules[name].__file__ + path = old_module.__file__ if path.endswith('.pyc') or path.endswith('.pyo'): path = path[:-1] if not os.path.isfile(path): From b5b42a3a7c6e95c4b72596f82a06620a1530f880 Mon Sep 17 00:00:00 2001 From: Ari Koivula Date: Tue, 21 May 2013 17:00:53 +0300 Subject: [PATCH 2/2] Remove check for __hash__ and rename function parameter callable to variables. --- willie/bot.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/willie/bot.py b/willie/bot.py index bbfc26cabc..5ad14f8611 100644 --- a/willie/bot.py +++ b/willie/bot.py @@ -156,13 +156,13 @@ def setup(self): def is_callable(obj): """Return true if object is a willie callable. - Object must be both be callable (function or object with __call__) and - have hashable. Furthermore, it must have either "commands" or "rule" - as attributes to mark it as a willie callable. + Object must be both be callable and have hashable. Furthermore, it must + have either "commands" or "rule" as attributes to mark it as a willie + callable. """ - if not hasattr(obj, '__call__') or not hasattr(obj, '__hash__'): - # There might not be any objects with __hash__ but no __call__, - # but I'm not sure so check both. __hash__ is required for set. + if not callable(obj): + # Check is to help distinguish between willie callables and objects + # which just happen to have parameter commands or rule. return False if hasattr(obj, 'commands') or hasattr(obj, 'rule'): return True @@ -178,15 +178,15 @@ def register(self, variables): if self.is_callable(obj): self.callables.add(obj) - def unregister(self, callables): - """Unregister all callables and their bindings. + def unregister(self, variables): + """Unregister all willie callables in variables, and their bindings. When unloading a module, this ensures that the unloaded modules will not get called and that the objects can be garbage collected. Objects that have not been registered are ignored. Args: - callables -- A list of callable objects from a willie module. + variables -- A list of callable objects from a willie module. """ def remove_func(func, commands): """Remove all traces of func from commands.""" @@ -194,7 +194,7 @@ def remove_func(func, commands): if func in func_list: func_list.remove(func) - for obj in callables.itervalues(): + for obj in variables.itervalues(): if not self.is_callable(obj): continue if obj in self.callables: