File: function.py

package info (click to toggle)
python-bytecode 0.16.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 780 kB
  • sloc: python: 8,300; makefile: 169; sh: 40
file content (164 lines) | stat: -rw-r--r-- 5,303 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
from collections import deque
from collections.abc import Iterator
from os.path import abspath
from types import FunctionType, ModuleType
from typing import Any, Dict, Optional, Protocol, Tuple, Type, Union, cast

from module import origin  # type: ignore

FunctionContainerType = Union[
    type, property, classmethod, staticmethod, Tuple, ModuleType
]

ContainerKey = Union[str, int, Type[staticmethod], Type[classmethod]]

CONTAINER_TYPES = (type, property, classmethod, staticmethod)


def set_cell_contents(cell, contents):  # type: ignore[misc]
    cell.cell_contents = contents


class FullyNamed(Protocol):
    """A fully named object."""

    __name__ = None  # type: Optional[str]
    __fullname__ = None  # type: Optional[str]


class FullyNamedFunction(FullyNamed):
    """A fully named function object."""

    def __call__(self, *args, **kwargs):
        pass


class ContainerIterator(Iterator, FullyNamedFunction):
    """Wrapper around different types of function containers.

    A container comes with an origin, i.e. a parent container and a position
    within it in the form of a key.
    """

    def __init__(
        self,
        container,  # type: FunctionContainerType
        origin=None,  # type: Optional[Union[Tuple[ContainerIterator, ContainerKey], Tuple[FullyNamedFunction, str]]]
    ):
        # type: (...) -> None
        if isinstance(container, (type, ModuleType)):
            self._iter = iter(container.__dict__.items())
            self.__name__ = container.__name__

        elif isinstance(container, tuple):
            self._iter = iter(enumerate(_.cell_contents for _ in container))  # type: ignore[arg-type]
            self.__name__ = "<locals>"

        elif isinstance(container, property):
            self._iter = iter(
                (m, getattr(container, a))
                for m, a in {
                    ("getter", "fget"),
                    ("setter", "fset"),
                    ("deleter", "fdel"),
                }
            )
            assert container.fget is not None
            self.__name__ = container.fget.__name__

        elif isinstance(container, (classmethod, staticmethod)):
            self._iter = iter([(type(container), container.__func__)])  # type: ignore[list-item]
            self.__name__ = None

        else:
            raise TypeError("Unsupported container type: %s", type(container))

        self._container = container

        if origin is not None and origin[0].__fullname__ is not None:
            origin_fullname = origin[0].__fullname__
            self.__fullname__ = (
                ".".join((origin_fullname, self.__name__))
                if self.__name__
                else origin_fullname
            )
        else:
            self.__fullname__ = self.__name__

    def __iter__(self):
        # type: () -> Iterator[Tuple[ContainerKey, Any]]
        return self._iter

    def __next__(self):
        # type: () -> Tuple[ContainerKey, Any]
        return next(self._iter)

    next = __next__


def _collect_functions(module):
    # type: (ModuleType) -> Dict[str, FullyNamedFunction]
    """Collect functions from a given module."""
    assert isinstance(module, ModuleType)

    path = origin(module)
    containers = deque([ContainerIterator(module)])
    functions = {}
    seen_containers = set()
    seen_functions = set()

    while containers:
        c = containers.pop()

        if id(c._container) in seen_containers:
            continue
        seen_containers.add(id(c._container))

        for k, o in c:
            code = getattr(o, "__code__", None) if isinstance(o, FunctionType) else None
            if code is not None and abspath(code.co_filename) == path:
                if o not in seen_functions:
                    seen_functions.add(o)
                    o = cast(FullyNamedFunction, o)
                    o.__fullname__ = (
                        ".".join((c.__fullname__, o.__name__))
                        if c.__fullname__
                        else o.__name__
                    )

                for name in (k, o.__name__) if isinstance(k, str) else (o.__name__,):
                    fullname = (
                        ".".join((c.__fullname__, name)) if c.__fullname__ else name
                    )
                    functions[fullname] = o

                try:
                    if o.__closure__:
                        containers.append(
                            ContainerIterator(o.__closure__, origin=(o, "<locals>"))
                        )
                except AttributeError:
                    pass

            elif isinstance(o, CONTAINER_TYPES):
                if isinstance(o, property) and not isinstance(o.fget, FunctionType):
                    continue
                containers.append(ContainerIterator(o, origin=(c, k)))

    return functions


class FunctionDiscovery(dict):
    """Discover all function objects in a module."""

    def __init__(self, module):
        # type: (ModuleType) -> None
        super(FunctionDiscovery, self).__init__()
        self._module = module

        functions = _collect_functions(module)
        seen_functions = set()

        for fname, function in functions.items():
            self[fname] = function
            seen_functions.add(function)