diff --git a/modin/config/__main__.py b/modin/config/__main__.py index 07c56415918..999762da70f 100644 --- a/modin/config/__main__.py +++ b/modin/config/__main__.py @@ -9,7 +9,7 @@ def get_help(): and obj is not EnvironmentVariable and obj is not Publisher ): - print(f"{obj._get_help()}\nvalue={obj.value}") + print(f"{obj.get_help()}\nvalue={obj.get()}") if __name__ == "__main__": diff --git a/modin/config/envvars.py b/modin/config/envvars.py index e6535b1b379..946f0005689 100644 --- a/modin/config/envvars.py +++ b/modin/config/envvars.py @@ -15,7 +15,7 @@ from textwrap import dedent import warnings -from .pubsub import Publisher, _TYPE_HELP +from .pubsub import Publisher, _TYPE_PARAMS class EnvironmentVariable(Publisher, type=str): @@ -30,8 +30,8 @@ def _get_raw_from_config(cls) -> str: return os.environ[cls.varname] @classmethod - def _get_help(cls) -> str: - help = f"{cls.varname}: {dedent(cls.__doc__ or 'Unknown').strip()}\n\tProvide a {_TYPE_HELP[cls.type]}" + def get_help(cls) -> str: + help = f"{cls.varname}: {dedent(cls.__doc__ or 'Unknown').strip()}\n\tProvide a {_TYPE_PARAMS[cls.type].help}" if cls.choices: help += f" (valid examples are: {', '.join(cls.choices)})" return help diff --git a/modin/config/pubsub.py b/modin/config/pubsub.py index f394ca3eae4..fdafec3fc85 100644 --- a/modin/config/pubsub.py +++ b/modin/config/pubsub.py @@ -15,54 +15,34 @@ import typing -class Caster(typing.NamedTuple): +class TypeDescriptor(typing.NamedTuple): decode: typing.Callable[[str], object] - normalize: typing.Callable[[object], object] = lambda x: x - encode: typing.Callable[[object], str] = str + normalize: typing.Callable[[object], object] + help: str -_CASTERS = { - str: Caster( +_TYPE_PARAMS = { + str: TypeDescriptor( decode=lambda value: value.strip().title(), normalize=lambda value: value.strip().title(), + help="string", ), - bool: Caster( + bool: TypeDescriptor( decode=lambda value: value.strip().lower() in {"true", "yes", "1"}, normalize=bool, + help="boolean flag (any of 'true', 'yes' or '1' in case insensitive manner is considered positive)", + ), + int: TypeDescriptor( + decode=lambda value: int(value.strip()), normalize=int, help="integer value" ), - int: Caster(decode=lambda value: int(value.strip()), normalize=int), -} - -_TYPE_HELP = { - str: "string", - bool: "boolean flag (any of 'true', 'yes' or '1' in case insensitive manner is considered positive)", - int: "integer value", } - -class _ValueMeta(type): - """ - Metaclass is needed to make classmethod property - """ - - @property - def value(cls): - if cls._value is None: - # get the value from env - try: - raw = cls._get_raw_from_config() - except KeyError: - cls._value = cls.default - else: - cls._value = _CASTERS[cls.type].decode(raw) - return cls._value - - @value.setter - def value(cls, value): - cls._check_callbacks(cls._put_nocallback(value)) +# special marker to distinguish unset value from None value +# as someone may want to use None as a real value for a parameter +_UNSET = object() -class Publisher(object, metaclass=_ValueMeta): +class Publisher(object): """ Base class describing interface for configuration entities """ @@ -82,16 +62,16 @@ def _get_raw_from_config(cls) -> str: raise NotImplementedError() @classmethod - def _get_help(cls) -> str: + def get_help(cls) -> str: """ Generate user-presentable help for the option """ raise NotImplementedError() - def __init_subclass__(cls, type=None, **kw): - assert type in _CASTERS, f"Unsupported variable type: {type}" + def __init_subclass__(cls, type, **kw): + assert type in _TYPE_PARAMS, f"Unsupported variable type: {type}" cls.type = type - cls._value = None + cls._value = _UNSET cls._subs = [] cls._once = collections.defaultdict(list) super().__init_subclass__(**kw) @@ -101,25 +81,41 @@ def subscribe(cls, callback): cls._subs.append(callback) callback(cls) + @classmethod + def get(cls): + if cls._value is _UNSET: + # get the value from env + try: + raw = cls._get_raw_from_config() + except KeyError: + cls._value = cls.default + else: + cls._value = _TYPE_PARAMS[cls.type].decode(raw) + return cls._value + + @classmethod + def put(cls, value): + cls._check_callbacks(cls._put_nocallback(value)) + @classmethod def once(cls, onvalue, callback): - onvalue = _CASTERS[cls.type].normalize(onvalue) - if onvalue == cls.value: + onvalue = _TYPE_PARAMS[cls.type].normalize(onvalue) + if onvalue == cls.get(): callback(cls) else: cls._once[onvalue].append(callback) @classmethod def _put_nocallback(cls, value): - value = _CASTERS[cls.type].normalize(value.title) - oldvalue, cls.value = cls.value, value + value = _TYPE_PARAMS[cls.type].normalize(value.title) + oldvalue, cls._value = cls.get(), value return oldvalue @classmethod def _check_callbacks(cls, oldvalue): - if oldvalue == cls.value: + if oldvalue == cls.get(): return for callback in cls._subs: callback(cls) - for callback in cls._once.pop(cls.value, ()): + for callback in cls._once.pop(cls.get(), ()): callback(cls)