aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6/doc/import_inheritance.py
blob: e694941c9441f99ea483a68cb503c1ee859d8234 (plain)
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
import inspect
import sys

"""Helpers for determining base classes by importing the class.
When passed something like:
 PySide6.QtCore.QStateMachine.SignalEvent
try to import the underlying module and return a
handle to the object. In a loop, import
  PySide6.QtCore.QStateMachine.SignalEvent
  PySide6.QtCore.QStateMachine
  PySide6.QtCore
until the import succeeds and walk up the attributes
to obtain the object."""


TEST_DRIVER_USAGE = """Usage: import_inheritance.py class_name [current_module]

Example:
python import_inheritance.py PySide6.QtWidgets.QWizard PySide6.QtWidgets
"""


class InheritanceException(Exception):
    pass


def _importClassOrModule(name):
    components = name.split('.')
    for i in range(len(components), 0, -1):
        importPath = '.'.join(components[: i])
        try:
            __import__(importPath)
        except ImportError:
            continue
        if i == len(components):
            return sys.modules[importPath]
        remaining = components[i:]
        cls = sys.modules[importPath]
        for component in remaining:
            try:
                cls = getattr(cls, component)
            except Exception:  # No such attribute
                return None
        return cls
    return None


def _import_class_or_module(name, currmodule):
    """
    Import a class using its fully-qualified *name*.
    """
    todoc = _importClassOrModule(name)
    if not todoc and currmodule is not None:
        todoc = _importClassOrModule(f"{currmodule}.{name}")
    if not todoc:
        moduleStr = f'(module {currmodule})' if currmodule else ''
        raise InheritanceException(f'Could not import class {name} specified for '
                                   f'inheritance diagram {moduleStr}.')
    if inspect.isclass(todoc):
        return [todoc]
    elif inspect.ismodule(todoc):
        classes = []
        for cls in todoc.__dict__.values():
            if inspect.isclass(cls) and cls.__module__ == todoc.__name__:
                classes.append(cls)
        return classes
    raise InheritanceException(f'{name} specified for inheritance diagram is '
                               'not a class or module')


def _import_classes(class_names, currmodule):
    """Import a list of classes."""
    classes = []
    for name in class_names:
        classes.extend(_import_class_or_module(name, currmodule))
    return classes


def _class_name(cls, parts=0):
    """Given a class object, return a fully-qualified name.

    This works for things I've tested in matplotlib so far, but may not be
    completely general.
    """
    module = cls.__module__
    if module == '__builtin__':
        fullname = cls.__name__
    else:
        fullname = f"{module}.{cls.__qualname__}"
    if parts == 0:
        return fullname
    name_parts = fullname.split('.')
    return '.'.join(name_parts[-parts:])


def _class_info(classes, builtins=None, show_builtins=False, parts=0):
    """Return name and bases for all classes that are ancestors of
    *classes*.

    *parts* gives the number of dotted name parts that is removed from the
    displayed node names.
    """
    all_classes = {}
    builtins_list = builtins.values() if builtins else []

    def recurse(cls):
        if not show_builtins and cls in builtins_list:
            return

        nodename = _class_name(cls, parts)
        fullname = _class_name(cls, 0)

        baselist = []
        all_classes[cls] = (nodename, fullname, baselist)
        for base in cls.__bases__:
            if not show_builtins and base in builtins_list:
                continue
            if base.__name__ == "Object" and base.__module__ == "Shiboken":
                continue
            baselist.append(_class_name(base, parts))
            if base not in all_classes:
                recurse(base)

    for cls in classes:
        recurse(cls)

    return list(all_classes.values())


def get_inheritance_entries_by_import(class_names, currmodule,
                                      builtins=None,
                                      show_builtins=False, parts=0):
    classes = _import_classes(class_names, currmodule)
    class_info = _class_info(classes, builtins, show_builtins, parts)
    if not class_info:
        raise InheritanceException('No classes found for '
                                   'inheritance diagram')
    return class_info


if __name__ == "__main__":
    module = None
    if len(sys.argv) < 2:
        print(TEST_DRIVER_USAGE)
        sys.exit(-1)
    class_name = sys.argv[1]
    if len(sys.argv) >= 3:
        module = sys.argv[2]
    entries = get_inheritance_entries_by_import([class_name], module, None,
                                                False, 2)
    for e in entries:
        print(e)