pyflyby 1.9.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyflyby might be problematic. Click here for more details.

Files changed (54) hide show
  1. pyflyby/__init__.py +56 -0
  2. pyflyby/__main__.py +9 -0
  3. pyflyby/_autoimp.py +2114 -0
  4. pyflyby/_cmdline.py +531 -0
  5. pyflyby/_comms.py +221 -0
  6. pyflyby/_dbg.py +1339 -0
  7. pyflyby/_docxref.py +379 -0
  8. pyflyby/_file.py +738 -0
  9. pyflyby/_flags.py +230 -0
  10. pyflyby/_format.py +182 -0
  11. pyflyby/_idents.py +233 -0
  12. pyflyby/_import_sorting.py +165 -0
  13. pyflyby/_importclns.py +642 -0
  14. pyflyby/_importdb.py +588 -0
  15. pyflyby/_imports2s.py +639 -0
  16. pyflyby/_importstmt.py +662 -0
  17. pyflyby/_interactive.py +2605 -0
  18. pyflyby/_livepatch.py +793 -0
  19. pyflyby/_log.py +199 -0
  20. pyflyby/_modules.py +515 -0
  21. pyflyby/_parse.py +1441 -0
  22. pyflyby/_py.py +2078 -0
  23. pyflyby/_util.py +459 -0
  24. pyflyby/_version.py +7 -0
  25. pyflyby/autoimport.py +20 -0
  26. pyflyby/importdb.py +19 -0
  27. pyflyby-1.9.4.data/data/etc/pyflyby/canonical.py +10 -0
  28. pyflyby-1.9.4.data/data/etc/pyflyby/common.py +27 -0
  29. pyflyby-1.9.4.data/data/etc/pyflyby/forget.py +10 -0
  30. pyflyby-1.9.4.data/data/etc/pyflyby/mandatory.py +10 -0
  31. pyflyby-1.9.4.data/data/etc/pyflyby/numpy.py +156 -0
  32. pyflyby-1.9.4.data/data/etc/pyflyby/std.py +335 -0
  33. pyflyby-1.9.4.data/data/libexec/pyflyby/colordiff +34 -0
  34. pyflyby-1.9.4.data/data/libexec/pyflyby/diff-colorize +148 -0
  35. pyflyby-1.9.4.data/data/share/doc/pyflyby/LICENSE.txt +23 -0
  36. pyflyby-1.9.4.data/data/share/doc/pyflyby/TODO.txt +115 -0
  37. pyflyby-1.9.4.data/data/share/doc/pyflyby/testing.txt +13 -0
  38. pyflyby-1.9.4.data/data/share/emacs/site-lisp/pyflyby.el +108 -0
  39. pyflyby-1.9.4.data/scripts/collect-exports +76 -0
  40. pyflyby-1.9.4.data/scripts/collect-imports +58 -0
  41. pyflyby-1.9.4.data/scripts/find-import +38 -0
  42. pyflyby-1.9.4.data/scripts/list-bad-xrefs +34 -0
  43. pyflyby-1.9.4.data/scripts/prune-broken-imports +34 -0
  44. pyflyby-1.9.4.data/scripts/pyflyby-diff +34 -0
  45. pyflyby-1.9.4.data/scripts/reformat-imports +27 -0
  46. pyflyby-1.9.4.data/scripts/replace-star-imports +37 -0
  47. pyflyby-1.9.4.data/scripts/tidy-imports +191 -0
  48. pyflyby-1.9.4.data/scripts/transform-imports +47 -0
  49. pyflyby-1.9.4.dist-info/LICENSE.txt +23 -0
  50. pyflyby-1.9.4.dist-info/METADATA +507 -0
  51. pyflyby-1.9.4.dist-info/RECORD +54 -0
  52. pyflyby-1.9.4.dist-info/WHEEL +5 -0
  53. pyflyby-1.9.4.dist-info/entry_points.txt +3 -0
  54. pyflyby-1.9.4.dist-info/top_level.txt +1 -0
pyflyby/_docxref.py ADDED
@@ -0,0 +1,379 @@
1
+ # pyflyby/_docxref.py.
2
+
3
+ # Module for checking Epydoc cross-references.
4
+
5
+ # Portions of the code below are derived from Epydoc, which is distributed
6
+ # under the MIT license:
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining a
9
+ # copy of this software and any associated documentation files (the
10
+ # "Software"), to deal in the Software without restriction, including
11
+ # without limitation the rights to use, copy, modify, merge, publish,
12
+ # distribute, sublicense, and/or sell copies of the Software, and to permit
13
+ # persons to whom the Software is furnished to do so, subject to the
14
+ # following conditions:
15
+ #
16
+ # The above copyright notice and this permission notice shall be included in
17
+ # all copies or substantial portions of the Software.
18
+ #
19
+ # The software is provided "as is", without warranty of any kind, express or
20
+ # implied, including but not limited to the warranties of merchantability,
21
+ # fitness for a particular purpose and noninfringement. In no event shall
22
+ # the authors or copyright holders be liable for any claim, damages or other
23
+ # liability, whether in an action of contract, tort or otherwise, arising
24
+ # from, out of or in connection with the software or the use or other
25
+ # dealings in the software.
26
+
27
+
28
+ import re
29
+ import builtins
30
+ from textwrap import dedent
31
+
32
+ from epydoc.apidoc import (ClassDoc, ModuleDoc, PropertyDoc,
33
+ RoutineDoc, UNKNOWN, VariableDoc)
34
+ from epydoc.docbuilder import build_doc_index
35
+ from epydoc.markup.plaintext import ParsedPlaintextDocstring
36
+
37
+ from pyflyby._file import Filename
38
+ from pyflyby._idents import DottedIdentifier
39
+ from pyflyby._log import logger
40
+ from pyflyby._modules import ModuleHandle
41
+ from pyflyby._util import cached_attribute, memoize, prefixes
42
+
43
+ # If someone references numpy.*, just assume it's OK - it's not worth
44
+ # following into numpy because it's too slow.
45
+ ASSUME_MODULES_OK = set(['numpy'])
46
+
47
+ @memoize
48
+ def map_strings_to_line_numbers(module):
49
+ """
50
+ Walk ``module.ast``, looking at all string literals. Return a map from
51
+ string literals to line numbers (1-index).
52
+
53
+ :rtype:
54
+ ``dict`` from ``str`` to (``int``, ``str``)
55
+ """
56
+ d = {}
57
+ for field in module.block.string_literals():
58
+ # Dedent because epydoc dedents strings and we need to look up by
59
+ # those. But keep track of original version because we need to count
60
+ # exact line numbers.
61
+ s = dedent(field.s).strip()
62
+ start_lineno = field.startpos.lineno
63
+ d[s] = (start_lineno, field.s)
64
+ return d
65
+
66
+
67
+ def get_string_linenos(module, searchstring, within_string):
68
+ """
69
+ Return the line numbers (1-indexed) within ``filename`` that contain
70
+ ``searchstring``. Only consider string literals (i.e. not comments).
71
+ First look for exact matches of ``within_string`` (modulo indenting) and
72
+ then search within that. Only if the ``within_string`` is not found,
73
+ search the entire file.
74
+
75
+ [If there's a comment on the same line as a string that also contains the
76
+ searchstring, we'll get confused.]
77
+ """
78
+ module = ModuleHandle(module)
79
+ regexp = re.compile(searchstring)
80
+ map = map_strings_to_line_numbers(module)
81
+ results = []
82
+ def scan_within_string(results, start_lineno, orig_full_string):
83
+ for i, line in enumerate(orig_full_string.splitlines()):
84
+ if regexp.search(line):
85
+ results.append( start_lineno + i )
86
+ try:
87
+ lineno, orig_full_string = map[within_string.strip()]
88
+ except KeyError:
89
+ pass
90
+ else:
91
+ # We found the larger string exactly within the ast.
92
+ scan_within_string(results, lineno, orig_full_string)
93
+ if results:
94
+ return tuple(results)
95
+ # We could continue down if this ever happened.
96
+ raise Exception(
97
+ "Found superstring in %r but not substring %r within superstring"
98
+ % (module.filename, searchstring))
99
+ # Try a full text search.
100
+ for lineno, orig_full_string in map.values():
101
+ scan_within_string(results, lineno, orig_full_string)
102
+ if results:
103
+ return tuple(sorted(results))
104
+ raise Exception(
105
+ "Could not find %r anywhere in %r" % (searchstring, module.filename))
106
+
107
+
108
+ def describe_xref(identifier, container):
109
+ module = ModuleHandle(str(container.defining_module.canonical_name))
110
+ assert module.filename == Filename(container.defining_module.filename)
111
+ linenos = get_string_linenos(
112
+ module,
113
+ "(L{|<)%s" % (identifier,),
114
+ container.docstring)
115
+ return (module, linenos, str(container.canonical_name), identifier)
116
+
117
+
118
+
119
+ def safe_build_doc_index(modules):
120
+ # build_doc_index isn't re-entrant due to crappy caching! >:(
121
+ from epydoc.docintrospecter import clear_cache
122
+ clear_cache()
123
+ from epydoc.docparser import _moduledoc_cache
124
+ _moduledoc_cache.clear()
125
+ # Build a new DocIndex. It swallows exceptions and returns None on error!
126
+ # >:(
127
+ result = build_doc_index(modules)
128
+ if result is None:
129
+ raise Exception("Failed to build doc index on %r" % (modules,))
130
+ return result
131
+
132
+
133
+ class ExpandedDocIndex(object):
134
+ """
135
+ A wrapper around DocIndex that automatically expands with more modules as
136
+ needed.
137
+ """
138
+ # TODO: this is kludgy and inefficient since it re-reads modules.
139
+ def __init__(self, modules):
140
+ self.modules = set([ModuleHandle(m) for m in modules])
141
+
142
+ def add_module(self, module):
143
+ """
144
+ Adds ``module`` and recreates the DocIndex with the updated set of
145
+ modules.
146
+
147
+ :return:
148
+ Whether anything was added.
149
+ """
150
+ module = ModuleHandle(module)
151
+ for prefix in module.ancestors:
152
+ if prefix in self.modules:
153
+ # The module, or a prefix of it, was already added.
154
+ return False
155
+
156
+ for existing_module in sorted(self.modules):
157
+ if existing_module.startswith(module):
158
+ # This supersedes an existing module.
159
+ assert existing_module != module
160
+ self.modules.remove(existing_module)
161
+
162
+ logger.debug("Expanding docindex to include %r", module)
163
+ self.modules.add(module)
164
+ del self.docindex
165
+ return True
166
+
167
+ def find(self, a, b):
168
+ return self.docindex.find(a, b)
169
+
170
+ def get_vardoc(self, a):
171
+ return self.docindex.get_vardoc(a)
172
+
173
+ @cached_attribute
174
+ def docindex(self):
175
+ return safe_build_doc_index(
176
+ [str(m.name) for m in sorted(self.modules)])
177
+
178
+
179
+ def remove_epydoc_sym_suffix(s):
180
+ """
181
+ Remove trailing "'" that Epydoc annoyingly adds to 'shadowed' names.
182
+
183
+ >>> remove_epydoc_sym_suffix("a.b'.c'.d")
184
+ 'a.b.c.d'
185
+
186
+ """
187
+ return re.sub(r"'([.]|$)", r'\1', s)
188
+
189
+ class XrefScanner(object):
190
+
191
+ def __init__(self, modules):
192
+ self.modules = modules
193
+ self.docindex = safe_build_doc_index(modules)
194
+
195
+ @cached_attribute
196
+ def expanded_docindex(self):
197
+ return ExpandedDocIndex(self.modules)
198
+
199
+ def scan(self):
200
+ self._failed_xrefs = []
201
+ valdocs = sorted(self.docindex.reachable_valdocs(
202
+ imports=False, packages=False, bases=False, submodules=False,
203
+ subclasses=False, private=True
204
+ ))
205
+ for doc in valdocs:
206
+ if isinstance(doc, ClassDoc):
207
+ self.scan_class(doc)
208
+ elif isinstance(doc, ModuleDoc):
209
+ self.scan_module(doc)
210
+ return tuple(sorted(self._failed_xrefs))
211
+
212
+ def scan_module(self, doc):
213
+ self.descr(doc)
214
+ if doc.is_package is True:
215
+ for submodule in doc.submodules:
216
+ self.scan_module(submodule)
217
+ # self.scan_module_list(doc)
218
+ self.scan_details_list(doc, "function")
219
+ self.scan_details_list(doc, "other")
220
+
221
+ def scan_class(self, doc):
222
+ self.descr(doc)
223
+ self.scan_details_list(doc, "method")
224
+ self.scan_details_list(doc, "classvariable")
225
+ self.scan_details_list(doc, "instancevariable")
226
+ self.scan_details_list(doc, "property")
227
+
228
+ def scan_details_list(self, doc, value_type):
229
+ detailed = True
230
+ if isinstance(doc, ClassDoc):
231
+ var_docs = doc.select_variables(value_type=value_type,
232
+ imported=False, inherited=False,
233
+ public=None,
234
+ detailed=detailed)
235
+ else:
236
+ var_docs = doc.select_variables(value_type=value_type,
237
+ imported=False,
238
+ public=None,
239
+ detailed=detailed)
240
+ for var_doc in var_docs:
241
+ self.scan_details(var_doc)
242
+
243
+ def scan_details(self, var_doc):
244
+ self.descr(var_doc)
245
+ if isinstance(var_doc.value, RoutineDoc):
246
+ self.return_type(var_doc)
247
+ self.return_descr(var_doc)
248
+ for (arg_names, arg_descr) in var_doc.value.arg_descrs:
249
+ self.scan_docstring(arg_descr, var_doc.value)
250
+ for arg in var_doc.value.arg_types:
251
+ self.scan_docstring(
252
+ var_doc.value.arg_types[arg], var_doc.value)
253
+ elif isinstance(var_doc.value, PropertyDoc):
254
+ prop_doc = var_doc.value
255
+ self.return_type(prop_doc.fget)
256
+ self.return_type(prop_doc.fset)
257
+ self.return_type(prop_doc.fdel)
258
+ else:
259
+ self.type_descr(var_doc)
260
+
261
+ def _scan_attr(self, attr, api_doc):
262
+ if api_doc in (None, UNKNOWN):
263
+ return ''
264
+ pds = getattr(api_doc, attr, None) # pds = ParsedDocstring.
265
+ if pds not in (None, UNKNOWN):
266
+ self.scan_docstring(pds, api_doc)
267
+ elif isinstance(api_doc, VariableDoc):
268
+ self._scan_attr(attr, api_doc.value)
269
+
270
+ def summary(self, api_doc):
271
+ self._scan_attr('summary', api_doc)
272
+
273
+ def descr(self, api_doc):
274
+ self._scan_attr('descr', api_doc)
275
+
276
+ def type_descr(self, api_doc):
277
+ self._scan_attr('type_descr', api_doc)
278
+
279
+ def return_type(self, api_doc):
280
+ self._scan_attr('return_type', api_doc)
281
+
282
+ def return_descr(self, api_doc):
283
+ self._scan_attr('return_descr', api_doc)
284
+
285
+ def check_xref(self, identifier, container):
286
+ """
287
+ Check that ``identifier`` cross-references a proper symbol.
288
+
289
+ Look in modules that we weren't explicitly asked to look in, if
290
+ needed.
291
+ """
292
+ if identifier in builtins.__dict__:
293
+ return True
294
+ def check_container():
295
+ if self.expanded_docindex.find(identifier, container) is not None:
296
+ return True
297
+ if isinstance(container, RoutineDoc):
298
+ tcontainer = self.expanded_docindex.get_vardoc(
299
+ container.canonical_name)
300
+ doc = self.expanded_docindex.find(identifier, tcontainer)
301
+ while (doc is not None and tcontainer not in (None, UNKNOWN)
302
+ and tcontainer.overrides not in (None, UNKNOWN)):
303
+ tcontainer = tcontainer.overrides
304
+ doc = self.expanded_docindex.find(identifier, tcontainer)
305
+ return doc is not None
306
+ return False
307
+ def check_defining_module(x):
308
+ if x is None:
309
+ return False
310
+ defining_module_name = remove_epydoc_sym_suffix(str(
311
+ x.defining_module.canonical_name))
312
+ if defining_module_name in ASSUME_MODULES_OK:
313
+ return True
314
+ if self.expanded_docindex.add_module(defining_module_name):
315
+ if check_container():
316
+ return True
317
+ return False
318
+ if check_container():
319
+ return True
320
+ if (isinstance(container, RoutineDoc) and
321
+ identifier in container.all_args()):
322
+ return True
323
+ if check_defining_module(container):
324
+ return True
325
+ # If the user has imported foo.bar.baz as baz and now uses
326
+ # ``baz.quux``, we need to add the module foo.bar.baz.
327
+ for prefix in reversed(list(prefixes(
328
+ DottedIdentifier(remove_epydoc_sym_suffix(identifier))))):
329
+ if check_defining_module(
330
+ self.docindex.find(str(prefix), container)):
331
+ return True
332
+ try:
333
+ module = ModuleHandle.containing(identifier)
334
+ except ImportError:
335
+ pass
336
+ else:
337
+ if str(module.name) in ASSUME_MODULES_OK:
338
+ return True
339
+ if self.expanded_docindex.add_module(module):
340
+ if check_container():
341
+ return True
342
+ return False
343
+
344
+ def scan_docstring(self, parsed_docstring, container):
345
+ if parsed_docstring in (None, UNKNOWN): return ''
346
+ if isinstance(parsed_docstring, ParsedPlaintextDocstring):
347
+ return ''
348
+
349
+ def scan_tree(tree):
350
+ if isinstance(tree, str):
351
+ return tree
352
+ variables = [scan_tree(child) for child in tree.children]
353
+ if tree.tag == 'link':
354
+ identifier = variables[1]
355
+ if not self.check_xref(identifier, container):
356
+ self._failed_xrefs.append(
357
+ describe_xref(identifier, container) )
358
+ return '?'
359
+ elif tree.tag == 'indexed':
360
+ return '?'
361
+ elif tree.tag in ('epytext', 'section', 'tag', 'arg',
362
+ 'name', 'target', 'html', 'para'):
363
+ return ''.join(variables)
364
+ return '?'
365
+
366
+ scan_tree(parsed_docstring._tree)
367
+
368
+
369
+ def find_bad_doc_cross_references(names):
370
+ """
371
+ Find docstring cross references that fail to resolve.
372
+
373
+ :type names:
374
+ Sequence of module names or filenames.
375
+ :return:
376
+ Sequence of ``(module, linenos, container_name, identifier)`` tuples.
377
+ """
378
+ xrs = XrefScanner(names)
379
+ return xrs.scan()