aboutsummaryrefslogtreecommitdiff
path: root/src/ModuleManager.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/ModuleManager.py')
-rw-r--r--src/ModuleManager.py124
1 files changed, 67 insertions, 57 deletions
diff --git a/src/ModuleManager.py b/src/ModuleManager.py
index 10e4fb58..2ad086ea 100644
--- a/src/ModuleManager.py
+++ b/src/ModuleManager.py
@@ -1,5 +1,5 @@
-import enum, gc, glob, importlib, importlib.util, io, inspect, os, sys
-import typing, uuid
+import dataclasses, datetime, enum, gc, glob, importlib, importlib.util, io
+import inspect, os, sys, typing, uuid
from src import Config, EventManager, Exports, IRCBot, Logging, Timers, utils
class ModuleException(Exception):
@@ -18,6 +18,8 @@ class ModuleLoadException(ModuleException):
pass
class ModuleUnloadException(ModuleException):
pass
+class ModuleCannotUnloadException(ModuleException):
+ pass
class ModuleNotLoadedWarning(ModuleWarning):
pass
@@ -42,19 +44,15 @@ class TryReloadResult(object):
self.success = success
self.message = message
+@dataclasses.dataclass
class BaseModule(object):
- def __init__(self,
- bot: "IRCBot.Bot",
- events: EventManager.Events,
- exports: Exports.Exports,
- timers: Timers.Timers,
- log: Logging.Log):
- self.bot = bot
- self.events = events
- self.exports = exports
- self.timers = timers
- self.log = log
- self.on_load()
+ definition: "ModuleDefinition"
+ bot: "IRCBot.Bot"
+ events: EventManager.Events
+ exports: Exports.Exports
+ timers: Timers.Timers
+ log: Logging.Log
+
def on_load(self):
pass
def unload(self):
@@ -65,16 +63,23 @@ class BaseModule(object):
def on_resume(self):
pass
+ def data_directory(self, filename: str):
+ path, filename = os.path.split(filename)
+ path = os.path.join(self.bot.data_directory, "mod-data",
+ self.definition.name, path)
+
+ if not os.path.isdir(path):
+ os.makedirs(path)
+ return os.path.join(path, filename)
+
+@dataclasses.dataclass
class ModuleDefinition(object):
- def __init__(self,
- name: str,
- filename: str,
- type: ModuleType,
- hashflags: typing.List[typing.Tuple[str, typing.Optional[str]]]):
- self.name = name
- self.filename = filename
- self.type = type
- self.hashflags = hashflags
+ name: str
+ filename: str
+ type: ModuleType
+ hashflags: typing.List[typing.Tuple[str, typing.Optional[str]]]
+ is_core: bool
+
def get_dependencies(self):
dependencies = []
for key, value in self.hashflags:
@@ -82,18 +87,18 @@ class ModuleDefinition(object):
dependencies.append(value)
return sorted(dependencies)
+@dataclasses.dataclass
class LoadedModule(object):
- def __init__(self,
- name: str,
- title: str,
- module: BaseModule,
- context: str,
- import_name: str):
- self.name = name
- self.title = title
- self.module = module
- self.context = context
- self.import_name = import_name
+ name: str
+ title: str
+ module: BaseModule
+ context: str
+ import_name: str
+ is_core: bool
+ commit: typing.Optional[str] = None
+
+ loaded_at: datetime.datetime = dataclasses.field(
+ default_factory=lambda: utils.datetime.utcnow())
class ModuleManager(object):
def __init__(self,
@@ -114,25 +119,27 @@ class ModuleManager(object):
self.modules = {} # type: typing.Dict[str, LoadedModule]
- def _list_modules(self, directory: str
+ def _list_modules(self, directory: str, is_core: bool
) -> typing.Dict[str, ModuleDefinition]:
modules = []
for file_module in glob.glob(os.path.join(directory, "*.py")):
- modules.append(self.define_module(ModuleType.FILE, file_module))
+ modules.append(
+ self.define_module(ModuleType.FILE, file_module, is_core))
for directory_module in glob.glob(os.path.join(
directory, "*", "__init__.py")):
modules.append(self.define_module(ModuleType.DIRECTORY,
- directory_module))
+ directory_module, is_core))
+
return {definition.name: definition for definition in modules}
def list_modules(self, whitelist: typing.List[str],
blacklist: typing.List[str]) -> typing.Dict[str, ModuleDefinition]:
- core_modules = self._list_modules(self._core_modules)
+ core_modules = self._list_modules(self._core_modules, True)
extra_modules: typing.Dict[str, ModuleDefinition] = {}
for directory in self._extra_modules:
- for name, module in self._list_modules(directory).items():
+ for name, module in self._list_modules(directory, False).items():
if (not name in extra_modules and
(name in whitelist or
(not whitelist and not name in blacklist))):
@@ -143,7 +150,7 @@ class ModuleManager(object):
modules.update(core_modules)
return modules
- def define_module(self, type: ModuleType, filename: str
+ def define_module(self, type: ModuleType, filename: str, is_core: bool,
) -> ModuleDefinition:
if type == ModuleType.DIRECTORY:
name = os.path.dirname(filename)
@@ -152,14 +159,13 @@ class ModuleManager(object):
name = self._module_name(name)
return ModuleDefinition(name, filename, type,
- utils.parse.hashflags(filename))
+ utils.parse.hashflags(filename), is_core)
def find_module(self, name: str) -> ModuleDefinition:
type = ModuleType.FILE
paths = self._module_paths(name)
- path = None
- for possible_path in paths:
+ for is_core, possible_path in paths:
if os.path.isdir(possible_path):
type = ModuleType.DIRECTORY
possible_path = os.path.join(possible_path, "__init__.py")
@@ -167,20 +173,17 @@ class ModuleManager(object):
possible_path = "%s.py" % possible_path
if os.path.isfile(possible_path):
- path = possible_path
- break
+ return self.define_module(type, possible_path, is_core)
- if not path:
- raise ModuleNotFoundException(name)
-
- return self.define_module(type, path)
+ raise ModuleNotFoundException(name)
def _module_name(self, path: str) -> str:
return os.path.basename(path).rsplit(".py", 1)[0].lower()
- def _module_paths(self, name: str) -> typing.List[str]:
+ def _module_paths(self, name: str) -> typing.List[typing.Tuple[bool, str]]:
paths = []
- for directory in [self._core_modules]+self._extra_modules:
- paths.append(os.path.join(directory, name))
+
+ for i, directory in enumerate([self._core_modules]+self._extra_modules):
+ paths.append((i==0, os.path.join(directory, name)))
return paths
def _import_name(self, name: str, context: str) -> str:
return "%s_%s" % (name, context)
@@ -212,6 +215,10 @@ class ModuleManager(object):
def _load_module(self, bot: "IRCBot.Bot", definition: ModuleDefinition,
check_dependency: bool=True) -> LoadedModule:
+ if definition.name in self.modules:
+ raise ModuleNameCollisionException("Module name '%s' "
+ "attempted to be used twice" % definition.name)
+
if check_dependency:
dependencies = definition.get_dependencies()
for dependency in dependencies:
@@ -242,8 +249,9 @@ class ModuleManager(object):
context_events = self.events.new_context(context)
context_exports = self.exports.new_context(context)
context_timers = self.timers.new_context(context)
- module_object = module_object_pointer(bot, context_events,
+ module_object = module_object_pointer(definition, bot, context_events,
context_exports, context_timers, self.log)
+ module_object.on_load()
module_title = (getattr(module_object, "_name", None) or
definition.name.title())
@@ -264,12 +272,10 @@ class ModuleManager(object):
for key, value in magic.get_exports():
context_exports.add(key, value)
- if definition.name in self.modules:
- raise ModuleNameCollisionException("Module name '%s' "
- "attempted to be used twice" % definition.name)
+ branch, commit = utils.git_commit(bot.directory)
return LoadedModule(definition.name, module_title, module_object,
- context, import_name)
+ context, import_name, definition.is_core, commit=commit)
def load_module(self, bot: "IRCBot.Bot", definition: ModuleDefinition
) -> LoadedModule:
@@ -354,6 +360,10 @@ class ModuleManager(object):
if not name in self.modules:
raise ModuleNotLoadedException(name)
loaded_module = self.modules[name]
+
+ if loaded_module.is_core:
+ raise ModuleCannotUnloadException("cannot unload core modules")
+
self._unload_module(loaded_module)
del self.modules[loaded_module.name]