pyflyby 1.9.3__tar.gz → 1.9.4__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.
Files changed (156) hide show
  1. {pyflyby-1.9.3/lib/python/pyflyby.egg-info → pyflyby-1.9.4}/PKG-INFO +6 -3
  2. {pyflyby-1.9.3 → pyflyby-1.9.4}/README.rst +1 -1
  3. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/autopython +1 -1
  4. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/create-imports +1 -1
  5. pyflyby-1.9.4/bin/pyflyby/.DS_Store +0 -0
  6. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/__init__.py +3 -1
  7. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_autoimp.py +80 -31
  8. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_cmdline.py +2 -2
  9. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_comms.py +3 -1
  10. {pyflyby-1.9.3/lib/python → pyflyby-1.9.4/bin}/pyflyby/_file.py +5 -3
  11. {pyflyby-1.9.3/lib/python → pyflyby-1.9.4/bin}/pyflyby/_flags.py +1 -1
  12. pyflyby-1.9.4/bin/pyflyby/_import_sorting.py +165 -0
  13. {pyflyby-1.9.3/lib/python → pyflyby-1.9.4/bin}/pyflyby/_importclns.py +28 -14
  14. {pyflyby-1.9.3/lib/python → pyflyby-1.9.4/bin}/pyflyby/_importdb.py +4 -2
  15. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_imports2s.py +54 -84
  16. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_importstmt.py +52 -11
  17. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_interactive.py +2 -2
  18. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_livepatch.py +4 -1
  19. {pyflyby-1.9.3/lib/python → pyflyby-1.9.4/bin}/pyflyby/_modules.py +20 -5
  20. {pyflyby-1.9.3/lib/python → pyflyby-1.9.4/bin}/pyflyby/_parse.py +259 -242
  21. {pyflyby-1.9.3/lib/python → pyflyby-1.9.4/bin}/pyflyby/_py.py +10 -20
  22. {pyflyby-1.9.3/lib/python → pyflyby-1.9.4/bin}/pyflyby/_version.py +1 -2
  23. {pyflyby-1.9.3/lib/python → pyflyby-1.9.4/bin}/pyflyby/autoimport.py +1 -1
  24. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby-diff +1 -1
  25. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/tidy-imports +37 -14
  26. pyflyby-1.9.4/doc/.DS_Store +0 -0
  27. pyflyby-1.9.4/lib/.DS_Store +0 -0
  28. pyflyby-1.9.4/lib/python/.DS_Store +0 -0
  29. pyflyby-1.9.4/lib/python/pyflyby/.DS_Store +0 -0
  30. {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/__init__.py +3 -1
  31. {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_autoimp.py +80 -31
  32. {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_cmdline.py +2 -2
  33. {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_comms.py +3 -1
  34. {pyflyby-1.9.3/bin → pyflyby-1.9.4/lib/python}/pyflyby/_file.py +5 -3
  35. {pyflyby-1.9.3/bin → pyflyby-1.9.4/lib/python}/pyflyby/_flags.py +1 -1
  36. pyflyby-1.9.4/lib/python/pyflyby/_import_sorting.py +165 -0
  37. {pyflyby-1.9.3/bin → pyflyby-1.9.4/lib/python}/pyflyby/_importclns.py +28 -14
  38. {pyflyby-1.9.3/bin → pyflyby-1.9.4/lib/python}/pyflyby/_importdb.py +4 -2
  39. {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_imports2s.py +54 -84
  40. {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_importstmt.py +52 -11
  41. {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_interactive.py +2 -2
  42. {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_livepatch.py +4 -1
  43. {pyflyby-1.9.3/bin → pyflyby-1.9.4/lib/python}/pyflyby/_modules.py +20 -5
  44. {pyflyby-1.9.3/bin → pyflyby-1.9.4/lib/python}/pyflyby/_parse.py +259 -242
  45. {pyflyby-1.9.3/bin → pyflyby-1.9.4/lib/python}/pyflyby/_py.py +10 -20
  46. {pyflyby-1.9.3/bin → pyflyby-1.9.4/lib/python}/pyflyby/_version.py +1 -2
  47. {pyflyby-1.9.3/bin → pyflyby-1.9.4/lib/python}/pyflyby/autoimport.py +1 -1
  48. {pyflyby-1.9.3 → pyflyby-1.9.4/lib/python/pyflyby.egg-info}/PKG-INFO +6 -3
  49. {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby.egg-info/SOURCES.txt +7 -0
  50. {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby.egg-info/requires.txt +0 -1
  51. {pyflyby-1.9.3 → pyflyby-1.9.4}/libexec/pyflyby/colordiff +1 -1
  52. {pyflyby-1.9.3 → pyflyby-1.9.4}/setup.py +2 -2
  53. {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_autoimp.py +46 -1
  54. {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_imports2s.py +3 -0
  55. {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_jupyterlab_pyflyby.py +2 -1
  56. {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_parse.py +116 -44
  57. {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_py.py +221 -207
  58. {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/tests_sorts.py +65 -12
  59. {pyflyby-1.9.3 → pyflyby-1.9.4}/.pyflyby +0 -0
  60. {pyflyby-1.9.3 → pyflyby-1.9.4}/LICENSE.txt +0 -0
  61. {pyflyby-1.9.3 → pyflyby-1.9.4}/MANIFEST.in +0 -0
  62. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/autoipython +0 -0
  63. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/collect-exports +0 -0
  64. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/collect-imports +0 -0
  65. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/find-import +0 -0
  66. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/list-bad-xrefs +0 -0
  67. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/prune-broken-imports +0 -0
  68. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/py +0 -0
  69. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/__main__.py +0 -0
  70. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_dbg.py +0 -0
  71. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_docxref.py +0 -0
  72. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_format.py +0 -0
  73. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_idents.py +0 -0
  74. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_log.py +0 -0
  75. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/_util.py +0 -0
  76. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/pyflyby/importdb.py +0 -0
  77. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/reformat-imports +0 -0
  78. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/replace-star-imports +0 -0
  79. {pyflyby-1.9.3 → pyflyby-1.9.4}/bin/transform-imports +0 -0
  80. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/LICENSE.txt +0 -0
  81. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/Makefile +0 -0
  82. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/TODO.txt +0 -0
  83. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/__init__.py +0 -0
  84. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/api.rst +0 -0
  85. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/autoimp.rst +0 -0
  86. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/cmdline.rst +0 -0
  87. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/comms.rst +0 -0
  88. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/dbg.rst +0 -0
  89. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/file.rst +0 -0
  90. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/flags.rst +0 -0
  91. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/format.rst +0 -0
  92. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/idents.rst +0 -0
  93. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/importclns.rst +0 -0
  94. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/importdb.rst +0 -0
  95. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/imports2s.rst +0 -0
  96. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/importstmt.rst +0 -0
  97. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/interactive.rst +0 -0
  98. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/livepatch.rst +0 -0
  99. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/log.rst +0 -0
  100. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/modules.rst +0 -0
  101. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/parse.rst +0 -0
  102. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/py.rst +0 -0
  103. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/api/util.rst +0 -0
  104. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/autoipython.rst +0 -0
  105. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/cli.rst +0 -0
  106. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/collect_exports.rst +0 -0
  107. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/collect_imports.rst +0 -0
  108. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/find_import.rst +0 -0
  109. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/prune_broken_imports.rst +0 -0
  110. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/py.rst +0 -0
  111. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/pyflyby_diff.rst +0 -0
  112. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/reformat_imports.rst +0 -0
  113. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/replace_star_imports.rst +0 -0
  114. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/tidy_imports.rst +0 -0
  115. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/cli/transform_imports.rst +0 -0
  116. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/conf.py +0 -0
  117. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/index.rst +0 -0
  118. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/make.bat +0 -0
  119. {pyflyby-1.9.3 → pyflyby-1.9.4}/doc/testing.txt +0 -0
  120. {pyflyby-1.9.3 → pyflyby-1.9.4}/etc/pyflyby/canonical.py +0 -0
  121. {pyflyby-1.9.3 → pyflyby-1.9.4}/etc/pyflyby/common.py +0 -0
  122. {pyflyby-1.9.3 → pyflyby-1.9.4}/etc/pyflyby/forget.py +0 -0
  123. {pyflyby-1.9.3 → pyflyby-1.9.4}/etc/pyflyby/mandatory.py +0 -0
  124. {pyflyby-1.9.3 → pyflyby-1.9.4}/etc/pyflyby/numpy.py +0 -0
  125. {pyflyby-1.9.3 → pyflyby-1.9.4}/etc/pyflyby/std.py +0 -0
  126. {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/emacs/pyflyby.el +0 -0
  127. {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/__main__.py +0 -0
  128. {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_dbg.py +0 -0
  129. {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_docxref.py +0 -0
  130. {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_format.py +0 -0
  131. {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_idents.py +0 -0
  132. {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_log.py +0 -0
  133. {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/_util.py +0 -0
  134. {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby/importdb.py +0 -0
  135. {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby.egg-info/dependency_links.txt +0 -0
  136. {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby.egg-info/entry_points.txt +0 -0
  137. {pyflyby-1.9.3 → pyflyby-1.9.4}/lib/python/pyflyby.egg-info/top_level.txt +0 -0
  138. {pyflyby-1.9.3 → pyflyby-1.9.4}/libexec/pyflyby/diff-colorize +0 -0
  139. {pyflyby-1.9.3 → pyflyby-1.9.4}/setup.cfg +0 -0
  140. {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/__init__.py +0 -0
  141. {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_0testconfig.py +0 -0
  142. {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_cmdline.py +0 -0
  143. {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_docxref.py +0 -0
  144. {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_file.py +0 -0
  145. {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_flags.py +0 -0
  146. {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_format.py +0 -0
  147. {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_idents.py +0 -0
  148. {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_importclns.py +0 -0
  149. {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_importdb.py +0 -0
  150. {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_importstmt.py +0 -0
  151. {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_interactive.py +0 -0
  152. {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_livepatch.py +0 -0
  153. {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_modules.py +0 -0
  154. {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/test_util.py +0 -0
  155. {pyflyby-1.9.3 → pyflyby-1.9.4}/tests/xrefs.py +0 -0
  156. {pyflyby-1.9.3 → pyflyby-1.9.4}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyflyby
3
- Version: 1.9.3
3
+ Version: 1.9.4
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
@@ -16,8 +16,11 @@ Classifier: Topic :: Software Development :: Interpreters
16
16
  Classifier: Intended Audience :: Developers
17
17
  Classifier: License :: OSI Approved :: MIT License
18
18
  Classifier: Programming Language :: Python
19
- Requires-Python: >3.6, <4
19
+ Requires-Python: >3.8, <4
20
20
  License-File: LICENSE.txt
21
+ Requires-Dist: six
22
+ Requires-Dist: toml
23
+ Requires-Dist: black
21
24
 
22
25
  #########
23
26
  Pyflyby
@@ -29,7 +32,7 @@ License-File: LICENSE.txt
29
32
  .. image:: https://travis-ci.org/deshaw/pyflyby.png?branch=master
30
33
  :target: https://travis-ci.org/deshaw/pyflyby
31
34
 
32
- Pyflyby is a set of Python programming productivity tools for Python 3.7+.
35
+ Pyflyby is a set of Python programming productivity tools for Python 3.8+.
33
36
 
34
37
  For command-line interaction:
35
38
  * ``py``: command-line multitool
@@ -8,7 +8,7 @@
8
8
  .. image:: https://travis-ci.org/deshaw/pyflyby.png?branch=master
9
9
  :target: https://travis-ci.org/deshaw/pyflyby
10
10
 
11
- Pyflyby is a set of Python programming productivity tools for Python 3.7+.
11
+ Pyflyby is a set of Python programming productivity tools for Python 3.8+.
12
12
 
13
13
  For command-line interaction:
14
14
  * ``py``: command-line multitool
@@ -1,4 +1,4 @@
1
- #!/bin/bash -e
1
+ #!/usr/bin/env -S bash -e
2
2
 
3
3
  # pyflyby/autopython
4
4
 
@@ -1,4 +1,4 @@
1
- #!/bin/bash -e
1
+ #!/usr/bin/env -S bash -e
2
2
 
3
3
  # pyflyby/create-imports
4
4
 
Binary file
@@ -40,6 +40,8 @@ from pyflyby._dbg import (breakpoint, debug_exception,
40
40
  enable_signal_handler_breakpoint,
41
41
  waitpoint)
42
42
 
43
+ from typing import Sequence
44
+
43
45
 
44
46
  # Promote the function & classes that we've chosen to expose publicly to be
45
47
  # known as pyflyby.Foo instead of pyflyby._module.Foo.
@@ -51,4 +53,4 @@ del x
51
53
 
52
54
  # Discourage "from pyflyby import *".
53
55
  # Use the tidy-imports/autoimporter instead!
54
- __all__ = []
56
+ __all__:Sequence[str] = []
@@ -6,13 +6,9 @@
6
6
 
7
7
  import ast
8
8
  import builtins
9
+ from collections.abc import Sequence
9
10
  import contextlib
10
11
  import copy
11
- from six import reraise
12
- import sys
13
- import types
14
-
15
- from collections.abc import Sequence
16
12
 
17
13
  from pyflyby._file import FileText, Filename
18
14
  from pyflyby._flags import CompilerFlags
@@ -22,7 +18,13 @@ from pyflyby._importdb import ImportDB
22
18
  from pyflyby._importstmt import Import
23
19
  from pyflyby._log import logger
24
20
  from pyflyby._modules import ModuleHandle
25
- from pyflyby._parse import PythonBlock, infer_compile_mode, _is_ast_str
21
+ from pyflyby._parse import (PythonBlock, _is_ast_str,
22
+ infer_compile_mode)
23
+
24
+ from six import reraise
25
+ import sys
26
+ import types
27
+ from typing import Any, Set
26
28
 
27
29
  if sys.version_info >= (3, 12):
28
30
  ATTRIBUTE_NAME = "value"
@@ -34,13 +36,13 @@ if sys.version_info > (3, 11):
34
36
  else:
35
37
  LOAD_SHIFT = 0
36
38
 
37
- if sys.version_info > (3, 11):
38
- LOAD_SHIFT = 1
39
+
40
+ if sys.version_info >= (3, 10):
41
+ from types import NoneType, EllipsisType
39
42
  else:
40
- LOAD_SHIFT = 0
43
+ NoneType = type(None)
44
+ EllipsisType = type(Ellipsis)
41
45
 
42
- NoneType = type(None)
43
- EllipsisType = type(Ellipsis)
44
46
 
45
47
  class _ClassScope(dict):
46
48
  pass
@@ -419,7 +421,8 @@ class _MissingImportFinder(object):
419
421
 
420
422
  def scan_for_import_issues(self, codeblock):
421
423
  # See global `scan_for_import_issues`
422
- codeblock = PythonBlock(codeblock)
424
+ if not isinstance(codeblock, PythonBlock):
425
+ codeblock = PythonBlock(codeblock)
423
426
  node = codeblock.ast_node
424
427
  self._scan_node(node)
425
428
  # Get missing imports now, before handling docstrings. We don't want
@@ -589,7 +592,10 @@ class _MissingImportFinder(object):
589
592
  logger.warning("Don't know how to handle __all__ with list elements other than str")
590
593
  return
591
594
  for e in node.value.elts:
592
- self._visit_Load_defered_global(e.s)
595
+ if sys.version_info > (3,10):
596
+ self._visit_Load_defered_global(e.value)
597
+ else:
598
+ self._visit_Load_defered_global(e.s)
593
599
 
594
600
  def visit_ClassDef(self, node):
595
601
  logger.debug("visit_ClassDef(%r)", node)
@@ -634,6 +640,10 @@ class _MissingImportFinder(object):
634
640
  else:
635
641
  assert node._fields == ('name', 'args', 'body', 'decorator_list', 'returns', 'type_comment'), node._fields
636
642
  with self._NewScopeCtx(include_class_scopes=True):
643
+ # we want `__class__` to only be defined in
644
+ # methods and not class body
645
+ if self._in_class_def:
646
+ self.scopestack[-1]["__class__"] = None # we just need to to be defined
637
647
  self.visit(node.decorator_list)
638
648
  self.visit(node.args)
639
649
  if node.returns:
@@ -1064,7 +1074,8 @@ def scan_for_import_issues(codeblock, find_unused_imports=True, parse_docstrings
1064
1074
 
1065
1075
  """
1066
1076
  logger.debug("scan_for_import_issues()")
1067
- codeblock = PythonBlock(codeblock)
1077
+ if not isinstance(codeblock, PythonBlock):
1078
+ codeblock = PythonBlock(codeblock)
1068
1079
  namespaces = ScopeStack([{}])
1069
1080
  finder = _MissingImportFinder(namespaces,
1070
1081
  find_unused_imports=find_unused_imports,
@@ -1152,10 +1163,13 @@ def _find_loads_without_stores_in_code(co, loads_without_stores):
1152
1163
  "_find_loads_without_stores_in_code(): expected a CodeType; got a %s"
1153
1164
  % (type(co).__name__,))
1154
1165
  # Initialize local constants for fast access.
1155
- from opcode import HAVE_ARGUMENT, EXTENDED_ARG, opmap
1166
+ from opcode import EXTENDED_ARG, opmap
1156
1167
 
1157
1168
  LOAD_ATTR = opmap['LOAD_ATTR']
1169
+ # LOAD_METHOD is _supposed_ to be removed in 3.12 but still present in opmap
1170
+ # if sys.version_info < (3, 12):
1158
1171
  LOAD_METHOD = opmap['LOAD_METHOD']
1172
+ # endif
1159
1173
  LOAD_GLOBAL = opmap['LOAD_GLOBAL']
1160
1174
  LOAD_NAME = opmap['LOAD_NAME']
1161
1175
  STORE_ATTR = opmap['STORE_ATTR']
@@ -1255,12 +1269,11 @@ def _find_loads_without_stores_in_code(co, loads_without_stores):
1255
1269
  earliest_backjump_label = _find_earliest_backjump_label(bytecode)
1256
1270
  # Loop through bytecode.
1257
1271
  while i < n:
1258
- c = bytecode[i]
1259
- op = _op(c)
1272
+ op = bytecode[i]
1260
1273
  i += 1
1261
1274
  if op == CACHE:
1262
1275
  continue
1263
- if op >= HAVE_ARGUMENT:
1276
+ if take_arg(op):
1264
1277
  oparg = bytecode[i] | extended_arg
1265
1278
  extended_arg = 0
1266
1279
  if op == EXTENDED_ARG:
@@ -1277,9 +1290,40 @@ def _find_loads_without_stores_in_code(co, loads_without_stores):
1277
1290
  stores.add(fullname)
1278
1291
  continue
1279
1292
  if op in [LOAD_ATTR, LOAD_METHOD]:
1280
- # {LOAD_GLOBAL|LOAD_NAME} {LOAD_ATTR}* so far;
1281
- # possibly more LOAD_ATTR/STORE_ATTR will follow
1282
- pending.append(co.co_names[oparg])
1293
+ if sys.version_info >= (3,12):
1294
+ # from the docs:
1295
+ #
1296
+ # If the low bit of namei is not set, this replaces
1297
+ # STACK[-1] with getattr(STACK[-1], co_names[namei>>1]).
1298
+ #
1299
+ # If the low bit of namei is set, this will attempt to load
1300
+ # a method named co_names[namei>>1] from the STACK[-1]
1301
+ # object. STACK[-1] is popped. This bytecode distinguishes
1302
+ # two cases: if STACK[-1] has a method with the correct
1303
+ # name, the bytecode pushes the unbound method and
1304
+ # STACK[-1]. STACK[-1] will be used as the first argument
1305
+ # (self) by CALL when calling the unbound method. Otherwise,
1306
+ # NULL and the object returned by the attribute lookup are
1307
+ # pushed.
1308
+ #
1309
+ # Changed in version 3.12: If the low bit of namei is set,
1310
+ # then a NULL or self is pushed to the stack before the
1311
+ # attribute or unbound method respectively.
1312
+ #
1313
+ # Implication for Pyflyby
1314
+ #
1315
+ # In our case I think it means we are always looking at
1316
+ # oparg>>1 as the name of the names we need to load,
1317
+ # Though we don't keep track of the stack, and so we may get
1318
+ # wrong results ?
1319
+ #
1320
+ # In any case this seem to match what load_method was doing
1321
+ # before.
1322
+ pending.append(co.co_names[oparg>>1])
1323
+ else:
1324
+ # {LOAD_GLOBAL|LOAD_NAME} {LOAD_ATTR}* so far;
1325
+ # possibly more LOAD_ATTR/STORE_ATTR will follow
1326
+ pending.append(co.co_names[oparg])
1283
1327
  continue
1284
1328
  # {LOAD_GLOBAL|LOAD_NAME} {LOAD_ATTR}* (and no more
1285
1329
  # LOAD_ATTR/STORE_ATTR)
@@ -1366,10 +1410,14 @@ def _find_loads_without_stores_in_code(co, loads_without_stores):
1366
1410
  if isinstance(arg, types.CodeType):
1367
1411
  _find_loads_without_stores_in_code(arg, loads_without_stores)
1368
1412
 
1369
-
1370
- def _op(c):
1371
- return c
1372
-
1413
+ if sys.version_info >= (3,12):
1414
+ from dis import hasarg
1415
+ def take_arg(op):
1416
+ return op in hasarg
1417
+ else:
1418
+ def take_arg(op):
1419
+ from opcode import HAVE_ARGUMENT
1420
+ return op >= HAVE_ARGUMENT
1373
1421
 
1374
1422
  def _find_earliest_backjump_label(bytecode):
1375
1423
  """
@@ -1411,19 +1459,20 @@ def _find_earliest_backjump_label(bytecode):
1411
1459
  The earliest target of a backward jump, as an offset into the bytecode.
1412
1460
  """
1413
1461
  # Code based on dis.findlabels().
1414
- from opcode import HAVE_ARGUMENT, hasjrel, hasjabs
1462
+ from opcode import hasjrel, hasjabs
1415
1463
  if not isinstance(bytecode, bytes):
1416
1464
  raise TypeError
1417
1465
  n = len(bytecode)
1418
1466
  earliest_backjump_label = n
1419
1467
  i = 0
1420
1468
  while i < n:
1421
- c = bytecode[i]
1422
- op = _op(c)
1469
+ op = bytecode[i]
1423
1470
  i += 1
1424
- if op < HAVE_ARGUMENT:
1471
+ if not take_arg(op):
1425
1472
  continue
1426
- oparg = _op(bytecode[i]) + _op(bytecode[i+1])*256
1473
+ if i+1 >= len(bytecode):
1474
+ break
1475
+ oparg = bytecode[i] + bytecode[i+1]*256
1427
1476
  i += 2
1428
1477
  label = None
1429
1478
  if op in hasjrel:
@@ -1605,7 +1654,7 @@ def get_known_import(fullname, db=None):
1605
1654
  return None
1606
1655
 
1607
1656
 
1608
- _IMPORT_FAILED = set()
1657
+ _IMPORT_FAILED:Set[Any] = set()
1609
1658
  """
1610
1659
  Set of imports we've already attempted and failed.
1611
1660
  """
@@ -415,8 +415,8 @@ def process_actions(filenames:List[str], actions, modify_function,
415
415
  continue
416
416
  if errors:
417
417
  msg = "\n%s: encountered the following problems:\n" % (sys.argv[0],)
418
- for e in errors:
419
- lines = e.splitlines()
418
+ for er in errors:
419
+ lines = er.splitlines()
420
420
  msg += " " + lines[0] + '\n'.join(
421
421
  (" %s"%line for line in lines[1:]))
422
422
  raise SystemExit(msg)
@@ -9,6 +9,8 @@ from pyflyby._imports2s import (SourceToSourceFileImportsTransformation,
9
9
  from pyflyby._importstmt import Import
10
10
  from pyflyby._log import logger
11
11
 
12
+ from typing import Dict, Any
13
+
12
14
  # These are comm targets that the frontend (lab/notebook) is expected to
13
15
  # open. At this point, we handle only missing imports and
14
16
  # formatting imports
@@ -23,7 +25,7 @@ PYFLYBY_END_MSG = "# END AUTO-GENERATED BLOCK\n"
23
25
  pyflyby_comm_targets= [MISSING_IMPORTS, FORMATTING_IMPORTS, TIDY_IMPORTS]
24
26
 
25
27
  # A map of the comms opened with a given target name.
26
- comms = {}
28
+ comms:Dict[str, Any] = {}
27
29
 
28
30
  # TODO: Document the expected contract for the different
29
31
  # custom comm messages
@@ -1,15 +1,14 @@
1
1
  # pyflyby/_file.py.
2
2
  # Copyright (C) 2011, 2012, 2013, 2014, 2015, 2018 Karl Chen.
3
3
  # License: MIT http://opensource.org/licenses/MIT
4
-
5
-
4
+ from __future__ import annotations
6
5
 
7
6
  from functools import total_ordering, cached_property
8
7
  import io
9
8
  import os
10
9
  import re
11
10
  import sys
12
- from typing import Optional, Tuple
11
+ from typing import Optional, Tuple, ClassVar
13
12
 
14
13
  from pyflyby._util import cmp, memoize
15
14
 
@@ -30,6 +29,7 @@ class Filename(object):
30
29
 
31
30
  """
32
31
  _filename: str
32
+ STDIN: Filename
33
33
 
34
34
  def __new__(cls, arg):
35
35
  if isinstance(arg, cls):
@@ -236,6 +236,8 @@ class FilePos(object):
236
236
  lineno: int
237
237
  colno: int
238
238
 
239
+ _ONE_ONE: ClassVar[FilePos]
240
+
239
241
  def __new__(cls, *args):
240
242
  if len(args) == 0:
241
243
  return cls._ONE_ONE
@@ -157,7 +157,7 @@ class CompilerFlags(int):
157
157
  return cls(flags)
158
158
 
159
159
  @cached_attribute
160
- def names(self) -> Tuple[str]:
160
+ def names(self) -> Tuple[str, ...]:
161
161
  return tuple(
162
162
  n
163
163
  for f, n in _FLAGNAME_ITEMS
@@ -0,0 +1,165 @@
1
+ """
2
+ This module contain utility functions to sort imports in a Python Block.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ from collections import Counter
7
+ from dataclasses import dataclass
8
+ from itertools import groupby
9
+ from pyflyby._importstmt import ImportStatement, PythonStatement
10
+ from pyflyby._parse import PythonBlock
11
+ from typing import List, Tuple, Union
12
+
13
+
14
+ # @dataclass
15
+ # class ImportSection:
16
+ # """
17
+ # This represent an Import
18
+ # """
19
+ # sections: List[ImportGroup]
20
+
21
+
22
+ @dataclass
23
+ class ImportGroup:
24
+ """
25
+ Typically at the top of a file, the first import will be part
26
+ of an ImportGroup, and subsequent imports
27
+ will be other import groups which.
28
+
29
+ Import sorting will affect only the imports in the same group,
30
+ as we want import sorting to be indempotent. If import sorting
31
+ were migrating imports between groups, then imports could be
32
+ moved to sections were comments would nto be relevant.
33
+ """
34
+
35
+ imports: List[ImportStatement]
36
+
37
+ @classmethod
38
+ def from_statements(cls, statements: List[PythonStatement]) -> ImportGroup:
39
+ return ImportGroup([ImportStatement(s) for s in statements])
40
+
41
+ def sorted(self) -> ImportGroup:
42
+ """
43
+ return an ImportGroup with import sorted lexicographically.
44
+ """
45
+ return ImportGroup(sorted(self.imports, key=lambda x: x._cmp()))
46
+
47
+ def sorted_subgroups(self) -> List[Tuple[bool, List[ImportStatement]]]:
48
+ """
49
+ Return a list of subgroup keyed by module and whether they are the sole import
50
+ from this module.
51
+
52
+ From issue #13, we will want to sort import from the same package together when
53
+ there is more than one import and separat with a blank line
54
+
55
+ We also group all imports from a package that appear only once together.
56
+
57
+ For this we need both to know if the import is from a single import (the boolean).
58
+
59
+ Returns
60
+ -------
61
+ bool:
62
+ wether from a single import
63
+ List[ImportStatement]
64
+ The actual import.
65
+ """
66
+ c = Counter(imp.module[0] for imp in self.imports)
67
+ return [
68
+ (c[k] > 1, list(v)) for k, v in groupby(self.imports, lambda x: x.module[0])
69
+ ]
70
+
71
+
72
+ def split_import_groups(
73
+ statements: Tuple[PythonStatement],
74
+ ) -> List[Union[ImportGroup, PythonStatement]]:
75
+ """
76
+ Given a list of statements split into import groups.
77
+
78
+ One of the question is how to treat split with comments.
79
+
80
+ - Do blank lines after groups with comments start a new block
81
+ - Does the comment line create a complete new block.
82
+
83
+ In particular because we want this to be indempotent,
84
+ we can't move imports between groups.
85
+ """
86
+
87
+ # these are the import groups we'll
88
+ groups: List[PythonStatement | ImportGroup] = []
89
+
90
+ current_group: List[PythonStatement] = []
91
+ statemt_iterator = iter(statements)
92
+ for statement in statemt_iterator:
93
+ if statement.is_blank and current_group:
94
+ pass
95
+ # currently do nothing with whitespace while in import groups.
96
+ elif statement.is_import:
97
+ # push on top of current_comment.
98
+ current_group.append(statement)
99
+ else:
100
+ if current_group:
101
+ groups.append(ImportGroup.from_statements(current_group))
102
+ current_group = []
103
+ groups.append(statement)
104
+
105
+ # we should break of and populate rest
106
+ # We can't do anything if we encounter any non-import statement,
107
+ # as we do no know if it can be a conditional.
108
+ # technically I guess we coudl find another import block, and reorder these.
109
+ if current_group:
110
+ groups.append(ImportGroup.from_statements(current_group))
111
+
112
+ # this is an iterator, not an iterable, we exaust it to reify the rest.
113
+
114
+ # first group may be empty if the first line is a comment.
115
+ # We filter and sort relevant statements.
116
+ groups = [g for g in groups if groups]
117
+ sorted_groups: List[PythonStatement | ImportGroup] = [
118
+ g.sorted() if isinstance(g, ImportGroup) else g for g in groups
119
+ ]
120
+ return sorted_groups
121
+
122
+
123
+ def regroup(groups: List[ImportGroup | PythonStatement]) -> PythonBlock:
124
+ """
125
+ given import groups and list of statement, return an Python block with sorted import
126
+ """
127
+ res: str = ""
128
+ in_single = False
129
+ for group in groups:
130
+ if isinstance(group, ImportGroup):
131
+ # the subgroup here will be responsible for groups reordering.
132
+ for mult, subgroup in group.sorted_subgroups():
133
+ if mult:
134
+ if in_single:
135
+ res += "\n"
136
+ if not res.endswith("\n"):
137
+ res += "\n"
138
+ for x in subgroup:
139
+ res += x.pretty_print(import_column=30).rstrip() + "\n"
140
+ if not res.endswith("\n\n"):
141
+ res += "\n"
142
+ in_single = False
143
+ else:
144
+ assert len(subgroup) == 1
145
+ in_single = True
146
+ for sub in subgroup:
147
+ res += str(sub).rstrip() + "\n"
148
+
149
+ if not res.endswith("\n\n"):
150
+ res += "\n"
151
+ else:
152
+ if in_single and not res.endswith("\n\n"):
153
+ res += "\n"
154
+ in_single = False
155
+ res += str(PythonBlock(group))
156
+
157
+ return PythonBlock.concatenate([PythonBlock(res)])
158
+
159
+
160
+ def sort_imports(block: PythonBlock) -> PythonBlock:
161
+ assert isinstance(block, PythonBlock)
162
+ # we ignore below that block.statement can be a List[Unknown]
163
+ gs = split_import_groups(block.statements) # type: ignore
164
+ # TODO: math the other fileds like filename....
165
+ return regroup(gs)
@@ -2,6 +2,8 @@
2
2
  # Copyright (C) 2011, 2012, 2013, 2014 Karl Chen.
3
3
  # License: MIT http://opensource.org/licenses/MIT
4
4
 
5
+ from __future__ import annotations
6
+
5
7
 
6
8
 
7
9
  from collections import defaultdict
@@ -16,7 +18,8 @@ from pyflyby._parse import PythonBlock
16
18
  from pyflyby._util import (cached_attribute, cmp, partition,
17
19
  stable_unique)
18
20
 
19
- from typing import Dict
21
+ from typing import (ClassVar, Dict, FrozenSet, List,
22
+ Sequence, Union)
20
23
 
21
24
 
22
25
  class NoSuchImportError(ValueError):
@@ -46,6 +49,9 @@ class ImportSet(object):
46
49
  An ``ImportSet`` is an immutable data structure.
47
50
  """
48
51
 
52
+ _EMPTY : ClassVar[ImportSet]
53
+ _importset : FrozenSet[Import]
54
+
49
55
  def __new__(cls, arg, ignore_nonimports=False, ignore_shadowed=False):
50
56
  """
51
57
  Return as an `ImportSet`.
@@ -66,7 +72,7 @@ class ImportSet(object):
66
72
  """
67
73
  if isinstance(arg, cls):
68
74
  if ignore_shadowed:
69
- return cls._from_imports(arg._importset, ignore_shadowed=True)
75
+ return cls._from_imports(list(arg._importset), ignore_shadowed=True)
70
76
  else:
71
77
  return arg
72
78
  return cls._from_args(
@@ -75,7 +81,7 @@ class ImportSet(object):
75
81
  ignore_shadowed=ignore_shadowed)
76
82
 
77
83
  @classmethod
78
- def _from_imports(cls, imports, ignore_shadowed=False):
84
+ def _from_imports(cls, imports:List[Import], ignore_shadowed:bool=False):
79
85
  """
80
86
  :type imports:
81
87
  Sequence of `Import` s
@@ -85,26 +91,30 @@ class ImportSet(object):
85
91
  `ImportSet`
86
92
  """
87
93
  # Canonicalize inputs.
88
- imports = [Import(imp) for imp in imports]
94
+ by_import_as: Dict[Union[str, Import], Import]
95
+ filtered_imports: Sequence[Import]
96
+ for imp in imports:
97
+ assert isinstance(imp, Import)
98
+ _imports = [Import(imp) for imp in imports]
89
99
  if ignore_shadowed:
90
100
  # Filter by overshadowed imports. Later imports take precedence.
91
101
  by_import_as = {}
92
- for imp in imports:
102
+ for imp in _imports:
93
103
  if imp.import_as == "*":
94
104
  # Keep all unique star imports.
95
105
  by_import_as[imp] = imp
96
106
  else:
97
107
  by_import_as[imp.import_as] = imp
98
- filtered_imports = by_import_as.values()
108
+ filtered_imports = list(by_import_as.values())
99
109
  else:
100
- filtered_imports = imports
110
+ filtered_imports = _imports
101
111
  # Construct and return.
102
112
  self = object.__new__(cls)
103
113
  self._importset = frozenset(filtered_imports)
104
114
  return self
105
115
 
106
116
  @classmethod
107
- def _from_args(cls, args, ignore_nonimports=False, ignore_shadowed=False):
117
+ def _from_args(cls, args, ignore_nonimports:bool=False, ignore_shadowed=False):
108
118
  """
109
119
  :type args:
110
120
  ``tuple`` or ``list`` of `ImportStatement` s, `PythonStatement` s,
@@ -139,7 +149,10 @@ class ImportSet(object):
139
149
  elif isinstance(arg, str) and is_identifier(arg, dotted=True):
140
150
  imports.append(Import(arg))
141
151
  else: # PythonBlock, PythonStatement, Filename, FileText, str
142
- block = PythonBlock(arg)
152
+ if not isinstance(arg, PythonBlock):
153
+ block = PythonBlock(arg)
154
+ else:
155
+ block = arg
143
156
  for statement in block.statements:
144
157
  # Ignore comments/blanks.
145
158
  if statement.is_comment_or_blank:
@@ -170,7 +183,7 @@ class ImportSet(object):
170
183
  `ImportSet`
171
184
  """
172
185
  other = ImportSet(other)
173
- return type(self)._from_imports(self._importset | other._importset)
186
+ return type(self)._from_imports(list(self._importset | other._importset))
174
187
 
175
188
  def without_imports(self, removals):
176
189
  """
@@ -468,17 +481,17 @@ class ImportSet(object):
468
481
  % (type(params.align_imports).__name__,))
469
482
  return ''.join(pp(statement, import_column) for statement in statements)
470
483
 
471
- def __contains__(self, x):
484
+ def __contains__(self, x) -> bool:
472
485
  return x in self._importset
473
486
 
474
- def __eq__(self, other):
487
+ def __eq__(self, other) -> bool:
475
488
  if self is other:
476
489
  return True
477
490
  if not isinstance(other, ImportSet):
478
491
  return NotImplemented
479
492
  return self._importset == other._importset
480
493
 
481
- def __ne__(self, other):
494
+ def __ne__(self, other) -> bool:
482
495
  return not (self == other)
483
496
 
484
497
  # The rest are defined by total_ordering
@@ -497,7 +510,7 @@ class ImportSet(object):
497
510
  def __hash__(self):
498
511
  return hash(self._importset)
499
512
 
500
- def __len__(self):
513
+ def __len__(self) -> int:
501
514
  return len(self.imports)
502
515
 
503
516
  def __iter__(self):
@@ -519,6 +532,7 @@ class ImportMap(object):
519
532
  """
520
533
 
521
534
  _data: Dict
535
+ _EMPTY : ClassVar[ImportSet]
522
536
 
523
537
  def __new__(cls, arg):
524
538
  if isinstance(arg, cls):