pyflyby 1.8.5__tar.gz → 1.8.7__tar.gz

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 (148) hide show
  1. {pyflyby-1.8.5/lib/python/pyflyby.egg-info → pyflyby-1.8.7}/PKG-INFO +2 -2
  2. {pyflyby-1.8.5 → pyflyby-1.8.7}/README.rst +1 -1
  3. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby/_autoimp.py +8 -21
  4. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby/_comms.py +81 -6
  5. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby/_idents.py +1 -1
  6. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby/_imports2s.py +44 -2
  7. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby/_modules.py +84 -24
  8. {pyflyby-1.8.5/lib/python → pyflyby-1.8.7/bin}/pyflyby/_parse.py +28 -109
  9. {pyflyby-1.8.5/lib/python → pyflyby-1.8.7/bin}/pyflyby/_py.py +22 -37
  10. {pyflyby-1.8.5/lib/python → pyflyby-1.8.7/bin}/pyflyby/_version.py +1 -1
  11. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/tidy-imports +10 -7
  12. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby/_autoimp.py +8 -21
  13. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby/_comms.py +81 -6
  14. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby/_idents.py +1 -1
  15. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby/_imports2s.py +44 -2
  16. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby/_modules.py +84 -24
  17. {pyflyby-1.8.5/bin → pyflyby-1.8.7/lib/python}/pyflyby/_parse.py +28 -109
  18. {pyflyby-1.8.5/bin → pyflyby-1.8.7/lib/python}/pyflyby/_py.py +22 -37
  19. {pyflyby-1.8.5/bin → pyflyby-1.8.7/lib/python}/pyflyby/_version.py +1 -1
  20. {pyflyby-1.8.5 → pyflyby-1.8.7/lib/python/pyflyby.egg-info}/PKG-INFO +2 -2
  21. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby.egg-info/requires.txt +1 -0
  22. {pyflyby-1.8.5 → pyflyby-1.8.7}/setup.py +1 -1
  23. {pyflyby-1.8.5 → pyflyby-1.8.7}/tests/test_autoimp.py +2 -25
  24. {pyflyby-1.8.5 → pyflyby-1.8.7}/tests/test_cmdline.py +139 -2
  25. {pyflyby-1.8.5 → pyflyby-1.8.7}/tests/test_imports2s.py +11 -4
  26. {pyflyby-1.8.5 → pyflyby-1.8.7}/tests/test_interactive.py +9 -14
  27. {pyflyby-1.8.5 → pyflyby-1.8.7}/tests/test_jupyterlab_pyflyby.py +26 -1
  28. {pyflyby-1.8.5 → pyflyby-1.8.7}/tests/test_livepatch.py +1 -4
  29. {pyflyby-1.8.5 → pyflyby-1.8.7}/tests/test_parse.py +12 -24
  30. {pyflyby-1.8.5 → pyflyby-1.8.7}/tests/test_py.py +0 -22
  31. {pyflyby-1.8.5 → pyflyby-1.8.7}/.pyflyby +0 -0
  32. {pyflyby-1.8.5 → pyflyby-1.8.7}/LICENSE.txt +0 -0
  33. {pyflyby-1.8.5 → pyflyby-1.8.7}/MANIFEST.in +0 -0
  34. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/autoipython +0 -0
  35. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/autopython +0 -0
  36. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/collect-exports +0 -0
  37. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/collect-imports +0 -0
  38. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/create-imports +0 -0
  39. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/find-import +0 -0
  40. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/list-bad-xrefs +0 -0
  41. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/prune-broken-imports +0 -0
  42. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/py +0 -0
  43. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby/__init__.py +0 -0
  44. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby/__main__.py +0 -0
  45. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby/_cmdline.py +0 -0
  46. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby/_dbg.py +0 -0
  47. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby/_docxref.py +0 -0
  48. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby/_file.py +0 -0
  49. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby/_flags.py +0 -0
  50. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby/_format.py +0 -0
  51. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby/_importclns.py +0 -0
  52. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby/_importdb.py +0 -0
  53. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby/_importstmt.py +0 -0
  54. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby/_interactive.py +0 -0
  55. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby/_livepatch.py +0 -0
  56. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby/_log.py +0 -0
  57. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby/_util.py +0 -0
  58. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby/autoimport.py +0 -0
  59. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby/importdb.py +0 -0
  60. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/pyflyby-diff +0 -0
  61. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/reformat-imports +0 -0
  62. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/replace-star-imports +0 -0
  63. {pyflyby-1.8.5 → pyflyby-1.8.7}/bin/transform-imports +0 -0
  64. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/LICENSE.txt +0 -0
  65. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/Makefile +0 -0
  66. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/TODO.txt +0 -0
  67. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/__init__.py +0 -0
  68. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/api/api.rst +0 -0
  69. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/api/autoimp.rst +0 -0
  70. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/api/cmdline.rst +0 -0
  71. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/api/comms.rst +0 -0
  72. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/api/dbg.rst +0 -0
  73. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/api/file.rst +0 -0
  74. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/api/flags.rst +0 -0
  75. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/api/format.rst +0 -0
  76. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/api/idents.rst +0 -0
  77. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/api/importclns.rst +0 -0
  78. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/api/importdb.rst +0 -0
  79. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/api/imports2s.rst +0 -0
  80. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/api/importstmt.rst +0 -0
  81. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/api/interactive.rst +0 -0
  82. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/api/livepatch.rst +0 -0
  83. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/api/log.rst +0 -0
  84. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/api/modules.rst +0 -0
  85. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/api/parse.rst +0 -0
  86. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/api/py.rst +0 -0
  87. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/api/util.rst +0 -0
  88. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/cli/autoipython.rst +0 -0
  89. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/cli/cli.rst +0 -0
  90. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/cli/collect_exports.rst +0 -0
  91. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/cli/collect_imports.rst +0 -0
  92. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/cli/find_import.rst +0 -0
  93. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/cli/prune_broken_imports.rst +0 -0
  94. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/cli/py.rst +0 -0
  95. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/cli/pyflyby_diff.rst +0 -0
  96. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/cli/reformat_imports.rst +0 -0
  97. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/cli/replace_star_imports.rst +0 -0
  98. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/cli/tidy_imports.rst +0 -0
  99. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/cli/transform_imports.rst +0 -0
  100. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/conf.py +0 -0
  101. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/index.rst +0 -0
  102. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/make.bat +0 -0
  103. {pyflyby-1.8.5 → pyflyby-1.8.7}/doc/testing.txt +0 -0
  104. {pyflyby-1.8.5 → pyflyby-1.8.7}/etc/pyflyby/canonical.py +0 -0
  105. {pyflyby-1.8.5 → pyflyby-1.8.7}/etc/pyflyby/common.py +0 -0
  106. {pyflyby-1.8.5 → pyflyby-1.8.7}/etc/pyflyby/forget.py +0 -0
  107. {pyflyby-1.8.5 → pyflyby-1.8.7}/etc/pyflyby/mandatory.py +0 -0
  108. {pyflyby-1.8.5 → pyflyby-1.8.7}/etc/pyflyby/numpy.py +0 -0
  109. {pyflyby-1.8.5 → pyflyby-1.8.7}/etc/pyflyby/std.py +0 -0
  110. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/emacs/pyflyby.el +0 -0
  111. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby/__init__.py +0 -0
  112. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby/__main__.py +0 -0
  113. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby/_cmdline.py +0 -0
  114. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby/_dbg.py +0 -0
  115. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby/_docxref.py +0 -0
  116. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby/_file.py +0 -0
  117. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby/_flags.py +0 -0
  118. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby/_format.py +0 -0
  119. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby/_importclns.py +0 -0
  120. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby/_importdb.py +0 -0
  121. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby/_importstmt.py +0 -0
  122. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby/_interactive.py +0 -0
  123. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby/_livepatch.py +0 -0
  124. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby/_log.py +0 -0
  125. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby/_util.py +0 -0
  126. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby/autoimport.py +0 -0
  127. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby/importdb.py +0 -0
  128. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby.egg-info/SOURCES.txt +0 -0
  129. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby.egg-info/dependency_links.txt +0 -0
  130. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby.egg-info/entry_points.txt +0 -0
  131. {pyflyby-1.8.5 → pyflyby-1.8.7}/lib/python/pyflyby.egg-info/top_level.txt +0 -0
  132. {pyflyby-1.8.5 → pyflyby-1.8.7}/libexec/pyflyby/colordiff +0 -0
  133. {pyflyby-1.8.5 → pyflyby-1.8.7}/libexec/pyflyby/diff-colorize +0 -0
  134. {pyflyby-1.8.5 → pyflyby-1.8.7}/setup.cfg +0 -0
  135. {pyflyby-1.8.5 → pyflyby-1.8.7}/tests/__init__.py +0 -0
  136. {pyflyby-1.8.5 → pyflyby-1.8.7}/tests/test_0testconfig.py +0 -0
  137. {pyflyby-1.8.5 → pyflyby-1.8.7}/tests/test_docxref.py +0 -0
  138. {pyflyby-1.8.5 → pyflyby-1.8.7}/tests/test_file.py +0 -0
  139. {pyflyby-1.8.5 → pyflyby-1.8.7}/tests/test_flags.py +0 -0
  140. {pyflyby-1.8.5 → pyflyby-1.8.7}/tests/test_format.py +0 -0
  141. {pyflyby-1.8.5 → pyflyby-1.8.7}/tests/test_idents.py +0 -0
  142. {pyflyby-1.8.5 → pyflyby-1.8.7}/tests/test_importclns.py +0 -0
  143. {pyflyby-1.8.5 → pyflyby-1.8.7}/tests/test_importdb.py +0 -0
  144. {pyflyby-1.8.5 → pyflyby-1.8.7}/tests/test_importstmt.py +0 -0
  145. {pyflyby-1.8.5 → pyflyby-1.8.7}/tests/test_modules.py +0 -0
  146. {pyflyby-1.8.5 → pyflyby-1.8.7}/tests/test_util.py +0 -0
  147. {pyflyby-1.8.5 → pyflyby-1.8.7}/tests/xrefs.py +0 -0
  148. {pyflyby-1.8.5 → pyflyby-1.8.7}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyflyby
3
- Version: 1.8.5
3
+ Version: 1.8.7
4
4
  Summary: pyflyby - Python development productivity tools, in particular automatic import management
5
5
  Home-page: https://pypi.org/project/pyflyby/
6
6
  Author: Karl Chen
@@ -257,7 +257,7 @@ Compatibility
257
257
  -------------
258
258
 
259
259
  Tested with:
260
- - Python 3.7, 3.8, 3.9, 3.10
260
+ - Python 3.8, 3.9, 3.10
261
261
  - IPython 0.10, 0.11, 0.12, 0.13, 1.0, 1.2, 2.0, 2.1, 2.2, 2.3, 2.4, 3.0,
262
262
  3.1, 3.2, 4.0., 7.11 (latest)
263
263
  - IPython (text console), IPython Notebook, Spyder
@@ -236,7 +236,7 @@ Compatibility
236
236
  -------------
237
237
 
238
238
  Tested with:
239
- - Python 3.7, 3.8, 3.9, 3.10
239
+ - Python 3.8, 3.9, 3.10
240
240
  - IPython 0.10, 0.11, 0.12, 0.13, 1.0, 1.2, 2.0, 2.1, 2.2, 2.3, 2.4, 3.0,
241
241
  3.1, 3.2, 4.0., 7.11 (latest)
242
242
  - IPython (text console), IPython Notebook, Spyder
@@ -609,17 +609,13 @@ class _MissingImportFinder(object):
609
609
  # scope.
610
610
  # - Store the name in the current scope (but not visibly to
611
611
  # args/decorator_list).
612
- if sys.version_info >= (3, 8):
613
- assert node._fields == ('name', 'args', 'body', 'decorator_list', 'returns', 'type_comment'), node._fields
614
- else:
615
- assert node._fields == ('name', 'args', 'body', 'decorator_list', 'returns'), node._fields
612
+ assert node._fields == ('name', 'args', 'body', 'decorator_list', 'returns', 'type_comment'), node._fields
616
613
  with self._NewScopeCtx(include_class_scopes=True):
617
614
  self.visit(node.args)
618
615
  self.visit(node.decorator_list)
619
616
  if node.returns:
620
617
  self.visit(node.returns)
621
- if sys.version_info >= (3, 8):
622
- self._visit_typecomment(node.type_comment)
618
+ self._visit_typecomment(node.type_comment)
623
619
  old_in_FunctionDef = self._in_FunctionDef
624
620
  self._in_FunctionDef = True
625
621
  with self._NewScopeCtx(unhide_classdef=True):
@@ -671,10 +667,7 @@ class _MissingImportFinder(object):
671
667
  self.visit(node)
672
668
 
673
669
  def visit_arguments(self, node):
674
- if sys.version_info >= (3, 8):
675
- assert node._fields == ('posonlyargs', 'args', 'vararg', 'kwonlyargs', 'kw_defaults', 'kwarg', 'defaults'), node._fields
676
- else:
677
- assert node._fields == ('args', 'vararg', 'kwonlyargs', 'kw_defaults', 'kwarg', 'defaults'), node._fields
670
+ assert node._fields == ('posonlyargs', 'args', 'vararg', 'kwonlyargs', 'kw_defaults', 'kwarg', 'defaults'), node._fields
678
671
  # Argument/parameter list. Note that the defaults should be
679
672
  # considered "Load"s from the upper scope, and the argument names are
680
673
  # "Store"s in the function scope.
@@ -693,8 +686,7 @@ class _MissingImportFinder(object):
693
686
  # Store arg names.
694
687
  self.visit(node.args)
695
688
  self.visit(node.kwonlyargs)
696
- if sys.version_info >= (3, 8):
697
- self.visit(node.posonlyargs)
689
+ self.visit(node.posonlyargs)
698
690
  # may be None.
699
691
  if node.vararg:
700
692
  self.visit(node.vararg)
@@ -807,16 +799,12 @@ class _MissingImportFinder(object):
807
799
  self._visit_fullname(node.id, node.ctx)
808
800
 
809
801
  def visit_arg(self, node):
810
- if sys.version_info >= (3, 8):
811
- assert node._fields == ('arg', 'annotation', 'type_comment'), node._fields
812
- else:
813
- assert node._fields == ('arg', 'annotation'), node._fields
802
+ assert node._fields == ('arg', 'annotation', 'type_comment'), node._fields
814
803
  if node.annotation:
815
804
  self.visit(node.annotation)
816
805
  # Treat it like a Name node would from Python 2
817
806
  self._visit_fullname(node.arg, ast.Param())
818
- if sys.version_info >= (3, 8):
819
- self._visit_typecomment(node.type_comment)
807
+ self._visit_typecomment(node.type_comment)
820
808
 
821
809
  def visit_Attribute(self, node):
822
810
  name_revparts = []
@@ -1473,7 +1461,7 @@ def find_missing_imports(arg, namespaces):
1473
1461
  ['x']
1474
1462
 
1475
1463
  >>> # Python 3
1476
- >>> [str(m) for m in find_missing_imports("[x+y+z for x,y in [(1,2)]], y", [{}])] # doctest: +SKIP
1464
+ >>> [str(m) for m in find_missing_imports("[x+y+z for x,y in [(1,2)]], y", [{}])]
1477
1465
  ['y', 'z']
1478
1466
 
1479
1467
  >>> [str(m) for m in find_missing_imports("(x+y+z for x,y in [(1,2)]), y", [{}])]
@@ -1512,8 +1500,7 @@ def find_missing_imports(arg, namespaces):
1512
1500
  else:
1513
1501
  return []
1514
1502
  # Parse the string into an AST.
1515
- kw = {} if sys.version_info < (3, 8) else {'type_comments': True}
1516
- node = ast.parse(arg, **kw) # may raise SyntaxError
1503
+ node = ast.parse(arg, type_comments=True) # may raise SyntaxError
1517
1504
  # Get missing imports from AST.
1518
1505
  return _find_missing_imports_in_ast(node, namespaces)
1519
1506
  elif isinstance(arg, PythonBlock):
@@ -1,7 +1,12 @@
1
1
 
2
- from pyflyby._log import logger
3
- from pyflyby._imports2s import SourceToSourceFileImportsTransformation
2
+ from __future__ import print_function
3
+
4
+ from pyflyby._imports2s import (SourceToSourceFileImportsTransformation,
5
+ SourceToSourceImportBlockTransformation,
6
+ fix_unused_and_missing_imports,
7
+ replace_star_imports)
4
8
  from pyflyby._importstmt import Import
9
+ from pyflyby._log import logger
5
10
  import six
6
11
 
7
12
  # These are comm targets that the frontend (lab/notebook) is expected to
@@ -11,10 +16,11 @@ import six
11
16
  MISSING_IMPORTS = "pyflyby.missing_imports"
12
17
  FORMATTING_IMPORTS = "pyflyby.format_imports"
13
18
  INIT_COMMS = "pyflyby.init_comms"
19
+ TIDY_IMPORTS = "pyflyby.tidy_imports"
14
20
  PYFLYBY_START_MSG = "# THIS CELL WAS AUTO-GENERATED BY PYFLYBY\n"
15
21
  PYFLYBY_END_MSG = "# END AUTO-GENERATED BLOCK\n"
16
22
 
17
- pyflyby_comm_targets= [MISSING_IMPORTS, FORMATTING_IMPORTS]
23
+ pyflyby_comm_targets= [MISSING_IMPORTS, FORMATTING_IMPORTS, TIDY_IMPORTS]
18
24
 
19
25
  # A map of the comms opened with a given target name.
20
26
  comms = {}
@@ -113,6 +119,41 @@ def _reformat_helper(input_code, imports):
113
119
 
114
120
  return reformat_import_statements(before + bmarker + middle + emarker + after)
115
121
 
122
+ def extract_import_statements(text):
123
+ """This is a util for notebook interactions and extracts import statements
124
+ from some python code. This function also re-orders imports.
125
+ Args:
126
+ code (str): The code from which import statements have to be extracted
127
+
128
+ Returns:
129
+ (str, str): The first returned value contains all the import statements.
130
+ The second returned value is the remaining code after
131
+ extracting the import statements.
132
+ """
133
+ transformer = SourceToSourceFileImportsTransformation(text)
134
+ imports = '\n'.join([str(im.pretty_print()) for im in transformer.import_blocks])
135
+ remaining_code = "\n".join([str(st.pretty_print()) if not isinstance(st, SourceToSourceImportBlockTransformation) else "" for st in transformer.blocks])
136
+ return imports, remaining_code
137
+
138
+ def collect_code_with_imports_on_top(imports: str, cell_array):
139
+ return (
140
+ imports
141
+ + "\n"
142
+ + "\n".join(
143
+ [
144
+ cell["text"] if cell["type"] == "code" else ""
145
+ for cell in cell_array
146
+ ]
147
+ )
148
+ )
149
+
150
+ def run_tidy_imports(code):
151
+ return str(
152
+ fix_unused_and_missing_imports(
153
+ replace_star_imports(code)
154
+ )
155
+ )
156
+
116
157
  def comm_open_handler(comm, message):
117
158
  """
118
159
  Handles comm_open message for pyflyby custom comm messages.
@@ -130,7 +171,41 @@ def comm_open_handler(comm, message):
130
171
  def _recv(msg):
131
172
  data = msg["content"]["data"]
132
173
  if data["type"] == FORMATTING_IMPORTS:
133
- msg_id = data.get('msg_id', None)
134
- imports = data.get('imports', None)
174
+ msg_id = data.get("msg_id", None)
175
+ imports = data.get("imports", None)
135
176
  fmt_code = _reformat_helper(data["input_code"], imports)
136
- comm.send({"msg_id": msg_id, "formatted_code": str(fmt_code), "type": FORMATTING_IMPORTS})
177
+ comm.send(
178
+ {
179
+ "msg_id": msg_id,
180
+ "formatted_code": str(fmt_code),
181
+ "type": FORMATTING_IMPORTS,
182
+ }
183
+ )
184
+ elif data["type"] == TIDY_IMPORTS:
185
+ checksum = data.get("checksum", '')
186
+ cell_array = data.get("cellArray", [])
187
+ # import_statements is a string because when
188
+ # SourceToSourceFileImportsTransformation is run on a piece of code
189
+ # it will club similar imports together and re-order the imports
190
+ # by making the imports a string, all the imports are processed
191
+ # together making sure tidy-imports has context on all the imports
192
+ # while clubbing similar imports and re-ordering them.
193
+ import_statements, processed_cell_array = "", []
194
+ for cell in cell_array:
195
+ text = cell.get("text")
196
+ cell_type = cell.get("type")
197
+ if cell_type == "code":
198
+ imports, text = extract_import_statements(text)
199
+ import_statements += imports
200
+ processed_cell_array.append({"text": text, "type": cell_type})
201
+ code_with_collected_imports = collect_code_with_imports_on_top(import_statements, processed_cell_array)
202
+ code_post_tidy_imports = run_tidy_imports(code_with_collected_imports)
203
+ import_statements, _ = extract_import_statements(code_post_tidy_imports)
204
+ comm.send(
205
+ {
206
+ "checksum": checksum,
207
+ "type": TIDY_IMPORTS,
208
+ "cells": processed_cell_array,
209
+ "imports": import_statements,
210
+ }
211
+ )
@@ -194,7 +194,7 @@ class DottedIdentifier(object):
194
194
  return (type(self)(x) for x in self.parts)
195
195
 
196
196
  def __add__(self, suffix):
197
- return type(self)("%s.%s") % (self, suffix)
197
+ return type(self)("%s.%s" % (self, suffix))
198
198
 
199
199
  def __str__(self):
200
200
  return self.name
@@ -1,9 +1,9 @@
1
1
  # pyflyby/_imports2s.py.
2
2
  # Copyright (C) 2011-2018 Karl Chen.
3
3
  # License: MIT http://opensource.org/licenses/MIT
4
+ from collections import defaultdict
4
5
 
5
-
6
-
6
+ import isort
7
7
  from pyflyby._autoimp import scan_for_import_issues
8
8
  from pyflyby._file import FileText, Filename
9
9
  from pyflyby._flags import CompilerFlags
@@ -534,6 +534,48 @@ def replace_star_imports(codeblock, params=None):
534
534
  return transformer.output(params=params)
535
535
 
536
536
 
537
+ def sort_imports(codeblock):
538
+ """
539
+ Sort imports for better grouping.
540
+ :param codeblock:
541
+ :return: codeblock
542
+ """
543
+ sorted_imports = isort.code(
544
+ str(codeblock),
545
+ # To sort all the import in lexicographic order
546
+ force_sort_within_sections=True,
547
+ # This is done below
548
+ lines_between_sections=0,
549
+ lines_after_imports=1
550
+ )
551
+ # Step 1: Split the input string into a list of lines
552
+ lines = sorted_imports.split('\n')
553
+
554
+ # Step 2: Identify groups of imports and keep track of their line numbers
555
+ pkg_lines = defaultdict(list)
556
+ line_pkg_dict = {}
557
+ for i, line in enumerate(lines):
558
+ match = re.match(r'(from (\w+)|import (\w+))', line)
559
+ if match:
560
+ current_pkg = match.groups()[1:3]
561
+ current_pkg = current_pkg[0] if current_pkg[0] is not None else current_pkg[1]
562
+ pkg_lines[current_pkg].append(i)
563
+ line_pkg_dict[i] = current_pkg
564
+
565
+ # Step 3: Create the output list of lines with blank lines around groups with more than one import
566
+ output_lines = []
567
+ for i, line in enumerate(lines):
568
+ if i > 0 and line_pkg_dict.get(i) != line_pkg_dict.get(i-1) and len(pkg_lines[line_pkg_dict.get(i)]) > 1:
569
+ output_lines.append('')
570
+ output_lines.append(line)
571
+ if i < len(lines) - 1 and line_pkg_dict.get(i) != line_pkg_dict.get(i+1) and len(pkg_lines[line_pkg_dict.get(i)]) > 1:
572
+ output_lines.append('')
573
+
574
+ # Step 4: Join the lines to create the output string
575
+ sorted_output_str = '\n'.join(output_lines)
576
+ return PythonBlock(sorted_output_str)
577
+
578
+
537
579
  def transform_imports(codeblock, transformations, params=None):
538
580
  """
539
581
  Transform imports as specified by ``transformations``.
@@ -4,7 +4,10 @@
4
4
 
5
5
 
6
6
 
7
+
8
+ import ast
7
9
  from functools import total_ordering
10
+ import itertools
8
11
  import os
9
12
  import re
10
13
  import six
@@ -67,13 +70,13 @@ def import_module(module_name):
67
70
  real_importerror1, real_importerror2, real_importerror3)
68
71
  if real_importerror1 and real_importerror2 and real_importerror3:
69
72
  raise
70
- reraise(ErrorDuringImportError(
73
+ reraise(ErrorDuringImportError, ErrorDuringImportError(
71
74
  "Error while attempting to import %s: %s: %s"
72
- % (module_name, type(e).__name__, e)), None, sys.exc_info()[2])
75
+ % (module_name, type(e).__name__, e)), sys.exc_info()[2])
73
76
  except Exception as e:
74
- reraise(ErrorDuringImportError(
77
+ reraise(ErrorDuringImportError, ErrorDuringImportError(
75
78
  "Error while attempting to import %s: %s: %s"
76
- % (module_name, type(e).__name__, e)), None, sys.exc_info()[2])
79
+ % (module_name, type(e).__name__, e)), sys.exc_info()[2])
77
80
 
78
81
 
79
82
  def _my_iter_modules(path, prefix=''):
@@ -320,13 +323,26 @@ class ModuleHandle(object):
320
323
  return tuple(ModuleHandle("%s.%s" % (self.name,m))
321
324
  for m in sorted(set(submodule_names)))
322
325
 
326
+ @staticmethod
327
+ def _member_from_node(node):
328
+ extractors = {
329
+ # Top-level assignments (as opposed to member assignments
330
+ # whose targets are of type ast.Attribute).
331
+ ast.Assign: lambda x: [t.id for t in x.targets if isinstance(t, ast.Name)],
332
+ ast.ClassDef: lambda x: [x.name],
333
+ ast.FunctionDef: lambda x: [x.name],
334
+ }
335
+ if isinstance(node, tuple(extractors.keys())):
336
+ return extractors[type(node)](node)
337
+ return []
338
+
323
339
  @cached_attribute
324
340
  def exports(self):
325
341
  """
326
342
  Get symbols exported by this module.
327
343
 
328
- Note that this requires involves actually importing this module, which
329
- may have side effects. (TODO: rewrite to avoid this?)
344
+ Note that this will not recognize symbols that are dynamically
345
+ introduced to the module's namespace or __all__ list.
330
346
 
331
347
  :rtype:
332
348
  `ImportSet` or ``None``
@@ -334,28 +350,72 @@ class ModuleHandle(object):
334
350
  Exports, or ``None`` if nothing exported.
335
351
  """
336
352
  from pyflyby._importclns import ImportStatement, ImportSet
337
- module = self.module
338
- try:
339
- members = module.__all__
340
- except AttributeError:
341
- members = dir(module)
342
- # Filter by non-private.
343
- members = [n for n in members if not n.startswith("_")]
344
- # Filter by definition in the module.
345
- def from_this_module(name):
346
- # TODO: could do this more robustly by parsing the AST and
347
- # looking for STOREs (definitions/assignments/etc).
348
- x = getattr(module, name)
349
- m = getattr(x, "__module__", None)
350
- if not m:
351
- return False
352
- return DottedIdentifier(m).startswith(self.name)
353
- members = [n for n in members if from_this_module(n)]
354
- else:
353
+
354
+ filename = getattr(self, 'filename', None)
355
+ if not filename or not filename.exists:
356
+ # Try to load the module to get the filename
357
+ filename = Filename(self.module.__file__)
358
+ text = FileText(filename)
359
+
360
+ ast_mod = ast.parse(str(text), str(filename)).body
361
+
362
+ # First, add members that are explicitly defined in the module
363
+ members = list(itertools.chain(*[self._member_from_node(n) \
364
+ for n in ast_mod]))
365
+
366
+ # If __all__ is defined, try to use it
367
+ all_is_good = False # pun intended
368
+ if "__all__" in members:
369
+ # Iterate through the nodes and reconstruct the
370
+ # value of __all__
371
+ for n in ast_mod:
372
+ if isinstance(n, ast.Assign):
373
+ if "__all__" in self._member_from_node(n):
374
+ try:
375
+ all_members = list(ast.literal_eval(n.value))
376
+ all_is_good = True
377
+ except (ValueError, TypeError):
378
+ all_is_good = False
379
+ elif isinstance(n, ast.AugAssign) and \
380
+ isinstance(n.target, ast.Name) and \
381
+ n.target.id == "__all__" and all_is_good:
382
+ try:
383
+ all_members += list(ast.literal_eval(n.value))
384
+ except (ValueError, TypeError):
385
+ all_is_good = False
355
386
  if not all(type(s) == str for s in members):
356
387
  raise Exception(
357
388
  "Module %r contains non-string entries in __all__"
358
389
  % (str(self.name),))
390
+
391
+ if all_is_good:
392
+ members = all_members
393
+ else:
394
+ # Add "from" imports that belong to submodules
395
+ # (note: this will fail to recognize implicit relative imports)
396
+ imp_nodes = [n for n in ast_mod if isinstance(n, ast.ImportFrom)]
397
+ for imp_node in imp_nodes:
398
+ if imp_node.level == 0:
399
+ from_mod = DottedIdentifier(imp_node.module)
400
+ if not from_mod.startswith(self.name):
401
+ continue
402
+ elif imp_node.level == 1 and \
403
+ filename.base == "__init__.py":
404
+ # Special case: a relative import can be from a submodule only if
405
+ # our module's filename is __init__.py.
406
+ from_mod = self.name
407
+ if imp_node.module:
408
+ from_mod += imp_node.module
409
+ else:
410
+ continue
411
+ for n in imp_node.names:
412
+ m = n.asname or n.name
413
+ if n.name != "*" and not ModuleHandle(from_mod + m).exists:
414
+ members.append(m)
415
+
416
+ # Filter by non-private.
417
+ members = [n for n in members if not n.startswith("_")]
418
+
359
419
  # Filter out artificially added "deep" members.
360
420
  members = [(n, None) for n in members if "." not in n]
361
421
  if not members:
@@ -24,19 +24,7 @@ from pyflyby._util import cached_attribute, cmp
24
24
 
25
25
  from ast import Bytes
26
26
 
27
- if sys.version_info >= (3, 8):
28
- from ast import TypeIgnore, AsyncFunctionDef
29
- else:
30
-
31
- # TypeIgnore, AsyncFunctionDef does not exist on Python 3.7 and before. thus
32
- # we define a dummy TypeIgnore, AsyncFunctionDef just to simplify remaining
33
- # code.
34
-
35
- class TypeIgnore:
36
- pass
37
-
38
- class AsyncFunctionDef:
39
- pass
27
+ from ast import TypeIgnore, AsyncFunctionDef
40
28
 
41
29
 
42
30
  def _is_comment_or_blank(line):
@@ -104,37 +92,27 @@ def _iter_child_nodes_in_order_internal_1(node):
104
92
  assert node._fields == ("keys", "values")
105
93
  yield list(zip(node.keys, node.values))
106
94
  elif isinstance(node, (ast.FunctionDef, AsyncFunctionDef)):
107
- if sys.version_info >= (3, 8):
108
- assert node._fields == (
109
- "name",
110
- "args",
111
- "body",
112
- "decorator_list",
113
- "returns",
114
- "type_comment",
115
- ), node._fields
116
- res = (
117
- node.type_comment,
118
- node.decorator_list,
119
- node.args,
120
- node.returns,
121
- node.body,
122
- )
123
- yield res
124
- else:
125
- assert node._fields == ('name', 'args', 'body', 'decorator_list',
126
- 'returns'), node._fields
127
- yield node.decorator_list, node.args, node.returns, node.body
95
+ assert node._fields == (
96
+ "name",
97
+ "args",
98
+ "body",
99
+ "decorator_list",
100
+ "returns",
101
+ "type_comment",
102
+ ), node._fields
103
+ res = (
104
+ node.type_comment,
105
+ node.decorator_list,
106
+ node.args,
107
+ node.returns,
108
+ node.body,
109
+ )
110
+ yield res
128
111
  # node.name is a string, not an AST node
129
112
  elif isinstance(node, ast.arguments):
130
- if sys.version_info >= (3, 8):
131
- assert node._fields == ('posonlyargs', 'args', 'vararg', 'kwonlyargs',
132
- 'kw_defaults', 'kwarg', 'defaults'), node._fields
133
- args = node.posonlyargs + node.args
134
- else:
135
- assert node._fields == ('args', 'vararg', 'kwonlyargs',
136
- 'kw_defaults', 'kwarg', 'defaults'), node._fields
137
- args = node.args
113
+ assert node._fields == ('posonlyargs', 'args', 'vararg', 'kwonlyargs',
114
+ 'kw_defaults', 'kwarg', 'defaults'), node._fields
115
+ args = node.posonlyargs + node.args
138
116
  defaults = node.defaults or ()
139
117
  num_no_default = len(args) - len(defaults)
140
118
  yield args[:num_no_default]
@@ -153,7 +131,7 @@ def _iter_child_nodes_in_order_internal_1(node):
153
131
  assert node._fields == ('name', 'bases', 'keywords', 'body', 'decorator_list')
154
132
  yield node.decorator_list, node.bases, node.body
155
133
  # node.name is a string, not an AST node
156
- elif sys.version_info >= (3, 7) and isinstance(node, ast.FormattedValue):
134
+ elif isinstance(node, ast.FormattedValue):
157
135
  assert node._fields == ('value', 'conversion', 'format_spec')
158
136
  yield node.value,
159
137
  else:
@@ -187,22 +165,10 @@ def _flags_to_try(source, flags, auto_flags, mode):
187
165
  If ``auto_flags`` is True, then yield ``flags`` and ``flags ^ print_function``.
188
166
  """
189
167
  flags = CompilerFlags(flags)
190
- if sys.version_info >= (3, 8):
191
- if re.search(r"# *type:", source):
192
- flags = flags | CompilerFlags('type_comments')
193
- yield flags
194
- return
195
- if not auto_flags:
196
- yield flags
197
- return
198
- if mode == "eval":
199
- if re.search(r"\bprint\b", source):
200
- flags = flags | CompilerFlags("print_function")
201
- yield flags
202
- return
168
+ if re.search(r"# *type:", source):
169
+ flags = flags | CompilerFlags('type_comments')
203
170
  yield flags
204
- if re.search(r"\bprint\b", source):
205
- yield flags ^ CompilerFlags("print_function")
171
+ return
206
172
 
207
173
 
208
174
  def _parse_ast_nodes(text, flags, auto_flags, mode):
@@ -348,14 +314,6 @@ def _annotate_ast_startpos(ast_node, parent_ast_node, minpos, text, flags):
348
314
  """
349
315
  assert isinstance(ast_node, (ast.AST, str, TypeIgnore)), ast_node
350
316
 
351
- # joined strings and children do not carry a column offset on pre-3.8
352
- # this prevent reformatting.
353
- # set the column offset to the parent value before 3.8
354
- if (3, 7) < sys.version_info < (3, 8):
355
- instances = (getattr(ast, "JoinedStr", None), ast.FormattedValue)
356
- if ((isinstance(ast_node, instances) or isinstance(parent_ast_node, instances)) and ast_node.col_offset == -1) or isinstance(ast_node, ast.keyword):
357
- ast_node.col_offset = parent_ast_node.col_offset
358
-
359
317
  # First, traverse child nodes. If the first child node (recursively) is a
360
318
  # multiline string, then we need to transfer its information to this node.
361
319
  # Walk all nodes/fields of the AST. We implement this as a custom
@@ -400,9 +358,8 @@ def _annotate_ast_startpos(ast_node, parent_ast_node, minpos, text, flags):
400
358
  if ast_node.col_offset >= 0:
401
359
  # In Python 3.8+, FunctionDef.lineno is the line with the def. To
402
360
  # account for decorators, we need the lineno of the first decorator
403
- if (sys.version_info >= (3, 8)
404
- and isinstance(ast_node, (ast.FunctionDef, ast.ClassDef, AsyncFunctionDef))
405
- and ast_node.decorator_list):
361
+ if (isinstance(ast_node, (ast.FunctionDef, ast.ClassDef, AsyncFunctionDef))
362
+ and ast_node.decorator_list):
406
363
  delta = (ast_node.decorator_list[0].lineno-1,
407
364
  # The col_offset doesn't include the @
408
365
  ast_node.decorator_list[0].col_offset - 1)
@@ -426,20 +383,6 @@ def _annotate_ast_startpos(ast_node, parent_ast_node, minpos, text, flags):
426
383
  # Since we use startpos for breaking lines, we need to set startpos to
427
384
  # the beginning of the line.
428
385
  # In Python 3, the col_offset for the with is 0 again.
429
- if (isinstance(ast_node, ast.With) and
430
- not isinstance(parent_ast_node, ast.With) and
431
- sys.version_info[:2] == (2,7)):
432
- assert ast_node.col_offset >= 5
433
- if startpos.lineno == text.startpos.lineno:
434
- linestart = text.startpos.colno
435
- else:
436
- linestart = 1
437
- line = text[(startpos.lineno,linestart):startpos]
438
- m = re.search(r"\bwith\s+$", str(line))
439
- assert m
440
- lk = len(m.group()) # length of 'with ' including spaces
441
- startpos = FilePos(startpos.lineno, startpos.colno - lk)
442
- assert str(text[startpos:(startpos+(0,4))]) == "with"
443
386
  ast_node.startpos = startpos
444
387
  if sys.version_info <= (3, 8):
445
388
  ast_node.startpos = max(startpos, minpos)
@@ -472,7 +415,7 @@ def _annotate_ast_startpos(ast_node, parent_ast_node, minpos, text, flags):
472
415
 
473
416
  # It should now be the case that we are looking at a multi-line string
474
417
  # literal.
475
- if sys.version_info >= (3, 7) and isinstance(ast_node, ast.FormattedValue):
418
+ if isinstance(ast_node, ast.FormattedValue):
476
419
  ast_node.startpos = ast_node.value.startpos
477
420
  ast_node.endpos = ast_node.value.startpos
478
421
 
@@ -500,9 +443,6 @@ def _annotate_ast_startpos(ast_node, parent_ast_node, minpos, text, flags):
500
443
  for _m in re.finditer("[bBrRuU]*[\"\']", start_line)])
501
444
  target_str = ast_node.s
502
445
 
503
- if isinstance(target_str, bytes) and sys.version_info[:2] == (3, 7):
504
- target_str = target_str.decode()
505
-
506
446
  # Loop over possible end_linenos. The first one we've identified is the
507
447
  # by far most likely one, but in theory it could be anywhere later in the
508
448
  # file. This could be because of a dastardly concatenated string like
@@ -567,18 +507,8 @@ def _annotate_ast_startpos(ast_node, parent_ast_node, minpos, text, flags):
567
507
  candidate_str = _test_parse_string_literal(subtext, flags)
568
508
  if candidate_str is None:
569
509
  continue
570
- if isinstance(candidate_str, bytes) and sys.version_info[:2] == (3, 7):
571
- candidate_str = candidate_str.decode()
572
510
 
573
511
  maybe_fstring = False
574
- try:
575
- if (3, 7) <= sys.version_info <= (3, 8):
576
- potential_start = text.lines[startpos.lineno - 1]
577
- maybe_fstring = ("f'" in potential_start) or (
578
- 'f"' in potential_start
579
- )
580
- except IndexError:
581
- pass
582
512
 
583
513
  if target_str == candidate_str and target_str:
584
514
  # Success!
@@ -603,17 +533,6 @@ def _annotate_ast_startpos(ast_node, parent_ast_node, minpos, text, flags):
603
533
  for (sq, sp) in startpos_candidates
604
534
  if sp in matched_prefix
605
535
  ]
606
- if (3, 7) <= sys.version_info <= (3, 8):
607
- if len(f_string_candidate_prefixes) == 1:
608
- # we did not find the string but there is one fstring candidate starting it
609
-
610
- ast_node.startpos, ast_node.endpos = f_string_candidate_prefixes[0]
611
- return True
612
- elif isinstance(parent_ast_node, ast.JoinedStr):
613
- self_pos = parent_ast_node.values.index(ast_node)
614
- ast_node.startpos = parent_ast_node.values[self_pos - 1].startpos
615
- ast_node.endpos = parent_ast_node.values[self_pos - 1].endpos
616
- return True
617
536
  raise ValueError("Couldn't find exact position of %s" % (ast.dump(ast_node)))
618
537
 
619
538
 
@@ -801,7 +720,7 @@ class PythonStatement(object):
801
720
  comments/blank lines.
802
721
 
803
722
  >>> PythonStatement('print("x",\n file=None)\n', flags='print_function') #doctest: +SKIP
804
- PythonStatement('print("x",\n file=None)\n', flags=0x10000)
723
+ PythonStatement('print("x",\n file=None)\n', flags=0x100000)
805
724
 
806
725
  Implemented as a wrapper around a `PythonBlock` containing at most one
807
726
  top-level AST node.