Skip to content

Commit

Permalink
Merge branch 'feat/deep-clone' into 'master'
Browse files Browse the repository at this point in the history
feat: clone method

See merge request utilities/config!4
  • Loading branch information
Vladimir Mikhaylov committed Sep 15, 2020
2 parents 0dd2d1b + 93782c4 commit 6c478b1
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 26 deletions.
43 changes: 35 additions & 8 deletions nt_config/config.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
from typing import Any, Dict, Type
from __future__ import annotations

from typing import Any, Dict, List, Tuple, Type, Union

import yaml


class BaseError(Exception):
pass


class NodeReassignment(BaseError):
pass


class CfgNode:
def __setattr__(self, key: str, value: Any) -> None:
if hasattr(self, key):
Expand All @@ -19,19 +29,23 @@ def __str__(self) -> str:
attrs = self.to_dict()
return yaml.safe_dump(attrs)

def clone(self) -> CfgNode:
cloned_node = CfgNode()
for key, attr in self._attrs():
setattr(cloned_node, key, attr.clone())
return cloned_node

def to_dict(self) -> Dict[str, Any]:
attrs = {}
for key in dir(self):
attr = super().__getattribute__(key)
for key, attr in self._attrs():
if isinstance(attr, CfgNode):
attrs[key] = attr.to_dict()
elif isinstance(attr, CfgLeaf):
attrs[key] = attr.value

return attrs

def _set_new_attr(self, key: str, value: Any) -> None:
if isinstance(value, (CfgLeaf, CfgNode)):
if isinstance(value, (CfgNode, CfgLeaf)):
value_to_set = value
elif isinstance(value, type):
value_to_set = CfgLeaf(None, value)
Expand All @@ -42,15 +56,25 @@ def _set_new_attr(self, key: str, value: Any) -> None:
def _set_existing_attr(self, key: str, value: Any) -> None:
cur_attr = super().__getattribute__(key)
value_type = type(value)
if isinstance(cur_attr, CfgLeaf):
if isinstance(cur_attr, CfgNode):
raise NodeReassignment(f"Nested CfgNode {key} cannot be reassigned.")
elif isinstance(cur_attr, CfgLeaf):
cur_attr.value = value
else:
if not isinstance(cur_attr, value_type):
raise TypeError(
f"Current value of attribute {key} is of type {type(cur_attr)}, but new one is {value_type}"
f"Current value of attribute {key} is of type {type(cur_attr)}, but new one is of {value_type}."
)
super().__setattr__(key, value)

def _attrs(self) -> List[Tuple[str, Union[CfgNode, CfgLeaf]]]:
attrs = []
for key in dir(self):
attr = super().__getattribute__(key)
if isinstance(attr, (CfgNode, CfgLeaf)):
attrs.append((key, attr))
return attrs


class CfgLeaf:
def __init__(self, value: Any, type_: Type, required: bool = False):
Expand All @@ -68,5 +92,8 @@ def value(self, val) -> None:
if val is not None and not isinstance(val, self.type_):
raise TypeError(f"Value of type {self.type_} expected, but {type(self.type_)} found.")
if val is None and self._required:
raise ValueError("Required config leaf cannot be None")
raise ValueError("Required config leaf cannot be None.")
self._value = val

def clone(self) -> CfgLeaf:
return CfgLeaf(self.value, self.type_, self._required)
36 changes: 18 additions & 18 deletions tests/test_config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest

from nt_config import CfgLeaf, CfgNode
from nt_config.config import NodeReassignment


def test_define_with_value():
Expand Down Expand Up @@ -49,26 +50,11 @@ def test_nested():
assert cfg.FOO.BAR == 42


def test_nested_redefine():
def test_nested_reassign_fail():
cfg = CfgNode()
cfg.FOO = CfgNode()

cfg.FOO.BAR = 32
assert cfg.FOO.BAR == 32

foo_node = CfgNode()
foo_node.BAR = 42

cfg.FOO = foo_node
assert cfg.FOO.BAR == 42


def test_nested_redefine_fail():
cfg = CfgNode()
cfg.FOO = CfgNode()

with pytest.raises(TypeError):
cfg.FOO = 42
with pytest.raises(NodeReassignment):
cfg.FOO = CfgNode()


def test_str():
Expand All @@ -94,3 +80,17 @@ def test_required():

cfg.FOO = 42
assert cfg.FOO == 42


def test_clone():
cfg1 = CfgNode()
cfg1.FOO = 32
cfg1.BAR = CfgNode()
cfg1.BAR.BAZ = "baz1"

cfg2 = cfg1.clone()

cfg2.BAR.BAZ = "baz2"

assert str(cfg1) == "BAR:\n BAZ: baz1\nFOO: 32\n"
assert str(cfg2) == "BAR:\n BAZ: baz2\nFOO: 32\n"

0 comments on commit 6c478b1

Please sign in to comment.