From 50aced7c5d18a853c1439637a25515a6951fd4d2 Mon Sep 17 00:00:00 2001 From: Louis Chauvet Date: Mon, 20 Apr 2020 19:36:45 +0200 Subject: [PATCH] =?UTF-8?q?[bot-base]=20[mod-all]=20Le=20bot=20d=C3=A9marr?= =?UTF-8?q?e,=20mais=20il=20en=20fait=20pas=20plus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/Types.py | 90 --------------------- config/config_types/__init__.py | 28 +++++-- config/config_types/bool.py | 128 ++++++++++++++++++++++++++++++ config/config_types/color.py | 116 +++++++++++++++++++++++++++ config/config_types/float.py | 134 +++++++++++++++++++++++++++++--- config/config_types/int.py | 19 +++-- config/config_types/list.py | 126 ++++++++++++++++++++++++++---- config/config_types/str.py | 80 +++++++++++++++---- main.py | 8 +- modules/base/Base.py | 14 +++- modules/errors/__init__.py | 6 ++ 11 files changed, 599 insertions(+), 150 deletions(-) delete mode 100644 config/Types.py create mode 100644 config/config_types/bool.py create mode 100644 config/config_types/color.py diff --git a/config/Types.py b/config/Types.py deleted file mode 100644 index 203f1f1..0000000 --- a/config/Types.py +++ /dev/null @@ -1,90 +0,0 @@ -from __future__ import annotations - -from typing import Dict, Any, Optional - - -class Config: - name: Optional[str] - parent: Optional[Config] - config: Dict[Any, Any] - - def __init__(self, parent: Config = None, name: str = None, client: LBI = None): - """Create Config - - :param parent: Parent configuration - :param name: Configuration name - :type parent: Config - :type name: str""" - self.parent = parent - self.config = dict() - self.name = None - self.client = client - if self.parent: - self.name = name - self.client = self.parent.client - - def init(self, config): - """Load default configuration - - :param config: Default configuration - :type config: dict - :return: None""" - # Load data from config file before initialisation - self.load() - # Get data from parent - if self.parent is not None: - self.parent.config[self.name] = self.parent.config.get(self.name) if self.parent.config.get( - self.name) is not None else self.config - self.config = self.parent.config[self.name] - # Set config only if not already defined - for k, v in config.items(): - self.config[k] = self.config.get(k) if self.config.get(k) is not None else v - # Save new datas - self.save() - - def _save(self): - """Internal function for save - - Must be overridden by all type of config to handle saving""" - # Call parent save if necessary - if self.parent: - self.parent.save() - - def save(self): - """Public save function - - Do not override""" - self._save() - - def _load(self): - """Internal function for load - - Mus be overridden by all type of config to handle loading""" - # Load parent if necessary - if self.parent: - self.parent.load() - self.config = self.parent.config.get(self.name) - # Initialize parent if necessary - if self.config is None: - self.parent.config[self.name] = {} - self.config = {} - - def load(self): - """Public load function - - Do not override""" - self._load() - - def __getattr__(self, item): - return self.config.get(item) - - def __getitem__(self, item): - return self.config.get(item) - - def __setitem__(self, key, value): - if self.parent: - self.parent[self.name][key] = value - self.config = self.parent[self.name] - else: - self.config[key] = value - self.save() diff --git a/config/config_types/__init__.py b/config/config_types/__init__.py index 35cdd0d..309b02a 100644 --- a/config/config_types/__init__.py +++ b/config/config_types/__init__.py @@ -1,13 +1,24 @@ from typing import Type +import config.config_types.discord_types from config.config_types.base_type import BaseType +from config.config_types.bool import Bool +from config.config_types.color import Color from config.config_types.dict import Dict from config.config_types.float import Float from config.config_types.int import Int from config.config_types.list import List from config.config_types.str import Str -__all__ = ['factory', "BaseType", 'Dict', 'Float', 'Int', 'List', 'Str'] +__all__ = ['factory', "BaseType", 'Dict', 'Float', 'Int', 'List', 'Str', 'discord_types', 'Bool', 'Color'] + + +class Meta(type): + def __repr__(cls): + if hasattr(cls, '__class_repr__'): + return getattr(cls, '__class_repr__')() + else: + return super(Meta, cls).__repr__() def factory(type: Type[BaseType], *args, **kwargs): @@ -16,17 +27,22 @@ def factory(type: Type[BaseType], *args, **kwargs): :Basic usage: - >>> factory(Int) # doctest: +ELLIPSIS - - >>> factory(Int, min=0, max=10) # doctest: +ELLIPSIS - + >>> factory(Int) + + >>> factory(Int, min=0, max=10) + :param type: Type to create :return: New type """ - class Type(type): + class Type(type, metaclass=Meta): + def __init__(self): super().__init__(*args, **kwargs) + @classmethod + def __class_repr__(cls): + return f"" + return Type diff --git a/config/config_types/bool.py b/config/config_types/bool.py new file mode 100644 index 0000000..ca08029 --- /dev/null +++ b/config/config_types/bool.py @@ -0,0 +1,128 @@ +from typing import Optional + +from config.config_types.base_type import BaseType + + +class Bool(BaseType): + #: Current value of parameter + value: Optional[bool] + + def __init__(self) -> None: + """ + Base Bool type for config + + :Basic usage: + + >>> Bool() + + """ + self.value = None + + def check_value(self, value: bool) -> bool: + """ + Check if value is a correct bool + + Check if value is int, and if applicable, between ``min`` and ``max`` or in ``values`` + + :Basic usage: + + >>> my_bool = Bool() + >>> my_bool.check_value(0) + True + >>> my_bool.check_value(-2) + True + >>> my_bool.check_value(345) + True + >>> my_bool.check_value(0) + True + >>> my_bool.check_value(-2) + True + >>> my_bool.check_value(345) + True + >>> my_bool.check_value(10) + True + >>> my_bool.check_value(-2) + True + >>> my_bool.check_value(20) + True + >>> my_bool.check_value(2) + True + >>> my_bool.check_value(4) + True + >>> my_bool.check_value(5) + True + + :param value: value to check + :return: True if value is correct + """ + try: + bool(value) + except ValueError: + return False + return True + + def set(self, value: bool) -> None: + """ + Set value of parameter + + :Basic usage: + + >>> my_bool = Bool() + >>> my_bool.set(34) + + :param value: Value to set + :return: None + """ + if not self.check_value(value): + raise ValueError("Tentative de définir une valeur incompatible") + self.value = bool(value) + + def get(self) -> Optional[int]: + """ + Get value of parameter + + :Basic usage: + + >>> my_bool = Bool() + >>> my_bool.set(34) + >>> my_bool.get() + True + + :return: Value of parameter + """ + return self.value + + def to_save(self) -> int: + """ + Build a serializable object + + :Basic usage: + + >>> my_bool = Bool() + >>> my_bool.to_save() + >>> my_bool.set(34) + >>> my_bool.to_save() + True + + :return: Current value + """ + return self.value + + def load(self, value: bool) -> None: + """ + Load serialized value + + >>> my_bool = Bool() + >>> my_bool.load(True) + >>> my_bool.get() + True + + :param value: Value to load + :return: None + """ + if not self.check_value(value): + raise ValueError("Tentative de charger une donnée incompatible.") + self.value = value + + def __repr__(self): + return f'' diff --git a/config/config_types/color.py b/config/config_types/color.py new file mode 100644 index 0000000..fd098ee --- /dev/null +++ b/config/config_types/color.py @@ -0,0 +1,116 @@ +from typing import Optional + +from config.config_types.base_type import BaseType + + +class Color(BaseType): + #: Current value + value: Optional[int] + + def __init__(self) -> None: + """ + Base Color type for config + + :Basic usage: + + >>> Color() + + """ + self.value = None + + def check_value(self, value: int) -> bool: + """ + Check if value is a correct bool + + Check if value is int, and if applicable, between ``min`` and ``max`` or in ``values`` + + :Basic usage: + + >>> my_color = Color() + >>> my_color.check_value(0xFF00FF) + True + >>> my_color.check_value(-2) + False + >>> my_color.check_value(345) + True + >>> my_color.check_value(0xFFFFFF) + True + >>> my_color.check_value(0x000000) + True + >>> my_color.check_value(0x1000000) + False + + :param value: value to check + :return: True if value is correct + """ + try: + int(value) + except ValueError: + return False + return 0xFFFFFF >= value >= 0x000000 + + def set(self, value: int) -> None: + """ + Set value of parameter + + :Basic usage: + + >>> my_color = Color() + >>> my_color.set(34) + + :param value: Value to set + :return: None + """ + if not self.check_value(value): + raise ValueError("Tentative de définir une valeur incompatible") + self.value = int(value) + + def get(self) -> Optional[int]: + """ + Get value of parameter + + :Basic usage: + + >>> my_color = Color() + >>> my_color.set(34) + >>> my_color.get() + 34 + + :return: Value of parameter + """ + return self.value + + def to_save(self) -> int: + """ + Build a serializable object + + :Basic usage: + + >>> my_color = Color() + >>> my_color.to_save() + >>> my_color.set(34) + >>> my_color.to_save() + 34 + + :return: Current value + """ + return self.value + + def load(self, value: int) -> None: + """ + Load serialized value + + >>> my_color = Color() + >>> my_color.load(True) + >>> my_color.get() + True + + :param value: Value to load + :return: None + """ + if not self.check_value(value): + raise ValueError("Tentative de charger une donnée incompatible.") + self.value = value + + def __repr__(self): + return f'' diff --git a/config/config_types/float.py b/config/config_types/float.py index 0020bdd..eebfd65 100644 --- a/config/config_types/float.py +++ b/config/config_types/float.py @@ -1,18 +1,79 @@ +from typing import Optional + from config.config_types.base_type import BaseType class Float(BaseType): - def __init__(self, min_=None, max_=None): + #: Max value for parameter + max: Optional[float] + #: Min value for parameter + min: Optional[float] + #: Current value of parameter + value: Optional[float] + + def __init__(self, min: Optional[float] = None, max: Optional[float] = None) -> None: + """ + Base Float type for config + + :Basic usage: + + >>> Float() + + >>> Float(min=0) + + >>> Float(max=0) + + >>> Float(min=10, max=20) + + + :param min: Minimal value for parameter + :param max: Maximal value for parameter + """ self.value = None - self.min = min_ - self.max = max_ + self.min = min + self.max = max def check_value(self, value): + """ + Check if value is a correct int + + Check if value is int, and if applicable, between ``min`` and ``max`` or in ``values`` + + :Basic usage: + + >>> positive = Float(min=0) + >>> negative = Float(max=0) + >>> ten_to_twenty = Float(min=10, max=20) + >>> positive.check_value(0) + True + >>> positive.check_value(-2.143) + False + >>> positive.check_value(345.124) + True + >>> negative.check_value(0) + True + >>> negative.check_value(-2.1324) + True + >>> negative.check_value(345.124) + False + >>> ten_to_twenty.check_value(10) + True + >>> ten_to_twenty.check_value(-2.1234) + False + >>> ten_to_twenty.check_value(13.34) + True + >>> ten_to_twenty.check_value(20) + True + >>> ten_to_twenty.check_value(23.34) + False + + :param value: value to check + :return: True if value is correct + """ try: float(value) except ValueError: return False - # TODO: < ou <=? > ou >=? # Check min/max if self.min is not None and float(value) < self.min: return False @@ -21,18 +82,73 @@ class Float(BaseType): return True def set(self, value): - if self.check_value(value): - self.value = value - return - raise ValueError("Tentative de définir une valeur incompatible") + """ + Set value of parameter + + :Basic usage: + + >>> my_float = Float(min=0) + >>> my_float.set(34.324) + >>> my_float.set(-34.32412) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ValueError: ... + + :raise ValueError: if attempt to set invalid value + :param value: Value to set + :return: None + """ + if not self.check_value(value): + raise ValueError("Tentative de définir une valeur incompatible") + self.value = float(value) def get(self): + """ + Get value of parameter + + :Basic usage: + + >>> my_float = Float() + >>> my_float.set(-3/4) + >>> my_float.get() + -0.75 + + :return: Value of parameter + """ return self.value def to_save(self): + """ + Build a serializable object + + :Basic usage: + + >>> my_float = Float() + >>> my_float.to_save() + >>> my_float.set(3/4) + >>> my_float.to_save() + 0.75 + + :return: Current value + """ return self.value def load(self, value): - if self.check_value(value): + """ + Load serialized value + + >>> my_float = Float() + >>> my_float.load(3/4) + >>> my_float.get() + 0.75 + + :param value: Value to load + :return: None + """ + if not self.check_value(value): raise ValueError("Tentative de charger une donnée incompatible.") self.value = value + + def __repr__(self): + if self.min is not None or self.max is not None: + return f'' + return f'' diff --git a/config/config_types/int.py b/config/config_types/int.py index 7e18206..8357e85 100644 --- a/config/config_types/int.py +++ b/config/config_types/int.py @@ -21,15 +21,15 @@ class Int(BaseType): :Basic usage: >>> Int() - + >>> Int(min=0) - + >>> Int(max=0) - + >>> Int(min=10, max=20) - + >>> Int(values=[2, 3, 5, 7]) - + >>> Int(min=0, values=[3, 4, 5]) # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ValueError: ... @@ -91,7 +91,6 @@ class Int(BaseType): int(value) except ValueError: return False - # TODO: < ou <=? > ou >=? # Check min/max if self.min is not None and int(value) < self.min: return False @@ -120,7 +119,7 @@ class Int(BaseType): """ if not self.check_value(value): raise ValueError("Tentative de définir une valeur incompatible") - self.value = value + self.value = int(value) def get(self) -> Optional[int]: """ @@ -171,7 +170,7 @@ class Int(BaseType): def __repr__(self): if self.min is not None or self.max is not None: - return f'' + return f'' if self.values: - return f'' - return f'' + return f'' + return f'' diff --git a/config/config_types/list.py b/config/config_types/list.py index 0b6fa97..f9dde70 100644 --- a/config/config_types/list.py +++ b/config/config_types/list.py @@ -1,22 +1,77 @@ +import typing from typing import Type from config.config_types.base_type import BaseType class List(BaseType): + #: Current list of value + values: typing.List[BaseType] + #: Type of values type_: Type[BaseType] - def __init__(self, type_, max_len=None): + def __init__(self, type_: Type[BaseType]) -> None: + """ + Base List type for config + + :Basic usage: + + >>> from config.config_types import factory, Int, Float + >>> List(factory(Int)) + objects with values []> + >>> List(factory(Float)) + objects with values []> + + :param type_: Type of items + """ self.type_ = type_ - self.max_len = max_len - self.values = None + self.values = [] - def check_value(self, value): + def check_value(self, value: typing.List[typing.Any]) -> bool: + """ + Check if value is correct + + :Basic usage: + + >>> from config.config_types import factory, Int + >>> my_list = List(factory(Int)) + >>> my_list.check_value([34,]) + True + >>> my_list.check_value(345) + False + >>> my_list.check_value([345, 34, 23, 45, 34, 46, 35, 2345, 'rt']) + False + + :param value: Value to check + :return: True if value is correct + """ new_object = self.type_() - return new_object.check_value(value) + try: + for v in value: + if not new_object.check_value(v): + return False + except TypeError: + return False + return True - def set(self, value): - """Check and set value""" + def set(self, value: typing.List[typing.Any]) -> None: + """ + Set value of parameter + + :Basic usage: + + >>> from config.config_types import factory, Int + >>> my_list = List(factory(Int)) + >>> my_list.set(34) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ValueError: ... + >>> my_list.set([45,]) + + :param value: Value to set + :return: None + """ + if not self.check_value(value): + raise ValueError('Tentative de définir une valeur incompatible') new_liste = [] for v in value: new_element = self.type_() @@ -24,19 +79,60 @@ class List(BaseType): new_liste.append(new_element) self.values = new_liste - def get(self): - """Get value""" - if self.values is None: - raise ValueError("Config non initialisée") + def get(self) -> typing.List[typing.Any]: + """ + Get value of parameter + + :Basic usage: + + >>> from config.config_types import factory, Int + >>> my_list = List(factory(Int)) + >>> my_list.set([34, ]) + >>> my_list.get() + [34] + + :raise ValueError: If config is empty + :return: Value of parameter + """ return [v.get() for v in self.values] - def to_save(self): - """Build a serializable data to save""" + def to_save(self) -> typing.List[typing.Any]: + """ + Build a serializable object + + :Basic usage: + + >>> from config.config_types import factory, Int + >>> my_list = List(factory(Int)) + >>> my_list.to_save() + [] + >>> my_list.set([34, ]) + >>> my_list.to_save() + [34] + + :return: Current value + """ return [v.to_save() for v in self.values] - def load(self, value): - """Fill with value""" + def load(self, value: typing.List[typing.Any]) -> None: + """ + Load serialized value + + >>> from config.config_types import factory, Int + >>> my_list = List(factory(Int)) + >>> my_list.load([34,]) + >>> my_list.get() + [34] + + :param value: Value to load + :return: None + """ + if not self.check_value(value): + raise ValueError("Tentative de charger une donnée incompatible.") for v in value: new_object = self.type_() new_object.load(v) self.values.append(new_object) + + def __repr__(self): + return f'' diff --git a/config/config_types/str.py b/config/config_types/str.py index 5067482..25fd27c 100644 --- a/config/config_types/str.py +++ b/config/config_types/str.py @@ -2,34 +2,86 @@ from config.config_types.base_type import BaseType class Str(BaseType): - def __init__(self): - self.value = None + value: str - def check_value(self, value): - """Check if value is good""" + def __init__(self) -> None: + """ + Base Str type for config + + :Basic usage: + + >>> Str() + + """ + + self.value = "" + + def check_value(self, value: str) -> bool: + """ + Check if value is usable as str + + :Basic usage: + + >>> my_str = Str() + >>> my_str.check_value("toto") + True + >>> my_str.check_value(45) + True + + :param value: Value to test + :return: True if value is usable as str + """ try: str(value) except ValueError: return False return True - def set(self, value): - """Check and set value""" - if self.check_value(value): - self.value = value - return - raise ValueError("Tentative de définir une valeur incompatible") + def set(self, value: str) -> None: + """ + Set value of parameter - def get(self): - """Get value""" + :Basic usage: + + >>> my_str = Str() + >>> my_str.set("e") + >>> my_str.set(34) + + :raise ValueError: if attempt to set invalid value + :param value: Value to set + :return: None + """ + if not self.check_value(value): + raise ValueError("Tentative de définir une valeur incompatible") + self.value = str(value) + + def get(self) -> str: + """ + Get value of parameter + + :Basic usage: + + >>> my_str = Str() + >>> my_str.set(34) + >>> print(my_str.get()) + 34 + >>> my_str.set("Hey") + >>> print(my_str.get()) + Hey + + :return: Value of parameter + """ return self.value - def to_save(self): + def to_save(self) -> str: """Build a serializable data to save""" return self.value - def load(self, value): + def load(self, value: str) -> None: """Fill with value""" if not self.check_value(value): raise ValueError("Tentative de charger une donnée incompatible.") self.value = value + + def __repr__(self): + return f'' diff --git a/main.py b/main.py index 2a5b031..4dff686 100644 --- a/main.py +++ b/main.py @@ -246,9 +246,9 @@ class LBI(discord.Client): self.config = config self.config.register("modules", factory(config_types.List, factory(config_types.Str))) self.config.register("prefix", factory(config_types.Str)) - self.config.register("admin_roles", factory(config_types.List, factory(config_types.discord.Role, self))) - self.config.register("admin_users", factory(config_types.List, factory(config_types.discord.User, self))) - self.config.register("main_guild", factory(config_types.discord.Guild, self)) + self.config.register("admin_roles", factory(config_types.List, factory(config_types.discord_types.Role, self))) + self.config.register("admin_users", factory(config_types.List, factory(config_types.discord_types.User, self))) + self.config.register("main_guild", factory(config_types.discord_types.Guild, self)) self.config.register("locale", factory(config_types.Str)) self.config.set({ @@ -361,7 +361,7 @@ class LBI(discord.Client): super().dispatch(event, *args, **kwargs) # Dispatch to modules for module in self.modules.values(): - if module["initialized_class"].config.configured: + if module["initialized_class"].config["configured"]: module["initialized_class"].dispatch(event, *args, **kwargs) else: self.warning(f"Module {module['initialized_class'].name} is not configured.") diff --git a/modules/base/Base.py b/modules/base/Base.py index 80d7b82..7d0d84f 100644 --- a/modules/base/Base.py +++ b/modules/base/Base.py @@ -5,7 +5,8 @@ from typing import List, Union, Optional import discord -from config import Config +from config import Config, config_types +from config.config_types import factory from storage import Objects from utils import emojis @@ -30,8 +31,17 @@ class BaseClass: self.client = client self.objects = Objects(path=os.path.join("data", self.name.lower())) self.config = Config(path=os.path.join("data", self.name.lower(), "config.toml")) + self.config.register("help_active", factory(config_types.Bool)) + self.config.register("color", factory(config_types.Color)) + self.config.register("auth_everyone", factory(config_types.Bool)) + self.config.register("authorized_roles", + factory(config_types.List, factory(config_types.discord_types.Role, client))) + self.config.register("authorized_users", + factory(config_types.List, factory(config_types.discord_types.User, client))) + self.config.register("command_text", factory(config_types.Str)) + self.config.register("configured", factory(config_types.Bool)) self.config.set({"help_active": True, "color": 0x000000, "auth_everyone": False, "authorized_roles": [], - "authorized_users": [], "command_text": self.name.lower(), "configured": False}) + "authorized_users": [], "command_text": self.name.lower(), "configured": False}) async def send_help(self, channel): embed = discord.Embed( diff --git a/modules/errors/__init__.py b/modules/errors/__init__.py index 0f31c21..51b85a5 100644 --- a/modules/errors/__init__.py +++ b/modules/errors/__init__.py @@ -5,6 +5,8 @@ import traceback import discord from discord import Message +from config import config_types +from config.config_types import factory from modules.base import BaseClassPython @@ -21,6 +23,10 @@ class MainClass(BaseClassPython): def __init__(self, client): super().__init__(client) + self.config.register("dev_chan", + factory(config_types.List, factory(config_types.discord_types.Channel, client))) + self.config.register("memes", factory(config_types.List, factory(config_types.Str))) + self.config.register("icon", factory(config_types.Str)) self.config.set({"dev_chan": [], "memes": [""], "icon": ""}) self.errorsList = None