Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
- name: Mypy check
run: |
pip install mypy
mypy lighttree
mypy --install-types --non-interactive lighttree
- name: Test with pytest
run: |
pytest
13 changes: 11 additions & 2 deletions lighttree/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
from .interactive import TreeBasedObj
from .tree import Tree, Node, Key, KeyedNode, KeyedTree
from .tree import Tree, Key, KeyedNode, KeyedTree
from .node import Node, AutoIdNode

__all__ = ["Tree", "Node", "TreeBasedObj", "Key", "KeyedNode", "KeyedTree"]
__all__ = [
"Tree",
"Node",
"AutoIdNode",
"TreeBasedObj",
"Key",
"KeyedNode",
"KeyedTree",
]
39 changes: 15 additions & 24 deletions lighttree/implementations/json_tree.py
Original file line number Diff line number Diff line change
@@ -1,57 +1,48 @@
from typing import Dict, Optional, Any, Union, List
from lighttree import Node, Tree, Key
from lighttree import Tree, Key, AutoIdNode
from lighttree.node import NodeId
from lighttree.interactive import TreeBasedObj


class JsonTree(Tree):
def __init__(
self, d: Optional[Dict] = None, strict: bool = True, path_separator: str = "."
) -> None:
def __init__(self, d: Optional[Dict] = None, strict: bool = True) -> None:
"""
:param d:
:param strict: if False, will convert tuples into arrays, else raise error
:param path_separator: separator used to build path
"""
super(JsonTree, self).__init__(path_separator=path_separator)
super(JsonTree, self).__init__()
if d is not None:
self._fill(d, strict=strict, key=None)

@staticmethod
def _concat(a: Any, b: Any) -> str:
if not a and not b:
return ""
if not a:
return str(b)
return ".".join([str(a), str(b)])

def _fill(self, data: Any, key: Key, strict: bool, path: str = "") -> None:
def _fill(
self, data: Any, key: Optional[Key], strict: bool, path: Optional[List] = None
) -> None:
pid: Optional[NodeId]
path_: List = path or []
if self.is_empty():
pid = None
else:
pid = self.get_node_id_by_path(path=path)
pid = self.get_node_id_by_path(path=path_)

if isinstance(data, list) or not strict and isinstance(data, tuple):
k = self.insert_node(Node(keyed=False), parent_id=pid, key=key)
path = self._concat(path, k)
k = self.insert_node(AutoIdNode(keyed=False), parent_id=pid, key=key)
for el in data:
self._fill(el, strict=strict, path=path, key=None)
self._fill(el, strict=strict, path=path_ + ([k] if k else []), key=None)
return
if isinstance(data, dict):
k = self.insert_node(Node(keyed=True), key=key, parent_id=pid)
path = self._concat(path, k)
k = self.insert_node(AutoIdNode(keyed=True), key=key, parent_id=pid)
for sk, el in data.items():
self._fill(el, strict=strict, path=path, key=sk)
self._fill(el, strict=strict, path=path_ + ([k] if k else []), key=sk)
return
if isinstance(data, (str, int, float)):
self.insert_node(
Node(accept_children=False, repr_=str(data), data=data),
AutoIdNode(accept_children=False, repr_=str(data), data=data),
parent_id=pid,
key=key,
)
return
if data is None:
self.insert_node(Node(accept_children=False), parent_id=pid)
self.insert_node(AutoIdNode(accept_children=False), parent_id=pid)
return
raise TypeError("Unsupported type %s" % type(data))

Expand Down
83 changes: 46 additions & 37 deletions lighttree/node.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,63 @@
import uuid
from typing import Optional, Any, Tuple
from dataclasses import dataclass

NodeId = str


class Node(object):
def __init__(
self,
identifier: Optional[NodeId] = None,
auto_uuid: bool = True,
keyed: bool = True,
accept_children: bool = True,
repr_: Optional[str] = None,
data: Any = None,
) -> None:
"""
:param identifier: node identifier, must be unique per tree
"""
if identifier is None:
if not auto_uuid:
raise ValueError("Required identifier")
identifier = str(uuid.uuid4())
self.identifier = identifier
self.keyed = keyed
self.accept_children = accept_children
self.repr = repr_
self.data = data
@dataclass
class Node:

identifier: NodeId
keyed: bool = True
accept_children: bool = True
repr_: Optional[str] = None
data: Any = None

def line_repr(self, depth: int, **kwargs: Any) -> Tuple[str, str]:
"""Control how node is displayed in tree representation.
_
├── one end
│ └── two myEnd
└── three
First returned string is how node is represented on left, second string is how node is represented on right.

MyTree
├── one OneEnd
│ └── two twoEnd
└── three threeEnd
"""
if self.repr is not None:
return self.repr, ""
if self.repr_ is not None:
return self.repr_, ""
if not self.accept_children:
return str(self.data), ""
if hasattr(self.data, "__str__"):
return str(self.data), ""
return "", ""
if self.keyed:
return "{}", ""
return "[]", ""

def __eq__(self, other: Any) -> bool:
if not isinstance(other, self.__class__):
return False
return self.identifier == other.identifier

def __str__(self) -> str:
return "%s, id=%s" % (self.__class__.__name__, self.identifier)
class AutoIdNode(Node):
def __init__(
self,
identifier: Optional[NodeId] = None,
keyed: bool = True,
accept_children: bool = True,
repr_: Optional[str] = None,
data: Any = None,
):

self._auto_generated_id: bool
identifier_: NodeId

if identifier is None:
identifier_ = str(uuid.uuid4())
self._auto_generated_id = True
else:
identifier_ = identifier
self._auto_generated_id = False

def __repr__(self) -> str:
return self.__str__()
super(AutoIdNode, self).__init__(
identifier=identifier_,
keyed=keyed,
accept_children=accept_children,
repr_=repr_,
data=data,
)
Loading