Reduce importance of importer; new tiny context for each module, instead of having entire bot context

This commit is contained in:
nemunaire 2015-02-10 03:46:50 +01:00
parent bafc14bd79
commit 1f5364c387
5 changed files with 165 additions and 158 deletions

View file

@ -102,7 +102,7 @@ if __name__ == "__main__":
prmpt = nemubot.prompt.Prompt() prmpt = nemubot.prompt.Prompt()
# Register the hook for futur import # Register the hook for futur import
sys.meta_path.append(ModuleFinder(context, prmpt)) sys.meta_path.append(ModuleFinder(context.modules_paths, context.add_module))
# Load requested configuration files # Load requested configuration files
for path in args.files: for path in args.files:

View file

@ -34,10 +34,14 @@ __author__ = 'nemunaire'
from nemubot.consumer import Consumer, EventConsumer, MessageConsumer from nemubot.consumer import Consumer, EventConsumer, MessageConsumer
from nemubot import datastore from nemubot import datastore
from nemubot.event import ModuleEvent from nemubot.event import ModuleEvent
import nemubot.hooks
from nemubot.hooks.messagehook import MessageHook from nemubot.hooks.messagehook import MessageHook
from nemubot.hooks.manager import HooksManager from nemubot.hooks.manager import HooksManager
from nemubot.modulecontext import ModuleContext
from nemubot.networkbot import NetworkBot from nemubot.networkbot import NetworkBot
context = ModuleContext(None, None)
logger = logging.getLogger("nemubot") logger = logging.getLogger("nemubot")
@ -228,7 +232,7 @@ class Bot(threading.Thread):
# Register the event in the source module # Register the event in the source module
if module_src is not None: if module_src is not None:
module_src.REGISTERED_EVENTS.append(evt.id) module_src.__nemubot_context__.events.append(evt.id)
evt.module_src = module_src evt.module_src = module_src
logger.info("New event registered: %s -> %s", evt.id, evt) logger.info("New event registered: %s -> %s", evt.id, evt)
@ -260,7 +264,7 @@ class Bot(threading.Thread):
self.events.remove(self.events[0]) self.events.remove(self.events[0])
self._update_event_timer() self._update_event_timer()
if module_src is not None: if module_src is not None:
module_src.REGISTERED_EVENTS.remove(id) module_src.__nemubot_context__.events.remove(id)
return True return True
for evt in self.events: for evt in self.events:
@ -268,7 +272,7 @@ class Bot(threading.Thread):
self.events.remove(evt) self.events.remove(evt)
if module_src is not None: if module_src is not None:
module_src.REGISTERED_EVENTS.remove(evt.id) module_src.__nemubot_context__.events.remove(evt.id)
return True return True
return False return False
@ -330,8 +334,7 @@ class Bot(threading.Thread):
if srv.id not in self.servers: if srv.id not in self.servers:
self.servers[srv.id] = srv self.servers[srv.id] = srv
if (autoconnect and not hasattr(self, "noautoconnect") and if autoconnect and not hasattr(self, "noautoconnect"):
hasattr(self, "stop") and not self.stop):
srv.open() srv.open()
return True return True
@ -370,23 +373,36 @@ class Bot(threading.Thread):
module.logger.info(" ".join(args)) module.logger.info(" ".join(args))
module.print = prnt module.print = prnt
# Create module context
module.__nemubot_context__ = ModuleContext(self, module)
# Replace imported context by real one
for attr in module.__dict__:
if attr != "__nemubot_context__" and type(module.__dict__[attr]) == ModuleContext:
module.__dict__[attr] = module.__nemubot_context__
# Register decorated functions
for s, h in nemubot.hooks.last_registered:
module.__nemubot_context__.add_hook(s, h)
nemubot.hooks.last_registered = []
# Save a reference to the module
self.modules[module.__name__] = module self.modules[module.__name__] = module
# Launch the module
if hasattr(module, "load"):
module.load(module.__nemubot_context__)
return True return True
def unload_module(self, name): def unload_module(self, name):
"""Unload a module""" """Unload a module"""
if name in self.modules: if name in self.modules:
self.modules[name].print_debug("Unloading module %s" % name) self.modules[name].print("Unloading module %s" % name)
self.modules[name].save()
if hasattr(self.modules[name], "unload"): if hasattr(self.modules[name], "unload"):
self.modules[name].unload(self) self.modules[name].unload(self)
# Remove registered hooks self.modules[name].__nemubot_context__.unload()
for (s, h) in self.modules[name].REGISTERED_HOOKS:
self.hooks.del_hook(h, s)
# Remove registered events
for e in self.modules[name].REGISTERED_EVENTS:
self.del_event(e)
# Remove from the dict # Remove from the dict
del self.modules[name] del self.modules[name]
logger.info("Module `%s' successfully unloaded.", name) logger.info("Module `%s' successfully unloaded.", name)

View file

@ -203,7 +203,7 @@ class EventConsumer:
# Or remove reference of this event # Or remove reference of this event
elif (hasattr(self.evt, "module_src") and elif (hasattr(self.evt, "module_src") and
self.evt.module_src is not None): self.evt.module_src is not None):
self.evt.module_src.REGISTERED_EVENTS.remove(self.evt.id) self.evt.module_src.__nemubot_context__.events.remove(self.evt.id)

View file

@ -34,21 +34,21 @@ logger = logging.getLogger("nemubot.importer")
class ModuleFinder(Finder): class ModuleFinder(Finder):
def __init__(self, context, prompt): def __init__(self, modules_paths, add_module):
self.context = context self.modules_paths = modules_paths
self.prompt = prompt self.add_module = add_module
def find_module(self, fullname, path=None): def find_module(self, fullname, path=None):
# print ("looking for", fullname, "in", path) # print ("looking for", fullname, "in", path)
# Search only for new nemubot modules (packages init) # Search only for new nemubot modules (packages init)
if path is None: if path is None:
for mpath in self.context.modules_paths: for mpath in self.modules_paths:
# print ("looking for", fullname, "in", mpath) # print ("looking for", fullname, "in", mpath)
if os.path.isfile(os.path.join(mpath, fullname + ".py")): if os.path.isfile(os.path.join(mpath, fullname + ".py")):
return ModuleLoader(self.context, self.prompt, fullname, return ModuleLoader(self.add_module, fullname,
os.path.join(mpath, fullname + ".py")) os.path.join(mpath, fullname + ".py"))
elif os.path.isfile(os.path.join(os.path.join(mpath, fullname), "__init__.py")): elif os.path.isfile(os.path.join(os.path.join(mpath, fullname), "__init__.py")):
return ModuleLoader(self.context, self.prompt, fullname, return ModuleLoader(self.add_module, fullname,
os.path.join( os.path.join(
os.path.join(mpath, fullname), os.path.join(mpath, fullname),
"__init__.py")) "__init__.py"))
@ -58,153 +58,19 @@ class ModuleFinder(Finder):
class ModuleLoader(SourceFileLoader): class ModuleLoader(SourceFileLoader):
def __init__(self, context, prompt, fullname, path): def __init__(self, add_module, fullname, path):
self.context = context self.add_module = add_module
self.prompt = prompt
if fullname in self.context.modules_configuration:
self.config = self.context.modules_configuration[fullname]
else:
self.config = None
SourceFileLoader.__init__(self, fullname, path) SourceFileLoader.__init__(self, fullname, path)
def load_module(self, fullname): def load_module(self, fullname):
module = SourceFileLoader.load_module(self, fullname) module = SourceFileLoader.load_module(self, fullname)
# Check that is a valid nemubot module
if not hasattr(module, "nemubotversion"):
raise ImportError("Module `%s' is not a nemubot module." %
fullname)
# Check module version
if LooseVersion(__version__) < LooseVersion(str(module.nemubotversion)):
raise ImportError("Module `%s' is not compatible with this "
"version." % fullname)
# Set module common functions and data
module.__LOADED__ = True
module.logger = logging.getLogger("nemubot.module." + fullname)
def prnt_dbg(*args):
if module.DEBUG:
print("{%s}" % module.__name__, *args)
module.logger.debug(" ".join(args))
def mod_save():
self.context.datastore.save(module.__name__, module.DATAS)
def send_response(server, res):
if server in self.context.servers:
r = res.next_response()
if r.server is not None:
return self.context.servers[r.server].send_response(r)
else:
return self.context.servers[server].send_response(r)
else:
module.logger.error("Try to send a message to the unknown server: %s", server)
return False
def add_hook(store, hook):
store = convert_legacy_store(store)
module.REGISTERED_HOOKS.append((store, hook))
return self.context.hooks.add_hook(hook, store)
def del_hook(store, hook):
store = convert_legacy_store(store)
module.REGISTERED_HOOKS.remove((store, hook))
return self.context.hooks.del_hook(hook, store)
def add_event(evt, eid=None):
return self.context.add_event(evt, eid, module_src=module)
def add_event_eid(evt, eid):
return add_event(evt, eid)
def del_event(evt):
return self.context.del_event(evt, module_src=module)
# Set module common functions and datas
module.REGISTERED_HOOKS = list()
module.REGISTERED_EVENTS = list()
module.DEBUG = self.context.verbosity > 0
module.print_debug = prnt_dbg
module.send_response = send_response
module.add_hook = add_hook
module.del_hook = del_hook
module.add_event = add_event
module.add_event_eid = add_event_eid
module.del_event = del_event
if not hasattr(module, "NODATA"):
module.DATAS = self.context.datastore.load(module.__name__)
module.save = mod_save
else:
module.DATAS = None
module.save = lambda: False
module.CONF = self.config
# Load dependancies
if module.CONF is not None and module.CONF.hasNode("dependson"):
module.MODS = dict()
for depend in module.CONF.getNodes("dependson"):
for md in MODS:
if md.name == depend["name"]:
mod.MODS[md.name] = md
break
if depend["name"] not in module.MODS:
logger.error("In module `%s', module `%s' require by this "
"module but is not loaded.", module.__name__,
depend["name"])
return
# Add the module to the global modules list # Add the module to the global modules list
if self.context.add_module(module): if self.add_module(module):
# Launch the module
if hasattr(module, "load"):
module.load(self.context)
# Register hooks
register_hooks(module, self.context, self.prompt)
logger.info("Module '%s' successfully loaded.", module.__name__) logger.info("Module '%s' successfully loaded.", module.__name__)
else: else:
logger.error("An error occurs while importing `%s'.", module.__name__) logger.error("An error occurs while importing `%s'.", module.__name__)
raise ImportError("An error occurs while importing `%s'." raise ImportError("An error occurs while importing `%s'."
% module.__name__) % module.__name__)
return module return module
def convert_legacy_store(old):
if old == "cmd_hook" or old == "cmd_rgxp" or old == "cmd_default":
return "in_Command"
elif old == "ask_hook" or old == "ask_rgxp" or old == "ask_default":
return "in_DirectAsk"
elif old == "msg_hook" or old == "msg_rgxp" or old == "msg_default":
return "in_TextMessage"
elif old == "all_post":
return "post"
elif old == "all_pre":
return "pre"
else:
print("UNKNOWN store:", old)
return old
def register_hooks(module, context, prompt):
"""Register all available hooks
Arguments:
module -- the loaded Python module
context -- bot context
prompt -- the current Prompt instance
"""
# Register decorated functions
for s, h in nemubot.hooks.last_registered:
if s == "prompt_cmd":
prompt.add_cap_hook(h.name, h.call)
elif s == "prompt_list":
prompt.add_list_hook(h.name, h.call)
else:
s = convert_legacy_store(s)
module.REGISTERED_HOOKS.append((s, h))
context.hooks.add_hook(h, s)
nemubot.hooks.last_registered = []

125
nemubot/modulecontext.py Normal file
View file

@ -0,0 +1,125 @@
# Nemubot is a smart and modulable IM bot.
# Copyright (C) 2012-2015 Mercier Pierre-Olivier
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from nemubot.tools.xmlparser import module_state
def convert_legacy_store(old):
if old == "cmd_hook" or old == "cmd_rgxp" or old == "cmd_default":
return "in_Command"
elif old == "ask_hook" or old == "ask_rgxp" or old == "ask_default":
return "in_DirectAsk"
elif old == "msg_hook" or old == "msg_rgxp" or old == "msg_default":
return "in_TextMessage"
elif old == "all_post":
return "post"
elif old == "all_pre":
return "pre"
else:
print("UNKNOWN store:", old)
return old
class ModuleContext:
def __init__(self, context, module):
"""Initialize the module context
arguments:
context -- the bot context
module -- the module
"""
# Load module configuration if exists
if (context is not None and
module.__name__ in context.modules_configuration):
self.config = context.modules_configuration[module.__name__]
else:
self.config = None
self.hooks = list()
self.events = list()
self.debug = context.verbosity > 0 if context is not None else False
# Define some callbacks
if context is not None:
# Load module data
self.data = context.datastore.load(module.__name__)
def add_hook(store, hook):
store = convert_legacy_store(store)
self.hooks.append((store, hook))
return context.hooks.add_hook(hook, store)
def del_hook(store, hook):
store = convert_legacy_store(store)
self.hooks.remove((store, hook))
return context.hooks.del_hook(hook, store)
def add_event(evt, eid=None):
return context.add_event(evt, eid, module_src=module)
def del_event(evt):
return context.del_event(evt, module_src=module)
def send_response(server, res):
if server in context.servers:
r = res.next_response()
if r.server is not None:
return context.servers[r.server].send_response(r)
else:
return context.servers[server].send_response(r)
else:
module.logger.error("Try to send a message to the unknown server: %s", server)
return False
else:
self.data = module_state.ModuleState("nemubotstate")
def add_hook(store, hook):
store = convert_legacy_store(store)
self.hooks.append((store, hook))
def del_hook(store, hook):
store = convert_legacy_store(store)
self.hooks.remove((store, hook))
def add_event(evt, eid=None):
return context.add_event(evt, eid, module_src=module)
def del_event(evt):
return context.del_event(evt, module_src=module)
def send_response(server, res):
print(res)
def save():
context.datastore.save(module.__name__, self.data)
self.add_hook = add_hook
self.del_hook = del_hook
self.add_event = add_event
self.del_event = del_event
self.save = save
self.send_response = send_response
def unload(self):
"""Perform actions for unloading the module"""
# Remove registered hooks
for (s, h) in self.hooks:
self.del_hook(s, h)
# Remove registered events
for e in self.events:
self.del_event(e)
self.save()