Skip to content

Commit

Permalink
REFACTOR-modin-project#2059: Simplify getter for parameter
Browse files Browse the repository at this point in the history
Signed-off-by: Vasilij Litvinov <[email protected]>
  • Loading branch information
vnlitvinov committed Oct 12, 2020
1 parent 30b75ec commit c238647
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 49 deletions.
2 changes: 1 addition & 1 deletion modin/config/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__":
Expand Down
6 changes: 3 additions & 3 deletions modin/config/envvars.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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
Expand Down
86 changes: 41 additions & 45 deletions modin/config/pubsub.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
"""
Expand All @@ -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)
Expand All @@ -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)

0 comments on commit c238647

Please sign in to comment.