Reduce importance of importer; new tiny context for each module, instead of having entire bot context
This commit is contained in:
parent
bafc14bd79
commit
1f5364c387
5 changed files with 165 additions and 158 deletions
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
125
nemubot/modulecontext.py
Normal 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()
|
Loading…
Add table
Add a link
Reference in a new issue