[bot-base] Ajout de la gestion des dépendances
This commit is contained in:
parent
10e1b2333c
commit
74bf2ebec1
1
.gitignore
vendored
1
.gitignore
vendored
@ -74,3 +74,4 @@ data/*
|
||||
.env
|
||||
|
||||
_build
|
||||
/src/datas/
|
||||
|
2
Pipfile
2
Pipfile
@ -20,4 +20,4 @@ sphinx-rtd-theme = "*"
|
||||
[dev-packages]
|
||||
|
||||
[requires]
|
||||
python_version = "3.7"
|
||||
python_version = "3.8"
|
||||
|
27
Pipfile.lock
generated
27
Pipfile.lock
generated
@ -1,11 +1,11 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "83d48f54ad14a40d4bc3b81715c5ef5bb4e604b11a7da3e9ad4db62826ff37ae"
|
||||
"sha256": "d52eccc06eb777b155f756b9aadf63b98a045a979bd4882fa0ea63278245a435"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.7"
|
||||
"python_version": "3.8"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@ -177,14 +177,6 @@
|
||||
],
|
||||
"version": "==1.2.0"
|
||||
},
|
||||
"importlib-metadata": {
|
||||
"hashes": [
|
||||
"sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f",
|
||||
"sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e"
|
||||
],
|
||||
"markers": "python_version < '3.8'",
|
||||
"version": "==1.6.0"
|
||||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0",
|
||||
@ -477,14 +469,6 @@
|
||||
"index": "pypi",
|
||||
"version": "==3.0.2"
|
||||
},
|
||||
"sphinx-autodoc-typehints": {
|
||||
"hashes": [
|
||||
"sha256:27c9e6ef4f4451766ab8d08b2d8520933b97beb21c913f3df9ab2e59b56e6c6c",
|
||||
"sha256:a6b3180167479aca2c4d1ed3b5cb044a70a76cccd6b38662d39288ebd9f0dff0"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.10.3"
|
||||
},
|
||||
"sphinx-rtd-theme": {
|
||||
"hashes": [
|
||||
"sha256:00cf895504a7895ee433807c62094cf1e95f065843bf3acd17037c3e9a2becd4",
|
||||
@ -605,13 +589,6 @@
|
||||
"sha256:e15199cdb423316e15f108f51249e44eb156ae5dba232cb73be555324a1d49c2"
|
||||
],
|
||||
"version": "==1.4.2"
|
||||
},
|
||||
"zipp": {
|
||||
"hashes": [
|
||||
"sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b",
|
||||
"sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"
|
||||
],
|
||||
"version": "==3.1.0"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
|
@ -12,7 +12,7 @@ from packaging.specifiers import SpecifierSet
|
||||
|
||||
from config import Config, config_types
|
||||
from config.config_types import factory
|
||||
from errors import IncompatibleModuleError
|
||||
import errors
|
||||
|
||||
__version__ = "0.1.0"
|
||||
MINIMAL_INFOS = ["version", "bot_version"]
|
||||
@ -57,10 +57,21 @@ class BotBase(discord.Client):
|
||||
})
|
||||
|
||||
self.config.load()
|
||||
self.load_module("test_module")
|
||||
|
||||
async def on_ready(self):
|
||||
self.info("Bot ready.")
|
||||
try:
|
||||
self.load_modules()
|
||||
except errors.ModuleException as e:
|
||||
self.loop.stop()
|
||||
raise e
|
||||
|
||||
def load_modules(self):
|
||||
self.info("Load modules...")
|
||||
for module in self.config["modules"]:
|
||||
if module not in self.modules.keys():
|
||||
self.load_module(module)
|
||||
self.info("Modules loaded.")
|
||||
|
||||
def load_module(self, module: str) -> None:
|
||||
"""
|
||||
@ -70,31 +81,54 @@ class BotBase(discord.Client):
|
||||
:raise IncompatibleModuleError: If module is incompatible
|
||||
:param str module: module to load
|
||||
"""
|
||||
self.info(f"Attempt to load module {module}...")
|
||||
# Check if module exists
|
||||
if not os.path.isdir(os.path.join(self.config["modules_folder"], module)):
|
||||
raise ModuleNotFoundError(f"Module {module} not found in modules folder ({self.config['modules_folder']}.)")
|
||||
self.warning(f"Attempt to load unknown module {module}.")
|
||||
raise errors.ModuleNotFoundError(f"Module {module} not found in modules folder ({self.config['modules_folder']}.)")
|
||||
if not os.path.isfile(os.path.join(self.config["modules_folder"], module, "infos.toml")):
|
||||
raise IncompatibleModuleError(f"Module {module} is incompatible: no infos.toml found.")
|
||||
self.warning(f"Attempt to load incompatible module {module}: no infos.toml found")
|
||||
raise errors.IncompatibleModuleError(f"Module {module} is incompatible: no infos.toml found.")
|
||||
# Check infos.toml integrity
|
||||
with open(os.path.join(self.config["modules_folder"], module, "infos.toml")) as f:
|
||||
infos = toml.load(f)
|
||||
for key in MINIMAL_INFOS:
|
||||
if key not in infos.keys():
|
||||
raise IncompatibleModuleError(f"Missing information for module {module}: missing {key}.")
|
||||
self.warning(f"Attempt to load incompatible module {module}: missing information {key}")
|
||||
raise errors.IncompatibleModuleError(f"Missing information for module {module}: missing {key}.")
|
||||
# Check bot_version
|
||||
bot_version_specifier = SpecifierSet(infos["bot_version"])
|
||||
if __version__ not in bot_version_specifier:
|
||||
raise IncompatibleModuleError(f"Module {module} is not compatible with your current bot version "
|
||||
self.warning(f"Attempt to load incompatible module {module}: need bot version {infos['bot_version']} "
|
||||
f"and you have {__version__}")
|
||||
raise errors.IncompatibleModuleError(f"Module {module} is not compatible with your current bot version "
|
||||
f"(need {infos['bot_version']} and you have {__version__}).")
|
||||
# Check dependencies
|
||||
if infos.get("dependencies"):
|
||||
for dep, version in infos["dependencies"].items():
|
||||
if not dep in self.modules.keys():
|
||||
self.load_module(dep)
|
||||
dep_version_specifier = SpecifierSet(version)
|
||||
if self.modules[dep]["infos"]["version"] not in dep_version_specifier:
|
||||
self.warning(f"Attempt to load incompatible module {module}: (require {dep} ({version}) "
|
||||
f"and you have {dep} ({self.modules[dep]['infos']['version']})")
|
||||
raise errors.IncompatibleModuleError(f"Module {module} is not compatible with your current install "
|
||||
f"(require {dep} ({version}) and you have {dep} "
|
||||
f"({self.modules[dep]['infos']['version']})")
|
||||
|
||||
# Check if module is meta
|
||||
if infos.get("metamodule", False) == False:
|
||||
# Check if module have __main_class__
|
||||
imported = importlib.import_module(module)
|
||||
try:
|
||||
main_class = imported.__main_class__
|
||||
except AttributeError:
|
||||
raise IncompatibleModuleError(f"Module {module} does not provide __main_class__.")
|
||||
self.warning(f"Attempt to load incompatible module {module}: no __main_class__ found")
|
||||
raise errors.IncompatibleModuleError(f"Module {module} does not provide __main_class__.")
|
||||
# Check if __main_class__ is a class
|
||||
if not inspect.isclass(main_class):
|
||||
raise IncompatibleModuleError(f"Module {module} contains __main_class__ but it is not a type.")
|
||||
self.warning(f"Attempt to load incompatible module {module}: __main_class__ is not a type")
|
||||
raise errors.IncompatibleModuleError(f"Module {module} contains __main_class__ but it is not a type.")
|
||||
try:
|
||||
main_class = main_class(self)
|
||||
except TypeError:
|
||||
@ -104,11 +138,14 @@ class BotBase(discord.Client):
|
||||
try:
|
||||
dispatch = main_class.__dispatch__
|
||||
except AttributeError:
|
||||
raise IncompatibleModuleError(f"Module {module} mainclass ({main_class}) does not provide __dispatch__"
|
||||
self.warning(f"Attempt to load incompatible module {module}: __dispatch_ not found")
|
||||
raise errors.IncompatibleModuleError(f"Module {module} mainclass ({main_class}) does not provide __dispatch__"
|
||||
f" attribute)")
|
||||
# Check if __dispatch__ is function
|
||||
if not inspect.isfunction(imported.__main_class__.__dispatch__):
|
||||
raise IncompatibleModuleError(f"Module {module} mainclass ({main_class}) provides __dispatch__, but it is "
|
||||
self.warning(f"Attempt to load incompatible module {module}: __dispatch__ is not a function")
|
||||
raise errors.IncompatibleModuleError(
|
||||
f"Module {module} mainclass ({main_class}) provides __dispatch__, but it is "
|
||||
f"not a function ({dispatch}).")
|
||||
# Check if __dispatch__ can have variable positional and keyword aguments (to avoid future error on each event)
|
||||
sig = inspect.signature(dispatch)
|
||||
@ -119,23 +156,39 @@ class BotBase(discord.Client):
|
||||
elif p.kind == p.VAR_KEYWORD:
|
||||
kwargs_present = True
|
||||
if not args_present:
|
||||
raise IncompatibleModuleError(
|
||||
self.warning(f"Attempt to load incompatible module {module}: __dispatch__ doesn't accept variable "
|
||||
f"positional arguments")
|
||||
raise errors.IncompatibleModuleError(
|
||||
f"Module {module} mainclass ({main_class}) provide __dispatch__ function, but "
|
||||
f"this function doesn't accept variable positionnal arguments.")
|
||||
f"this function doesn't accept variable positional arguments.")
|
||||
if not kwargs_present:
|
||||
raise IncompatibleModuleError(
|
||||
self.warning(f"Attempt to load incompatible module {module}: __dispatch__ doesn't accept variable "
|
||||
f"keywords arguments.")
|
||||
raise errors.IncompatibleModuleError(
|
||||
f"Module {module} mainclass ({main_class}) provide __dispatch__ function, but "
|
||||
f"this function doesn't accept variable keywords arguments.")
|
||||
# Module is compatible!
|
||||
# Add module to loaded modules
|
||||
|
||||
self.info(f"Add modules {module} to current modules.")
|
||||
self.modules.update({
|
||||
module: {
|
||||
"infos": infos,
|
||||
"imported": imported,
|
||||
"initialized_class": main_class,
|
||||
"dispatch": dispatch,
|
||||
}
|
||||
})
|
||||
else: # Module is metamodule
|
||||
self.info(f"Add modules {module} to current modules")
|
||||
self.modules.update({
|
||||
module: {
|
||||
"infos": infos,
|
||||
"dispatch": lambda *x, **y: None
|
||||
}
|
||||
})
|
||||
if module not in self.config["modules"]:
|
||||
self.config.set({"modules": self.config["modules"] + [module]})
|
||||
self.config.save()
|
||||
|
||||
def dispatch(self, event, *args, **kwargs):
|
||||
"""Dispatch event"""
|
||||
@ -147,14 +200,14 @@ class BotBase(discord.Client):
|
||||
def info(self, *args, **kwargs):
|
||||
if self.log:
|
||||
self.log.info(*args, **kwargs)
|
||||
self.dispatch("on_log_info", *args, **kwargs)
|
||||
self.dispatch("log_info", *args, **kwargs)
|
||||
|
||||
def error(self, *args, **kwargs):
|
||||
if self.log:
|
||||
self.log.error(*args, **kwargs)
|
||||
self.dispatch("on_log_error", *args, **kwargs)
|
||||
self.dispatch("log_error", *args, **kwargs)
|
||||
|
||||
def warning(self, *args, **kwargs):
|
||||
if self.log:
|
||||
self.log.warning(*args, **kwargs)
|
||||
self.dispatch("on_log_warning", *args, **kwargs)
|
||||
self.dispatch("log_warning", *args, **kwargs)
|
||||
|
@ -6,7 +6,7 @@ class ModuleException(BotBaseException):
|
||||
pass
|
||||
|
||||
|
||||
class ModuleNotFound(ModuleException):
|
||||
class ModuleNotFoundError(ModuleException):
|
||||
pass
|
||||
|
||||
|
||||
|
@ -25,7 +25,7 @@ def setup_logging(default_path='data/log_config.json', default_level=logging.INF
|
||||
setup_logging()
|
||||
|
||||
if __name__ == "__main__":
|
||||
client = BotBase(max_messages=500000)
|
||||
client = BotBase(max_messages=500000, data_folder="datas", modules_folder=os.environ.get("LOCAL_MODULES", "modules"))
|
||||
|
||||
async def start_bot():
|
||||
await client.start(os.environ.get("DISCORD_TOKEN"))
|
||||
|
Loading…
Reference in New Issue
Block a user