diff --git a/matchy/files/config.py b/matchy/files/config.py deleted file mode 100644 index c157bc1..0000000 --- a/matchy/files/config.py +++ /dev/null @@ -1,152 +0,0 @@ -"""Very simple config loading library""" -from schema import Schema, Use, Optional -import matchy.files.ops as ops -import os -import logging -import json - -logger = logging.getLogger("config") -logger.setLevel(logging.INFO) - -# Envar takes precedent -_ENVAR = "MATCHY_CONFIG" -_FILE = ".matchy/config.json" - -# Warning: Changing any of the below needs proper thought to ensure backwards compatibility -_VERSION = 2 - - -class _Key(): - VERSION = "version" - - MATCH = "match" - - SCORE_FACTORS = "score_factors" - REPEAT_ROLE = "repeat_role" - REPEAT_MATCH = "repeat_match" - EXTRA_MEMBER = "extra_member" - UPPER_THRESHOLD = "upper_threshold" - - # Removed - _OWNERS = "owners" - _TOKEN = "token" - - -_SCHEMA = Schema( - { - # The current version - _Key.VERSION: Use(int), - - # Settings for the match algorithmn, see matching.py for explanations on usage - Optional(_Key.MATCH): { - Optional(_Key.SCORE_FACTORS): { - - Optional(_Key.REPEAT_ROLE): Use(int), - Optional(_Key.REPEAT_MATCH): Use(int), - Optional(_Key.EXTRA_MEMBER): Use(int), - Optional(_Key.UPPER_THRESHOLD): Use(int), - } - } - } -) - -_EMPTY_DICT = { - _Key.VERSION: _VERSION -} - - -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 - 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] - - -# Set of migration functions to apply -_MIGRATIONS = [ - _migrate_to_v1, - _migrate_to_v2 -] - - -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(): - def __init__(self, data: dict): - """Initialise and validate the config""" - _SCHEMA.validate(data) - self._dict = data - - @property - def token(self) -> str: - return self._dict["token"] - - @property - def score_factors(self) -> _ScoreFactors: - return _ScoreFactors(self._dict.get(_Key.SCORE_FACTORS, {})) - - -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 - - -def _load() -> _Config: - """ - Load the state from an envar or file - Apply any required migrations - """ - - # Try the envar first - envar = os.environ.get(_ENVAR) - if envar: - loaded = json.loads(envar) - logger.info("Config loaded from $%s", _ENVAR) - else: - # Otherwise try the file - if os.path.isfile(_FILE): - loaded = ops.load(_FILE) - logger.info("Config loaded from %s", _FILE) - else: - loaded = _EMPTY_DICT - logger.warning("No %s file found, using defaults", _FILE) - - _migrate(loaded) - return _Config(loaded) - - -# Core config for users to use -# Singleton as there should only be one, it's static, and global -Config = _load() diff --git a/matchy/files/state.py b/matchy/files/state.py index 438cf59..33c3122 100644 --- a/matchy/files/state.py +++ b/matchy/files/state.py @@ -191,7 +191,7 @@ class State(): before storing the dict back in the State """ @wraps(func) - def inner(self: State, *args, **kwargs): + def inner(self, *args, **kwargs): tmp = State(self._dict, self._file) func(tmp, *args, **kwargs) _SCHEMA.validate(tmp._dict) diff --git a/matchy/matching.py b/matchy/matching.py index 5d9b9b2..94d84f5 100644 --- a/matchy/matching.py +++ b/matchy/matching.py @@ -5,7 +5,6 @@ from datetime import datetime from typing import Protocol, runtime_checkable from matchy.files.state import State, ts_to_datetime import matchy.util as util -import matchy.files.config as config class _ScoreFactors(int): @@ -15,14 +14,14 @@ class _ScoreFactors(int): """ # Added for each role the matchee has that another group member has - REPEAT_ROLE = config.Config.score_factors.repeat_role or 2**2 + REPEAT_ROLE = 2**2 # Added for each member in the group that the matchee has already matched with - REPEAT_MATCH = config.Config.score_factors.repeat_match or 2**3 + REPEAT_MATCH = 2**3 # Added for each additional member over the set "per group" value - EXTRA_MEMBER = config.Config.score_factors.extra_member or 2**5 + EXTRA_MEMBER = 2**5 # Upper threshold, if the user scores higher than this they will not be placed in that group - UPPER_THRESHOLD = config.Config.score_factors.upper_threshold or 2**6 + UPPER_THRESHOLD = 2**6 logger = logging.getLogger("matching") diff --git a/tests/matching_test.py b/tests/matching_test.py index b99c33f..0a1dc05 100644 --- a/tests/matching_test.py +++ b/tests/matching_test.py @@ -403,7 +403,8 @@ def test_auth_scopes(): tmp_state.set_user_scope(id, state.AuthScope.MATCHER) assert tmp_state.get_user_has_scope(id, state.AuthScope.MATCHER) - tmp_state.validate() + # Validate the state by constucting a new one + _ = state.State(tmp_state._dict) def test_iterate_all_shifts(): diff --git a/tests/state_test.py b/tests/state_test.py index de7a185..f34578f 100644 --- a/tests/state_test.py +++ b/tests/state_test.py @@ -4,6 +4,7 @@ import matchy.files.state as state import tempfile import os +import matchy.files.ops as ops def test_basic_state(): @@ -18,10 +19,11 @@ def test_simple_load_reload(): with tempfile.TemporaryDirectory() as tmp: path = os.path.join(tmp, 'tmp.json') st = state.load_from_file(path) - st._save_to_file() + ops.save(st._file, st._dict) + ops.save(st._file, st._dict) st = state.load_from_file(path) - st._save_to_file() + ops.save(st._file, st._dict) st = state.load_from_file(path) @@ -30,13 +32,13 @@ def test_authscope(): with tempfile.TemporaryDirectory() as tmp: path = os.path.join(tmp, 'tmp.json') st = state.load_from_file(path) - st._save_to_file() + ops.save(st._file, st._dict) assert not st.get_user_has_scope(1, state.AuthScope.MATCHER) st = state.load_from_file(path) st.set_user_scope(1, state.AuthScope.MATCHER) - st._save_to_file() + ops.save(st._file, st._dict) st = state.load_from_file(path) assert st.get_user_has_scope(1, state.AuthScope.MATCHER) @@ -50,13 +52,13 @@ def test_channeljoin(): with tempfile.TemporaryDirectory() as tmp: path = os.path.join(tmp, 'tmp.json') st = state.load_from_file(path) - st._save_to_file() + ops.save(st._file, st._dict) assert not st.get_user_active_in_channel(1, "2") st = state.load_from_file(path) st.set_user_active_in_channel(1, "2", True) - st._save_to_file() + ops.save(st._file, st._dict) st = state.load_from_file(path) assert st.get_user_active_in_channel(1, "2")