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/_importdb.py ADDED
@@ -0,0 +1,588 @@
1
+ # pyflyby/_importdb.py.
2
+ # Copyright (C) 2011, 2012, 2013, 2014, 2015 Karl Chen.
3
+ # License: MIT http://opensource.org/licenses/MIT
4
+
5
+
6
+
7
+ from collections import defaultdict
8
+ import os
9
+ import re
10
+
11
+ from pyflyby._file import Filename, expand_py_files_from_args, UnsafeFilenameError
12
+ from pyflyby._idents import dotted_prefixes
13
+ from pyflyby._importclns import ImportMap, ImportSet
14
+ from pyflyby._importstmt import Import, ImportStatement
15
+ from pyflyby._log import logger
16
+ from pyflyby._parse import PythonBlock
17
+ from pyflyby._util import cached_attribute, memoize, stable_unique
18
+
19
+ from typing import Dict, Any
20
+
21
+
22
+ @memoize
23
+ def _find_etc_dirs():
24
+ result = []
25
+ dirs = Filename(__file__).real.dir.ancestors[:-1]
26
+ for dir in dirs:
27
+ candidate = dir / "etc/pyflyby"
28
+ if candidate.isdir:
29
+ result.append(candidate)
30
+ break
31
+ global_dir = Filename("/etc/pyflyby")
32
+ if global_dir.exists:
33
+ result.append(global_dir)
34
+ return result
35
+
36
+
37
+ def _get_env_var(env_var_name, default):
38
+ '''
39
+ Get an environment variable and split on ":", replacing ``-`` with the
40
+ default.
41
+ '''
42
+ assert re.match("^[A-Z_]+$", env_var_name)
43
+ assert isinstance(default, (tuple, list))
44
+ value = list(filter(None, os.environ.get(env_var_name, '').split(':')))
45
+ if not value:
46
+ return default
47
+ # Replace '-' with ``default``
48
+ try:
49
+ idx = value.index('-')
50
+ except ValueError:
51
+ pass
52
+ else:
53
+ value[idx:idx+1] = default
54
+ return value
55
+
56
+
57
+ def _get_python_path(env_var_name, default_path, target_dirname):
58
+ '''
59
+ Expand an environment variable specifying pyflyby input config files.
60
+
61
+ - Default to ``default_path`` if the environment variable is undefined.
62
+ - Process colon delimiters.
63
+ - Replace "-" with ``default_path``.
64
+ - Expand triple dots.
65
+ - Recursively traverse directories.
66
+
67
+ :rtype:
68
+ ``tuple`` of ``Filename`` s
69
+ '''
70
+ pathnames = _get_env_var(env_var_name, default_path)
71
+ if pathnames == ["EMPTY"]:
72
+ # The special code PYFLYBY_PATH=EMPTY means we intentionally want to
73
+ # use an empty PYFLYBY_PATH (and don't fall back to the default path,
74
+ # nor warn about an empty path).
75
+ return ()
76
+ for p in pathnames:
77
+ if re.match("/|[.]/|[.][.][.]/|~/", p):
78
+ continue
79
+ raise ValueError(
80
+ "{env_var_name} components should start with / or ./ or ~/ or .../. "
81
+ "Use {env_var_name}=./{p} instead of {env_var_name}={p} if you really "
82
+ "want to use the current directory."
83
+ .format(env_var_name=env_var_name, p=p))
84
+ pathnames = [os.path.expanduser(p) for p in pathnames]
85
+ pathnames = _expand_tripledots(pathnames, target_dirname)
86
+ pathnames = [Filename(fn) for fn in pathnames]
87
+ pathnames = stable_unique(pathnames)
88
+ pathnames = expand_py_files_from_args(pathnames)
89
+ if not pathnames:
90
+ logger.warning(
91
+ "No import libraries found (%s=%r, default=%r)"
92
+ % (env_var_name, os.environ.get(env_var_name), default_path))
93
+ return tuple(pathnames)
94
+
95
+
96
+ # TODO: stop memoizing here after using StatCache. Actually just inline into
97
+ # _ancestors_on_same_partition
98
+ @memoize
99
+ def _get_st_dev(filename):
100
+ filename = Filename(filename)
101
+ try:
102
+ return os.stat(str(filename)).st_dev
103
+ except OSError:
104
+ return None
105
+
106
+
107
+ def _ancestors_on_same_partition(filename):
108
+ """
109
+ Generate ancestors of ``filename`` that exist and are on the same partition
110
+ as the first existing ancestor of ``filename``.
111
+
112
+ For example, suppose a partition is mounted on /u/homer; /u is a different
113
+ partition. Suppose /u/homer/aa exists but /u/homer/aa/bb does not exist.
114
+ Then::
115
+
116
+ >>> _ancestors_on_same_partition(Filename("/u/homer/aa/bb/cc")) # doctest: +SKIP
117
+ [Filename("/u/homer", Filename("/u/homer/aa")]
118
+
119
+ :rtype:
120
+ ``list`` of ``Filename``
121
+ """
122
+ result = []
123
+ dev = None
124
+ for f in filename.ancestors:
125
+ this_dev = _get_st_dev(f)
126
+ if this_dev is None:
127
+ continue
128
+ if dev is None:
129
+ dev = this_dev
130
+ elif dev != this_dev:
131
+ break
132
+ result.append(f)
133
+ return result
134
+
135
+
136
+ def _expand_tripledots(pathnames, target_dirname):
137
+ """
138
+ Expand pathnames of the form ``".../foo/bar"`` as "../../foo/bar",
139
+ "../foo/bar", "./foo/bar" etc., up to the oldest ancestor with the same
140
+ st_dev.
141
+
142
+ For example, suppose a partition is mounted on /u/homer; /u is a different
143
+ partition. Then::
144
+
145
+ >>> _expand_tripledots(["/foo", ".../tt"], "/u/homer/aa") # doctest: +SKIP
146
+ [Filename("/foo"), Filename("/u/homer/tt"), Filename("/u/homer/aa/tt")]
147
+
148
+ :type pathnames:
149
+ sequence of ``str`` (not ``Filename``)
150
+ :type target_dirname:
151
+ `Filename`
152
+ :rtype:
153
+ ``list`` of `Filename`
154
+ """
155
+ target_dirname = Filename(target_dirname)
156
+ if not isinstance(pathnames, (tuple, list)):
157
+ pathnames = [pathnames]
158
+ result = []
159
+ for pathname in pathnames:
160
+ if not pathname.startswith(".../"):
161
+ result.append(Filename(pathname))
162
+ continue
163
+ suffix = pathname[4:]
164
+ expanded = []
165
+ for p in _ancestors_on_same_partition(target_dirname):
166
+ try:
167
+ expanded.append(p / suffix)
168
+ except UnsafeFilenameError:
169
+ continue
170
+ result.extend(expanded[::-1])
171
+ return result
172
+
173
+
174
+ class ImportDB(object):
175
+ """
176
+ A database of known, mandatory, canonical imports.
177
+
178
+ @iattr known_imports:
179
+ Set of known imports. For use by tidy-imports and autoimporter.
180
+ @iattr mandatory_imports:
181
+ Set of imports that must be added by tidy-imports.
182
+ @iattr canonical_imports:
183
+ Map of imports that tidy-imports transforms on every run.
184
+ @iattr forget_imports:
185
+ Set of imports to remove from known_imports, mandatory_imports,
186
+ canonical_imports.
187
+ """
188
+
189
+ def __new__(cls, *args):
190
+ if len(args) != 1:
191
+ raise TypeError
192
+ arg, = args
193
+ if isinstance(arg, cls):
194
+ return arg
195
+ if isinstance(arg, ImportSet):
196
+ return cls._from_data(arg, [], [], [])
197
+ return cls._from_args(arg) # PythonBlock, Filename, etc
198
+
199
+
200
+ _default_cache: Dict[Any, Any] = {}
201
+
202
+ @classmethod
203
+ def clear_default_cache(cls):
204
+ """
205
+ Clear the class cache of default ImportDBs.
206
+
207
+ Subsequent calls to ImportDB.get_default() will not reuse previously
208
+ cached results. Existing ImportDB instances are not affected by this
209
+ call.
210
+ """
211
+ if cls._default_cache:
212
+ if logger.debug_enabled:
213
+ allpyfiles = set()
214
+ for tup in cls._default_cache:
215
+ if tup[0] != 2:
216
+ continue
217
+ for tup2 in tup[1:]:
218
+ for f in tup2:
219
+ assert isinstance(f, Filename)
220
+ if f.ext == '.py':
221
+ allpyfiles.add(f)
222
+ nfiles = len(allpyfiles)
223
+ logger.debug("ImportDB: Clearing default cache of %d files",
224
+ nfiles)
225
+ cls._default_cache.clear()
226
+
227
+ @classmethod
228
+ def get_default(cls, target_filename):
229
+ """
230
+ Return the default import library for the given target filename.
231
+
232
+ This will read various .../.pyflyby files as specified by
233
+ $PYFLYBY_PATH as well as older deprecated environment variables.
234
+
235
+ Memoized.
236
+
237
+ :param target_filename:
238
+ The target filename for which to get the import database. Note that
239
+ the target filename itself is not read. Instead, the target
240
+ filename is relevant because we look for .../.pyflyby based on the
241
+ target filename.
242
+ :rtype:
243
+ `ImportDB`
244
+ """
245
+ # We're going to canonicalize target_filenames in a number of steps.
246
+ # At each step, see if we've seen the input so far. We do the cache
247
+ # checking incrementally since the steps involve syscalls. Since this
248
+ # is going to potentially be executed inside the IPython interactive
249
+ # loop, we cache as much as possible.
250
+ # TODO: Consider refreshing periodically. Check if files have
251
+ # been touched, and if so, return new data. Check file timestamps at
252
+ # most once every 60 seconds.
253
+ cache_keys = []
254
+ target_filename = Filename(target_filename or ".")
255
+ if target_filename.startswith("/dev"):
256
+ target_filename = Filename(".")
257
+ target_dirname = target_filename
258
+ # TODO: with StatCache
259
+ while True:
260
+ cache_keys.append((1,
261
+ target_dirname,
262
+ os.getenv("PYFLYBY_PATH"),
263
+ os.getenv("PYFLYBY_KNOWN_IMPORTS_PATH"),
264
+ os.getenv("PYFLYBY_MANDATORY_IMPORTS_PATH")))
265
+ try:
266
+ return cls._default_cache[cache_keys[-1]]
267
+ except KeyError:
268
+ pass
269
+ if target_dirname.isdir:
270
+ break
271
+ target_dirname = target_dirname.dir
272
+ try:
273
+ target_dirname = target_dirname.real
274
+ except UnsafeFilenameError:
275
+ pass
276
+ if target_dirname != cache_keys[-1][0]:
277
+ cache_keys.append((1,
278
+ target_dirname,
279
+ os.getenv("PYFLYBY_PATH"),
280
+ os.getenv("PYFLYBY_KNOWN_IMPORTS_PATH"),
281
+ os.getenv("PYFLYBY_MANDATORY_IMPORTS_PATH")))
282
+ try:
283
+ return cls._default_cache[cache_keys[-1]]
284
+ except KeyError:
285
+ pass
286
+ DEFAULT_PYFLYBY_PATH = []
287
+ DEFAULT_PYFLYBY_PATH += [str(p) for p in _find_etc_dirs()]
288
+ DEFAULT_PYFLYBY_PATH += [
289
+ ".../.pyflyby",
290
+ "~/.pyflyby",
291
+ ]
292
+ logger.debug("DEFAULT_PYFLYBY_PATH=%s", DEFAULT_PYFLYBY_PATH)
293
+ filenames = _get_python_path("PYFLYBY_PATH", DEFAULT_PYFLYBY_PATH,
294
+ target_dirname)
295
+ mandatory_imports_filenames = ()
296
+ if "SUPPORT DEPRECATED BEHAVIOR":
297
+ PYFLYBY_PATH = _get_env_var("PYFLYBY_PATH", DEFAULT_PYFLYBY_PATH)
298
+ # If the old deprecated environment variables are set, then heed
299
+ # them.
300
+ if os.getenv("PYFLYBY_KNOWN_IMPORTS_PATH"):
301
+ # Use PYFLYBY_PATH as the default for
302
+ # PYFLYBY_KNOWN_IMPORTS_PATH. Note that the default is
303
+ # relevant even though we only enter this code path when the
304
+ # variable is set to anything, because the env var can
305
+ # reference "-" to include the default.
306
+ # Before pyflyby version 0.8, the default value would have
307
+ # been
308
+ # [d/"known_imports" for d in PYFLYBY_PATH]
309
+ # Instead of using that, we just use PYFLYBY_PATH directly as
310
+ # the default. This simplifies things and avoids need for a
311
+ # "known_imports=>." symlink for backwards compatibility. It
312
+ # means that ~/.pyflyby/**/*.py (as opposed to only
313
+ # ~/.pyflyby/known_imports/**/*.py) would be included.
314
+ # Although this differs slightly from the old behavior, it
315
+ # matches the behavior of the newer PYFLYBY_PATH; matching the
316
+ # new behavior seems higher utility than exactly matching the
317
+ # old behavior. Files under ~/.pyflyby/mandatory_imports will
318
+ # be included in known_imports as well, but that should not
319
+ # cause any problems.
320
+ default_path = PYFLYBY_PATH
321
+ # Expand $PYFLYBY_KNOWN_IMPORTS_PATH.
322
+ filenames = _get_python_path(
323
+ "PYFLYBY_KNOWN_IMPORTS_PATH", default_path, target_dirname)
324
+ logger.debug(
325
+ "The environment variable PYFLYBY_KNOWN_IMPORTS_PATH is deprecated. "
326
+ "Use PYFLYBY_PATH.")
327
+ if os.getenv("PYFLYBY_MANDATORY_IMPORTS_PATH"):
328
+ # Compute the "default" path.
329
+ # Note that we still calculate the erstwhile default value,
330
+ # even though it's no longer the defaults, in order to still
331
+ # allow the "-" in the variable.
332
+ default_path = [
333
+ os.path.join(d,"mandatory_imports") for d in PYFLYBY_PATH]
334
+ # Expand $PYFLYBY_MANDATORY_IMPORTS_PATH.
335
+ mandatory_imports_filenames = _get_python_path(
336
+ "PYFLYBY_MANDATORY_IMPORTS_PATH",
337
+ default_path, target_dirname)
338
+ logger.debug(
339
+ "The environment variable PYFLYBY_MANDATORY_IMPORTS_PATH is deprecated. "
340
+ "Use PYFLYBY_PATH and write __mandatory_imports__=['...'] in your files.")
341
+ cache_keys.append((2, filenames, mandatory_imports_filenames))
342
+ try:
343
+ return cls._default_cache[cache_keys[-1]]
344
+ except KeyError:
345
+ pass
346
+ result = cls._from_filenames(filenames, mandatory_imports_filenames)
347
+ for k in cache_keys:
348
+ cls._default_cache[k] = result
349
+ return result
350
+
351
+ @classmethod
352
+ def interpret_arg(cls, arg, target_filename):
353
+ if arg is None:
354
+ return cls.get_default(target_filename)
355
+ else:
356
+ return cls(arg)
357
+
358
+ @classmethod
359
+ def _from_data(cls, known_imports, mandatory_imports,
360
+ canonical_imports, forget_imports):
361
+ self = object.__new__(cls)
362
+ self.forget_imports = ImportSet(forget_imports )
363
+ self.known_imports = ImportSet(known_imports ).without_imports(forget_imports)
364
+ self.mandatory_imports = ImportSet(mandatory_imports).without_imports(forget_imports)
365
+ # TODO: provide more fine-grained control about canonical_imports.
366
+ self.canonical_imports = ImportMap(canonical_imports).without_imports(forget_imports)
367
+ return self
368
+
369
+ @classmethod
370
+ def _from_args(cls, args):
371
+ # TODO: support merging input ImportDBs. For now we support
372
+ # `PythonBlock` s and convertibles such as `Filename`.
373
+ return cls._from_code(args)
374
+
375
+ @classmethod
376
+ def _from_code(cls, blocks,
377
+ _mandatory_imports_blocks_deprecated=(),
378
+ _forget_imports_blocks_deprecated=(),
379
+ ):
380
+ """
381
+ Load an import database from code.
382
+
383
+ >>> ImportDB._from_code('''
384
+ ... import foo, bar as barf
385
+ ... from xx import yy
386
+ ... __mandatory_imports__ = ['__future__.division',
387
+ ... 'import aa . bb . cc as dd']
388
+ ... __forget_imports__ = ['xx.yy', 'from xx import zz']
389
+ ... __canonical_imports__ = {'bad.baad': 'good.goood'}
390
+ ... ''')
391
+ ImportDB('''
392
+ import bar as barf
393
+ import foo
394
+ <BLANKLINE>
395
+ __mandatory_imports__ = [
396
+ 'from __future__ import division',
397
+ 'from aa.bb import cc as dd',
398
+ ]
399
+ <BLANKLINE>
400
+ __canonical_imports__ = {
401
+ 'bad.baad': 'good.goood',
402
+ }
403
+ <BLANKLINE>
404
+ __forget_imports__ = [
405
+ 'from xx import yy',
406
+ 'from xx import zz',
407
+ ]
408
+ ''')
409
+
410
+ :rtype:
411
+ `ImportDB`
412
+ """
413
+ if not isinstance(blocks, (tuple, list)):
414
+ blocks = [blocks]
415
+ if not isinstance(_mandatory_imports_blocks_deprecated, (tuple, list)):
416
+ _mandatory_imports_blocks_deprecated = [_mandatory_imports_blocks_deprecated]
417
+ if not isinstance(_forget_imports_blocks_deprecated, (tuple, list)):
418
+ _forget_imports_blocks_deprecated = [_forget_imports_blocks_deprecated]
419
+ known_imports = []
420
+ mandatory_imports = []
421
+ canonical_imports = []
422
+ forget_imports = []
423
+ blocks = [PythonBlock(b) for b in blocks]
424
+ for block in blocks:
425
+ for statement in block.statements:
426
+ if statement.is_comment_or_blank:
427
+ continue
428
+ if statement.is_import:
429
+ known_imports.extend(ImportStatement(statement).imports)
430
+ continue
431
+ try:
432
+ name, value = statement.get_assignment_literal_value()
433
+ if name == "__mandatory_imports__":
434
+ mandatory_imports.append(cls._parse_import_set(value))
435
+ elif name == "__canonical_imports__":
436
+ canonical_imports.append(cls._parse_import_map(value))
437
+ elif name == "__forget_imports__":
438
+ forget_imports.append(cls._parse_import_set(value))
439
+ else:
440
+ raise ValueError(
441
+ "Unknown assignment to %r (expected one of "
442
+ "__mandatory_imports__, __canonical_imports__, "
443
+ "__forget_imports__)" % (name,))
444
+ except ValueError as e:
445
+ raise ValueError(
446
+ "While parsing %s: error in %r: %s"
447
+ % (block.filename, statement, e))
448
+ for block in _mandatory_imports_blocks_deprecated:
449
+ mandatory_imports.append(ImportSet(block))
450
+ for block in _forget_imports_blocks_deprecated:
451
+ forget_imports.append(ImportSet(block))
452
+ return cls._from_data(known_imports,
453
+ mandatory_imports,
454
+ canonical_imports,
455
+ forget_imports)
456
+
457
+ @classmethod
458
+ def _from_filenames(cls, filenames, _mandatory_filenames_deprecated=[]):
459
+ """
460
+ Load an import database from filenames.
461
+
462
+ This function exists to support deprecated behavior.
463
+ When we stop supporting the old behavior, we will delete this function.
464
+
465
+ :type filenames:
466
+ Sequence of `Filename` s
467
+ :param filenames:
468
+ Filenames of files to read.
469
+ :rtype:
470
+ `ImportDB`
471
+ """
472
+ if not isinstance(filenames, (tuple, list)):
473
+ filenames = [filenames]
474
+ filenames = [Filename(f) for f in filenames]
475
+ logger.debug("ImportDB: loading [%s], mandatory=[%s]",
476
+ ', '.join(map(str, filenames)),
477
+ ', '.join(map(str, _mandatory_filenames_deprecated)))
478
+ if "SUPPORT DEPRECATED BEHAVIOR":
479
+ # Before 2014-10, pyflyby read the following:
480
+ # * known_imports from $PYFLYBY_PATH/known_imports/**/*.py or
481
+ # $PYFLYBY_KNOWN_IMPORTS_PATH/**/*.py,
482
+ # * mandatory_imports from $PYFLYBY_PATH/mandatory_imports/**/*.py or
483
+ # $PYFLYBY_MANDATORY_IMPORTS_PATH/**/*.py, and
484
+ # * forget_imports from $PYFLYBY_PATH/known_imports/**/__remove__.py
485
+ # After 2014-10, pyflyby reads the following:
486
+ # * $PYFLYBY_PATH/**/*.py
487
+ # (with directives inside the file)
488
+ # For backwards compatibility, for now we continue supporting the
489
+ # old, deprecated behavior.
490
+ blocks = []
491
+ mandatory_imports_blocks = [
492
+ Filename(f) for f in _mandatory_filenames_deprecated]
493
+ forget_imports_blocks = []
494
+ for filename in filenames:
495
+ if filename.base == "__remove__.py":
496
+ forget_imports_blocks.append(filename)
497
+ elif "mandatory_imports" in str(filename).split("/"):
498
+ mandatory_imports_blocks.append(filename)
499
+ else:
500
+ blocks.append(filename)
501
+ return cls._from_code(
502
+ blocks, mandatory_imports_blocks, forget_imports_blocks)
503
+ else:
504
+ return cls._from_code(filenames)
505
+
506
+ @classmethod
507
+ def _parse_import_set(cls, arg):
508
+ if isinstance(arg, str):
509
+ arg = [arg]
510
+ if not isinstance(arg, (tuple, list)):
511
+ raise ValueError("Expected a list, not a %s" % (type(arg).__name__,))
512
+ for item in arg:
513
+ if not isinstance(item, str):
514
+ raise ValueError(
515
+ "Expected a list of str, not %s" % (type(item).__name__,))
516
+ return ImportSet(arg)
517
+
518
+ @classmethod
519
+ def _parse_import_map(cls, arg):
520
+ if isinstance(arg, str):
521
+ arg = [arg]
522
+ if not isinstance(arg, dict):
523
+ raise ValueError("Expected a dict, not a %s" % (type(arg).__name__,))
524
+ for k, v in arg.items():
525
+ if not isinstance(k, str):
526
+ raise ValueError(
527
+ "Expected a dict of str, not %s" % (type(k).__name__,))
528
+ if not isinstance(v, str):
529
+ raise ValueError(
530
+ "Expected a dict of str, not %s" % (type(v).__name__,))
531
+ return ImportMap(arg)
532
+
533
+ @cached_attribute
534
+ def by_fullname_or_import_as(self):
535
+ """
536
+ Map from ``fullname`` and ``import_as`` to `Import` s.
537
+
538
+ >>> import pprint
539
+ >>> db = ImportDB('from aa.bb import cc as dd')
540
+ >>> pprint.pprint(db.by_fullname_or_import_as)
541
+ {'aa': (Import('import aa'),),
542
+ 'aa.bb': (Import('import aa.bb'),),
543
+ 'dd': (Import('from aa.bb import cc as dd'),)}
544
+
545
+ :rtype:
546
+ ``dict`` mapping from ``str`` to tuple of `Import` s
547
+ """
548
+ # TODO: make known_imports take into account the below forget_imports,
549
+ # then move this function into ImportSet
550
+ d = defaultdict(set)
551
+ for imp in self.known_imports.imports:
552
+ # Given an import like "from foo.bar import quux as QUUX", add the
553
+ # following entries:
554
+ # - "QUUX" => "from foo.bar import quux as QUUX"
555
+ # - "foo.bar" => "import foo.bar"
556
+ # - "foo" => "import foo"
557
+ # We don't include an entry labeled "quux" because the user has
558
+ # implied he doesn't want to pollute the global namespace with
559
+ # "quux", only "QUUX".
560
+ d[imp.import_as].add(imp)
561
+ for prefix in dotted_prefixes(imp.fullname)[:-1]:
562
+ d[prefix].add(Import.from_parts(prefix, prefix))
563
+ return dict( (k, tuple(sorted(v - set(self.forget_imports.imports))))
564
+ for k, v in d.items())
565
+
566
+ def __repr__(self):
567
+ printed = self.pretty_print()
568
+ lines = "".join(" "+line for line in printed.splitlines(True))
569
+ return "%s('''\n%s''')" % (type(self).__name__, lines)
570
+
571
+ def pretty_print(self):
572
+ s = self.known_imports.pretty_print()
573
+ if self.mandatory_imports:
574
+ s += "\n__mandatory_imports__ = [\n"
575
+ for imp in self.mandatory_imports.imports:
576
+ s += " '%s',\n" % imp
577
+ s += "]\n"
578
+ if self.canonical_imports:
579
+ s += "\n__canonical_imports__ = {\n"
580
+ for k, v in sorted(self.canonical_imports.items()):
581
+ s += " '%s': '%s',\n" % (k, v)
582
+ s += "}\n"
583
+ if self.forget_imports:
584
+ s += "\n__forget_imports__ = [\n"
585
+ for imp in self.forget_imports.imports:
586
+ s += " '%s',\n" % imp
587
+ s += "]\n"
588
+ return s