diff --git a/chanfig/config.py b/chanfig/config.py index d8ca6ea1..a471300d 100755 --- a/chanfig/config.py +++ b/chanfig/config.py @@ -209,6 +209,162 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setattr("parser", ConfigParser()) + def freeze(self, recursive: bool = True) -> Config: + r""" + Freeze `Config`. + + Args: + recursive: + + **Alias**: + + + `lock` + + Examples: + ```python + >>> c = Config(**{'i.d': 1013}) + >>> c.getattr('frozen') + False + >>> c.freeze(recursive=False).dict() + {'i': {'d': 1013}} + >>> c.getattr('frozen') + True + >>> c.i.getattr('frozen') + False + >>> c.freeze(recursive=True).dict() + {'i': {'d': 1013}} + >>> c.i.getattr('frozen') + True + + ``` + """ + + @wraps(self.freeze) + def freeze(config: Config) -> None: + config.setattr("frozen", True) + + if recursive: + self.apply(freeze) + else: + freeze(self) + return self + + lock = freeze + + def defrost(self, recursive: bool = True) -> Config: + r""" + Defrost `Config`. + + Args: + recursive: + + **Alias**: + + + `unlock` + + Examples: + ```python + >>> c = Config(**{'i.d': 1013}) + >>> c.getattr('frozen') + False + >>> c.freeze().dict() + {'i': {'d': 1013}} + >>> c.getattr('frozen') + True + >>> c.defrost(recursive=False).dict() + {'i': {'d': 1013}} + >>> c.getattr('frozen') + False + >>> c.i.getattr('frozen') + True + >>> c.defrost().dict() + {'i': {'d': 1013}} + >>> c.i.getattr('frozen') + False + + ``` + """ + + @wraps(self.defrost) + def defrost(config: Config) -> None: + config.setattr("frozen", False) + + if recursive: + self.apply(defrost) + else: + defrost(self) + return self + + unlock = defrost + + @contextmanager + def unlocked(self): + """ + Context manager which temporarily unlocks `Config`. + + Examples: + ```python + >>> c = Config() + >>> c.freeze().dict() + {} + >>> with c.unlocked(): + ... c['i.d'] = 1013 + >>> c.defrost().dict() + {'i': {'d': 1013}} + + ``` + """ + + was_frozen = self.getattr("frozen", False) + try: + self.defrost() + yield self + finally: + if was_frozen: + self.freeze() + + def parse( + self, + args: Optional[Iterable[str]] = None, + default_config: Optional[str] = None, + ) -> Config: + r""" + + Parse command line arguments with `ConfigParser`. + + See Also: [`chanfig.ConfigParser.parse`][chanfig.ConfigParser.parse] + + Examples: + ```python + >>> c = Config(a=0) + >>> c.dict() + {'a': 0} + >>> c.parse(['--a', '1', '--b', '2', '--c', '3']).dict() + {'a': 1, 'b': 2, 'c': 3} + + ``` + """ + + return self.getattr("parser", ConfigParser()).parse(args, self, default_config) + + parse_config = parse + + def add_argument(self, *args, **kwargs) -> None: + r""" + Add an argument to `ConfigParser`. + + Examples: + ```python + >>> c = Config(a=0) + >>> c.add_argument("--a", type=int, default=1) + >>> c.parse([]).dict() + {'a': 1} + + ``` + """ + + self.getattr("parser", ConfigParser()).add_argument(*args, **kwargs) + def get(self, name: str, default: Any = Null) -> Any: r""" Get value from `Config`. @@ -392,159 +548,3 @@ def pop(self, name: str, default: Any = Null) -> Any: """ return super().pop(name, default) - - def freeze(self, recursive: bool = True) -> Config: - r""" - Freeze `Config`. - - Args: - recursive: - - **Alias**: - - + `lock` - - Examples: - ```python - >>> c = Config(**{'i.d': 1013}) - >>> c.getattr('frozen') - False - >>> c.freeze(recursive=False).dict() - {'i': {'d': 1013}} - >>> c.getattr('frozen') - True - >>> c.i.getattr('frozen') - False - >>> c.freeze(recursive=True).dict() - {'i': {'d': 1013}} - >>> c.i.getattr('frozen') - True - - ``` - """ - - @wraps(self.freeze) - def freeze(config: Config) -> None: - config.setattr("frozen", True) - - if recursive: - self.apply(freeze) - else: - freeze(self) - return self - - lock = freeze - - def defrost(self, recursive: bool = True) -> Config: - r""" - Defrost `Config`. - - Args: - recursive: - - **Alias**: - - + `unlock` - - Examples: - ```python - >>> c = Config(**{'i.d': 1013}) - >>> c.getattr('frozen') - False - >>> c.freeze().dict() - {'i': {'d': 1013}} - >>> c.getattr('frozen') - True - >>> c.defrost(recursive=False).dict() - {'i': {'d': 1013}} - >>> c.getattr('frozen') - False - >>> c.i.getattr('frozen') - True - >>> c.defrost().dict() - {'i': {'d': 1013}} - >>> c.i.getattr('frozen') - False - - ``` - """ - - @wraps(self.defrost) - def defrost(config: Config) -> None: - config.setattr("frozen", False) - - if recursive: - self.apply(defrost) - else: - defrost(self) - return self - - unlock = defrost - - @contextmanager - def unlocked(self): - """ - Context manager which temporarily unlocks `Config`. - - Examples: - ```python - >>> c = Config() - >>> c.freeze().dict() - {} - >>> with c.unlocked(): - ... c['i.d'] = 1013 - >>> c.defrost().dict() - {'i': {'d': 1013}} - - ``` - """ - - was_frozen = self.getattr("frozen", False) - try: - self.defrost() - yield self - finally: - if was_frozen: - self.freeze() - - def parse( - self, - args: Optional[Iterable[str]] = None, - default_config: Optional[str] = None, - ) -> Config: - r""" - - Parse command line arguments with `ConfigParser`. - - See Also: [`chanfig.ConfigParser.parse`][chanfig.ConfigParser.parse] - - Examples: - ```python - >>> c = Config(a=0) - >>> c.dict() - {'a': 0} - >>> c.parse(['--a', '1', '--b', '2', '--c', '3']).dict() - {'a': 1, 'b': 2, 'c': 3} - - ``` - """ - - return self.getattr("parser", ConfigParser()).parse(args, self, default_config) - - parse_config = parse - - def add_argument(self, *args, **kwargs) -> None: - r""" - Add an argument to `ConfigParser`. - - Examples: - ```python - >>> c = Config(a=0) - >>> c.add_argument("--a", type=int, default=1) - >>> c.parse([]).dict() - {'a': 1} - - ``` - """ - - self.getattr("parser", ConfigParser()).add_argument(*args, **kwargs) diff --git a/chanfig/nested_dict.py b/chanfig/nested_dict.py index 477fb3fe..c4cc2cca 100644 --- a/chanfig/nested_dict.py +++ b/chanfig/nested_dict.py @@ -90,6 +90,113 @@ def _init(self, *args, **kwargs) -> None: for key, value in kwargs.items(): self.set(key, value, convert_mapping=True) + def all_keys(self) -> Iterator: + r""" + Get all keys of `NestedDict`. + + Returns: + (Iterator): + + Examples: + ```python + >>> d = NestedDict(**{'a': 1, 'b': {'c': 2, 'd': 3}}) + >>> list(d.all_keys()) + ['a', 'b.c', 'b.d'] + + ``` + """ + + delimiter = self.getattr("delimiter", ".") + + @wraps(self.all_keys) + def all_keys(self, prefix=""): + for key, value in self.items(): + if prefix: + key = prefix + delimiter + key + if isinstance(value, NestedDict): + yield from all_keys(value, key) + else: + yield key + + return all_keys(self) + + def all_values(self) -> Iterator: + r""" + Get all values of `NestedDict`. + + Returns: + (Iterator): + + Examples: + ```python + >>> d = NestedDict(**{'a': 1, 'b': {'c': 2, 'd': 3}}) + >>> list(d.all_values()) + [1, 2, 3] + + ``` + """ + + for value in self.values(): + if isinstance(value, NestedDict): + yield from value.all_values() + else: + yield value + + def all_items(self) -> Iterator[Tuple]: + r""" + Get all items of `NestedDict`. + + Returns: + (Iterator): + + Examples: + ```python + >>> d = NestedDict(**{'a': 1, 'b': {'c': 2, 'd': 3}}) + >>> list(d.all_items()) + [('a', 1), ('b.c', 2), ('b.d', 3)] + + ``` + """ + + delimiter = self.getattr("delimiter", ".") + + @wraps(self.all_items) + def all_items(self, prefix=""): + for key, value in self.items(): + if prefix: + key = prefix + delimiter + key + if isinstance(value, NestedDict): + yield from all_items(value, key) + else: + yield key, value + + return all_items(self) + + def apply(self, func: Callable) -> NestedDict: + r""" + Recursively apply a function to `NestedDict` and its children. + + Args: + func(Callable): + + Examples: + ```python + >>> d = NestedDict() + >>> d.a = NestedDict() + >>> def func(d): + ... d.t = 1 + >>> d.apply(func).dict() + {'a': {'t': 1}, 't': 1} + + ``` + """ + + for value in self.values(): + if isinstance(value, NestedDict): + value.apply(func) + func(self) + return self + def get(self, name: str, default: Any = Null) -> Any: r""" Get value from `NestedDict`. @@ -253,152 +360,33 @@ def delete(self, name: str) -> None: __delitem__ = delete __delattr__ = delete - def __contains__(self, name: str) -> bool: # type: ignore - delimiter = self.getattr("delimiter", ".") - while isinstance(name, str) and delimiter in name: - name, rest = name.split(delimiter, 1) - self, name = self[name], rest # pylint: disable=W0642 - return super().__contains__(name) - - def pop(self, name: str, default: Any = Null) -> Any: + def dict(self, cls: Callable = dict) -> Mapping: r""" - Pop value from `NestedDict`. + Convert `NestedDict` to other `Mapping`. Args: - name: - default: - - Returns: - value: If name does not exist, return `default`. - - Examples: - ```python - >>> d = NestedDict(**{"i.d": 1013, "f.n": "chang"}, default_factory=NestedDict) - >>> d.pop('i.d') - 1013 - >>> d.pop('i.d', True) - True - >>> d.pop('i.d') - Traceback (most recent call last): - KeyError: 'd' - - ``` - """ - - delimiter = self.getattr("delimiter", ".") - if delimiter in name: - name, rest = name.split(delimiter, 1) - if name not in self: - raise KeyError(f"{self.__class__.__name__} does not contain {name}") - return self[name].pop(rest, default) - return super().pop(name, default) if default is not Null else super().pop(name) - - def all_keys(self) -> Iterator: - r""" - Get all keys of `NestedDict`. - - Returns: - (Iterator): - - Examples: - ```python - >>> d = NestedDict(**{'a': 1, 'b': {'c': 2, 'd': 3}}) - >>> list(d.all_keys()) - ['a', 'b.c', 'b.d'] - - ``` - """ - - delimiter = self.getattr("delimiter", ".") - - @wraps(self.all_keys) - def all_keys(self, prefix=""): - for key, value in self.items(): - if prefix: - key = prefix + delimiter + key - if isinstance(value, NestedDict): - yield from all_keys(value, key) - else: - yield key - - return all_keys(self) - - def all_values(self) -> Iterator: - r""" - Get all values of `NestedDict`. - - Returns: - (Iterator): - - Examples: - ```python - >>> d = NestedDict(**{'a': 1, 'b': {'c': 2, 'd': 3}}) - >>> list(d.all_values()) - [1, 2, 3] - - ``` - """ - - for value in self.values(): - if isinstance(value, NestedDict): - yield from value.all_values() - else: - yield value - - def all_items(self) -> Iterator[Tuple]: - r""" - Get all items of `NestedDict`. - - Returns: - (Iterator): + cls: Target class to be converted to. Examples: ```python - >>> d = NestedDict(**{'a': 1, 'b': {'c': 2, 'd': 3}}) - >>> list(d.all_items()) - [('a', 1), ('b.c', 2), ('b.d', 3)] + >>> d = NestedDict(**{"f.n": "chang"}, default_factory=NestedDict) + >>> d['i.d'] = 1013 + >>> d.dict() + {'f': {'n': 'chang'}, 'i': {'d': 1013}} ``` """ - delimiter = self.getattr("delimiter", ".") - - @wraps(self.all_items) - def all_items(self, prefix=""): - for key, value in self.items(): - if prefix: - key = prefix + delimiter + key - if isinstance(value, NestedDict): - yield from all_items(value, key) - else: - yield key, value - - return all_items(self) - - def apply(self, func: Callable) -> NestedDict: - r""" - Recursively apply a function to `NestedDict` and its children. - - Args: - func(Callable): - - Examples: - ```python - >>> d = NestedDict() - >>> d.a = NestedDict() - >>> def func(d): - ... d.t = 1 - >>> d.apply(func).dict() - {'a': {'t': 1}, 't': 1} - - ``` - """ + # pylint: disable=C0103 - for value in self.values(): - if isinstance(value, NestedDict): - value.apply(func) - func(self) - return self + ret = cls() + for k, v in self.items(): + if isinstance(v, Variable): + v = v.value + if isinstance(v, FlatDict): + v = v.dict(cls) + ret[k] = v + return ret def difference( # pylint: disable=W0221, C0103 self, other: Union[Mapping, Iterable, PathStr], recursive: bool = True @@ -528,33 +516,45 @@ def to(self, cls: Union[str, TorchDevice, TorchDtype]) -> Any: return self.apply(lambda _: super().to(cls)) - def dict(self, cls: Callable = dict) -> Mapping: + def pop(self, name: str, default: Any = Null) -> Any: r""" - Convert `NestedDict` to other `Mapping`. + Pop value from `NestedDict`. Args: - cls: Target class to be converted to. + name: + default: + + Returns: + value: If name does not exist, return `default`. Examples: ```python - >>> d = NestedDict(**{"f.n": "chang"}, default_factory=NestedDict) - >>> d['i.d'] = 1013 - >>> d.dict() - {'f': {'n': 'chang'}, 'i': {'d': 1013}} + >>> d = NestedDict(**{"i.d": 1013, "f.n": "chang"}, default_factory=NestedDict) + >>> d.pop('i.d') + 1013 + >>> d.pop('i.d', True) + True + >>> d.pop('i.d') + Traceback (most recent call last): + KeyError: 'd' ``` """ - # pylint: disable=C0103 + delimiter = self.getattr("delimiter", ".") + if delimiter in name: + name, rest = name.split(delimiter, 1) + if name not in self: + raise KeyError(f"{self.__class__.__name__} does not contain {name}") + return self[name].pop(rest, default) + return super().pop(name, default) if default is not Null else super().pop(name) - ret = cls() - for k, v in self.items(): - if isinstance(v, Variable): - v = v.value - if isinstance(v, FlatDict): - v = v.dict(cls) - ret[k] = v - return ret + def __contains__(self, name: str) -> bool: # type: ignore + delimiter = self.getattr("delimiter", ".") + while isinstance(name, str) and delimiter in name: + name, rest = name.split(delimiter, 1) + self, name = self[name], rest # pylint: disable=W0642 + return super().__contains__(name) class DefaultDict(NestedDict): diff --git a/chanfig/variable.py b/chanfig/variable.py index 9d768c0f..8cd080d6 100644 --- a/chanfig/variable.py +++ b/chanfig/variable.py @@ -22,6 +22,14 @@ class Variable: r""" Mutable wrapper for immutable objects. + Notes: + `Variable` by default wrap the instance type to type of the wrapped object. + Therefore, `isinstance(Variable(1), int)` will return `True`. + + To temporarily disable this behavior, you can call context manager `with Variable.unwraped()`. + + To permanently disable this behavior, you can call `Variable.unwrap()`. + Examples: ```python >>> v = Variable(1) @@ -74,7 +82,7 @@ def __class__(self) -> type: @property def value(self) -> Any: r""" - Actual object stored in the `Variable`. + Fetch the object wrapped in `Variable`. """ return self.storage[0] @@ -82,7 +90,7 @@ def value(self) -> Any: @value.setter def value(self, value) -> None: r""" - Assign value to object stored in the `Variable`. + Assign value to the object wrapped in `Variable`. """ self.storage[0] = self._get_value(value) @@ -90,7 +98,7 @@ def value(self, value) -> None: @property def dtype(self) -> type: r""" - Data type of `Variable`. + Data type of the object wrapped in `Variable`. Examples: ```python @@ -99,7 +107,7 @@ def dtype(self) -> type: >>> id.dtype - >>> isinstance(id, int) + >>> issubclass(id.dtype, int) True ``` @@ -109,18 +117,147 @@ def dtype(self) -> type: def get(self) -> Any: r""" - alias of `value`. + Fetch the object wrapped in `Variable`. """ return self.value def set(self, value) -> None: r""" - alias of `value.setter`. + Assign value to the object wrapped in `Variable`. + + `Variable.set` is extremely useful when you want to change the value without changing the reference. + + In `FlatDict.set`, all assignments of `Variable` calls `Variable.set` Internally. """ self.value = value + def to(self, cls: Callable) -> Any: # pylint: disable=C0103 + r""" + Convert the object wrapped in `Variable` to target `cls`. + + Args: + cls: The type to convert to. + + Examples: + ```python + >>> id = Variable(1013) + >>> id.to(float) + 1013.0 + >>> id.to(str) + '1013.0' + + ``` + """ + + self.value = cls(self.value) + return self + + def int(self) -> int: + r""" + Convert the object wrapped in `Variable` to python `int`. + + Examples: + ```python + >>> id = Variable(1013.0) + >>> id.int() + 1013 + + ``` + """ + + return self.to(int) + + def float(self) -> float: + r""" + Convert the object wrapped in `Variable` to python `float`. + + Examples: + ```python + >>> id = Variable(1013) + >>> id.float() + 1013.0 + + ``` + """ + + return self.to(float) + + def str(self) -> str: + r""" + Convert the object wrapped in `Variable` to python `float`. + + Examples: + ```python + >>> id = Variable(1013) + >>> id.str() + '1013' + + ``` + """ + + return self.to(str) + + def wrap(self) -> None: + r""" + Wrap the type of `Variable`. + + Examples: + ```python + >>> id = Variable(1013) + >>> id.unwrap() + >>> isinstance(id, int) + False + >>> id.wrap() + >>> isinstance(id, int) + True + + ``` + """ + + self.wrap_type = True + + def unwrap(self) -> None: + r""" + Unwrap the type of `Variable`. + + Examples: + ```python + >>> id = Variable(1013) + >>> id.unwrap() + >>> isinstance(id, int) + False + + ``` + """ + + self.wrap_type = False + + @contextmanager + def unwraped(self): + r""" + Context manager which temporarily unwrap the `Variable`. + + Examples: + ```python + >>> id = Variable(1013) + >>> isinstance(id, int) + True + >>> with id.unwraped(): + ... isinstance(id, int) + False + + ``` + """ + + wrap_type = self.wrap_type + self.wrap_type = False + try: + yield self + finally: + self.wrap_type = wrap_type + @staticmethod def _get_value(obj) -> Any: if isinstance(obj, Variable): @@ -148,8 +285,8 @@ def __ge__(self, other) -> bool: def __gt__(self, other) -> bool: return self.value > self._get_value(other) - def __index__(self) -> int: # pylint: disable=E0601 - return int(self.value) + def __index__(self): + return self.value.__index__() def __invert__(self): return ~self.value @@ -258,128 +395,3 @@ def __format__(self, format_spec): def __repr__(self): return repr(self.value) - - def to(self, cls: Callable) -> Any: # pylint: disable=C0103 - r""" - Convert the value to a different type. - - Args: - cls: The type to convert to. - - Examples: - ```python - >>> id = Variable(1013) - >>> id.to(float) - 1013.0 - >>> id.to(str) - '1013.0' - - ``` - """ - - self.value = cls(self.value) - return self - - def int(self) -> int: - r""" - Convert the value to a python default int. - - Examples: - ```python - >>> id = Variable(1013.0) - >>> id.int() - 1013 - - ``` - """ - - return self.to(int) - - def float(self) -> float: - r""" - Convert the value to a python default float. - - Examples: - ```python - >>> id = Variable(1013) - >>> id.float() - 1013.0 - - ``` - """ - - return self.to(float) - - def str(self) -> str: - r""" - Convert the value to a python default float. - - Examples: - ```python - >>> id = Variable(1013) - >>> id.str() - '1013' - - ``` - """ - - return self.to(str) - - def wrap(self) -> None: - r""" - Wrap the type of `Variable`. - - Examples: - ```python - >>> id = Variable(1013) - >>> id.unwrap() - >>> isinstance(id, int) - False - >>> id.wrap() - >>> isinstance(id, int) - True - - ``` - """ - - self.wrap_type = True - - def unwrap(self) -> None: - r""" - Unwrap the type of `Variable`. - - Examples: - ```python - >>> id = Variable(1013) - >>> id.unwrap() - >>> isinstance(id, int) - False - - ``` - """ - - self.wrap_type = False - - @contextmanager - def unwraped(self): - r""" - Context manager which temporarily unwrap the `Variable`. - - Examples: - ```python - >>> id = Variable(1013) - >>> isinstance(id, int) - True - >>> with id.unwraped(): - ... isinstance(id, int) - False - - ``` - """ - - wrap_type = self.wrap_type - self.wrap_type = False - try: - yield self - finally: - self.wrap_type = wrap_type diff --git a/docs/config.md b/docs/config.md index d774a343..a2ef2a26 100644 --- a/docs/config.md +++ b/docs/config.md @@ -9,12 +9,3 @@ date: 2022-05-04 ::: chanfig.Config options: members: - - freeze - - defrost - - unlocked - - add_argument - - parse - # - get - # - set - # - delete - # - pop diff --git a/docs/flat_dict.md b/docs/flat_dict.md index 9cba65f1..85bf0895 100644 --- a/docs/flat_dict.md +++ b/docs/flat_dict.md @@ -7,35 +7,3 @@ date: 2022-05-04 # FlatDict ::: chanfig.FlatDict - options: - members: - - get - - set - - delete - - getattr - - setattr - - delattr - - hasattr - - dict - - update - - difference - - intersection - - to - - cpu - - gpu - - tpu - - copy - - deepcopy - - dump - - load - - json - - from_json - - jsons - - from_jsons - - yaml - - from_yaml - - yamls - - from_yamls - - open - - empty - - empty_like diff --git a/docs/nested_dict.md b/docs/nested_dict.md index 10356d2c..7a81ddca 100644 --- a/docs/nested_dict.md +++ b/docs/nested_dict.md @@ -9,15 +9,3 @@ date: 2022-05-04 ::: chanfig.NestedDict options: members: - - all_keys - - all_values - - all_items - - apply - # - get - # - set - # - delete - # - pop - # - dict - # - difference - # - intersection - # - to diff --git a/docs/variable.md b/docs/variable.md index cd1e1089..c49d483f 100644 --- a/docs/variable.md +++ b/docs/variable.md @@ -7,14 +7,3 @@ date: 2022-05-04 # Variable ::: chanfig.Variable - options: - members: - - value - - dtype - - unwrapped - - to - - int - - float - - str - - get - - set diff --git a/mkdocs.yml b/mkdocs.yml index 9e1ac56a..21645ea3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -158,7 +158,11 @@ plugins: # post_date_format: full # post_url_format: "{slug}" - git-committers - - mkdocstrings + - mkdocstrings: + handlers: + python: + rendering: + members_order: source - i18n: default_language: en languages: