File: dependencies.py

package info (click to toggle)
python-apischema 0.18.3-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,636 kB
  • sloc: python: 15,281; makefile: 3; sh: 2
file content (62 lines) | stat: -rw-r--r-- 2,014 bytes parent folder | download | duplicates (2)
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
import ast
import inspect
import textwrap
from typing import AbstractSet, Callable, Collection, Dict, Set

Dependencies = AbstractSet[str]


class DependencyFinder(ast.NodeVisitor):
    def __init__(self, param: str):
        self.param = param
        self.dependencies: Set[str] = set()

    def visit_Attribute(self, node):
        self.generic_visit(node)
        if isinstance(node.value, ast.Name) and node.value.id == self.param:
            self.dependencies.add(node.attr)

    # TODO Add warning in case of function call with self in parameter
    # or better, follow the call, but it would be too hard (local import, etc.)


def first_parameter(func: Callable) -> str:
    try:
        return next(iter(inspect.signature(func).parameters))
    except StopIteration:
        raise TypeError("Cannot compute dependencies if no parameter")


def find_dependencies(func: Callable) -> Dependencies:
    try:
        finder = DependencyFinder(first_parameter(func))
        finder.visit(ast.parse(textwrap.dedent(inspect.getsource(func))))
    except ValueError:
        return set()
    return finder.dependencies


cache: Dict[Callable, Dependencies] = {}


def find_all_dependencies(
    cls: type, func: Callable, rec_guard: Collection[str] = ()
) -> Dependencies:
    """Dependencies contains class variables (because they can be "fake" ones as in
    dataclasses)"""
    if func not in cache:
        dependencies = set(find_dependencies(func))
        for attr in list(dependencies):
            if not hasattr(cls, attr):
                continue
            member = getattr(cls, attr)
            if isinstance(member, property):
                member = member.fget
            if callable(member):
                dependencies.remove(attr)
                if member in rec_guard:
                    continue
                rec_deps = find_all_dependencies(cls, member, {*rec_guard, member})
                dependencies.update(rec_deps)
        cache[func] = dependencies
    return cache[func]