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.
- pyflyby/__init__.py +61 -0
- pyflyby/__main__.py +9 -0
- pyflyby/_autoimp.py +2228 -0
- pyflyby/_cmdline.py +591 -0
- pyflyby/_comms.py +221 -0
- pyflyby/_dbg.py +1383 -0
- pyflyby/_dynimp.py +154 -0
- pyflyby/_fast_iter_modules.cpython-312-x86_64-linux-gnu.so +0 -0
- pyflyby/_file.py +771 -0
- pyflyby/_flags.py +230 -0
- pyflyby/_format.py +186 -0
- pyflyby/_idents.py +227 -0
- pyflyby/_import_sorting.py +165 -0
- pyflyby/_importclns.py +658 -0
- pyflyby/_importdb.py +535 -0
- pyflyby/_imports2s.py +643 -0
- pyflyby/_importstmt.py +723 -0
- pyflyby/_interactive.py +2113 -0
- pyflyby/_livepatch.py +793 -0
- pyflyby/_log.py +107 -0
- pyflyby/_modules.py +646 -0
- pyflyby/_parse.py +1396 -0
- pyflyby/_py.py +2165 -0
- pyflyby/_saveframe.py +1145 -0
- pyflyby/_saveframe_reader.py +471 -0
- pyflyby/_util.py +458 -0
- pyflyby/_version.py +8 -0
- pyflyby/autoimport.py +20 -0
- pyflyby/etc/pyflyby/canonical.py +10 -0
- pyflyby/etc/pyflyby/common.py +27 -0
- pyflyby/etc/pyflyby/forget.py +10 -0
- pyflyby/etc/pyflyby/mandatory.py +10 -0
- pyflyby/etc/pyflyby/numpy.py +156 -0
- pyflyby/etc/pyflyby/std.py +335 -0
- pyflyby/importdb.py +19 -0
- pyflyby/libexec/pyflyby/colordiff +34 -0
- pyflyby/libexec/pyflyby/diff-colorize +148 -0
- pyflyby/share/emacs/site-lisp/pyflyby.el +112 -0
- pyflyby-1.10.4.data/scripts/collect-exports +76 -0
- pyflyby-1.10.4.data/scripts/collect-imports +58 -0
- pyflyby-1.10.4.data/scripts/find-import +38 -0
- pyflyby-1.10.4.data/scripts/prune-broken-imports +34 -0
- pyflyby-1.10.4.data/scripts/pyflyby-diff +34 -0
- pyflyby-1.10.4.data/scripts/reformat-imports +27 -0
- pyflyby-1.10.4.data/scripts/replace-star-imports +37 -0
- pyflyby-1.10.4.data/scripts/saveframe +299 -0
- pyflyby-1.10.4.data/scripts/tidy-imports +170 -0
- pyflyby-1.10.4.data/scripts/transform-imports +47 -0
- pyflyby-1.10.4.dist-info/METADATA +605 -0
- pyflyby-1.10.4.dist-info/RECORD +53 -0
- pyflyby-1.10.4.dist-info/WHEEL +6 -0
- pyflyby-1.10.4.dist-info/entry_points.txt +4 -0
- 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({})
|