pyflyby 1.10.4__cp311-cp311-macosx_11_0_arm64.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-311-darwin.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/_importstmt.py ADDED
@@ -0,0 +1,723 @@
1
+ # pyflyby/_importstmt.py.
2
+ # Copyright (C) 2011, 2012, 2013, 2014 Karl Chen.
3
+ # License: MIT http://opensource.org/licenses/MIT
4
+
5
+
6
+
7
+ import ast
8
+ from collections import namedtuple
9
+ from functools import total_ordering
10
+
11
+ from pyflyby._flags import CompilerFlags
12
+ from pyflyby._format import FormatParams, pyfill
13
+ from pyflyby._idents import is_identifier
14
+ from pyflyby._parse import PythonStatement
15
+ from pyflyby._util import (Inf, cached_attribute, cmp,
16
+ longest_common_prefix)
17
+
18
+
19
+ from typing import Dict, Tuple, Optional, Union
20
+
21
+
22
+
23
+ def read_black_config() -> Dict:
24
+ """Read the black configuration from ``pyproject.toml``"""
25
+ from black.files import find_pyproject_toml, parse_pyproject_toml
26
+
27
+ pyproject_path = find_pyproject_toml((".",))
28
+
29
+ raw_config = parse_pyproject_toml(pyproject_path) if pyproject_path else {}
30
+
31
+ config = {}
32
+ for key in [
33
+ "line_length",
34
+ "skip_magic_trailing_comma",
35
+ "skip_string_normalization",
36
+ ]:
37
+ if key in raw_config:
38
+ config[key] = raw_config[key]
39
+ if "target_version" in raw_config:
40
+ target_version = raw_config["target_version"]
41
+ if isinstance(target_version, str):
42
+ config["target_version"] = target_version
43
+ elif isinstance(target_version, list):
44
+ # Convert TOML list to a Python set
45
+ config["target_version"] = set(target_version)
46
+ else:
47
+ raise ValueError(
48
+ f"Invalid config for black = {target_version!r} in {pyproject_path}"
49
+ )
50
+ return config
51
+
52
+
53
+ class ImportFormatParams(FormatParams):
54
+ align_imports:Union[bool, set, list, tuple, str] = True
55
+ """
56
+ Whether and how to align 'from modulename import aliases...'. If ``True``,
57
+ then the 'import' keywords will be aligned within a block. If an integer,
58
+ then the 'import' keyword will always be at that column. They will be
59
+ wrapped if necessary.
60
+ """
61
+
62
+ from_spaces:int = 1
63
+ """
64
+ The number of spaces after the 'from' keyword. (Must be at least 1.)
65
+ """
66
+
67
+ separate_from_imports:bool = True
68
+ """
69
+ Whether all 'from ... import ...' in an import block should come after
70
+ 'import ...' statements. ``separate_from_imports = False`` works well with
71
+ ``from_spaces = 3``. ('from __future__ import ...' always comes first.)
72
+ """
73
+
74
+ align_future:bool = False
75
+ """
76
+ Whether 'from __future__ import ...' statements should be aligned with
77
+ others. If False, uses a single space after the 'from' and 'import'
78
+ keywords.
79
+ """
80
+
81
+
82
+ class NonImportStatementError(TypeError):
83
+ """
84
+ Unexpectedly got a statement that wasn't an import.
85
+ """
86
+
87
+ ImportSplit = namedtuple("ImportSplit",
88
+ "module_name member_name import_as")
89
+ """
90
+ Representation of a single import at the token level::
91
+
92
+ from [...]<module_name> import <member_name> as <import_as>
93
+
94
+ If <module_name> is ``None``, then there is no "from" clause; instead just::
95
+ import <member_name> as <import_as>
96
+ """
97
+
98
+ @total_ordering
99
+ class Import:
100
+ """
101
+ Representation of the desire to import a single name into the current
102
+ namespace.
103
+
104
+ >>> Import.from_parts(".foo.bar", "bar")
105
+ Import('from .foo import bar')
106
+
107
+ >>> Import("from . import foo")
108
+ Import('from . import foo')
109
+
110
+ >>> Import("from . import foo").fullname
111
+ '.foo'
112
+
113
+ >>> Import("import foo . bar")
114
+ Import('import foo.bar')
115
+
116
+ >>> Import("import foo . bar as baz")
117
+ Import('from foo import bar as baz')
118
+
119
+ >>> Import("import foo . bar as bar")
120
+ Import('from foo import bar')
121
+
122
+ >>> Import("foo.bar")
123
+ Import('from foo import bar')
124
+
125
+ """
126
+
127
+ fullname:str
128
+ import_as:str
129
+ comment: Optional[str] = None
130
+
131
+ def __new__(cls, arg):
132
+ if isinstance(arg, cls):
133
+ return arg
134
+ if isinstance(arg, ImportSplit):
135
+ return cls.from_split(arg)
136
+ if isinstance(arg, (ImportStatement, PythonStatement)):
137
+ return cls._from_statement(arg)
138
+ if isinstance(arg, str):
139
+ return cls._from_identifier_or_statement(arg)
140
+ raise TypeError
141
+
142
+ @classmethod
143
+ def from_parts(cls, fullname, import_as, comment=None):
144
+ assert isinstance(fullname, str)
145
+ assert isinstance(import_as, str)
146
+ self = object.__new__(cls)
147
+ self.fullname = fullname
148
+ self.import_as = import_as
149
+ self.comment = comment
150
+ return self
151
+
152
+ @classmethod
153
+ def _from_statement(cls, statement):
154
+ """
155
+ :type statement:
156
+ `ImportStatement` or convertible (`PythonStatement`, ``str``)
157
+ :rtype:
158
+ `Import`
159
+ """
160
+ statement = ImportStatement(statement)
161
+ imports = statement.imports
162
+ if len(imports) != 1:
163
+ raise ValueError(
164
+ "Got %d imports instead of 1 in %r" % (len(imports), statement))
165
+ return imports[0]
166
+
167
+ @classmethod
168
+ def _from_identifier_or_statement(cls, arg):
169
+ """
170
+ Parse either a raw identifier or a statement.
171
+
172
+ >>> Import._from_identifier_or_statement('foo.bar.baz')
173
+ Import('from foo.bar import baz')
174
+
175
+ >>> Import._from_identifier_or_statement('import foo.bar.baz')
176
+ Import('import foo.bar.baz')
177
+
178
+ :rtype:
179
+ `Import`
180
+ """
181
+ if is_identifier(arg, dotted=True):
182
+ return cls.from_parts(arg, arg.split('.')[-1])
183
+ else:
184
+ return cls._from_statement(arg)
185
+
186
+ @cached_attribute
187
+ def split(self):
188
+ """
189
+ Split this `Import` into a ``ImportSplit`` which represents the
190
+ token-level ``module_name``, ``member_name``, ``import_as``.
191
+
192
+ Note that at the token level, ``import_as`` can be ``None`` to represent
193
+ that the import statement doesn't have an "as ..." clause, whereas the
194
+ ``import_as`` attribute on an ``Import`` object is never ``None``.
195
+
196
+ >>> Import.from_parts(".foo.bar", "bar").split
197
+ ImportSplit(module_name='.foo', member_name='bar', import_as=None)
198
+
199
+ >>> Import("from . import foo").split
200
+ ImportSplit(module_name='.', member_name='foo', import_as=None)
201
+
202
+ >>> Import.from_parts(".foo", "foo").split
203
+ ImportSplit(module_name='.', member_name='foo', import_as=None)
204
+
205
+ >>> Import.from_parts("foo.bar", "foo.bar").split
206
+ ImportSplit(module_name=None, member_name='foo.bar', import_as=None)
207
+
208
+ :rtype:
209
+ `ImportSplit`
210
+ """
211
+ if self.import_as == self.fullname:
212
+ return ImportSplit(None, self.fullname, None)
213
+ level = 0
214
+ qname = self.fullname
215
+ for level, char in enumerate(qname):
216
+ if char != '.':
217
+ break
218
+ prefix = qname[:level]
219
+ qname = qname[level:]
220
+ if '.' in qname:
221
+ module_name, member_name = qname.rsplit(".", 1)
222
+ else:
223
+ module_name = ''
224
+ member_name = qname
225
+ module_name = prefix + module_name
226
+ import_as = self.import_as
227
+ if import_as == member_name:
228
+ import_as = None
229
+ return ImportSplit(module_name or None, member_name, import_as)
230
+
231
+ @classmethod
232
+ def from_split(cls, impsplit, comment=None):
233
+ """
234
+ Construct an `Import` instance from ``module_name``, ``member_name``,
235
+ ``import_as``.
236
+
237
+ :rtype:
238
+ `Import`
239
+ """
240
+ module_name, member_name, import_as = ImportSplit(*impsplit)
241
+ if import_as is None:
242
+ import_as = member_name
243
+ if module_name is None:
244
+ result = cls.from_parts(member_name, import_as, comment)
245
+ else:
246
+ fullname = "%s%s%s" % (
247
+ module_name,
248
+ "" if module_name.endswith(".") else ".",
249
+ member_name)
250
+ result = cls.from_parts(fullname, import_as, comment)
251
+ # result.split will usually be the same as impsplit, but could be
252
+ # different if the input was 'import foo.bar as baz', which we
253
+ # canonicalize to 'from foo import bar as baz'.
254
+ return result
255
+
256
+ def prefix_match(self, imp):
257
+ """
258
+ Return the longest common prefix between ``self`` and ``imp``.
259
+
260
+ >>> Import("import ab.cd.ef").prefix_match(Import("import ab.cd.xy"))
261
+ ('ab', 'cd')
262
+
263
+ :type imp:
264
+ `Import`
265
+ :rtype:
266
+ ``tuple`` of ``str``
267
+ """
268
+ imp = Import(imp)
269
+ n1 = self.fullname.split('.')
270
+ n2 = imp.fullname.split('.')
271
+ return tuple(longest_common_prefix(n1, n2))
272
+
273
+ def replace(self, prefix, replacement):
274
+ """
275
+ Return a new ``Import`` that replaces ``prefix`` with ``replacement``.
276
+
277
+ >>> Import("from aa.bb import cc").replace("aa.bb", "xx.yy")
278
+ Import('from xx.yy import cc')
279
+
280
+ >>> Import("from aa import bb").replace("aa.bb", "xx.yy")
281
+ Import('from xx import yy as bb')
282
+
283
+ :rtype:
284
+ ``Import``
285
+ """
286
+ prefix_parts = prefix.split('.')
287
+ replacement_parts = replacement.split('.')
288
+ fullname_parts = self.fullname.split('.')
289
+ if fullname_parts[:len(prefix_parts)] != prefix_parts:
290
+ # No prefix match.
291
+ return self
292
+ fullname_parts[:len(prefix_parts)] = replacement_parts
293
+ import_as_parts = self.import_as.split('.')
294
+ if import_as_parts[:len(prefix_parts)] == prefix_parts:
295
+ import_as_parts[:len(prefix_parts)] = replacement_parts
296
+ return self.from_parts('.'.join(fullname_parts),
297
+ '.'.join(import_as_parts))
298
+
299
+ @cached_attribute
300
+ def flags(self):
301
+ """
302
+ If this is a __future__ import, then the compiler_flag associated with
303
+ it. Otherwise, 0.
304
+ """
305
+ if self.split.module_name == "__future__":
306
+ return CompilerFlags(self.split.member_name)
307
+ else:
308
+ return CompilerFlags.from_int(0)
309
+
310
+ @property
311
+ def _data(self):
312
+ return (self.fullname, self.import_as)
313
+
314
+ def pretty_print(self, params=FormatParams()):
315
+ return ImportStatement([self]).pretty_print(params)
316
+
317
+ def __str__(self):
318
+ return self.pretty_print(FormatParams(max_line_length=Inf)).rstrip()
319
+
320
+ def __repr__(self):
321
+ return "%s(%r)" % (type(self).__name__, str(self))
322
+
323
+ def __hash__(self):
324
+ return hash(self._data)
325
+
326
+ def __cmp__(self, other):
327
+ if self is other:
328
+ return 0
329
+ if not isinstance(other, Import):
330
+ return NotImplemented
331
+ return cmp(self._data, other._data)
332
+
333
+ def __eq__(self, other):
334
+ if self is other:
335
+ return True
336
+ if not isinstance(other, Import):
337
+ return NotImplemented
338
+ return self._data == other._data
339
+
340
+ def __ne__(self, other):
341
+ return not (self == other)
342
+
343
+ # The rest are defined by total_ordering
344
+ def __lt__(self, other):
345
+ if self is other:
346
+ return False
347
+ if not isinstance(other, Import):
348
+ return NotImplemented
349
+ return self._data < other._data
350
+
351
+ def _validate_alias(arg) -> Tuple[str, Optional[str]]:
352
+ """
353
+ Ensure each alias is a tuple (str, None|str), and return it.
354
+
355
+ """
356
+ assert isinstance(arg, tuple)
357
+ # Pyright does not seem to be able to infer the length from a
358
+ # the unpacking.
359
+ assert len(arg) == 2
360
+ a0, a1 = arg
361
+ assert isinstance(a0, str)
362
+ assert isinstance(a1, (str, type(None)))
363
+ return arg
364
+
365
+ @total_ordering
366
+ class ImportStatement:
367
+ """
368
+ Token-level representation of an import statement containing multiple
369
+ imports from a single module. Corresponds to an ``ast.ImportFrom`` or
370
+ ``ast.Import``.
371
+ """
372
+
373
+ aliases : Tuple[Tuple[str, Optional[str]],...]
374
+ fromname : Optional[str]
375
+ comments : Optional[list[Optional[str]]] = None
376
+
377
+ def __new__(cls, arg):
378
+ if isinstance(arg, cls):
379
+ return arg
380
+ if isinstance(arg, str):
381
+ return cls._from_str(arg)
382
+ if isinstance(arg, PythonStatement):
383
+ return cls._from_statement(arg)
384
+ if isinstance(arg, (ast.ImportFrom, ast.Import)):
385
+ return cls._from_ast_node(arg)
386
+ if isinstance(arg, Import):
387
+ return cls._from_imports([arg])
388
+ if isinstance(arg, (tuple, list)) and len(arg):
389
+ if isinstance(arg[0], Import):
390
+ return cls._from_imports(arg)
391
+ raise TypeError
392
+
393
+ @classmethod
394
+ def from_parts(
395
+ cls,
396
+ fromname: Optional[str],
397
+ aliases: Tuple[Tuple[str, Optional[str]], ...],
398
+ comments: Optional[list[Optional[str]]] = None,
399
+ ):
400
+ assert isinstance(aliases, tuple)
401
+ assert len(aliases) > 0
402
+
403
+ self = object.__new__(cls)
404
+ self.fromname = fromname
405
+ self.aliases = tuple(_validate_alias(a) for a in aliases)
406
+ self.comments = comments
407
+ return self
408
+
409
+ @classmethod
410
+ def _from_str(cls, code:str, /):
411
+ """
412
+ >>> ImportStatement._from_str("from foo import bar, bar2, bar")
413
+ ImportStatement('from foo import bar, bar2, bar')
414
+
415
+ >>> ImportStatement._from_str("from foo import bar as bar")
416
+ ImportStatement('from foo import bar as bar')
417
+
418
+ >>> ImportStatement._from_str("from foo.bar import baz")
419
+ ImportStatement('from foo.bar import baz')
420
+
421
+ >>> ImportStatement._from_str("import foo.bar")
422
+ ImportStatement('import foo.bar')
423
+
424
+ >>> ImportStatement._from_str("from .foo import bar")
425
+ ImportStatement('from .foo import bar')
426
+
427
+ >>> ImportStatement._from_str("from . import bar, bar2")
428
+ ImportStatement('from . import bar, bar2')
429
+
430
+ :type statement:
431
+ `PythonStatement`
432
+ :rtype:
433
+ `ImportStatement`
434
+ """
435
+ return cls._from_statement(
436
+ PythonStatement(code)
437
+ )
438
+
439
+ @classmethod
440
+ def _from_statement(cls, statement):
441
+ stmt = PythonStatement.from_statement(statement)
442
+ return cls._from_ast_node(
443
+ stmt.ast_node,
444
+ comments=stmt.text.get_comments()
445
+ )
446
+
447
+ @classmethod
448
+ def _from_ast_node(cls, node, comments: Optional[list[Optional[str]]] = None):
449
+ """
450
+ Construct an `ImportStatement` from an `ast` node.
451
+
452
+ :rtype:
453
+ `ImportStatement`
454
+ """
455
+ if isinstance(node, ast.ImportFrom):
456
+ if node.module is None:
457
+ module = ''
458
+ else:
459
+ assert isinstance(node.module, str)
460
+ module = node.module
461
+ fromname = '.' * node.level + module
462
+ elif isinstance(node, ast.Import):
463
+ fromname = None
464
+ else:
465
+ raise NonImportStatementError(
466
+ 'Expected ImportStatement, got {node}'.format(node=node)
467
+ )
468
+ aliases = tuple( (alias.name, alias.asname) for alias in node.names )
469
+ return cls.from_parts(fromname, aliases, comments)
470
+
471
+ @classmethod
472
+ def _from_imports(cls, imports):
473
+ """
474
+ Construct an `ImportStatement` from a sequence of ``Import`` s. They
475
+ must all have the same ``fromname``.
476
+
477
+ :type imports:
478
+ Sequence of `Import` s
479
+ :rtype:
480
+ `ImportStatement`
481
+ """
482
+ if not all(isinstance(imp, Import) for imp in imports):
483
+ raise TypeError
484
+ if not len(imports) > 0:
485
+ raise ValueError
486
+ module_names = set(imp.split.module_name for imp in imports)
487
+ if len(module_names) > 1:
488
+ raise ValueError(
489
+ "Inconsistent module names %r" % (sorted(module_names),))
490
+
491
+ return cls.from_parts(
492
+ fromname=list(module_names)[0],
493
+ aliases=tuple(imp.split[1:] for imp in imports),
494
+ comments=[imp.comment for imp in imports]
495
+ )
496
+
497
+ @cached_attribute
498
+ def imports(self):
499
+ """
500
+ Return a sequence of `Import` s.
501
+
502
+ If the returned sequence of imports has only one entry, any single line
503
+ comment will be included with it. Otherwise, line comments are not included
504
+ because there's too much ambiguity about where they should be placed otherwise.
505
+
506
+ :rtype:
507
+ ``tuple`` of `Import` s
508
+ """
509
+ result = []
510
+ for alias in self.aliases:
511
+ result.append(
512
+ Import.from_split(
513
+ (self.fromname, alias[0], alias[1]),
514
+ comment=self.get_valid_comment(),
515
+ )
516
+ )
517
+ return tuple(result)
518
+
519
+ def get_valid_comment(self):
520
+ """Get the comment for the ImportStatment, if possible.
521
+
522
+ A comment is only valid if there is a single comment.
523
+
524
+ # 1. The ImportStatement has a single alias
525
+ # 2. There is a single string comment in self.comments
526
+
527
+ :rtype:
528
+ ``Optional[str]`` containing the valid comment, if any
529
+ """
530
+ if self.comments and len(self.aliases) == 1:
531
+ valid = [comment for comment in self.comments if comment is not None]
532
+ if len(valid) == 1:
533
+ return valid[0]
534
+ return None
535
+
536
+ @property
537
+ def module(self) -> Tuple[str, ...]:
538
+ """
539
+
540
+ return the import module as a list of string (which would be joined by
541
+ dot in the original import form.
542
+
543
+ This is useful for sorting purposes
544
+
545
+ Note that this may contain some empty string in particular with relative
546
+ imports
547
+ """
548
+ if self.fromname:
549
+ return tuple(self.fromname.split('.'))
550
+
551
+
552
+ assert len(self.aliases) == 1, self.aliases
553
+
554
+ return tuple(self.aliases[0][0].split('.'))
555
+
556
+
557
+ def _cmp(self):
558
+ """
559
+ Comparison function for sorting.
560
+
561
+ We want to sort:
562
+ - by the root module
563
+ - whether it is an "import ... as", or "from ... import as" import
564
+ - then lexicographically
565
+
566
+ """
567
+ return (self.module[0], 0 if self.fromname is not None else 1, self.fromname)
568
+
569
+ @cached_attribute
570
+ def flags(self):
571
+ """
572
+ If this is a __future__ import, then the bitwise-ORed of the
573
+ compiler_flag values associated with the features. Otherwise, 0.
574
+ """
575
+ return CompilerFlags(*[imp.flags for imp in self.imports])
576
+
577
+ def pretty_print(self, params=FormatParams(),
578
+ import_column=None, from_spaces=1):
579
+ """
580
+ Pretty-print into a single string.
581
+
582
+ ImportStatement objects represent python import statements, which can span
583
+ multiple lines or multiple aliases. Here we append comments
584
+
585
+ - If the output is one line
586
+ - If there is only one comment
587
+
588
+ This way we avoid worrying about combining comments from multiple lines,
589
+ or where to place comments if the resulting output is more than one line
590
+
591
+ :type params:
592
+ `FormatParams`
593
+ :param modulename_ljust:
594
+ Number of characters to left-justify the 'from' name.
595
+ :rtype:
596
+ ``str``
597
+ """
598
+ s0 = ''
599
+ s = ''
600
+ assert from_spaces >= 1
601
+ if self.fromname is not None:
602
+ s += "from%s%s " % (' ' * from_spaces, self.fromname)
603
+ if import_column is not None:
604
+ if len(s) > import_column:
605
+ # The caller wants the 'import' statement lined up left of
606
+ # where the current end of the line is. So wrap it
607
+ # specially like this::
608
+ # from foo import ...
609
+ # from foo.bar.baz \
610
+ # import ...
611
+ s0 = s + '\\\n'
612
+ s = ' ' * import_column
613
+ else:
614
+ s = s.ljust(import_column)
615
+ s += "import "
616
+ tokens = []
617
+ for importname, asname in self.aliases:
618
+ if asname is not None:
619
+ t = "%s as %s" % (importname, asname)
620
+ else:
621
+ t = "%s" % (importname,)
622
+
623
+ tokens.append(t)
624
+ res = s0 + pyfill(s, tokens, params=params)
625
+
626
+ comment = self.get_valid_comment()
627
+ if comment is not None:
628
+ # Only append to text on the first line
629
+ lines = res.split('\n')
630
+ if len(lines) == 2:
631
+ lines[0] += f" #{comment}"
632
+ res = "\n".join(lines)
633
+
634
+ if params.use_black:
635
+ res = self.run_black(res, params)
636
+
637
+ return res
638
+
639
+ @staticmethod
640
+ def run_black(src_contents: str, params:FormatParams) -> str:
641
+ """Run the black formatter for the Python source code given as a string
642
+
643
+ This is adapted from https://github.com/akaihola/darker
644
+
645
+ """
646
+ from black import format_str, FileMode
647
+ from black.mode import TargetVersion
648
+
649
+ black_config = read_black_config()
650
+ mode = dict()
651
+
652
+ if params.max_line_length is None:
653
+ mode["line_length"] = black_config.get("line_length", params.max_line_length_default)
654
+ else:
655
+ mode["line_length"] = params.max_line_length
656
+
657
+ if "target_version" in black_config:
658
+ if isinstance(black_config["target_version"], set):
659
+ target_versions_in = black_config["target_version"]
660
+ else:
661
+ target_versions_in = {black_config["target_version"]}
662
+ all_target_versions = {
663
+ tgt_v.name.lower(): tgt_v for tgt_v in TargetVersion
664
+ }
665
+ bad_target_versions = target_versions_in - set(all_target_versions)
666
+ if bad_target_versions:
667
+ raise ValueError(
668
+ f"Invalid target version(s) {bad_target_versions}"
669
+ )
670
+ mode["target_versions"] = {
671
+ all_target_versions[n] for n in target_versions_in
672
+ }
673
+ if "skip_magic_trailing_comma" in black_config:
674
+ mode["magic_trailing_comma"] = not black_config[
675
+ "skip_magic_trailing_comma"
676
+ ]
677
+ if "skip_string_normalization" in black_config:
678
+ # The ``black`` command line argument is
679
+ # ``--skip-string-normalization``, but the parameter for
680
+ # ``black.Mode`` needs to be the opposite boolean of
681
+ # ``skip-string-normalization``, hence the inverse boolean
682
+ mode["string_normalization"] = not black_config["skip_string_normalization"]
683
+
684
+ # The custom handling of empty and all-whitespace files below will be unnecessary if
685
+ # https://github.com/psf/black/pull/2484 lands in Black.
686
+ contents_for_black = src_contents
687
+ return format_str(contents_for_black, mode=FileMode(**mode))
688
+
689
+ @property
690
+ def _data(self):
691
+ return (self.fromname, self.aliases)
692
+
693
+ def __str__(self):
694
+ return self.pretty_print(FormatParams(max_line_length=Inf)).rstrip()
695
+
696
+ def __repr__(self):
697
+ return "%s(%r)" % (type(self).__name__, str(self))
698
+
699
+ def __cmp__(self, other):
700
+ if self is other:
701
+ return 0
702
+ if not isinstance(other, ImportStatement):
703
+ return NotImplemented
704
+ return cmp(self._data, other._data)
705
+
706
+ def __eq__(self, other):
707
+ if self is other:
708
+ return True
709
+ if not isinstance(other, ImportStatement):
710
+ return NotImplemented
711
+ return self._data == other._data
712
+
713
+ def __ne__(self, other):
714
+ return not (self == other)
715
+
716
+ # The rest are defined by total_ordering
717
+ def __lt__(self, other):
718
+ if not isinstance(other, ImportStatement):
719
+ return NotImplemented
720
+ return self._data < other._data
721
+
722
+ def __hash__(self):
723
+ return hash(self._data)