Add version system

Remove BaseClass verification
This commit is contained in:
fomys 2019-04-19 11:24:10 +02:00
parent 52bcb60e3a
commit 273d92ec5e
15 changed files with 275 additions and 35 deletions

View File

@ -4,8 +4,8 @@ verify_ssl = true
name = "pypi"
[packages]
discord = {extras = ["voice", "doc"], ref = "rewrite", git = "https://github.com/Rapptz/discord.py"}
packaging = "*"
discord-py = {extras = ["voice"],git = "https://github.com/Rapptz/discord.py",ref = "84c1eac62a775a37b03bd0971b221b0c50724630"}
[dev-packages]

28
errors.py Normal file
View File

@ -0,0 +1,28 @@
class LBIException(Exception):
"""
Base exception class for LBI
All other exceptions are subclasses
"""
pass
class ModuleException(LBIException):
"""
Base exception class for all module errors
"""
pass
class ModuleNotInstalled(ModuleException):
"""
Raised when a module is not found in module directory
"""
pass
class IncompatibleModule(ModuleException):
"""
Raised when a module is not compatible with bot version
"""
pass

166
main.py
View File

@ -6,11 +6,142 @@ import logging
import logging.config
import os
import traceback
from typing import Dict
import discord
from packaging.version import Version
from errors import IncompatibleModule
from modules.base import BaseClass
__version__ = "0.1.0"
class Module:
name: str
def __init__(self, name: str):
"""
Init module
:param name: Module name
:type name: str
"""
self.name = name
MODULES.update({self.name: self})
@property
def exists(self) -> bool:
"""
Check if module exists
:return: True if module is present in modules folders
:rtype: bool
"""
if not os.path.isdir(os.path.join("modules", self.name)):
return False
return True
@property
def complete(self) -> bool:
"""
Check if module is complete
:return: True if module is compatible
:rtype: Boolean
"""
# Check if version.json exists
if not os.path.exists(os.path.join("modules", self.name, "version.json")):
return False
with open(os.path.join("modules", self.name, "version.json")) as file:
versions = json.load(file)
if "version" not in versions.keys():
return False
if "dependencies" not in versions.keys():
return False
if "bot_version" not in versions.keys():
return False
return True
@property
def version(self) -> Version:
"""
Returns module version
:return: current module version
:rtype: Version
"""
with open(os.path.join("modules", self.name, "version.json")) as file:
versions = json.load(file)
return Version(versions["version"])
@property
def bot_version(self) -> dict:
"""
returns the min and max version of the bot that is compatible with the module
:return: Min and max version for bot
:rtype: dict
:raises IncompatibleModule: If bot_version is not properly formated (there must be min and max keys)
"""
with open(os.path.join("modules", self.name, "version.json")) as file:
versions = json.load(file)
try:
return {"min": Version(versions["bot_version"]["min"]),
"max": Version(versions["bot_version"]["max"])}
except KeyError:
raise IncompatibleModule(f"Module {self.name} is not compatible with bot (version.json does not "
f"contain bot_version.max or bot_version.min item)")
@property
def dependencies(self) -> dict:
"""
return list of dependencies version
:return: list of dependencies version
:rtype: dict
:raises IncompatibleModule: If bot_version is not properly formated (there must be min and max keys for each
dependencies)
"""
with open(os.path.join("modules", self.name, "version.json")) as file:
versions = json.load(file)
try:
deps = {}
for name, dep in versions["dependencies"].items():
dep_ver = {"min": Version(dep["min"]),
"max": Version(dep["max"])}
deps.update({name: dep_ver})
return deps
except KeyError:
raise IncompatibleModule(f"Module {self.name} is not compatible with bot (version.json does not "
f"contain dependencies.modulename.max or dependencies.modulename.min item)")
@property
def compatible(self) -> bool:
"""
Check if module is compatible with current installation
:return: True if all dependencies are okays
:rtype: bool
"""
# Check bot version
bot_ver = Version(__version__)
if bot_ver < self.bot_version["min"]:
return False
if bot_ver > self.bot_version["max"]:
return False
for name, dep in self.dependencies.items():
if name not in MODULES.keys():
Module(name)
if MODULES[name].version < dep["min"]:
return False
if MODULES[name].version > dep["max"]:
return False
return True
MODULES: Dict[str, Module] = {}
def setup_logging(default_path='config/log_config.json', default_level=logging.INFO, env_key='LBI_LOG_CONFIG'):
"""Setup logging configuration
@ -63,6 +194,11 @@ error = log_LBI.error
critical = log_LBI.critical
def load_modules_info():
for mod in os.listdir("modules"):
Module(mod)
class LBI(discord.Client):
base_path = "storage"
debug = log_LBI.debug
@ -113,11 +249,36 @@ class LBI(discord.Client):
@modules_edit
def load_module(self, module):
"""
Status codes:
- 0: Module loaded
- 1: Module not in modules folder
- 2: Module incomplete
- 3: Module incompatible
:param module: Module name
:return: Status code
"""
# Check module compatibility
load_modules_info()
if not MODULES.get(module):
return 1
if not MODULES[module].exists:
return 1
if not MODULES[module].complete:
return 2
if not MODULES[module].compatible:
return 3
deps = MODULES[module].dependencies
for dep in deps.keys():
if dep not in self.modules.keys():
self.load_module(dep)
try:
info("Start loading module {module}...".format(module=module))
imported = importlib.import_module('modules.' + module)
importlib.reload(imported)
if issubclass(imported.MainClass, BaseClass):
initialized_class = imported.MainClass(self)
self.modules.update({module: {"imported": imported, "initialized_class": initialized_class}})
info("Module {module} successfully imported.".format(module=module))
@ -125,11 +286,10 @@ class LBI(discord.Client):
if module not in self.config["modules"]:
self.config["modules"].append(module)
self.save_config()
else:
error("Module {module} isn't an instance of BaseClass.".format(module=module))
except AttributeError as e:
error("Module {module} doesn't have MainClass.".format(module=module))
return e
return 0
@modules_edit
def unload_module(self, module):

View File

@ -4,7 +4,6 @@ import pickle
import zipfile
import discord
from packaging import version
class Storage:
@ -51,9 +50,6 @@ class BaseClass:
color = 0x000000
command_text = None
authorized_roles = []
version = version.parse("0.0.1")
min_base_version = version.parse("0.0.1")
max_base_version = "0.0.1"
def __init__(self, client):
"""Initialize module class

10
modules/base/version.json Normal file
View File

@ -0,0 +1,10 @@
{
"version":"0.1.0",
"dependencies": {
},
"bot_version": {
"min": "0.1.0",
"max": "0.1.0"
}
}

View File

@ -1,8 +1,8 @@
import asyncio
import collections
import random
import traceback
import collections
import discord
from modules.base import BaseClass

View File

@ -0,0 +1,10 @@
{
"version":"0.1.0",
"dependencies": {
"base":"0.1.0"
},
"bot_version": {
"min": "0.1.0",
"max": "0.1.0"
}
}

View File

@ -4,7 +4,7 @@ from modules.base import BaseClass
class MainClass(BaseClass):
name = "Git"
name = "git"
super_users = [431043517217898496]

13
modules/git/version.json Normal file
View File

@ -0,0 +1,13 @@
{
"version": "0.1.0",
"dependencies": {
"base": {
"min": "0.1.0",
"max": "0.1.0"
}
},
"bot_version": {
"min": "0.1.0",
"max": "0.1.0"
}
}

View File

@ -1,8 +1,5 @@
import discord
from modules.base import BaseClass
import discord
from modules.base import BaseClass

10
modules/help/version.json Normal file
View File

@ -0,0 +1,10 @@
{
"version":"0.1.0",
"dependencies": {
"base":"==0.1.0"
},
"bot_version": {
"min": "0.1.0",
"max": "0.1.0"
}
}

View File

@ -1,25 +1,26 @@
import os
import discord
from aiohttp import ClientSession
from .base import BaseClass
from modules.base import BaseClass
# todo: tout réécrire
class MainClass(BaseClass):
name = "modules"
super_users = [431043517217898496]
command_text = "modules"
color = 0x000000
help_active = True
help = {
"description": "Manage bot modules.",
"commands": {
"`{prefix}{command} list`": "List of available modules.",
"`{prefix}{command} enable <module>`": "Enable module `<module>`.",
"`{prefix}{command} disable <module>`": "Disable module `<module>`.",
"`{prefix}{command} web_list`": "List all available modules from repository",
}
}
command_text = "modules"
def __init__(self, client):
super().__init__(client)
@ -89,13 +90,5 @@ class MainClass(BaseClass):
)
await message.channel.send(embed=embed)
async def com_download(self, message, args, kwargs):
if len(args) == 3:
async with ClientSession() as session:
async with session.get("http://127.0.0.1:5000/api/modules/"+args[1]+"/"+args[2]) as response:
if response.status != 200:
await message.channel.send(f"Erreur lors du téléchargement de {args[1]}")
return
with self.storage.open(os.path.join("modules", args[1]+".zip"), "wb") as file:
file.write(await response.read())
async def com_web_list(self, message, args, kwargs):
pass

View File

@ -0,0 +1,13 @@
{
"version": "0.1.0",
"dependencies": {
"base": {
"min": "0.1.0",
"max": "0.1.0"
}
},
"bot_version": {
"min": "0.1.0",
"max": "0.1.0"
}
}

View File

@ -0,0 +1,10 @@
{
"version":"0.1.0",
"dependencies": {
"base":"==0.1.0"
},
"bot_version": {
"min": "0.1.0",
"max": "0.1.0"
}
}