diff options
Diffstat (limited to 'src/ModuleManager.py')
| -rw-r--r-- | src/ModuleManager.py | 124 |
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] |
