Skip to content

Commit

Permalink
Merge pull request #259 from Venti9874/reload_fix
Browse files Browse the repository at this point in the history
Module reloading fix.
  • Loading branch information
embolalia committed May 28, 2013
2 parents 0af9faa + b5b42a3 commit 1e0cdca
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 7 deletions.
51 changes: 45 additions & 6 deletions willie/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def contains(self, key):

def setup(self):
stderr("\nWelcome to Willie. Loading modules...\n\n")
self.callables = {}
self.callables = set()

filenames = self.config.enumerate_modules()
# Coretasks is special. No custom user coretasks.
Expand Down Expand Up @@ -152,16 +152,55 @@ 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 and have hashable. Furthermore, it must
have either "commands" or "rule" as attributes to mark it as a willie
callable.
"""
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
return False

def register(self, variables):
"""
With the ``__dict__`` attribute from a Willie module, update or add the
trigger commands and rules to allow the function to be triggered.
"""
# This is used by reload.py, hence it being methodised
for name, obj in variables.iteritems():
if hasattr(obj, 'commands') or hasattr(obj, 'rule'):
full_name = '%s.%s' % (variables['__name__'], name)
self.callables[full_name] = obj
for obj in variables.itervalues():
if self.is_callable(obj):
self.callables.add(obj)

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:
variables -- 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 variables.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': {}}
Expand All @@ -186,7 +225,7 @@ def sub(pattern, self=self):
pattern = pattern.replace('$nickname', r'%s' % re.escape(self.nick))
return pattern.replace('$nick', r'%s[,:] +' % re.escape(self.nick))

for func in self.callables.itervalues():
for func in self.callables:
if not hasattr(func, 'priority'):
func.priority = 'medium'

Expand Down
16 changes: 15 additions & 1 deletion willie/modules/reload.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down

0 comments on commit 1e0cdca

Please sign in to comment.