Skip to content

Commit

Permalink
fix
Browse files Browse the repository at this point in the history
  • Loading branch information
Vladimir Mikhaylov committed Sep 17, 2020
1 parent ded42e8 commit d21e149
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 37 deletions.
68 changes: 57 additions & 11 deletions ntc/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,39 +36,52 @@ class SpecError(ConfigError):
pass


class NodeFrozenError(ConfigError):
pass


class CfgNode:
_SCHEMA_FROZEN = "_schema_frozen"
_FROZEN = "_frozen"
_LEAF_SPEC = "_leaf_spec"

def __init__(self, cfg_leaf: CfgLeaf = None):
# TODO: make access to attributes prettier
super().__setattr__("_schema_frozen", False)
super().__setattr__("_frozen", False)
super().__setattr__(CfgNode._SCHEMA_FROZEN, False)
super().__setattr__(CfgNode._FROZEN, False)
if cfg_leaf:
leaf_spec = _CfgLeafSpec.from_leaf(cfg_leaf)
else:
leaf_spec = None
super().__setattr__("_leaf_spec", leaf_spec)
super().__setattr__(CfgNode._LEAF_SPEC, leaf_spec)

def __setattr__(self, key: str, value: Any) -> None:
if self.is_frozen():
raise NodeFrozenError()
if hasattr(self, key):
self._set_existing_attr(key, value)
else:
self._set_new_attr(key, value)

def __getattribute__(self, item) -> Any:
def __getattribute__(self, item: str) -> Any:
attr = super().__getattribute__(item)
if isinstance(attr, CfgLeaf):
return attr.value
return attr

def __str__(self) -> str:
# TODO: handle custom class objects dump
attrs = self.to_dict()
return yaml.dump(attrs)
def __eq__(self, other: CfgNode) -> bool:
return self.to_dict() == other.to_dict()

# def __str__(self) -> str:
# # TODO: handle custom class objects dump
# attrs = self.to_dict()
# return yaml.dump(attrs)

def __len__(self):
return len(self.attrs())

def load(self, cfg_path: Path) -> CfgNode:
super().__setattr__("_schema_frozen", True)
self.freeze_schema()
import_module(cfg_path)
self.validate()
return self
Expand Down Expand Up @@ -101,17 +114,50 @@ def to_dict(self) -> Dict[str, Any]:
return attrs

def attrs(self) -> List[Tuple[str, Union[CfgNode, CfgLeaf]]]:
# TODO: try to make it property
attrs_list = []
for key in dir(self):
attr = super().__getattribute__(key)
if isinstance(attr, (CfgNode, CfgLeaf)):
attrs_list.append((key, attr))
return attrs_list

def freeze_schema(self):
super().__setattr__(CfgNode._SCHEMA_FROZEN, True)
for key, attr in self.attrs():
if isinstance(attr, CfgNode):
attr.freeze_schema()

def unfreeze_schema(self):
super().__setattr__(CfgNode._SCHEMA_FROZEN, False)
for key, attr in self.attrs():
if isinstance(attr, CfgNode):
attr.unfreeze_schema()

def freeze(self):
super().__setattr__(CfgNode._FROZEN, True)
for key, attr in self.attrs():
if isinstance(attr, CfgNode):
attr.freeze()

def unfreeze(self):
super().__setattr__(CfgNode._FROZEN, False)
for key, attr in self.attrs():
if isinstance(attr, CfgNode):
attr.unfreeze()

def is_schema_frozen(self):
# TODO: try to make it property
return super().__getattribute__(CfgNode._SCHEMA_FROZEN)

def is_frozen(self):
# TODO: try to make it property
return super().__getattribute__(CfgNode._FROZEN)

def _set_new_attr(self, key: str, value: Any) -> None:
if super().__getattribute__("_schema_frozen"):
if self.is_schema_frozen():
raise SchemaError(f"Trying to add new key {key}, but schema is frozen.")
leaf_spec = super().__getattribute__("_leaf_spec")
leaf_spec = super().__getattribute__(CfgNode._LEAF_SPEC)
if isinstance(value, CfgNode):
if leaf_spec:
raise SchemaError(f"Key {key} cannot contain nested nodes as leaf spec is defined for it.")
Expand Down
39 changes: 13 additions & 26 deletions tests/test_files.py
Original file line number Diff line number Diff line change
@@ -1,74 +1,61 @@
import sys
from pathlib import Path

import pytest

from ntc import CN, SchemaError, TypeMismatch, ValidationError
from ntc import SchemaError, TypeMismatch, ValidationError
from tests.data.base_cfg import cfg, BaseClass
from tests.data.subclass_class import SubClass

DATA_DIR = Path(__file__).parent / "data"


@pytest.fixture
def cfg() -> CN:
for key in tuple(sys.modules.keys()):
if key.startswith("tests.data"):
del sys.modules[key]
from tests.data.base_cfg import cfg

return cfg


def test_good(cfg: CN):
def test_good():
good = cfg.load(DATA_DIR / "good.py")
assert good.NAME == "Name"


def test_bad(cfg: CN):
def test_bad():
with pytest.raises(TypeMismatch):
cfg.load(DATA_DIR / "bad.py")


def test_missing(cfg: CN):
def test_missing():
with pytest.raises(ValidationError):
cfg.load(DATA_DIR / "missing.py")


def test_bad_attr(cfg: CN):
def test_bad_attr():
with pytest.raises(SchemaError):
cfg.load(DATA_DIR / "bad_attr.py")


def test_subclass(cfg: CN):
from tests.data.subclass_class import SubClass

def test_subclass():
subclass = cfg.load(DATA_DIR / "subclass.py")
assert isinstance(subclass.CLASS, SubClass)


def test_bad_class(cfg: CN):
def test_bad_class():
with pytest.raises(TypeMismatch):
cfg.load(DATA_DIR / "bad_class.py")


def test_list(cfg: CN):
def test_list():
lst = cfg.load(DATA_DIR / "list.py")
assert lst.LIST == [3, 2, 1]


def test_node(cfg: CN):
from tests.data.base_cfg import BaseClass

def test_node():
node = cfg.load(DATA_DIR / "node.py")
assert len(node.CLASSES) == 1
assert isinstance(node.CLASSES.ONE, BaseClass)


def test_bad_node(cfg: CN):
def test_bad_node():
with pytest.raises(SchemaError):
cfg.load(DATA_DIR / "bad_node.py")


def test_save(cfg: CN):
def test_save():
good = cfg.load(DATA_DIR / "good.py")
good.save(DATA_DIR / "good2.py")

Expand Down

0 comments on commit d21e149

Please sign in to comment.