aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Joe Banks <[email protected]>2020-08-25 13:23:43 +0100
committerGravatar Joe Banks <[email protected]>2020-08-25 13:23:43 +0100
commit04b6ab3ba8db2c90fadebd114bb25d569ec8fc23 (patch)
tree41926ebc79cab28d6d71ebc879ca9c76aa567219
parentAdd database (diff)
Add config
-rw-r--r--metricity/config.py133
1 files changed, 133 insertions, 0 deletions
diff --git a/metricity/config.py b/metricity/config.py
new file mode 100644
index 0000000..8b043f5
--- /dev/null
+++ b/metricity/config.py
@@ -0,0 +1,133 @@
+"""Configuration reader for Metricity."""
+import logging
+from os import environ
+from pathlib import Path
+from typing import Any, Dict, List, Optional, Tuple, Union
+
+import toml
+from deepmerge import Merger
+from dotenv import load_dotenv
+
+load_dotenv()
+
+log = logging.getLogger(__name__)
+
+
+class MetricityConfigurationError(Exception):
+ """Exception signifying something has gone awry whilst parsing Metricity config."""
+
+
+def get_section(section: str) -> Dict[str, Any]:
+ """
+ Load the section config from config-default.toml and config.toml.
+
+ Use deepmerge.Merger to merge the configuration from config.toml
+ and override that of config-default.toml.
+ """
+ # Load default configuration
+ if not Path("config-default.toml").exists():
+ raise MetricityConfigurationError("config-default.toml is missing")
+
+ with open("config-default.toml", "r") as default_config_file:
+ default_config = toml.load(default_config_file)
+
+ # Load user configuration
+ user_config = {}
+
+ if Path("config.toml").exists():
+ with open("config.toml", "r") as default_config_file:
+ user_config = toml.load(default_config_file)
+
+ # Merge the configuration
+ merger = Merger(
+ [
+ (dict, "merge")
+ ],
+ ["override"],
+ ["override"]
+ )
+
+ conf = merger.merge(default_config, user_config)
+
+ # Check whether we are missing the requested section
+ if not conf.get(section):
+ raise MetricityConfigurationError(
+ f"Config is missing section '{section}'"
+ )
+
+ return conf[section]
+
+
+class ConfigSection(type):
+ """Metaclass for loading TOML configuration into the relevant class."""
+
+ def __new__(
+ cls: type,
+ name: str,
+ bases: Tuple[type],
+ dictionary: Dict[str, Any]
+ ) -> type:
+ """Use the section attr in the subclass to fill in the values from the TOML."""
+ config = get_section(dictionary["section"])
+
+ log.info(f"Loading configuration section {dictionary['section']}")
+
+ for key, value in config.items():
+ if isinstance(value, dict):
+ if env_var := value.get("env"):
+ if env_value := environ.get(env_var):
+ config[key] = env_value
+ else:
+ if not value.get("optional"):
+ raise MetricityConfigurationError(
+ f"Required config option '{key}' in"
+ f" '{dictionary['section']}' is missing, either set"
+ f" the environment variable {env_var} or override "
+ "it in your config.toml file"
+ )
+ else:
+ config[key] = None
+
+ dictionary.update(config)
+
+ config_section = super().__new__(cls, name, bases, dictionary)
+
+ return config_section
+
+
+class PythonConfig(metaclass=ConfigSection):
+ """Settings relating to the Python environment the application runs in."""
+
+ section = "python"
+
+ log_level: str
+ discord_log_level: str
+
+
+class BotConfig(metaclass=ConfigSection):
+ """Configuration for the Metricity bot."""
+
+ section = "bot"
+
+ command_prefix: Union[List[str], str]
+ token: str
+
+ guild_id: int
+ staff_role_id: int
+
+ staff_categories: List[int]
+ ignore_categories: List[int]
+
+
+class DatabaseConfig(metaclass=ConfigSection):
+ """Configuration about the database Metricity will use."""
+
+ section = "database"
+
+ uri: Optional[str]
+
+ host: Optional[str]
+ port: Optional[int]
+ database: Optional[str]
+ username: Optional[str]
+ password: Optional[str]