Completed
Push — master ( a1fcd1...4af13f )
by Guibert
13s queued 11s
created

async_btree.analyze._get_analyze_edges()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
"""Analyze definition."""
2
from inspect import getclosurevars
3
from typing import Any, 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
# pylint: disable=protected-access
41
@no_type_check  # it's a shortcut for hasattr ...
42
def analyze(target: CallableFunction) -> Node:
43
    """Analyze specified target and return a Node representation.
44
45
    Args:
46
        target (CallableFunction): async function to analyze.
47
48
    Returns:
49
        (Node): a node instance representation of target function
50
    """
51
52
    nonlocals = getclosurevars(target).nonlocals
53
54
    def _get_nonlocals_value_for(name):
55
        return nonlocals[name] if name in nonlocals else None
56
57
    def _analyze_property(p):
58
        """Return a tuple (name, value) or (name, function name) as property."""
59
        value = _get_nonlocals_value_for(name=p)
60
        p_name = p.lstrip('_')
61
        if value and callable(value):
62
            return p_name, get_node_metadata(target=value).name if hasattr(value, "__node_metadata") else value.__name__
63
        return p_name, value
64
65
    def _analyze_edges(egde_name):
66
        """Lookup children node from egde_name local var."""
67
        value = None
68
        edge = _get_nonlocals_value_for(name=egde_name)
69
        if edge:
70
            # it could be a collection of node
71
            if hasattr(edge, "__iter__"):
72
                value = list(map(analyze, edge))
73
            else:  # or a single node
74
                value = [analyze(edge)]
75
        return (egde_name.lstrip('_'), value)
76
77
    if hasattr(target, "__node_metadata"):
78
        node = get_node_metadata(target=target)
79
        return Node(
80
            name=node.name,
81
            properties=list(map(_analyze_property, node.properties)) if node.properties else [],
82
            edges=list(filter(lambda p: p is not None, map(_analyze_edges, node.edges or _DEFAULT_EDGES))),
83
        )
84
85
    # simple function
86
    return Node(
87
        name=target.__name__.lstrip("_") if hasattr(target, "__name__") else "anonymous",
88
        properties=list(map(_analyze_property, nonlocals.keys())),
89
        edges=[],
90
    )
91
92
93
def stringify_analyze(target: Node, indent: int = 0, label: Optional[str] = None) -> str:
94
    """Stringify node representation of specified target.
95
96
    Args:
97
        target (CallableFunction): async function to analyze.
98
        indent (int): level identation (default to zero).
99
        label (Optional[str]): label of current node (default None).
100
101
    Returns:
102
        (str): a string node representation.
103
    """
104
    _ident = '    '
105
    _space = f'{_ident * indent} '
106
    result: str = ''
107
    if label:
108
        result += f'{_space}--({label})--> {target.name}:\n'
109
        _space += f"{_ident}{' ' * len(label)}"
110
    else:
111
        result += f'{_space}--> {target.name}:\n'
112
113
    for k, v in target.properties:
114
        result += f'{_space}    {k}: {v}\n'
115
116
    for _label, children in target.edges:
117
        if children:
118
            for child in children:
119
                result += stringify_analyze(target=child, indent=indent + 1, label=_label)
120
    return result
121