diff --git a/Makefile b/Makefile index e8a37ca..15675aa 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ black: black lighttree tests setup.py mypy: - mypy lighttree + mypy lighttree --disallow-untyped-defs check: mypy black lint diff --git a/lighttree/__init__.py b/lighttree/__init__.py index 2077e58..578dd7c 100644 --- a/lighttree/__init__.py +++ b/lighttree/__init__.py @@ -1,4 +1,4 @@ from .interactive import TreeBasedObj -from .tree import Tree, Node +from .tree import Tree, Node, Key, KeyedNode, KeyedTree -__all__ = ["Tree", "Node", "TreeBasedObj"] +__all__ = ["Tree", "Node", "TreeBasedObj", "Key", "KeyedNode", "KeyedTree"] diff --git a/lighttree/implementations/json_tree.py b/lighttree/implementations/json_tree.py index 326a96d..c8ac704 100644 --- a/lighttree/implementations/json_tree.py +++ b/lighttree/implementations/json_tree.py @@ -1,8 +1,11 @@ -from lighttree import Node, Tree +from typing import Dict, Optional, Any, Union, List +from lighttree import Node, Tree, Key class JsonTree(Tree): - def __init__(self, d=None, strict=True, path_separator="."): + def __init__( + self, d: Dict = None, strict: bool = True, path_separator: str = "." + ) -> None: """ :param d: :param strict: if False, will convert tuples into arrays, else raise error @@ -13,14 +16,16 @@ def __init__(self, d=None, strict=True, path_separator="."): self._fill(d, strict=strict, key=None) @staticmethod - def _concat(a, b): + 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, key, strict, path=None): + def _fill( + self, data: Any, key: Key, strict: bool, path: Optional[str] = None + ) -> None: if isinstance(data, list) or not strict and isinstance(data, tuple): k = self.insert_node( Node(keyed=False), parent_id=path, key=key, by_path=True @@ -50,10 +55,12 @@ def _fill(self, data, key, strict, path=None): return raise TypeError("Unsupported type %s" % type(data)) - def to_dict(self): + def to_dict(self) -> Union[Dict, List, None]: + if self.root is None: + return None return self._to_dict(self.root) - def _to_dict(self, nid): + def _to_dict(self, nid: str) -> Any: _, n = self.get(nid) if not n.accept_children: return n.data @@ -62,7 +69,7 @@ def _to_dict(self, nid): for sk, sn in self.children(n.identifier): d[sk] = self._to_dict(sn.identifier) return d - d = [] + l_ = [] for _, sn in self.children(n.identifier): - d.append(self._to_dict(sn.identifier)) - return d + l_.append(self._to_dict(sn.identifier)) + return l_ diff --git a/lighttree/interactive.py b/lighttree/interactive.py index e9d9018..9bb2f74 100644 --- a/lighttree/interactive.py +++ b/lighttree/interactive.py @@ -8,7 +8,7 @@ from lighttree.tree import Tree -def is_valid_attr_name(item) -> bool: +def is_valid_attr_name(item: Any) -> bool: if not isinstance(item, str): return False if item.startswith("__"): @@ -20,7 +20,7 @@ def is_valid_attr_name(item) -> bool: return True -def _coerce_attr(attr) -> Union[str, None]: +def _coerce_attr(attr: Any) -> Union[str, None]: if not len(attr): return None new_attr = unicodedata.normalize("NFD", attr).encode("ASCII", "ignore").decode() @@ -55,7 +55,7 @@ class Obj(object): _STRING_KEY_CONSTRAINT = True _COERCE_ATTR = False - def __init__(self, **kwargs) -> None: + def __init__(self, **kwargs: Any) -> None: # will store non-valid names self.__d: Dict[str, Any] = dict() for k, v in kwargs.items(): @@ -69,14 +69,14 @@ def __init__(self, **kwargs) -> None: ) self[k] = v - def __getitem__(self, item): + def __getitem__(self, item: Any) -> Any: # when calling d[key] if is_valid_attr_name(item): return self.__getattribute__(item) else: return self.__d[item] - def __setitem__(self, key, value): + def __setitem__(self, key: Any, value: Any) -> None: # d[key] = value if not isinstance(key, str): if self._STRING_KEY_CONSTRAINT: @@ -101,7 +101,7 @@ def __keys(self) -> List[Any]: k for k in self.__dict__.keys() if k not in ("_REPR_NAME", "_Obj__d") ] - def __contains__(self, item) -> bool: + def __contains__(self, item: Any) -> bool: return item in self.__keys() def __str__(self) -> str: @@ -174,14 +174,14 @@ def _expand_attrs(self, depth: int) -> None: child_node.identifier, root_path=child_root, depth=depth - 1 ) - def __getattribute__(self, item): + def __getattribute__(self, item: Any) -> Any: # called by __getattribute__ will always refer to valid attribute item r = super(TreeBasedObj, self).__getattribute__(item) if isinstance(r, TreeBasedObj): r._expand_attrs(depth=1) return r - def __getitem__(self, item): + def __getitem__(self, item: Any) -> Any: if is_valid_attr_name(item): r = super(TreeBasedObj, self).__getattribute__(item) else: @@ -190,7 +190,7 @@ def __getitem__(self, item): r._expand_attrs(depth=1) return r - def _show(self, *args, **kwargs) -> str: + def _show(self, *args: Any, **kwargs: Any) -> str: tree_repr = self._tree.show(*args, **kwargs) if self._root_path is None: return "<%s>\n%s" % ( @@ -204,7 +204,7 @@ def _show(self, *args, **kwargs) -> str: str(tree_repr), ) - def __call__(self, *args, **kwargs) -> Tree: + def __call__(self, *args: Any, **kwargs: Any) -> Tree: return self._tree def __str__(self) -> str: diff --git a/lighttree/node.py b/lighttree/node.py index 28802f5..a2d516e 100644 --- a/lighttree/node.py +++ b/lighttree/node.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- import uuid -from typing import Optional, Any, Tuple +from typing import Optional, Any, Tuple, Union class Node(object): @@ -12,7 +12,7 @@ def __init__( auto_uuid: bool = True, keyed: bool = True, accept_children: bool = True, - repr_: Optional[str] = None, + repr_: Optional[Union[str, float]] = None, data: Any = None, ) -> None: """ @@ -25,10 +25,10 @@ def __init__( self.identifier = identifier self.keyed = keyed self.accept_children = accept_children - self.repr = repr_ + self.repr = str(repr_) if repr_ is not None else None self.data = data - def line_repr(self, depth: int, **kwargs) -> Tuple[str, str]: + def line_repr(self, depth: int, **kwargs: Any) -> Tuple[str, str]: """Control how node is displayed in tree representation. _ ├── one end @@ -43,7 +43,7 @@ def line_repr(self, depth: int, **kwargs) -> Tuple[str, str]: return "{}", "" return "[]", "" - def __eq__(self, other) -> bool: + def __eq__(self, other: Any) -> bool: if not isinstance(other, self.__class__): return False return self.identifier == other.identifier diff --git a/lighttree/py.typed b/lighttree/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/lighttree/tree.py b/lighttree/tree.py index 4d19537..a7238ee 100644 --- a/lighttree/tree.py +++ b/lighttree/tree.py @@ -13,6 +13,7 @@ Iterable, cast, Dict, + Any, ) from collections import defaultdict from operator import itemgetter @@ -718,7 +719,7 @@ def show( limit: Optional[int] = None, line_max_length: int = 60, key_delimiter: str = ": ", - **kwargs + **kwargs: Any ) -> str: """Return tree structure in hierarchy style. @@ -832,13 +833,13 @@ def _line_prefix_repr(line_type: str, is_last_list: Tuple[bool, ...]) -> str: if not is_last_list: return "" dt_vertical_line, dt_line_box, dt_line_corner = STYLES[line_type] - leading = "".join( + leading: str = "".join( [ dt_vertical_line + " " * 3 if not is_last else " " * 4 for is_last in cast(Iterable[bool], is_last_list[0:-1]) ] ) - lasting = dt_line_corner if is_last_list[-1] else dt_line_box + lasting: str = dt_line_corner if is_last_list[-1] else dt_line_box return leading + lasting def merge( diff --git a/lighttree/utils.py b/lighttree/utils.py index 6fa004c..49e63d6 100644 --- a/lighttree/utils.py +++ b/lighttree/utils.py @@ -1,8 +1,9 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +from typing import Dict, Tuple -STYLES = { +STYLES: Dict[str, Tuple[str, str, str]] = { "ascii": ("|", "|-- ", "+-- "), "ascii-ex": ("\u2502", "\u251c\u2500\u2500 ", "\u2514\u2500\u2500 "), "ascii-exr": ("\u2502", "\u251c\u2500\u2500 ", "\u2570\u2500\u2500 "), diff --git a/setup.cfg b/setup.cfg index ba06156..abfb47b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,4 +2,4 @@ description-file = README.md [flake8] -ignore=E501,W503 \ No newline at end of file +ignore=E501,W503,W605 \ No newline at end of file diff --git a/setup.py b/setup.py index 0a4a48b..0825856 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import setup -__version__ = "1.1.2" +__version__ = "1.1.3" develop_requires = [ "pre-commit", @@ -24,6 +24,9 @@ author_email="leonardbinet@gmail.com", license="MIT", packages=["lighttree"], + package_data={ + "lighttree": ["py.typed"], + }, keywords=["tree", "interactive"], extras_require={"develop": develop_requires}, tests_require=develop_requires, diff --git a/tests/test_tree.py b/tests/test_tree.py index b4183dc..f5c54e1 100644 --- a/tests/test_tree.py +++ b/tests/test_tree.py @@ -536,23 +536,27 @@ def test_show(self): def test_prefix_repr(self): self.assertEqual( - Tree._line_prefix_repr(line_type="ascii-ex", is_last_list=[]), "" + Tree._line_prefix_repr( + line_type="ascii-ex", + is_last_list=(), + ), + "", ) self.assertEqual( - Tree._line_prefix_repr(line_type="ascii-ex", is_last_list=[True]), "└── " + Tree._line_prefix_repr(line_type="ascii-ex", is_last_list=(True,)), "└── " ) self.assertEqual( - Tree._line_prefix_repr(line_type="ascii-ex", is_last_list=[False]), "├── " + Tree._line_prefix_repr(line_type="ascii-ex", is_last_list=(False,)), "├── " ) self.assertEqual( Tree._line_prefix_repr( - line_type="ascii-ex", is_last_list=[True, False, True] + line_type="ascii-ex", is_last_list=(True, False, True) ), " │ └── ", ) self.assertEqual( Tree._line_prefix_repr( - line_type="ascii-ex", is_last_list=[False, False, False] + line_type="ascii-ex", is_last_list=(False, False, False) ), "│ │ ├── ", )