2024-08-10 09:44:22 +01:00
|
|
|
"""Very simple config loading library"""
|
2024-08-12 23:00:49 +01:00
|
|
|
from schema import Schema, Use, Optional
|
2024-08-10 10:58:31 +01:00
|
|
|
import files
|
2024-08-11 17:53:37 +01:00
|
|
|
import os
|
|
|
|
import logging
|
2024-08-14 16:55:23 +01:00
|
|
|
import json
|
2024-08-11 17:53:37 +01:00
|
|
|
|
|
|
|
logger = logging.getLogger("config")
|
|
|
|
logger.setLevel(logging.INFO)
|
2024-08-10 09:44:22 +01:00
|
|
|
|
2024-08-14 16:55:23 +01:00
|
|
|
# Envar takes precedent
|
|
|
|
_ENVAR = "MATCHY_CONFIG"
|
|
|
|
_FILE = ".matchy/config.json"
|
2024-08-11 17:53:37 +01:00
|
|
|
|
|
|
|
# Warning: Changing any of the below needs proper thought to ensure backwards compatibility
|
2024-08-14 16:55:23 +01:00
|
|
|
_VERSION = 2
|
2024-08-11 17:53:37 +01:00
|
|
|
|
|
|
|
|
2024-08-11 22:07:43 +01:00
|
|
|
class _Key():
|
2024-08-11 17:53:37 +01:00
|
|
|
VERSION = "version"
|
|
|
|
|
2024-08-11 22:07:43 +01:00
|
|
|
MATCH = "match"
|
|
|
|
|
|
|
|
SCORE_FACTORS = "score_factors"
|
|
|
|
REPEAT_ROLE = "repeat_role"
|
|
|
|
REPEAT_MATCH = "repeat_match"
|
|
|
|
EXTRA_MEMBER = "extra_member"
|
|
|
|
UPPER_THRESHOLD = "upper_threshold"
|
|
|
|
|
2024-08-11 17:53:37 +01:00
|
|
|
# Removed
|
2024-08-11 22:31:20 +01:00
|
|
|
_OWNERS = "owners"
|
2024-08-14 16:55:23 +01:00
|
|
|
_TOKEN = "token"
|
2024-08-11 17:53:37 +01:00
|
|
|
|
|
|
|
|
2024-08-10 15:12:14 +01:00
|
|
|
_SCHEMA = Schema(
|
|
|
|
{
|
2024-08-11 17:53:37 +01:00
|
|
|
# The current version
|
2024-08-12 23:00:49 +01:00
|
|
|
_Key.VERSION: Use(int),
|
2024-08-10 15:12:14 +01:00
|
|
|
|
2024-08-11 22:07:43 +01:00
|
|
|
# Settings for the match algorithmn, see matching.py for explanations on usage
|
|
|
|
Optional(_Key.MATCH): {
|
|
|
|
Optional(_Key.SCORE_FACTORS): {
|
|
|
|
|
2024-08-12 23:00:49 +01:00
|
|
|
Optional(_Key.REPEAT_ROLE): Use(int),
|
|
|
|
Optional(_Key.REPEAT_MATCH): Use(int),
|
|
|
|
Optional(_Key.EXTRA_MEMBER): Use(int),
|
|
|
|
Optional(_Key.UPPER_THRESHOLD): Use(int),
|
2024-08-11 22:07:43 +01:00
|
|
|
}
|
|
|
|
}
|
2024-08-10 15:12:14 +01:00
|
|
|
}
|
|
|
|
)
|
2024-08-10 09:44:22 +01:00
|
|
|
|
2024-08-11 22:42:23 +01:00
|
|
|
_EMPTY_DICT = {
|
|
|
|
_Key.VERSION: _VERSION
|
|
|
|
}
|
|
|
|
|
2024-08-10 09:44:22 +01:00
|
|
|
|
2024-08-11 17:53:37 +01:00
|
|
|
def _migrate_to_v1(d: dict):
|
|
|
|
# Owners moved to History in v1
|
|
|
|
# Note: owners will be required to be re-added to the state.json
|
2024-08-14 16:55:23 +01:00
|
|
|
if _Key._OWNERS in d:
|
|
|
|
owners = d.pop(_Key._OWNERS)
|
|
|
|
logger.warning(
|
|
|
|
"Migration removed owners from config, these must be re-added to the state.json")
|
|
|
|
logger.warning("Owners: %s", owners)
|
|
|
|
|
|
|
|
|
|
|
|
def _migrate_to_v2(d: dict):
|
|
|
|
# Token moved to the environment
|
|
|
|
if _Key._TOKEN in d:
|
|
|
|
del d[_Key._TOKEN]
|
2024-08-11 17:53:37 +01:00
|
|
|
|
|
|
|
|
|
|
|
# Set of migration functions to apply
|
|
|
|
_MIGRATIONS = [
|
2024-08-14 16:55:23 +01:00
|
|
|
_migrate_to_v1,
|
|
|
|
_migrate_to_v2
|
2024-08-11 17:53:37 +01:00
|
|
|
]
|
|
|
|
|
|
|
|
|
2024-08-11 22:07:43 +01:00
|
|
|
class _ScoreFactors():
|
|
|
|
def __init__(self, data: dict):
|
|
|
|
"""Initialise and validate the config"""
|
|
|
|
self._dict = data
|
|
|
|
|
|
|
|
@property
|
|
|
|
def repeat_role(self) -> int:
|
|
|
|
return self._dict.get(_Key.REPEAT_ROLE, None)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def repeat_match(self) -> int:
|
|
|
|
return self._dict.get(_Key.REPEAT_MATCH, None)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def extra_member(self) -> int:
|
|
|
|
return self._dict.get(_Key.EXTRA_MEMBER, None)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def upper_threshold(self) -> int:
|
|
|
|
return self._dict.get(_Key.UPPER_THRESHOLD, None)
|
|
|
|
|
|
|
|
|
|
|
|
class _Config():
|
2024-08-10 10:45:44 +01:00
|
|
|
def __init__(self, data: dict):
|
2024-08-10 15:12:14 +01:00
|
|
|
"""Initialise and validate the config"""
|
|
|
|
_SCHEMA.validate(data)
|
2024-08-11 17:53:37 +01:00
|
|
|
self._dict = data
|
2024-08-10 10:45:44 +01:00
|
|
|
|
|
|
|
@property
|
|
|
|
def token(self) -> str:
|
2024-08-11 17:53:37 +01:00
|
|
|
return self._dict["token"]
|
2024-08-10 10:45:44 +01:00
|
|
|
|
2024-08-11 22:07:43 +01:00
|
|
|
@property
|
|
|
|
def score_factors(self) -> _ScoreFactors:
|
2024-08-14 16:55:23 +01:00
|
|
|
return _ScoreFactors(self._dict.get(_Key.SCORE_FACTORS, {}))
|
2024-08-11 22:07:43 +01:00
|
|
|
|
2024-08-10 15:12:14 +01:00
|
|
|
|
2024-08-11 17:53:37 +01:00
|
|
|
def _migrate(dict: dict):
|
|
|
|
"""Migrate a dict through versions"""
|
|
|
|
version = dict.get("version", 0)
|
|
|
|
for i in range(version, _VERSION):
|
|
|
|
_MIGRATIONS[i](dict)
|
|
|
|
dict["version"] = _VERSION
|
2024-08-10 10:45:44 +01:00
|
|
|
|
|
|
|
|
2024-08-14 16:55:23 +01:00
|
|
|
def _load() -> _Config:
|
2024-08-11 17:53:37 +01:00
|
|
|
"""
|
2024-08-14 16:55:23 +01:00
|
|
|
Load the state from an envar or file
|
2024-08-11 17:53:37 +01:00
|
|
|
Apply any required migrations
|
|
|
|
"""
|
2024-08-14 16:55:23 +01:00
|
|
|
|
|
|
|
# Try the envar first
|
|
|
|
envar = os.environ.get(_ENVAR)
|
|
|
|
if envar:
|
|
|
|
loaded = json.loads(envar)
|
|
|
|
logger.info("Config loaded from $%s", _ENVAR)
|
2024-08-11 22:35:07 +01:00
|
|
|
else:
|
2024-08-14 16:55:23 +01:00
|
|
|
# Otherwise try the file
|
|
|
|
if os.path.isfile(_FILE):
|
|
|
|
loaded = files.load(_FILE)
|
|
|
|
logger.info("Config loaded from %s", _FILE)
|
|
|
|
else:
|
|
|
|
loaded = _EMPTY_DICT
|
|
|
|
logger.warning("No %s file found, using defaults", _FILE)
|
|
|
|
|
|
|
|
_migrate(loaded)
|
2024-08-11 22:07:43 +01:00
|
|
|
return _Config(loaded)
|
|
|
|
|
|
|
|
|
|
|
|
# Core config for users to use
|
2024-08-14 16:55:23 +01:00
|
|
|
# Singleton as there should only be one, it's static, and global
|
|
|
|
Config = _load()
|