1
0
Fork 0

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()
# 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
for path in args.files:

View File

@ -34,10 +34,14 @@ __author__ = 'nemunaire'
from nemubot.consumer import Consumer, EventConsumer, MessageConsumer
from nemubot import datastore
from nemubot.event import ModuleEvent
import nemubot.hooks
from nemubot.hooks.messagehook import MessageHook
from nemubot.hooks.manager import HooksManager
from nemubot.modulecontext import ModuleContext
from nemubot.networkbot import NetworkBot
context = ModuleContext(None, None)
logger = logging.getLogger("nemubot")
@ -228,7 +232,7 @@ class Bot(threading.Thread):
# Register the event in the source module
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
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._update_event_timer()
if module_src is not None:
module_src.REGISTERED_EVENTS.remove(id)
module_src.__nemubot_context__.events.remove(id)
return True
for evt in self.events:
@ -268,7 +272,7 @@ class Bot(threading.Thread):
self.events.remove(evt)
if module_src is not None:
module_src.REGISTERED_EVENTS.remove(evt.id)
module_src.__nemubot_context__.events.remove(evt.id)
return True
return False
@ -330,8 +334,7 @@ class Bot(threading.Thread):
if srv.id not in self.servers:
self.servers[srv.id] = srv
if (autoconnect and not hasattr(self, "noautoconnect") and
hasattr(self, "stop") and not self.stop):
if autoconnect and not hasattr(self, "noautoconnect"):
srv.open()
return True
@ -370,23 +373,36 @@ class Bot(threading.Thread):
module.logger.info(" ".join(args))
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
# Launch the module
if hasattr(module, "load"):
module.load(module.__nemubot_context__)
return True
def unload_module(self, name):
"""Unload a module"""
if name in self.modules:
self.modules[name].print_debug("Unloading module %s" % name)
self.modules[name].save()
self.modules[name].print("Unloading module %s" % name)
if hasattr(self.modules[name], "unload"):
self.modules[name].unload(self)
# Remove registered hooks
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)
self.modules[name].__nemubot_context__.unload()
# Remove from the dict
del self.modules[name]
logger.info("Module `%s' successfully unloaded.", name)

View File

@ -203,7 +203,7 @@ class EventConsumer:
# Or remove reference of this event
elif (hasattr(self.evt, "module_src") and
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):
def __init__(self, context, prompt):
self.context = context
self.prompt = prompt
def __init__(self, modules_paths, add_module):
self.modules_paths = modules_paths
self.add_module = add_module
def find_module(self, fullname, path=None):
# print ("looking for", fullname, "in", path)
# Search only for new nemubot modules (packages init)
if path is None:
for mpath in self.context.modules_paths:
for mpath in self.modules_paths:
# print ("looking for", fullname, "in", mpath)
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"))
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(mpath, fullname),
"__init__.py"))
@ -58,153 +58,19 @@ class ModuleFinder(Finder):
class ModuleLoader(SourceFileLoader):
def __init__(self, context, prompt, fullname, path):
self.context = context
self.prompt = prompt
if fullname in self.context.modules_configuration:
self.config = self.context.modules_configuration[fullname]
else:
self.config = None
def __init__(self, add_module, fullname, path):
self.add_module = add_module
SourceFileLoader.__init__(self, fullname, path)
def 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
if self.context.add_module(module):
# Launch the module
if hasattr(module, "load"):
module.load(self.context)
# Register hooks
register_hooks(module, self.context, self.prompt)
if self.add_module(module):
logger.info("Module '%s' successfully loaded.", module.__name__)
else:
logger.error("An error occurs while importing `%s'.", module.__name__)
raise ImportError("An error occurs while importing `%s'."
% module.__name__)
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()