Passed
Pull Request — master (#22)
by Guibert
01:17
created

async_btree.analyze._get_target_propertie_name()   A

Complexity

Conditions 4

Size

Total Lines 4
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 4
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
"""Analyze definition."""
2
from inspect import getclosurevars
3
from typing import Any, Callable, List, NamedTuple, Optional, Tuple, no_type_check
4
5
from .definition import CallableFunction, get_node_metadata
6
7
__all__ = ["analyze", "stringify_analyze", "Node"]
8
9
_DEFAULT_EDGES = ['child', 'children', '_child', '_children']
10
11
12
class Node(NamedTuple):
13
    """Node aggregate node definition implemented with NamedTuple.
14
15
    A Node is used to keep information on name, properties, and relations ship
16
    between a hierachical construct of functions.
17
    It's like an instance of NodeMetadata.
18
19
    Attributes:
20
        name (str): named operation.
21
        properties (List[Tuple[str, Any]]): a list of tuple (name, value) for definition.
22
        edges (List[Tuple[str, List[Any]]]): a list of tuple (name, node list) for
23
            definition.
24
25
    Notes:
26
        Edges attribut should be edges: ```List[Tuple[str, List['Node']]]```
27
        But it is impossible for now, see [mypy issues 731](https://github.com/python/mypy/issues/731)
28
    """
29
30
    name: str
31
    properties: List[Tuple[str, Any]]
32
    # edges: List[Tuple[str, List['Node']]]
33
    # https://github.com/python/mypy/issues/731
34
    edges: List[Tuple[str, List[Any]]]
35
36
    def __str__(self):
37
        return stringify_analyze(target=self)
38
39
40
def _get_function_name(target: Callable) -> str:
41
    return target.__name__.lstrip("_") if hasattr(target, "__name__") else "anonymous"
42
43
44
def _get_target_propertie_name(value):
45
    if value and callable(value):
46
        return get_node_metadata(target=value).name if hasattr(value, "__node_metadata") else _get_function_name(value)
47
    return value
48
49
50
# pylint: disable=protected-access
51
@no_type_check  # it's a shortcut for hasattr ...
52
def analyze(target: CallableFunction) -> Node:
53
    """Analyze specified target and return a Node representation.
54
55
    Args:
56
        target (CallableFunction): async function to analyze.
57
58
    Returns:
59
        (Node): a node instance representation of target function
60
    """
61
62
    nonlocals = getclosurevars(target).nonlocals
63
64
    def _get_nonlocals_value_for(name):
65
        return nonlocals.get(name, None)
66
67
    def _analyze_property(p):
68
        """Return a tuple (name, value) or (name, function name) as property."""
69
        value = _get_nonlocals_value_for(name=p)
70
        return p.lstrip('_'), _get_target_propertie_name(value=value)
71
72
    def _analyze_edges(egde_name):
73
        """Lookup children node from egde_name local var."""
74
        value = None
75
        edges = _get_nonlocals_value_for(name=egde_name)
76
        if edges:
77
            # it could be a collection of node or a single node
78
            value = list(map(analyze, edges if hasattr(edges, "__iter__") else [edges]))
79
        return (egde_name.lstrip('_'), value)
80
81
    if hasattr(target, "__node_metadata"):
82
        node = get_node_metadata(target=target)
83
        return Node(
84
            name=node.name,
85
            properties=list(map(_analyze_property, node.properties)) if node.properties else [],
86
            edges=list(filter(lambda p: p is not None, map(_analyze_edges, node.edges or _DEFAULT_EDGES))),
87
        )
88
89
    # simple function
90
    return Node(
91
        name=_get_function_name(target=target), properties=list(map(_analyze_property, nonlocals.keys())), edges=[]
92
    )
93
94
95
def stringify_analyze(target: Node, indent: int = 0, label: Optional[str] = None) -> str:
96
    """Stringify node representation of specified target.
97
98
    Args:
99
        target (CallableFunction): async function to analyze.
100
        indent (int): level identation (default to zero).
101
        label (Optional[str]): label of current node (default None).
102
103
    Returns:
104
        (str): a string node representation.
105
    """
106
    _ident = '    '
107
    _space = f'{_ident * indent} '
108
    result: str = ''
109
    if label:
110
        result += f'{_space}--({label})--> {target.name}:\n'
111
        _space += f"{_ident}{' ' * len(label)}"
112
    else:
113
        result += f'{_space}--> {target.name}:\n'
114
115
    for k, v in target.properties:
116
        result += f'{_space}    {k}: {v}\n'
117
118
    for _label, children in target.edges:
119
        if children:
120
            for child in children:
121
                result += stringify_analyze(target=child, indent=indent + 1, label=_label)
122
    return result
123