from copy import deepcopy
from functools import wraps
from typing import Dict, Any, Union, List, Callable
from .base import BaseTree
from ...utils import init_magic
class _RawWrapped:
def __init__(self, value):
self.__value = value
@property
def value(self):
return self.__value
_RAW_NEEDED_TYPES = (dict,)
[docs]def raw(value) -> _RawWrapped:
"""
Overview:
Wrap raw value to init tree or set item, can be used for dict.
Can only performs when value is a dict object, otherwise just return the original value.
Arguments:
- value (:obj:): Original value.
Returns:
- wrapped (:obj:`_RawWrapped`): Wrapped value.
Example:
>>> t = Tree({
>>> 'a': raw({'a': 1, 'b': 2}),
>>> 'b': raw({'a': 3, 'b': 4}),
>>> 'x': {
>>> 'c': raw({'a': 5, 'b': 6}),
>>> 'd': raw({'a': 7, 'b': 8}),
>>> }
>>> })
>>>
>>> t['a'] # {'a': 1, 'b': 2}
>>> t['b'] # {'a': 3, 'b': 4}
>>> t['x']['c'] # {'a': 5, 'b': 6}
>>> t['x']['d'] # {'a': 7, 'b': 8}
>>>
>>> t['a'] = raw({'a': 9, 'b': 10})
>>> t['a'] # {'a': 9, 'b': 10}
>>> t['a'] = {'a': 9, 'b': 10}
>>> t['a'] # should be a Tree object when raw not used
"""
if not isinstance(value, _RawWrapped) and isinstance(value, _RAW_NEEDED_TYPES):
return _RawWrapped(value)
else:
return value
def _unraw(value):
if isinstance(value, _RawWrapped):
return value.value
else:
return value
def _to_tree_decorator(init_func):
@wraps(init_func)
def _new_init_func(data):
if isinstance(data, BaseTree):
_new_init_func(_tree_dump(data))
elif isinstance(data, dict):
init_func({
str(key): Tree(value) if isinstance(value, dict) else _unraw(value)
for key, value in data.items()
})
else:
raise TypeError(
"Dict value expected for dispatch value but {type} actually.".format(type=repr(type(data).__name__)))
return _new_init_func
def _copy_func(copy):
if hasattr(copy, '__call__'):
return copy
elif copy is None or isinstance(copy, (bool,)):
return (lambda x: deepcopy(x)) if copy else (lambda x: x)
elif isinstance(copy, (list, tuple)):
dumper, loader = copy[:2]
return lambda x: loader(dumper(x))
else:
dumper, loader = getattr(copy, 'dumps'), getattr(copy, 'loads')
return lambda x: loader(dumper(x))
def _tree_dump(tree: 'BaseTree', copy_value: Union[None, bool, Callable] = None):
copy_value = _copy_func(copy_value)
def _recursion(t):
if isinstance(t, BaseTree):
return {key: _recursion(value) for key, value in t.items()}
else:
return raw(copy_value(t))
return _recursion(tree.actual())
[docs]@init_magic(_to_tree_decorator)
class Tree(BaseTree):
"""
Overview:
Tree node data model, based on `BaseTree`.
"""
[docs] def __init__(self, data: Union[Dict[str, Union['Tree', Any]], 'Tree']):
"""
Overview:
Constructor of `Tree`, can be `dict`, `Tree`, `TreeView`.
When dict passed in, a new tree structure will be created once.
When `Tree` or `TreeView` passed in, a fully copy will be constructed in this object.
Arguments:
- data (:obj:`Union[Dict[str, Union['Tree', Any]], 'Tree']`): Any data can be parsed into `Tree`.
Example:
>>> t = Tree({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}}) # t is a new tree structure
>>> t2 = Tree(t) # t2 is a full copy of t1
>>> t3 = Tree({'a': t, 'b': t2}) # t3 is a tree with subtree of t and t2 (not copy)
"""
self.__dict = data
def __check_key_exist(self, key):
if key not in self.__dict.keys():
raise KeyError("Key {key} not found.".format(key=repr(key)))
[docs] def __getitem__(self, key):
self.__check_key_exist(key)
return self.__dict[key]
[docs] def __setitem__(self, key, value):
if isinstance(value, dict):
value = Tree(value)
self.__dict[key] = _unraw(value)
[docs] def __delitem__(self, key):
self.__check_key_exist(key)
del self.__dict[key]
[docs] def view(self, path: List[str]):
from .view import TreeView
return TreeView(self, path)
[docs] def clone(self, copy_value: Union[None, bool, Callable, Any] = None):
return self.__class__(_tree_dump(self, copy_value))
[docs] def items(self):
return self.__dict.items()
[docs] def keys(self):
return self.__dict.keys()
[docs] def values(self):
return self.__dict.values()
[docs] def actual(self):
return self