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