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/_importclns.py ADDED
@@ -0,0 +1,642 @@
1
+ # pyflyby/_importclns.py.
2
+ # Copyright (C) 2011, 2012, 2013, 2014 Karl Chen.
3
+ # License: MIT http://opensource.org/licenses/MIT
4
+
5
+ from __future__ import annotations
6
+
7
+
8
+
9
+ from collections import defaultdict
10
+ from functools import total_ordering
11
+
12
+ from pyflyby._flags import CompilerFlags
13
+ from pyflyby._idents import dotted_prefixes, is_identifier
14
+ from pyflyby._importstmt import (Import, ImportFormatParams,
15
+ ImportStatement,
16
+ NonImportStatementError)
17
+ from pyflyby._parse import PythonBlock
18
+ from pyflyby._util import (cached_attribute, cmp, partition,
19
+ stable_unique)
20
+
21
+ from typing import (ClassVar, Dict, FrozenSet, List,
22
+ Sequence, Union)
23
+
24
+
25
+ class NoSuchImportError(ValueError):
26
+ pass
27
+
28
+
29
+ class ConflictingImportsError(ValueError):
30
+ pass
31
+
32
+ @total_ordering
33
+ class ImportSet(object):
34
+ r"""
35
+ Representation of a set of imports organized into import statements.
36
+
37
+ >>> ImportSet('''
38
+ ... from m1 import f1
39
+ ... from m2 import f1
40
+ ... from m1 import f2
41
+ ... import m3.m4 as m34
42
+ ... ''')
43
+ ImportSet('''
44
+ from m1 import f1, f2
45
+ from m2 import f1
46
+ from m3 import m4 as m34
47
+ ''')
48
+
49
+ An ``ImportSet`` is an immutable data structure.
50
+ """
51
+
52
+ _EMPTY : ClassVar[ImportSet]
53
+ _importset : FrozenSet[Import]
54
+
55
+ def __new__(cls, arg, ignore_nonimports=False, ignore_shadowed=False):
56
+ """
57
+ Return as an `ImportSet`.
58
+
59
+ :param ignore_nonimports:
60
+ If ``False``, complain about non-imports. If ``True``, ignore
61
+ non-imports.
62
+ :param ignore_shadowed:
63
+ Whether to ignore shadowed imports. If ``False``, then keep all
64
+ unique imports, even if they shadow each other. Note that an
65
+ ``ImportSet`` is unordered; an ``ImportSet`` with conflicts will only
66
+ be useful for very specific cases (e.g. set of imports to forget
67
+ from known-imports database), and not useful for outputting as code.
68
+ If ``ignore_shadowed`` is ``True``, then earlier shadowed imports are
69
+ ignored.
70
+ :rtype:
71
+ `ImportSet`
72
+ """
73
+ if isinstance(arg, cls):
74
+ if ignore_shadowed:
75
+ return cls._from_imports(list(arg._importset), ignore_shadowed=True)
76
+ else:
77
+ return arg
78
+ return cls._from_args(
79
+ arg,
80
+ ignore_nonimports=ignore_nonimports,
81
+ ignore_shadowed=ignore_shadowed)
82
+
83
+ @classmethod
84
+ def _from_imports(cls, imports:List[Import], ignore_shadowed:bool=False):
85
+ """
86
+ :type imports:
87
+ Sequence of `Import` s
88
+ :param ignore_shadowed:
89
+ See `ImportSet.__new__`.
90
+ :rtype:
91
+ `ImportSet`
92
+ """
93
+ # Canonicalize inputs.
94
+ by_import_as: Dict[Union[str, Import], Import]
95
+ filtered_imports: Sequence[Import]
96
+ for imp in imports:
97
+ assert isinstance(imp, Import)
98
+ _imports = [Import(imp) for imp in imports]
99
+ if ignore_shadowed:
100
+ # Filter by overshadowed imports. Later imports take precedence.
101
+ by_import_as = {}
102
+ for imp in _imports:
103
+ if imp.import_as == "*":
104
+ # Keep all unique star imports.
105
+ by_import_as[imp] = imp
106
+ else:
107
+ by_import_as[imp.import_as] = imp
108
+ filtered_imports = list(by_import_as.values())
109
+ else:
110
+ filtered_imports = _imports
111
+ # Construct and return.
112
+ self = object.__new__(cls)
113
+ self._importset = frozenset(filtered_imports)
114
+ return self
115
+
116
+ @classmethod
117
+ def _from_args(cls, args, ignore_nonimports:bool=False, ignore_shadowed=False):
118
+ """
119
+ :type args:
120
+ ``tuple`` or ``list`` of `ImportStatement` s, `PythonStatement` s,
121
+ `PythonBlock` s, `FileText`, and/or `Filename` s
122
+ :param ignore_nonimports:
123
+ If ``False``, complain about non-imports. If ``True``, ignore
124
+ non-imports.
125
+ :param ignore_shadowed:
126
+ See `ImportSet.__new__`.
127
+ :rtype:
128
+ `ImportSet`
129
+ """
130
+ if not isinstance(args, (tuple, list)):
131
+ args = [args]
132
+ # Filter empty arguments to allow the subsequent optimizations to work
133
+ # more often.
134
+ args = [a for a in args if a]
135
+ if not args:
136
+ return cls._EMPTY
137
+ # If we only got one ``ImportSet``, just return it.
138
+ if len(args) == 1 and type(args[0]) is cls and not ignore_shadowed:
139
+ return args[0]
140
+ # Collect all `Import` s from arguments.
141
+ imports = []
142
+ for arg in args:
143
+ if isinstance(arg, Import):
144
+ imports.append(arg)
145
+ elif isinstance(arg, ImportSet):
146
+ imports.extend(arg.imports)
147
+ elif isinstance(arg, ImportStatement):
148
+ imports.extend(arg.imports)
149
+ elif isinstance(arg, str) and is_identifier(arg, dotted=True):
150
+ imports.append(Import(arg))
151
+ else: # PythonBlock, PythonStatement, Filename, FileText, str
152
+ if not isinstance(arg, PythonBlock):
153
+ block = PythonBlock(arg)
154
+ else:
155
+ block = arg
156
+ for statement in block.statements:
157
+ # Ignore comments/blanks.
158
+ if statement.is_comment_or_blank:
159
+ pass
160
+ elif statement.is_import:
161
+ imports.extend(ImportStatement(statement).imports)
162
+ elif ignore_nonimports:
163
+ pass
164
+ else:
165
+ raise NonImportStatementError(
166
+ "Got non-import statement %r" % (statement,))
167
+ return cls._from_imports(imports, ignore_shadowed=ignore_shadowed)
168
+
169
+ def with_imports(self, other):
170
+ """
171
+ Return a new `ImportSet` that is the union of ``self`` and
172
+ ``new_imports``.
173
+
174
+ >>> impset = ImportSet('from m import t1, t2, t3')
175
+ >>> impset.with_imports('import m.t2a as t2b')
176
+ ImportSet('''
177
+ from m import t1, t2, t2a as t2b, t3
178
+ ''')
179
+
180
+ :type other:
181
+ `ImportSet` (or convertible)
182
+ :rtype:
183
+ `ImportSet`
184
+ """
185
+ other = ImportSet(other)
186
+ return type(self)._from_imports(list(self._importset | other._importset))
187
+
188
+ def without_imports(self, removals):
189
+ """
190
+ Return a copy of self without the given imports.
191
+
192
+ >>> imports = ImportSet('from m import t1, t2, t3, t4')
193
+ >>> imports.without_imports(['from m import t3'])
194
+ ImportSet('''
195
+ from m import t1, t2, t4
196
+ ''')
197
+
198
+ :type removals:
199
+ `ImportSet` (or convertible)
200
+ :rtype:
201
+ `ImportSet`
202
+ """
203
+ removals = ImportSet(removals)
204
+ if not removals:
205
+ return self # Optimization
206
+ # Preprocess star imports to remove.
207
+ star_module_removals = set(
208
+ [imp.split.module_name
209
+ for imp in removals if imp.split.member_name == "*"])
210
+ # Filter imports.
211
+ new_imports = []
212
+ for imp in self:
213
+ if imp in removals:
214
+ continue
215
+ if star_module_removals and imp.split.module_name:
216
+ prefixes = dotted_prefixes(imp.split.module_name)
217
+ if any(pfx in star_module_removals for pfx in prefixes):
218
+ continue
219
+ new_imports.append(imp)
220
+ # Return.
221
+ if len(new_imports) == len(self):
222
+ return self # Space optimization
223
+ return type(self)._from_imports(new_imports)
224
+
225
+ @cached_attribute
226
+ def _by_module_name(self):
227
+ """
228
+ :return:
229
+ (mapping from name to __future__ imports,
230
+ mapping from name to non-'from' imports,
231
+ mapping from name to 'from' imports)
232
+ """
233
+ ftr_imports = defaultdict(set)
234
+ pkg_imports = defaultdict(set)
235
+ frm_imports = defaultdict(set)
236
+ for imp in self._importset:
237
+ module_name, member_name, import_as = imp.split
238
+ if module_name is None:
239
+ pkg_imports[member_name].add(imp)
240
+ elif module_name == '__future__':
241
+ ftr_imports[module_name].add(imp)
242
+ else:
243
+ frm_imports[module_name].add(imp)
244
+ return tuple(
245
+ dict((k, frozenset(v)) for k, v in imports.items())
246
+ for imports in [ftr_imports, pkg_imports, frm_imports]
247
+ )
248
+
249
+ def get_statements(self, separate_from_imports=True):
250
+ """
251
+ Canonicalized `ImportStatement` s.
252
+ These have been merged by module and sorted.
253
+
254
+ >>> importset = ImportSet('''
255
+ ... import a, b as B, c, d.dd as DD
256
+ ... from __future__ import division
257
+ ... from _hello import there
258
+ ... from _hello import *
259
+ ... from _hello import world
260
+ ... ''')
261
+
262
+ >>> for s in importset.get_statements(): print(s)
263
+ from __future__ import division
264
+ import a
265
+ import b as B
266
+ import c
267
+ from _hello import *
268
+ from _hello import there, world
269
+ from d import dd as DD
270
+
271
+ :rtype:
272
+ ``tuple`` of `ImportStatement` s
273
+ """
274
+ groups = self._by_module_name
275
+ if not separate_from_imports:
276
+ def union_dicts(*dicts):
277
+ result = {}
278
+ for label, dct in enumerate(dicts):
279
+ for k, v in dct.items():
280
+ result[(k, label)] = v
281
+ return result
282
+ groups = [groups[0], union_dicts(*groups[1:])]
283
+ result = []
284
+ for importgroup in groups:
285
+ for _, imports in sorted(importgroup.items()):
286
+ star_imports, nonstar_imports = (
287
+ partition(imports, lambda imp: imp.import_as == "*"))
288
+ assert len(star_imports) <= 1
289
+ if star_imports:
290
+ result.append(ImportStatement(star_imports))
291
+ if nonstar_imports:
292
+ result.append(ImportStatement(sorted(nonstar_imports)))
293
+ return tuple(result)
294
+
295
+ @cached_attribute
296
+ def statements(self):
297
+ """
298
+ Canonicalized `ImportStatement` s.
299
+ These have been merged by module and sorted.
300
+
301
+ :rtype:
302
+ ``tuple`` of `ImportStatement` s
303
+ """
304
+ return self.get_statements(separate_from_imports=True)
305
+
306
+ @cached_attribute
307
+ def imports(self):
308
+ """
309
+ Canonicalized imports, in the same order as ``self.statements``.
310
+
311
+ :rtype:
312
+ ``tuple`` of `Import` s
313
+ """
314
+ return tuple(
315
+ imp
316
+ for importgroup in self._by_module_name
317
+ for _, imports in sorted(importgroup.items())
318
+ for imp in sorted(imports))
319
+
320
+ @cached_attribute
321
+ def by_import_as(self):
322
+ """
323
+ Map from ``import_as`` to `Import`.
324
+
325
+ >>> ImportSet('from aa.bb import cc as dd').by_import_as
326
+ {'dd': (Import('from aa.bb import cc as dd'),)}
327
+
328
+ :rtype:
329
+ ``dict`` mapping from ``str`` to tuple of `Import` s
330
+ """
331
+ d = defaultdict(list)
332
+ for imp in self._importset:
333
+ d[imp.import_as].append(imp)
334
+ return dict((k, tuple(sorted(stable_unique(v)))) for k, v in d.items())
335
+
336
+ @cached_attribute
337
+ def member_names(self):
338
+ r"""
339
+ Map from parent module/package ``fullname`` to known member names.
340
+
341
+ >>> impset = ImportSet("import numpy.linalg.info\nfrom sys import exit as EXIT")
342
+ >>> import pprint
343
+ >>> pprint.pprint(impset.member_names)
344
+ {'': ('EXIT', 'numpy', 'sys'),
345
+ 'numpy': ('linalg',),
346
+ 'numpy.linalg': ('info',),
347
+ 'sys': ('exit',)}
348
+
349
+ This is used by the autoimporter module for implementing tab completion.
350
+
351
+ :rtype:
352
+ ``dict`` mapping from ``str`` to tuple of ``str``
353
+ """
354
+ d = defaultdict(set)
355
+ for imp in self._importset:
356
+ if '.' not in imp.import_as:
357
+ d[""].add(imp.import_as)
358
+ prefixes = dotted_prefixes(imp.fullname)
359
+ d[""].add(prefixes[0])
360
+ for prefix in prefixes[1:]:
361
+ splt = prefix.rsplit(".", 1)
362
+ d[splt[0]].add(splt[1])
363
+ return dict((k, tuple(sorted(v))) for k, v in d.items())
364
+
365
+ @cached_attribute
366
+ def conflicting_imports(self):
367
+ r"""
368
+ Returns imports that conflict with each other.
369
+
370
+ >>> ImportSet('import b\nfrom f import a as b\n').conflicting_imports
371
+ ('b',)
372
+
373
+ >>> ImportSet('import b\nfrom f import a\n').conflicting_imports
374
+ ()
375
+
376
+ :rtype:
377
+ ``bool``
378
+ """
379
+ return tuple(k for k, v in self.by_import_as.items() if len(v) > 1 and k != "*")
380
+
381
+ @cached_attribute
382
+ def flags(self):
383
+ """
384
+ If this contains __future__ imports, then the bitwise-ORed of the
385
+ compiler_flag values associated with the features. Otherwise, 0.
386
+ """
387
+ imports = self._by_module_name[0].get("__future__", [])
388
+ return CompilerFlags(*[imp.flags for imp in imports])
389
+
390
+ def __repr__(self):
391
+ printed = self.pretty_print(allow_conflicts=True)
392
+ lines = "".join(" "+line for line in printed.splitlines(True))
393
+ return "%s('''\n%s''')" % (type(self).__name__, lines)
394
+
395
+ def pretty_print(self, params=None, allow_conflicts=False):
396
+ """
397
+ Pretty-print a block of import statements into a single string.
398
+
399
+ :type params:
400
+ `ImportFormatParams`
401
+ :rtype:
402
+ ``str``
403
+ """
404
+ params = ImportFormatParams(params)
405
+ # TODO: instead of complaining about conflicts, just filter out the
406
+ # shadowed imports at construction time.
407
+ if not allow_conflicts and self.conflicting_imports:
408
+ raise ConflictingImportsError(
409
+ "Refusing to pretty-print because of conflicting imports: " +
410
+ '; '.join(
411
+ "%r imported as %r" % (
412
+ [imp.fullname for imp in self.by_import_as[i]], i)
413
+ for i in self.conflicting_imports))
414
+ from_spaces = max(1, params.from_spaces)
415
+ def do_align(statement):
416
+ return statement.fromname != '__future__' or params.align_future
417
+ def pp(statement, import_column):
418
+ if do_align(statement):
419
+ return statement.pretty_print(
420
+ params=params, import_column=import_column,
421
+ from_spaces=from_spaces)
422
+ else:
423
+ return statement.pretty_print(
424
+ params=params, import_column=None, from_spaces=1)
425
+ statements = self.get_statements(
426
+ separate_from_imports=params.separate_from_imports)
427
+ def isint(x): return isinstance(x, int) and not isinstance(x, bool)
428
+ if not statements:
429
+ import_column = None
430
+ elif isinstance(params.align_imports, bool):
431
+ if params.align_imports:
432
+ fromimp_stmts = [
433
+ s for s in statements if s.fromname and do_align(s)]
434
+ if fromimp_stmts:
435
+ import_column = (
436
+ max(len(s.fromname) for s in fromimp_stmts)
437
+ + from_spaces + 5)
438
+ else:
439
+ import_column = None
440
+ else:
441
+ import_column = None
442
+ elif isinstance(params.align_imports, int):
443
+ import_column = params.align_imports
444
+ elif isinstance(params.align_imports, (tuple, list, set)):
445
+ # If given a set of candidate alignment columns, then try each
446
+ # alignment column and pick the one that yields the fewest number
447
+ # of output lines.
448
+ if not all(isinstance(x, int) for x in params.align_imports):
449
+ raise TypeError("expected set of integers; got %r"
450
+ % (params.align_imports,))
451
+ candidates = sorted(set(params.align_imports))
452
+ if len(candidates) == 0:
453
+ raise ValueError("list of zero candidate alignment columns specified")
454
+ elif len(candidates) == 1:
455
+ # Optimization.
456
+ import_column = next(iter(candidates))
457
+ else:
458
+ def argmin(map):
459
+ items = iter(sorted(map.items()))
460
+ min_k, min_v = next(items)
461
+ for k, v in items:
462
+ if v < min_v:
463
+ min_k = k
464
+ min_v = v
465
+ return min_k
466
+ def count_lines(import_column):
467
+ return sum(
468
+ s.pretty_print(
469
+ params=params, import_column=import_column,
470
+ from_spaces=from_spaces).count("\n")
471
+ for s in statements)
472
+ # Construct a map from alignment column to total number of
473
+ # lines.
474
+ col2length = dict((c, count_lines(c)) for c in candidates)
475
+ # Pick the column that yields the fewest lines. Break ties by
476
+ # picking the smaller column.
477
+ import_column = argmin(col2length)
478
+ else:
479
+ raise TypeError(
480
+ "ImportSet.pretty_print(): unexpected params.align_imports type %s"
481
+ % (type(params.align_imports).__name__,))
482
+ return ''.join(pp(statement, import_column) for statement in statements)
483
+
484
+ def __contains__(self, x) -> bool:
485
+ return x in self._importset
486
+
487
+ def __eq__(self, other) -> bool:
488
+ if self is other:
489
+ return True
490
+ if not isinstance(other, ImportSet):
491
+ return NotImplemented
492
+ return self._importset == other._importset
493
+
494
+ def __ne__(self, other) -> bool:
495
+ return not (self == other)
496
+
497
+ # The rest are defined by total_ordering
498
+ def __lt__(self, other):
499
+ if not isinstance(other, ImportSet):
500
+ return NotImplemented
501
+ return self._importset < other._importset
502
+
503
+ def __cmp__(self, other):
504
+ if self is other:
505
+ return 0
506
+ if not isinstance(other, ImportSet):
507
+ return NotImplemented
508
+ return cmp(self._importset, other._importset)
509
+
510
+ def __hash__(self):
511
+ return hash(self._importset)
512
+
513
+ def __len__(self) -> int:
514
+ return len(self.imports)
515
+
516
+ def __iter__(self):
517
+ return iter(self.imports)
518
+
519
+
520
+ ImportSet._EMPTY = ImportSet._from_imports([])
521
+
522
+
523
+ @total_ordering
524
+ class ImportMap(object):
525
+ r"""
526
+ A map from import fullname identifier to fullname identifier.
527
+
528
+ >>> ImportMap({'a.b': 'aa.bb', 'a.b.c': 'aa.bb.cc'})
529
+ ImportMap({'a.b': 'aa.bb', 'a.b.c': 'aa.bb.cc'})
530
+
531
+ An ``ImportMap`` is an immutable data structure.
532
+ """
533
+
534
+ _data: Dict
535
+ _EMPTY : ClassVar[ImportSet]
536
+
537
+ def __new__(cls, arg):
538
+ if isinstance(arg, cls):
539
+ return arg
540
+ if isinstance(arg, (tuple, list)):
541
+ return cls._merge(arg)
542
+ if isinstance(arg, dict):
543
+ if not len(arg):
544
+ return cls._EMPTY
545
+ return cls._from_map(arg)
546
+ else:
547
+ raise TypeError("ImportMap: expected a dict, not a %s"
548
+ % (type(arg).__name__,))
549
+
550
+ @classmethod
551
+ def _from_map(cls, arg):
552
+ data = dict((Import(k).fullname, Import(v).fullname)
553
+ for k, v in arg.items())
554
+ self = object.__new__(cls)
555
+ self._data = data
556
+ return self
557
+
558
+ @classmethod
559
+ def _merge(cls, maps):
560
+ maps = [cls(m) for m in maps]
561
+ maps = [m for m in maps if m]
562
+ if not maps:
563
+ return cls._EMPTY
564
+ data = {}
565
+ for map in maps:
566
+ data.update(map._data)
567
+ return cls(data)
568
+
569
+ def __getitem__(self, k):
570
+ k = Import(k).fullname
571
+ return self._data.__getitem__(k)
572
+
573
+ def __iter__(self):
574
+ return iter(self._data)
575
+
576
+ def items(self):
577
+ return self._data.items()
578
+
579
+ def iteritems(self):
580
+ return self._data.items()
581
+
582
+ def iterkeys(self):
583
+ return iter(self._data.keys())
584
+
585
+ def keys(self):
586
+ return self._data.keys()
587
+
588
+ def values(self):
589
+ return self._data.values()
590
+
591
+ def __len__(self):
592
+ return len(self._data)
593
+
594
+ def without_imports(self, removals):
595
+ """
596
+ Return a copy of self without the given imports.
597
+ Matches both keys and values.
598
+ """
599
+ removals = ImportSet(removals)
600
+ if not removals:
601
+ return self # Optimization
602
+ cls = type(self)
603
+ result = [(k, v) for k, v in self._data.items()
604
+ if Import(k) not in removals and Import(v) not in removals]
605
+ if len(result) == len(self._data):
606
+ return self # Space optimization
607
+ return cls(dict(result))
608
+
609
+ def __repr__(self):
610
+ s = ", ".join("%r: %r" % (k,v) for k,v in sorted(self.items()))
611
+ return "ImportMap({%s})" % s
612
+
613
+ def __eq__(self, other):
614
+ if self is other:
615
+ return True
616
+ if not isinstance(other, ImportMap):
617
+ return NotImplemented
618
+ return self._data == other._data
619
+
620
+ def __ne__(self, other):
621
+ return not (self == other)
622
+
623
+ # The rest are defined by total_ordering
624
+ def __lt__(self, other):
625
+ if not isinstance(other, ImportMap):
626
+ return NotImplemented
627
+ return self._data < other._data
628
+
629
+ def __cmp__(self, other):
630
+ if self is other:
631
+ return 0
632
+ if not isinstance(other, ImportMap):
633
+ return NotImplemented
634
+ return cmp(self._data, other._data)
635
+
636
+ def __hash__(self):
637
+ h = hash(self._data)
638
+ self.__hash__ = lambda: h
639
+ return h
640
+
641
+
642
+ ImportMap._EMPTY = ImportMap._from_map({})