ยปCore Development>Code coverage>Lib/pyclbr.py

Python code coverage for Lib/pyclbr.py

#countcontent
1n/a"""Parse a Python module and describe its classes and methods.
2n/a
3n/aParse enough of a Python file to recognize imports and class and
4n/amethod definitions, and to find out the superclasses of a class.
5n/a
6n/aThe interface consists of a single function:
7n/a readmodule_ex(module [, path])
8n/awhere module is the name of a Python module, and path is an optional
9n/alist of directories where the module is to be searched. If present,
10n/apath is prepended to the system search path sys.path. The return
11n/avalue is a dictionary. The keys of the dictionary are the names of
12n/athe classes defined in the module (including classes that are defined
13n/avia the from XXX import YYY construct). The values are class
14n/ainstances of the class Class defined here. One special key/value pair
15n/ais present for packages: the key '__path__' has a list as its value
16n/awhich contains the package search path.
17n/a
18n/aA class is described by the class Class in this module. Instances
19n/aof this class have the following instance variables:
20n/a module -- the module name
21n/a name -- the name of the class
22n/a super -- a list of super classes (Class instances)
23n/a methods -- a dictionary of methods
24n/a file -- the file in which the class was defined
25n/a lineno -- the line in the file on which the class statement occurred
26n/aThe dictionary of methods uses the method names as keys and the line
27n/anumbers on which the method was defined as values.
28n/aIf the name of a super class is not recognized, the corresponding
29n/aentry in the list of super classes is not a class instance but a
30n/astring giving the name of the super class. Since import statements
31n/aare recognized and imported modules are scanned as well, this
32n/ashouldn't happen often.
33n/a
34n/aA function is described by the class Function in this module.
35n/aInstances of this class have the following instance variables:
36n/a module -- the module name
37n/a name -- the name of the class
38n/a file -- the file in which the class was defined
39n/a lineno -- the line in the file on which the class statement occurred
40n/a"""
41n/a
42n/aimport io
43n/aimport sys
44n/aimport importlib.util
45n/aimport tokenize
46n/afrom token import NAME, DEDENT, OP
47n/a
48n/a__all__ = ["readmodule", "readmodule_ex", "Class", "Function"]
49n/a
50n/a_modules = {} # cache of modules we've seen
51n/a
52n/a# each Python class is represented by an instance of this class
53n/aclass Class:
54n/a '''Class to represent a Python class.'''
55n/a def __init__(self, module, name, super, file, lineno):
56n/a self.module = module
57n/a self.name = name
58n/a if super is None:
59n/a super = []
60n/a self.super = super
61n/a self.methods = {}
62n/a self.file = file
63n/a self.lineno = lineno
64n/a
65n/a def _addmethod(self, name, lineno):
66n/a self.methods[name] = lineno
67n/a
68n/aclass Function:
69n/a '''Class to represent a top-level Python function'''
70n/a def __init__(self, module, name, file, lineno):
71n/a self.module = module
72n/a self.name = name
73n/a self.file = file
74n/a self.lineno = lineno
75n/a
76n/adef readmodule(module, path=None):
77n/a '''Backwards compatible interface.
78n/a
79n/a Call readmodule_ex() and then only keep Class objects from the
80n/a resulting dictionary.'''
81n/a
82n/a res = {}
83n/a for key, value in _readmodule(module, path or []).items():
84n/a if isinstance(value, Class):
85n/a res[key] = value
86n/a return res
87n/a
88n/adef readmodule_ex(module, path=None):
89n/a '''Read a module file and return a dictionary of classes.
90n/a
91n/a Search for MODULE in PATH and sys.path, read and parse the
92n/a module and return a dictionary with one entry for each class
93n/a found in the module.
94n/a '''
95n/a return _readmodule(module, path or [])
96n/a
97n/adef _readmodule(module, path, inpackage=None):
98n/a '''Do the hard work for readmodule[_ex].
99n/a
100n/a If INPACKAGE is given, it must be the dotted name of the package in
101n/a which we are searching for a submodule, and then PATH must be the
102n/a package search path; otherwise, we are searching for a top-level
103n/a module, and PATH is combined with sys.path.
104n/a '''
105n/a # Compute the full module name (prepending inpackage if set)
106n/a if inpackage is not None:
107n/a fullmodule = "%s.%s" % (inpackage, module)
108n/a else:
109n/a fullmodule = module
110n/a
111n/a # Check in the cache
112n/a if fullmodule in _modules:
113n/a return _modules[fullmodule]
114n/a
115n/a # Initialize the dict for this module's contents
116n/a dict = {}
117n/a
118n/a # Check if it is a built-in module; we don't do much for these
119n/a if module in sys.builtin_module_names and inpackage is None:
120n/a _modules[module] = dict
121n/a return dict
122n/a
123n/a # Check for a dotted module name
124n/a i = module.rfind('.')
125n/a if i >= 0:
126n/a package = module[:i]
127n/a submodule = module[i+1:]
128n/a parent = _readmodule(package, path, inpackage)
129n/a if inpackage is not None:
130n/a package = "%s.%s" % (inpackage, package)
131n/a if not '__path__' in parent:
132n/a raise ImportError('No package named {}'.format(package))
133n/a return _readmodule(submodule, parent['__path__'], package)
134n/a
135n/a # Search the path for the module
136n/a f = None
137n/a if inpackage is not None:
138n/a search_path = path
139n/a else:
140n/a search_path = path + sys.path
141n/a # XXX This will change once issue19944 lands.
142n/a spec = importlib.util._find_spec_from_path(fullmodule, search_path)
143n/a _modules[fullmodule] = dict
144n/a # is module a package?
145n/a if spec.submodule_search_locations is not None:
146n/a dict['__path__'] = spec.submodule_search_locations
147n/a try:
148n/a source = spec.loader.get_source(fullmodule)
149n/a if source is None:
150n/a return dict
151n/a except (AttributeError, ImportError):
152n/a # not Python source, can't do anything with this module
153n/a return dict
154n/a
155n/a fname = spec.loader.get_filename(fullmodule)
156n/a
157n/a f = io.StringIO(source)
158n/a
159n/a stack = [] # stack of (class, indent) pairs
160n/a
161n/a g = tokenize.generate_tokens(f.readline)
162n/a try:
163n/a for tokentype, token, start, _end, _line in g:
164n/a if tokentype == DEDENT:
165n/a lineno, thisindent = start
166n/a # close nested classes and defs
167n/a while stack and stack[-1][1] >= thisindent:
168n/a del stack[-1]
169n/a elif token == 'def':
170n/a lineno, thisindent = start
171n/a # close previous nested classes and defs
172n/a while stack and stack[-1][1] >= thisindent:
173n/a del stack[-1]
174n/a tokentype, meth_name, start = next(g)[0:3]
175n/a if tokentype != NAME:
176n/a continue # Syntax error
177n/a if stack:
178n/a cur_class = stack[-1][0]
179n/a if isinstance(cur_class, Class):
180n/a # it's a method
181n/a cur_class._addmethod(meth_name, lineno)
182n/a # else it's a nested def
183n/a else:
184n/a # it's a function
185n/a dict[meth_name] = Function(fullmodule, meth_name,
186n/a fname, lineno)
187n/a stack.append((None, thisindent)) # Marker for nested fns
188n/a elif token == 'class':
189n/a lineno, thisindent = start
190n/a # close previous nested classes and defs
191n/a while stack and stack[-1][1] >= thisindent:
192n/a del stack[-1]
193n/a tokentype, class_name, start = next(g)[0:3]
194n/a if tokentype != NAME:
195n/a continue # Syntax error
196n/a # parse what follows the class name
197n/a tokentype, token, start = next(g)[0:3]
198n/a inherit = None
199n/a if token == '(':
200n/a names = [] # List of superclasses
201n/a # there's a list of superclasses
202n/a level = 1
203n/a super = [] # Tokens making up current superclass
204n/a while True:
205n/a tokentype, token, start = next(g)[0:3]
206n/a if token in (')', ',') and level == 1:
207n/a n = "".join(super)
208n/a if n in dict:
209n/a # we know this super class
210n/a n = dict[n]
211n/a else:
212n/a c = n.split('.')
213n/a if len(c) > 1:
214n/a # super class is of the form
215n/a # module.class: look in module for
216n/a # class
217n/a m = c[-2]
218n/a c = c[-1]
219n/a if m in _modules:
220n/a d = _modules[m]
221n/a if c in d:
222n/a n = d[c]
223n/a names.append(n)
224n/a super = []
225n/a if token == '(':
226n/a level += 1
227n/a elif token == ')':
228n/a level -= 1
229n/a if level == 0:
230n/a break
231n/a elif token == ',' and level == 1:
232n/a pass
233n/a # only use NAME and OP (== dot) tokens for type name
234n/a elif tokentype in (NAME, OP) and level == 1:
235n/a super.append(token)
236n/a # expressions in the base list are not supported
237n/a inherit = names
238n/a cur_class = Class(fullmodule, class_name, inherit,
239n/a fname, lineno)
240n/a if not stack:
241n/a dict[class_name] = cur_class
242n/a stack.append((cur_class, thisindent))
243n/a elif token == 'import' and start[1] == 0:
244n/a modules = _getnamelist(g)
245n/a for mod, _mod2 in modules:
246n/a try:
247n/a # Recursively read the imported module
248n/a if inpackage is None:
249n/a _readmodule(mod, path)
250n/a else:
251n/a try:
252n/a _readmodule(mod, path, inpackage)
253n/a except ImportError:
254n/a _readmodule(mod, [])
255n/a except:
256n/a # If we can't find or parse the imported module,
257n/a # too bad -- don't die here.
258n/a pass
259n/a elif token == 'from' and start[1] == 0:
260n/a mod, token = _getname(g)
261n/a if not mod or token != "import":
262n/a continue
263n/a names = _getnamelist(g)
264n/a try:
265n/a # Recursively read the imported module
266n/a d = _readmodule(mod, path, inpackage)
267n/a except:
268n/a # If we can't find or parse the imported module,
269n/a # too bad -- don't die here.
270n/a continue
271n/a # add any classes that were defined in the imported module
272n/a # to our name space if they were mentioned in the list
273n/a for n, n2 in names:
274n/a if n in d:
275n/a dict[n2 or n] = d[n]
276n/a elif n == '*':
277n/a # don't add names that start with _
278n/a for n in d:
279n/a if n[0] != '_':
280n/a dict[n] = d[n]
281n/a except StopIteration:
282n/a pass
283n/a
284n/a f.close()
285n/a return dict
286n/a
287n/adef _getnamelist(g):
288n/a # Helper to get a comma-separated list of dotted names plus 'as'
289n/a # clauses. Return a list of pairs (name, name2) where name2 is
290n/a # the 'as' name, or None if there is no 'as' clause.
291n/a names = []
292n/a while True:
293n/a name, token = _getname(g)
294n/a if not name:
295n/a break
296n/a if token == 'as':
297n/a name2, token = _getname(g)
298n/a else:
299n/a name2 = None
300n/a names.append((name, name2))
301n/a while token != "," and "\n" not in token:
302n/a token = next(g)[1]
303n/a if token != ",":
304n/a break
305n/a return names
306n/a
307n/adef _getname(g):
308n/a # Helper to get a dotted name, return a pair (name, token) where
309n/a # name is the dotted name, or None if there was no dotted name,
310n/a # and token is the next input token.
311n/a parts = []
312n/a tokentype, token = next(g)[0:2]
313n/a if tokentype != NAME and token != '*':
314n/a return (None, token)
315n/a parts.append(token)
316n/a while True:
317n/a tokentype, token = next(g)[0:2]
318n/a if token != '.':
319n/a break
320n/a tokentype, token = next(g)[0:2]
321n/a if tokentype != NAME:
322n/a break
323n/a parts.append(token)
324n/a return (".".join(parts), token)
325n/a
326n/adef _main():
327n/a # Main program for testing.
328n/a import os
329n/a from operator import itemgetter
330n/a mod = sys.argv[1]
331n/a if os.path.exists(mod):
332n/a path = [os.path.dirname(mod)]
333n/a mod = os.path.basename(mod)
334n/a if mod.lower().endswith(".py"):
335n/a mod = mod[:-3]
336n/a else:
337n/a path = []
338n/a dict = readmodule_ex(mod, path)
339n/a objs = list(dict.values())
340n/a objs.sort(key=lambda a: getattr(a, 'lineno', 0))
341n/a for obj in objs:
342n/a if isinstance(obj, Class):
343n/a print("class", obj.name, obj.super, obj.lineno)
344n/a methods = sorted(obj.methods.items(), key=itemgetter(1))
345n/a for name, lineno in methods:
346n/a if name != "__path__":
347n/a print(" def", name, lineno)
348n/a elif isinstance(obj, Function):
349n/a print("def", obj.name, obj.lineno)
350n/a
351n/aif __name__ == "__main__":
352n/a _main()