pyflyby 1.9.8__tar.gz → 1.9.10__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 (158) hide show
  1. {pyflyby-1.9.8/lib/python/pyflyby.egg-info → pyflyby-1.9.10}/PKG-INFO +1 -1
  2. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/_autoimp.py +56 -24
  3. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/_dbg.py +29 -1
  4. {pyflyby-1.9.8/lib/python → pyflyby-1.9.10/bin}/pyflyby/_dynimp.py +3 -1
  5. {pyflyby-1.9.8/lib/python → pyflyby-1.9.10/bin}/pyflyby/_importdb.py +3 -1
  6. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/_imports2s.py +10 -9
  7. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/_interactive.py +7 -196
  8. {pyflyby-1.9.8/lib/python → pyflyby-1.9.10/bin}/pyflyby/_parse.py +0 -76
  9. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/_saveframe.py +1 -1
  10. {pyflyby-1.9.8/lib/python → pyflyby-1.9.10/bin}/pyflyby/_version.py +1 -1
  11. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/saveframe +2 -2
  12. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/_autoimp.py +56 -24
  13. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/_dbg.py +29 -1
  14. {pyflyby-1.9.8/bin → pyflyby-1.9.10/lib/python}/pyflyby/_dynimp.py +3 -1
  15. {pyflyby-1.9.8/bin → pyflyby-1.9.10/lib/python}/pyflyby/_importdb.py +3 -1
  16. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/_imports2s.py +10 -9
  17. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/_interactive.py +7 -196
  18. {pyflyby-1.9.8/bin → pyflyby-1.9.10/lib/python}/pyflyby/_parse.py +0 -76
  19. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/_saveframe.py +1 -1
  20. {pyflyby-1.9.8/bin → pyflyby-1.9.10/lib/python}/pyflyby/_version.py +1 -1
  21. {pyflyby-1.9.8 → pyflyby-1.9.10/lib/python/pyflyby.egg-info}/PKG-INFO +1 -1
  22. {pyflyby-1.9.8 → pyflyby-1.9.10}/.pyflyby +0 -0
  23. {pyflyby-1.9.8 → pyflyby-1.9.10}/LICENSE.txt +0 -0
  24. {pyflyby-1.9.8 → pyflyby-1.9.10}/MANIFEST.in +0 -0
  25. {pyflyby-1.9.8 → pyflyby-1.9.10}/README.rst +0 -0
  26. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/autoipython +0 -0
  27. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/autopython +0 -0
  28. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/collect-exports +0 -0
  29. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/collect-imports +0 -0
  30. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/create-imports +0 -0
  31. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/find-import +0 -0
  32. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/list-bad-xrefs +0 -0
  33. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/prune-broken-imports +0 -0
  34. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/py +0 -0
  35. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/__init__.py +0 -0
  36. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/__main__.py +0 -0
  37. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/_cmdline.py +0 -0
  38. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/_comms.py +0 -0
  39. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/_docxref.py +0 -0
  40. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/_file.py +0 -0
  41. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/_flags.py +0 -0
  42. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/_format.py +0 -0
  43. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/_idents.py +0 -0
  44. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/_import_sorting.py +0 -0
  45. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/_importclns.py +0 -0
  46. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/_importstmt.py +0 -0
  47. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/_livepatch.py +0 -0
  48. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/_log.py +0 -0
  49. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/_modules.py +0 -0
  50. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/_py.py +0 -0
  51. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/_util.py +0 -0
  52. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/autoimport.py +0 -0
  53. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby/importdb.py +0 -0
  54. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/pyflyby-diff +0 -0
  55. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/reformat-imports +0 -0
  56. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/replace-star-imports +0 -0
  57. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/tidy-imports +0 -0
  58. {pyflyby-1.9.8 → pyflyby-1.9.10}/bin/transform-imports +0 -0
  59. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/LICENSE.txt +0 -0
  60. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/Makefile +0 -0
  61. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/TODO.txt +0 -0
  62. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/__init__.py +0 -0
  63. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/api/api.rst +0 -0
  64. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/api/autoimp.rst +0 -0
  65. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/api/cmdline.rst +0 -0
  66. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/api/comms.rst +0 -0
  67. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/api/dbg.rst +0 -0
  68. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/api/file.rst +0 -0
  69. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/api/flags.rst +0 -0
  70. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/api/format.rst +0 -0
  71. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/api/idents.rst +0 -0
  72. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/api/importclns.rst +0 -0
  73. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/api/importdb.rst +0 -0
  74. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/api/imports2s.rst +0 -0
  75. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/api/importstmt.rst +0 -0
  76. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/api/interactive.rst +0 -0
  77. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/api/livepatch.rst +0 -0
  78. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/api/log.rst +0 -0
  79. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/api/modules.rst +0 -0
  80. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/api/parse.rst +0 -0
  81. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/api/py.rst +0 -0
  82. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/api/util.rst +0 -0
  83. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/cli/autoipython.rst +0 -0
  84. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/cli/cli.rst +0 -0
  85. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/cli/collect_exports.rst +0 -0
  86. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/cli/collect_imports.rst +0 -0
  87. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/cli/find_import.rst +0 -0
  88. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/cli/prune_broken_imports.rst +0 -0
  89. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/cli/py.rst +0 -0
  90. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/cli/pyflyby_diff.rst +0 -0
  91. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/cli/reformat_imports.rst +0 -0
  92. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/cli/replace_star_imports.rst +0 -0
  93. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/cli/tidy_imports.rst +0 -0
  94. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/cli/transform_imports.rst +0 -0
  95. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/conf.py +0 -0
  96. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/index.rst +0 -0
  97. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/make.bat +0 -0
  98. {pyflyby-1.9.8 → pyflyby-1.9.10}/doc/testing.txt +0 -0
  99. {pyflyby-1.9.8 → pyflyby-1.9.10}/etc/pyflyby/canonical.py +0 -0
  100. {pyflyby-1.9.8 → pyflyby-1.9.10}/etc/pyflyby/common.py +0 -0
  101. {pyflyby-1.9.8 → pyflyby-1.9.10}/etc/pyflyby/forget.py +0 -0
  102. {pyflyby-1.9.8 → pyflyby-1.9.10}/etc/pyflyby/mandatory.py +0 -0
  103. {pyflyby-1.9.8 → pyflyby-1.9.10}/etc/pyflyby/numpy.py +0 -0
  104. {pyflyby-1.9.8 → pyflyby-1.9.10}/etc/pyflyby/std.py +0 -0
  105. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/emacs/pyflyby.el +0 -0
  106. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/__init__.py +0 -0
  107. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/__main__.py +0 -0
  108. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/_cmdline.py +0 -0
  109. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/_comms.py +0 -0
  110. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/_docxref.py +0 -0
  111. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/_file.py +0 -0
  112. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/_flags.py +0 -0
  113. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/_format.py +0 -0
  114. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/_idents.py +0 -0
  115. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/_import_sorting.py +0 -0
  116. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/_importclns.py +0 -0
  117. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/_importstmt.py +0 -0
  118. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/_livepatch.py +0 -0
  119. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/_log.py +0 -0
  120. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/_modules.py +0 -0
  121. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/_py.py +0 -0
  122. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/_util.py +0 -0
  123. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/autoimport.py +0 -0
  124. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby/importdb.py +0 -0
  125. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby.egg-info/SOURCES.txt +0 -0
  126. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby.egg-info/dependency_links.txt +0 -0
  127. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby.egg-info/entry_points.txt +0 -0
  128. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby.egg-info/requires.txt +0 -0
  129. {pyflyby-1.9.8 → pyflyby-1.9.10}/lib/python/pyflyby.egg-info/top_level.txt +0 -0
  130. {pyflyby-1.9.8 → pyflyby-1.9.10}/libexec/pyflyby/colordiff +0 -0
  131. {pyflyby-1.9.8 → pyflyby-1.9.10}/libexec/pyflyby/diff-colorize +0 -0
  132. {pyflyby-1.9.8 → pyflyby-1.9.10}/pyproject.toml +0 -0
  133. {pyflyby-1.9.8 → pyflyby-1.9.10}/setup.cfg +0 -0
  134. {pyflyby-1.9.8 → pyflyby-1.9.10}/setup.py +0 -0
  135. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/__init__.py +0 -0
  136. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/test_0testconfig.py +0 -0
  137. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/test_autoimp.py +0 -0
  138. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/test_cmdline.py +0 -0
  139. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/test_docxref.py +0 -0
  140. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/test_file.py +0 -0
  141. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/test_flags.py +0 -0
  142. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/test_format.py +0 -0
  143. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/test_idents.py +0 -0
  144. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/test_importclns.py +0 -0
  145. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/test_importdb.py +0 -0
  146. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/test_imports2s.py +0 -0
  147. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/test_importstmt.py +0 -0
  148. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/test_interactive.py +0 -0
  149. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/test_jupyterlab_pyflyby.py +0 -0
  150. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/test_livepatch.py +0 -0
  151. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/test_modules.py +0 -0
  152. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/test_parse.py +0 -0
  153. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/test_py.py +0 -0
  154. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/test_saveframe.py +0 -0
  155. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/test_util.py +0 -0
  156. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/tests_sorts.py +0 -0
  157. {pyflyby-1.9.8 → pyflyby-1.9.10}/tests/xrefs.py +0 -0
  158. {pyflyby-1.9.8 → pyflyby-1.9.10}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyflyby
3
- Version: 1.9.8
3
+ Version: 1.9.10
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
@@ -4,6 +4,8 @@
4
4
 
5
5
 
6
6
 
7
+ from __future__ import annotations, print_function
8
+
7
9
  import ast
8
10
  import builtins
9
11
  from collections.abc import Sequence
@@ -18,13 +20,14 @@ from pyflyby._importdb import ImportDB
18
20
  from pyflyby._importstmt import Import
19
21
  from pyflyby._log import logger
20
22
  from pyflyby._modules import ModuleHandle
21
- from pyflyby._parse import (PythonBlock, _is_ast_str,
22
- infer_compile_mode, MatchAs)
23
+ from pyflyby._parse import (MatchAs, PythonBlock, _is_ast_str,
24
+ infer_compile_mode)
23
25
 
24
26
  from six import reraise
25
27
  import sys
26
28
  import types
27
- from typing import Any, Set, Optional, List, Tuple
29
+ from typing import (Any, Dict, List, Optional, Set, Tuple,
30
+ Union)
28
31
 
29
32
  if sys.version_info >= (3, 12):
30
33
  ATTRIBUTE_NAME = "value"
@@ -130,9 +133,13 @@ class ScopeStack(Sequence):
130
133
  def __len__(self):
131
134
  return len(self._tup)
132
135
 
133
- def with_new_scope(
134
- self, include_class_scopes=False, new_class_scope=False, unhide_classdef=False
135
- ):
136
+ def _with_new_scope(
137
+ self,
138
+ *,
139
+ include_class_scopes: bool,
140
+ new_class_scope: bool,
141
+ unhide_classdef: bool,
142
+ ) -> ScopeStack:
136
143
  """
137
144
  Return a new ``ScopeStack`` with an additional empty scope.
138
145
 
@@ -148,8 +155,8 @@ class ScopeStack(Sequence):
148
155
  if include_class_scopes:
149
156
  scopes = tuple(self)
150
157
  else:
151
- scopes = tuple(s for s in self
152
- if not isinstance(s, _ClassScope))
158
+ scopes = tuple(s for s in self if not isinstance(s, _ClassScope))
159
+ new_scope: Union[_ClassScope, Dict[str, Any]]
153
160
  if new_class_scope:
154
161
  new_scope = _ClassScope()
155
162
  else:
@@ -199,7 +206,7 @@ class ScopeStack(Sequence):
199
206
  return (d, self[-1])
200
207
 
201
208
 
202
- def has_star_import(self):
209
+ def has_star_import(self) -> bool:
203
210
  """
204
211
  Return whether there are any star-imports in this ScopeStack.
205
212
  Only relevant in AST-based static analysis mode.
@@ -355,9 +362,10 @@ class _UseChecker:
355
362
  self.name = name
356
363
  self.source = source # generally an Import
357
364
  self.lineno = lineno
365
+ logger.debug("Create _UseChecker : %r", self)
358
366
 
359
367
  def __repr__(self):
360
- return f"<{type(self).__name__}: name:{self.name} source:{self.source!r} lineno:{self.lineno} used:{self.used}>"
368
+ return f"<{type(self).__name__}: name:{self.name!r} source:{self.source!r} lineno:{self.lineno} used:{self.used}>"
361
369
 
362
370
 
363
371
  class _MissingImportFinder:
@@ -397,13 +405,13 @@ class _MissingImportFinder:
397
405
  # Create a stack of namespaces. The caller should pass in a list that
398
406
  # includes the globals dictionary. ScopeStack() will make sure this
399
407
  # includes builtins.
400
- scopestack = ScopeStack(scopestack)
408
+ _scopestack = ScopeStack(scopestack)
401
409
 
402
410
  # Add an empty namespace to the stack. This facilitates adding stuff
403
411
  # to scopestack[-1] without ever modifying user globals.
404
- scopestack = scopestack.with_new_scope()
405
-
406
- self.scopestack = scopestack
412
+ self.scopestack = _scopestack._with_new_scope(
413
+ include_class_scopes=False, new_class_scope=False, unhide_classdef=False
414
+ )
407
415
 
408
416
  # Create data structure to hold the result.
409
417
  # missing_imports is a list of (lineno, DottedIdentifier) tuples.
@@ -450,6 +458,7 @@ class _MissingImportFinder:
450
458
  # references in doctests to be noted as missing-imports. For now we
451
459
  # just let the code accumulate into self.missing_imports and ignore
452
460
  # the result.
461
+ logger.debug("unused: %r", self.unused_imports)
453
462
  missing_imports = sorted(self.missing_imports)
454
463
  if self.parse_docstrings and self.unused_imports is not None:
455
464
  doctest_blocks = codeblock.get_doctests()
@@ -467,10 +476,8 @@ class _MissingImportFinder:
467
476
  # Currently we don't support the 'global' keyword anyway so
468
477
  # this doesn't matter yet, and it's uncommon to use 'global'
469
478
  # in a doctest, so this is low priority to fix.
470
- oldstack = self.scopestack
471
- self.scopestack = self.scopestack.with_new_scope()
472
- self._scan_node(block.ast_node)
473
- self.scopestack = oldstack
479
+ with self._NewScopeCtx(check_unused_imports=False):
480
+ self._scan_node(block.ast_node)
474
481
  # Find literal brace identifiers like "... `Foo` ...".
475
482
  # TODO: Do this inline: (1) faster; (2) can use proper scope of vars
476
483
  # Once we do that, use _check_load() with new args
@@ -553,17 +560,36 @@ class _MissingImportFinder:
553
560
 
554
561
 
555
562
  @contextlib.contextmanager
556
- def _NewScopeCtx(self, **kwargs):
563
+ def _NewScopeCtx(
564
+ self,
565
+ include_class_scopes=False,
566
+ new_class_scope=False,
567
+ unhide_classdef=False,
568
+ check_unused_imports=True,
569
+ ):
557
570
  """
558
571
  Context manager that temporarily pushes a new empty namespace onto the
559
572
  stack of namespaces.
560
573
  """
561
574
  prev_scopestack = self.scopestack
562
- new_scopestack = prev_scopestack.with_new_scope(**kwargs)
575
+ new_scopestack = prev_scopestack._with_new_scope(
576
+ include_class_scopes=include_class_scopes,
577
+ new_class_scope=new_class_scope,
578
+ unhide_classdef=unhide_classdef,
579
+ )
563
580
  self.scopestack = new_scopestack
564
581
  try:
565
582
  yield
566
583
  finally:
584
+ logger.debug("throwing last scope from scopestack: %r", new_scopestack[-1])
585
+ for name, use_checker in new_scopestack[-1].items():
586
+ if use_checker and use_checker.used == False and check_unused_imports:
587
+ logger.debug(
588
+ "unused checker %r scopestack_depth %r",
589
+ use_checker,
590
+ len(self.scopestack),
591
+ )
592
+ self.unused_imports.append((use_checker.lineno, use_checker.source))
567
593
  assert self.scopestack is new_scopestack
568
594
  self.scopestack = prev_scopestack
569
595
 
@@ -693,7 +719,7 @@ class _MissingImportFinder:
693
719
  self.visit(node.body)
694
720
  self._in_FunctionDef = old_in_FunctionDef
695
721
 
696
- def _visit_typecomment(self, typecomment):
722
+ def _visit_typecomment(self, typecomment: str) -> None:
697
723
  """
698
724
  Warning, when a type comment the node is a string, not an ast node.
699
725
  We also get two types of type comments:
@@ -716,6 +742,7 @@ class _MissingImportFinder:
716
742
  """
717
743
  if typecomment is None:
718
744
  return
745
+ node: Union[ast.Module, ast.FunctionType]
719
746
  if '->' in typecomment:
720
747
  node = ast.parse(typecomment, mode='func_type')
721
748
  else:
@@ -723,7 +750,7 @@ class _MissingImportFinder:
723
750
 
724
751
  self.visit(node)
725
752
 
726
- def visit_arguments(self, node):
753
+ def visit_arguments(self, node) -> None:
727
754
  assert node._fields == ('posonlyargs', 'args', 'vararg', 'kwonlyargs', 'kw_defaults', 'kwarg', 'defaults'), node._fields
728
755
  # Argument/parameter list. Note that the defaults should be
729
756
  # considered "Load"s from the upper scope, and the argument names are
@@ -754,7 +781,7 @@ class _MissingImportFinder:
754
781
  else:
755
782
  self._visit_Store(node.kwarg)
756
783
 
757
- def visit_ExceptHandler(self, node):
784
+ def visit_ExceptHandler(self, node) -> None:
758
785
  assert node._fields == ('type', 'name', 'body')
759
786
  if node.type:
760
787
  self.visit(node.type)
@@ -948,7 +975,12 @@ class _MissingImportFinder:
948
975
  value = _UseChecker(name, imp, self._lineno)
949
976
  self._visit_Store(name, value)
950
977
 
951
- def _visit_Store(self, fullname:str, value=None):
978
+ def _visit_Store(self, fullname: str, value: Optional[_UseChecker] = None):
979
+ """
980
+ Visit a Store action, check for unused import
981
+ and add current value to the last scope.
982
+ """
983
+ assert isinstance(value, (_UseChecker, type(None)))
952
984
  logger.debug("_visit_Store(%r)", fullname)
953
985
  if fullname is None:
954
986
  return
@@ -316,6 +316,14 @@ def _debug_exception(*exc_info, **kwargs):
316
316
  # will cause print_verbose_tb to include a line with just a colon.
317
317
  # TODO: avoid that line.
318
318
  exc_info = ("", "", exc_info)
319
+ if exc_info[1]:
320
+ # Explicitly set sys.last_value / sys.last_exc to ensure they are available
321
+ # in the debugger. One use case is that this allows users to call
322
+ # pyflyby.saveframe() within the debugger.
323
+ if sys.version_info < (3, 12):
324
+ sys.last_value = exc_info[1]
325
+ else:
326
+ sys.last_exc = exc_info[1]
319
327
 
320
328
  with _DebuggerCtx(tty=tty) as pdb:
321
329
  if debugger_attached:
@@ -324,7 +332,27 @@ def _debug_exception(*exc_info, **kwargs):
324
332
  # keep the process waiting for debugger to attach.
325
333
  pdb.postloop = _prompt_continue_waiting_for_debugger
326
334
  print_verbose_tb(*exc_info)
327
- pdb.interaction(None, exc_info[2])
335
+ # Starting Py3.13, pdb.interaction() supports chained exceptions in case
336
+ # exception (and not traceback) is specified. This support is backported
337
+ # to IPython8.16 for earlier Python versions. So the conditions where
338
+ # chained exceptions won't be supported from here would be with the
339
+ # Python version < 3.13 and ipython not installed, or IPython's version
340
+ # is lesser than 8.16.
341
+ tb_or_exc = exc_info[2]
342
+ if sys.version_info < (3, 13):
343
+ # Check if the instance is of IPython's Pdb and its version.
344
+ try:
345
+ import IPython
346
+ if IPython.version_info >= (8, 16):
347
+ from IPython.core.debugger import Pdb as IPdb
348
+ # This is expected to be True, hence just a safe check.
349
+ if isinstance(pdb, IPdb):
350
+ tb_or_exc = exc_info[1]
351
+ except ModuleNotFoundError:
352
+ pass
353
+ else:
354
+ tb_or_exc = exc_info[1]
355
+ pdb.interaction(None, tb_or_exc)
328
356
 
329
357
 
330
358
  def _debug_code(arg, globals=None, locals=None, auto_import=True, tty="/dev/tty"):
@@ -49,6 +49,7 @@ from pyflyby._importclns import ImportSet, Import
49
49
 
50
50
  module_dict = {}
51
51
 
52
+ PYFLYBY_LAZY_LOAD_PREFIX = "from pyflyby_autoimport_"
52
53
 
53
54
  def add_import(names: str, code: str, *, strict: bool = True):
54
55
  """
@@ -110,7 +111,8 @@ def _add_import(ip, names: str, code: str) -> None:
110
111
  private version of add_import
111
112
  """
112
113
  assert ip is not None
113
- mang = "pyflyby_autoimport_" + names.replace(",", "_").replace(" ", "_")
114
+ module = PYFLYBY_LAZY_LOAD_PREFIX.split()[1]
115
+ mang = module + names.replace(",", "_").replace(" ", "_")
114
116
  a: FrozenSet[Import] = ImportSet(f"from {mang} import {names}")._importset
115
117
  b: FrozenSet[Import] = ip._auto_importer.db.known_imports._importset
116
118
  s_import: FrozenSet[Import] = a | b
@@ -2,6 +2,8 @@
2
2
  # Copyright (C) 2011, 2012, 2013, 2014, 2015 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
@@ -417,7 +419,7 @@ class ImportDB:
417
419
  return result
418
420
 
419
421
  @classmethod
420
- def interpret_arg(cls, arg, target_filename):
422
+ def interpret_arg(cls, arg, target_filename) -> ImportDB:
421
423
  if arg is None:
422
424
  return cls.get_default(target_filename)
423
425
  else:
@@ -13,12 +13,12 @@ from pyflyby._parse import PythonBlock
13
13
  from pyflyby._util import ImportPathCtx, Inf, NullCtx, memoize
14
14
  import re
15
15
 
16
- from typing import Union
16
+ from typing import Union, Optional, Literal
17
17
 
18
18
  from textwrap import indent
19
19
 
20
20
 
21
- class SourceToSourceTransformationBase(object):
21
+ class SourceToSourceTransformationBase:
22
22
 
23
23
  input: PythonBlock
24
24
 
@@ -121,7 +121,7 @@ class SourceToSourceFileImportsTransformation(SourceToSourceTransformationBase):
121
121
  result = [block.pretty_print(params=params) for block in self.blocks]
122
122
  return FileText.concatenate(result)
123
123
 
124
- def find_import_block_by_lineno(self, lineno):
124
+ def find_import_block_by_lineno(self, lineno: int):
125
125
  """
126
126
  Find the import block containing the given line number.
127
127
 
@@ -312,11 +312,11 @@ def ImportPathForRelativeImportsCtx(codeblock):
312
312
 
313
313
 
314
314
  def fix_unused_and_missing_imports(
315
- codeblock: Union[PythonBlock, str],
316
- add_missing=True,
317
- remove_unused="AUTOMATIC",
318
- add_mandatory=True,
319
- db=None,
315
+ codeblock: Union[PythonBlock, str, Filename],
316
+ add_missing: bool = True,
317
+ remove_unused: Union[Literal["AUTOMATIC"], bool] = "AUTOMATIC",
318
+ add_mandatory: bool = True,
319
+ db: Optional[ImportDB] = None,
320
320
  params=None,
321
321
  ) -> PythonBlock:
322
322
  r"""
@@ -345,6 +345,7 @@ def fix_unused_and_missing_imports(
345
345
  :rtype:
346
346
  `PythonBlock`
347
347
  """
348
+ _codeblock: PythonBlock
348
349
  if isinstance(codeblock, Filename):
349
350
  _codeblock = PythonBlock(codeblock)
350
351
  if not isinstance(codeblock, PythonBlock):
@@ -393,7 +394,7 @@ def fix_unused_and_missing_imports(
393
394
  logger.error(
394
395
  "%s: couldn't remove import %r", filename, imp,)
395
396
  except LineNumberNotFoundError as e:
396
- logger.error(
397
+ logger.debug(
397
398
  "%s: unused import %r on line %d not global",
398
399
  filename, str(imp), e.args[0])
399
400
  else:
@@ -22,7 +22,8 @@ from pyflyby._autoimp import (LoadSymbolError, ScopeStack, auto_eval,
22
22
  auto_import,
23
23
  clear_failed_imports_cache,
24
24
  load_symbol)
25
- from pyflyby._dynimp import inject as inject_dynamic_import
25
+ from pyflyby._dynimp import (inject as inject_dynamic_import,
26
+ PYFLYBY_LAZY_LOAD_PREFIX)
26
27
  from pyflyby._comms import (initialize_comms, remove_comms,
27
28
  send_comm_message, MISSING_IMPORTS)
28
29
  from pyflyby._file import Filename, atomic_write_file, read_file
@@ -35,7 +36,6 @@ from pyflyby._util import (AdviceCtx, Aspect, CwdCtx,
35
36
  FunctionWithGlobals, NullCtx, advise,
36
37
  indent)
37
38
 
38
-
39
39
  if False:
40
40
  __original__ = None # for pyflakes
41
41
 
@@ -85,9 +85,6 @@ def _get_or_create_ipython_terminal_app():
85
85
  pass
86
86
  else:
87
87
  return TerminalIPythonApp.instance()
88
- # The following has been tested on IPython 0.10.
89
- if hasattr(IPython, "ipapi"):
90
- return _IPython010TerminalApplication.instance()
91
88
  raise RuntimeError(
92
89
  "Couldn't get TerminalIPythonApp class. "
93
90
  "Is your IPython version too old (or too new)? "
@@ -122,51 +119,6 @@ def _app_is_initialized(app):
122
119
 
123
120
 
124
121
 
125
- class _IPython010TerminalApplication(object):
126
- """
127
- Shim class that mimics IPython 0.11+ application classes, for use in
128
- IPython 0.10.
129
- """
130
-
131
- # IPython.ipapi.launch_instance() => IPython.Shell.start() creates an
132
- # instance of "IPShell". IPShell has an attribute named "IP" which is an
133
- # "InteractiveShell".
134
-
135
- _instance = None
136
-
137
- @classmethod
138
- def instance(cls):
139
- if cls._instance is not None:
140
- self = cls._instance
141
- self.init_shell()
142
- return self
143
- import IPython
144
- if not hasattr(IPython, "ipapi"):
145
- raise RuntimeError("Inappropriate version of IPython %r"
146
- % (IPython.__version__,))
147
- self = cls._instance = cls()
148
- self.init_shell()
149
- return self
150
-
151
- def init_shell(self):
152
- import IPython
153
- ipapi = IPython.ipapi.get() # IPApi instance
154
- if ipapi is not None:
155
- self.shell = ipapi.IP # InteractiveShell instance
156
- else:
157
- self.shell = None
158
-
159
- def initialize(self, argv=None):
160
- import IPython
161
- logger.debug("Creating IPython 0.10 session")
162
- self._session = IPython.ipapi.make_session() # IPShell instance
163
- self.init_shell()
164
- assert self._session is not None
165
-
166
- def start(self):
167
- self._session.mainloop()
168
-
169
-
170
122
 
171
123
  class _DummyIPythonEmbeddedApp(object):
172
124
  """
@@ -412,30 +364,7 @@ def install_in_ipython_config_file():
412
364
  else:
413
365
  _install_in_ipython_config_file_40()
414
366
  return
415
- # The following has been tested on IPython 0.12, 0.13, 1.0, 1.2, 2.0, 2.1,
416
- # 2.2, 2.3, 2.4, 3.0, 3.1, 3.2, 4.0.
417
- try:
418
- IPython.core.profiledir.ProfileDir.startup_dir
419
- except AttributeError:
420
- pass
421
- else:
422
- _install_in_ipython_config_file_012()
423
- return
424
- # The following has been tested on IPython 0.11.
425
- try:
426
- IPython.core.profiledir.ProfileDir
427
- except AttributeError:
428
- pass
429
- else:
430
- _install_in_ipython_config_file_011()
431
- return
432
- try:
433
- IPython.genutils.get_ipython_dir
434
- except AttributeError:
435
- pass
436
- else:
437
- _install_in_ipython_config_file_010()
438
- return
367
+
439
368
  raise RuntimeError(
440
369
  "Couldn't install pyflyby autoimporter in IPython. "
441
370
  "Is your IPython version too old (or too new)? "
@@ -555,96 +484,10 @@ def _install_in_ipython_config_file_40():
555
484
  logger.info("[DONE] Removed old file %s (moved to %s)", old_fn, trash_fn)
556
485
 
557
486
 
558
- def _install_in_ipython_config_file_012():
559
- """
560
- Implementation of `install_in_ipython_config_file` for IPython 0.12+.
561
- Tested with IPython 0.12, 0.13, 1.0, 1.2, 2.0, 2.1, 2.2, 2.3, 2.4, 3.0,
562
- 3.1, 3.2, 4.0.
563
- """
564
- import IPython
565
- ipython_dir = Filename(IPython.utils.path.get_ipython_dir())
566
- if not ipython_dir.isdir:
567
- raise RuntimeError(
568
- "Couldn't find IPython config dir. Tried %s" % (ipython_dir,))
569
- startup_dir = ipython_dir / "profile_default" / "startup"
570
- if not startup_dir.isdir:
571
- raise RuntimeError(
572
- "Couldn't find IPython startup dir. Tried %s" % (startup_dir,))
573
- fn = startup_dir / "50-pyflyby.py"
574
- if fn.exists:
575
- logger.info("Doing nothing, because %s already exists", fn)
576
- return
577
- argv = sys.argv[:]
578
- argv[0] = os.path.realpath(argv[0])
579
- argv = ' '.join(argv)
580
- header = (
581
- "# File: {fn}\n"
582
- "#\n"
583
- "# Generated by {argv}\n"
584
- "#\n"
585
- "# This file causes IPython to enable the Pyflyby Auto Importer.\n"
586
- "#\n"
587
- "# To uninstall, just delete this file.\n"
588
- "#\n"
589
- ).format(**locals())
590
- contents = header + _generate_enabler_code()
591
- logger.info("Installing pyflyby auto importer in your IPython startup")
592
- logger.info("Writing to %s:\n%s", fn, contents)
593
- atomic_write_file(fn, contents)
594
-
595
-
596
- def _install_in_ipython_config_file_011():
597
- """
598
- Implementation of `install_in_ipython_config_file` for IPython 0.11.
599
- """
600
- import IPython
601
- ipython_dir = Filename(IPython.utils.path.get_ipython_dir())
602
- fn = ipython_dir / "profile_default" / "ipython_config.py"
603
- if not fn.exists:
604
- raise RuntimeError(
605
- "Couldn't find IPython startup file. Tried %s" % (fn,))
606
- old_contents = read_file(fn).joined
607
- if re.search(r"^ *(pyflyby[.])?enable_auto_importer[(][)]", old_contents, re.M):
608
- logger.info("Doing nothing, because already installed in %s", fn)
609
- return
610
- header = (
611
- "\n"
612
- "\n"
613
- "#\n"
614
- "# Enable the Pyflyby Auto Importer.\n"
615
- )
616
- new_contents = header + _generate_enabler_code()
617
- contents = old_contents.rstrip() + new_contents
618
- logger.info("Installing pyflyby auto importer in your IPython startup")
619
- logger.info("Appending to %s:\n%s", fn, new_contents)
620
- atomic_write_file(fn, contents)
621
487
 
622
488
 
623
- def _install_in_ipython_config_file_010():
624
- """
625
- Implementation of `install_in_ipython_config_file` for IPython 0.10.
626
- """
627
- import IPython
628
- ipython_dir = Filename(IPython.genutils.get_ipython_dir())
629
- fn = ipython_dir / "ipy_user_conf.py"
630
- if not fn.exists:
631
- raise RuntimeError(
632
- "Couldn't find IPython config file. Tried %s" % (fn,))
633
- old_contents = read_file(fn).joined
634
- if re.search(r"^ *(pyflyby[.])?enable_auto_importer[(][)]", old_contents, re.M):
635
- logger.info("Doing nothing, because already installed in %s", fn)
636
- return
637
- header = (
638
- "\n"
639
- "\n"
640
- "#\n"
641
- "# Enable the Pyflyby Auto Importer.\n"
642
- )
643
- new_contents = header + _generate_enabler_code()
644
- contents = old_contents.rstrip() + new_contents
645
- logger.info("Installing pyflyby auto importer in your IPython startup")
646
- logger.info("Appending to %s:\n%s", fn, new_contents)
647
- atomic_write_file(fn, contents)
489
+
490
+
648
491
 
649
492
 
650
493
  def _ipython_in_multiline(ip):
@@ -722,37 +565,6 @@ def InterceptPrintsDuringPromptCtx(ip):
722
565
  else:
723
566
  return ip.separate_in + ip.prompt_in1.format(ip.execution_count)
724
567
  get_prompt = get_prompt_rlipython
725
- elif hasattr(ip, "prompt_manager"):
726
- # IPython >= 0.12 (known to work including up to 1.2, 2.1)
727
- prompt_manager = ip.prompt_manager
728
- def get_prompt_ipython_012():
729
- pdb_instance = _get_pdb_if_is_in_pdb()
730
- if pdb_instance is not None:
731
- return pdb_instance.prompt
732
- elif _ipython_in_multiline(ip):
733
- return prompt_manager.render("in2")
734
- else:
735
- return ip.separate_in + prompt_manager.render("in")
736
- get_prompt = get_prompt_ipython_012
737
- elif hasattr(ip.hooks, "generate_prompt"):
738
- # IPython 0.10, 0.11
739
- generate_prompt = ip.hooks.generate_prompt
740
- def get_prompt_ipython_010():
741
- pdb_instance = _get_pdb_if_is_in_pdb()
742
- if pdb_instance is not None:
743
- return pdb_instance.prompt
744
- elif _ipython_in_multiline(ip):
745
- return generate_prompt(True)
746
- else:
747
- if hasattr(ip, "outputcache"):
748
- # IPython 0.10 (but not 0.11+):
749
- # Decrement the prompt_count since it otherwise
750
- # auto-increments. (It's hard to avoid the
751
- # auto-increment as it happens as a side effect of
752
- # __str__!)
753
- ip.outputcache.prompt_count -= 1
754
- return generate_prompt(False)
755
- get_prompt = get_prompt_ipython_010
756
568
  else:
757
569
  # Too old or too new IPython version?
758
570
  return NullCtx()
@@ -818,8 +630,6 @@ def _get_ipython_app():
818
630
  # No active IPython app/shell.
819
631
  raise NoActiveIPythonAppError("No active IPython application")
820
632
  # The following has been tested on IPython 0.10.
821
- if hasattr(IPython, "ipapi"):
822
- return _IPython010TerminalApplication.instance()
823
633
  raise NoActiveIPythonAppError(
824
634
  "Could not figure out how to get active IPython application for IPython version %s"
825
635
  % (IPython.__version__,))
@@ -2341,7 +2151,8 @@ class AutoImporter:
2341
2151
  namespaces = get_global_namespaces(self._ip)
2342
2152
 
2343
2153
  def post_import_hook(imp):
2344
- send_comm_message(MISSING_IMPORTS, {"missing_imports": str(imp)})
2154
+ if not str(imp).startswith(PYFLYBY_LAZY_LOAD_PREFIX):
2155
+ send_comm_message(MISSING_IMPORTS, {"missing_imports": str(imp)})
2345
2156
 
2346
2157
  return self._safe_call(
2347
2158
  auto_import, arg=arg, namespaces=namespaces,