forked from python/mypy
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdumpmodule.py
More file actions
160 lines (125 loc) · 3.85 KB
/
dumpmodule.py
File metadata and controls
160 lines (125 loc) · 3.85 KB
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
"""Dump the runtime structure of a module as JSON.
This is used for testing stubs.
This needs to run in Python 2.7 and 3.x.
"""
from __future__ import print_function
import importlib
import json
import sys
import types
from typing import Text
if sys.version_info >= (3, 0):
import inspect
long = int
else:
import inspect2 as inspect
def dump_module(id):
m = importlib.import_module(id)
data = module_to_json(m)
print(json.dumps(data, ensure_ascii=True, indent=4, sort_keys=True))
def module_to_json(m):
result = {}
for name, value in m.__dict__.items():
# Filter out some useless attributes.
if name in ('__file__',
'__doc__',
'__name__',
'__builtins__',
'__package__'):
continue
if name == '__all__':
result[name] = {'type': 'list', 'values': sorted(value)}
else:
result[name] = dump_value(value)
try:
_, line = inspect.getsourcelines(getattr(m, name))
except (TypeError, OSError):
line = None
result[name]['line'] = line
return result
def dump_value(value, depth=0):
if depth > 10:
return 'max_recursion_depth_exceeded'
if isinstance(value, type):
return dump_class(value, depth + 1)
if inspect.isfunction(value):
return dump_function(value)
if callable(value):
return {'type': 'callable'} # TODO more information
if isinstance(value, types.ModuleType):
return {'type': 'module'} # TODO module name
if inspect.isdatadescriptor(value):
return {'type': 'datadescriptor'}
if inspect.ismemberdescriptor(value):
return {'type': 'memberdescriptor'}
return dump_simple(value)
def dump_simple(value):
if type(value) in (int, bool, float, str, bytes, Text, long, list, set, dict, tuple):
return {'type': type(value).__name__}
if value is None:
return {'type': 'None'}
if value is inspect.Parameter.empty:
return {'type': None} # 'None' and None: Ruh-Roh
return {'type': 'unknown'}
def dump_class(value, depth):
return {
'type': 'class',
'attributes': dump_attrs(value, depth),
}
special_methods = [
'__init__',
'__str__',
'__int__',
'__float__',
'__bool__',
'__contains__',
'__iter__',
]
# Change to return a dict
def dump_attrs(d, depth):
result = {}
seen = set()
try:
mro = d.mro()
except TypeError:
mro = [d]
for base in mro:
v = vars(base)
for name, value in v.items():
if name not in seen:
result[name] = dump_value(value, depth + 1)
seen.add(name)
for m in special_methods:
if hasattr(d, m) and m not in seen:
result[m] = dump_value(getattr(d, m), depth + 1)
return result
kind_map = {
inspect.Parameter.POSITIONAL_ONLY: 'POS_ONLY',
inspect.Parameter.POSITIONAL_OR_KEYWORD: 'POS_OR_KW',
inspect.Parameter.VAR_POSITIONAL: 'VAR_POS',
inspect.Parameter.KEYWORD_ONLY: 'KW_ONLY',
inspect.Parameter.VAR_KEYWORD: 'VAR_KW',
}
def param_kind(p):
s = kind_map[p.kind]
if p.default != inspect.Parameter.empty:
assert s in ('POS_ONLY', 'POS_OR_KW', 'KW_ONLY')
s += '_OPT'
return s
def dump_function(value):
try:
sig = inspect.signature(value)
except ValueError:
# The signature call sometimes fails for some reason.
return {'type': 'invalid_signature'}
params = list(sig.parameters.items())
return {
'type': 'function',
'args': [(name, param_kind(p), dump_simple(p.default))
for name, p in params],
}
if __name__ == '__main__':
import sys
if len(sys.argv) != 2:
sys.exit('usage: dumpmodule.py module-name')
dump_module(sys.argv[1])