pyflyby 1.9.0__tar.gz → 1.9.1__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 (149) hide show
  1. {pyflyby-1.9.0/lib/python/pyflyby.egg-info → pyflyby-1.9.1}/PKG-INFO +1 -1
  2. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/pyflyby/_autoimp.py +17 -4
  3. {pyflyby-1.9.0/lib/python → pyflyby-1.9.1/bin}/pyflyby/_importclns.py +2 -2
  4. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/pyflyby/_importstmt.py +27 -20
  5. {pyflyby-1.9.0/lib/python → pyflyby-1.9.1/bin}/pyflyby/_modules.py +13 -11
  6. {pyflyby-1.9.0/lib/python → pyflyby-1.9.1/bin}/pyflyby/_parse.py +4 -1
  7. {pyflyby-1.9.0/lib/python → pyflyby-1.9.1/bin}/pyflyby/_py.py +18 -10
  8. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/pyflyby/_util.py +20 -42
  9. {pyflyby-1.9.0/lib/python → pyflyby-1.9.1/bin}/pyflyby/_version.py +1 -1
  10. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby/_autoimp.py +17 -4
  11. {pyflyby-1.9.0/bin → pyflyby-1.9.1/lib/python}/pyflyby/_importclns.py +2 -2
  12. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby/_importstmt.py +27 -20
  13. {pyflyby-1.9.0/bin → pyflyby-1.9.1/lib/python}/pyflyby/_modules.py +13 -11
  14. {pyflyby-1.9.0/bin → pyflyby-1.9.1/lib/python}/pyflyby/_parse.py +4 -1
  15. {pyflyby-1.9.0/bin → pyflyby-1.9.1/lib/python}/pyflyby/_py.py +18 -10
  16. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby/_util.py +20 -42
  17. {pyflyby-1.9.0/bin → pyflyby-1.9.1/lib/python}/pyflyby/_version.py +1 -1
  18. {pyflyby-1.9.0 → pyflyby-1.9.1/lib/python/pyflyby.egg-info}/PKG-INFO +1 -1
  19. {pyflyby-1.9.0 → pyflyby-1.9.1}/tests/test_importstmt.py +10 -10
  20. {pyflyby-1.9.0 → pyflyby-1.9.1}/tests/test_interactive.py +3 -4
  21. {pyflyby-1.9.0 → pyflyby-1.9.1}/tests/test_modules.py +31 -17
  22. {pyflyby-1.9.0 → pyflyby-1.9.1}/.pyflyby +0 -0
  23. {pyflyby-1.9.0 → pyflyby-1.9.1}/LICENSE.txt +0 -0
  24. {pyflyby-1.9.0 → pyflyby-1.9.1}/MANIFEST.in +0 -0
  25. {pyflyby-1.9.0 → pyflyby-1.9.1}/README.rst +0 -0
  26. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/autoipython +0 -0
  27. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/autopython +0 -0
  28. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/collect-exports +0 -0
  29. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/collect-imports +0 -0
  30. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/create-imports +0 -0
  31. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/find-import +0 -0
  32. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/list-bad-xrefs +0 -0
  33. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/prune-broken-imports +0 -0
  34. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/py +0 -0
  35. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/pyflyby/__init__.py +0 -0
  36. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/pyflyby/__main__.py +0 -0
  37. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/pyflyby/_cmdline.py +0 -0
  38. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/pyflyby/_comms.py +0 -0
  39. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/pyflyby/_dbg.py +0 -0
  40. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/pyflyby/_docxref.py +0 -0
  41. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/pyflyby/_file.py +0 -0
  42. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/pyflyby/_flags.py +0 -0
  43. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/pyflyby/_format.py +0 -0
  44. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/pyflyby/_idents.py +0 -0
  45. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/pyflyby/_importdb.py +0 -0
  46. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/pyflyby/_imports2s.py +0 -0
  47. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/pyflyby/_interactive.py +0 -0
  48. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/pyflyby/_livepatch.py +0 -0
  49. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/pyflyby/_log.py +0 -0
  50. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/pyflyby/autoimport.py +0 -0
  51. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/pyflyby/importdb.py +0 -0
  52. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/pyflyby-diff +0 -0
  53. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/reformat-imports +0 -0
  54. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/replace-star-imports +0 -0
  55. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/tidy-imports +0 -0
  56. {pyflyby-1.9.0 → pyflyby-1.9.1}/bin/transform-imports +0 -0
  57. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/LICENSE.txt +0 -0
  58. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/Makefile +0 -0
  59. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/TODO.txt +0 -0
  60. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/__init__.py +0 -0
  61. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/api/api.rst +0 -0
  62. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/api/autoimp.rst +0 -0
  63. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/api/cmdline.rst +0 -0
  64. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/api/comms.rst +0 -0
  65. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/api/dbg.rst +0 -0
  66. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/api/file.rst +0 -0
  67. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/api/flags.rst +0 -0
  68. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/api/format.rst +0 -0
  69. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/api/idents.rst +0 -0
  70. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/api/importclns.rst +0 -0
  71. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/api/importdb.rst +0 -0
  72. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/api/imports2s.rst +0 -0
  73. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/api/importstmt.rst +0 -0
  74. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/api/interactive.rst +0 -0
  75. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/api/livepatch.rst +0 -0
  76. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/api/log.rst +0 -0
  77. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/api/modules.rst +0 -0
  78. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/api/parse.rst +0 -0
  79. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/api/py.rst +0 -0
  80. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/api/util.rst +0 -0
  81. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/cli/autoipython.rst +0 -0
  82. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/cli/cli.rst +0 -0
  83. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/cli/collect_exports.rst +0 -0
  84. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/cli/collect_imports.rst +0 -0
  85. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/cli/find_import.rst +0 -0
  86. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/cli/prune_broken_imports.rst +0 -0
  87. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/cli/py.rst +0 -0
  88. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/cli/pyflyby_diff.rst +0 -0
  89. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/cli/reformat_imports.rst +0 -0
  90. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/cli/replace_star_imports.rst +0 -0
  91. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/cli/tidy_imports.rst +0 -0
  92. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/cli/transform_imports.rst +0 -0
  93. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/conf.py +0 -0
  94. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/index.rst +0 -0
  95. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/make.bat +0 -0
  96. {pyflyby-1.9.0 → pyflyby-1.9.1}/doc/testing.txt +0 -0
  97. {pyflyby-1.9.0 → pyflyby-1.9.1}/etc/pyflyby/canonical.py +0 -0
  98. {pyflyby-1.9.0 → pyflyby-1.9.1}/etc/pyflyby/common.py +0 -0
  99. {pyflyby-1.9.0 → pyflyby-1.9.1}/etc/pyflyby/forget.py +0 -0
  100. {pyflyby-1.9.0 → pyflyby-1.9.1}/etc/pyflyby/mandatory.py +0 -0
  101. {pyflyby-1.9.0 → pyflyby-1.9.1}/etc/pyflyby/numpy.py +0 -0
  102. {pyflyby-1.9.0 → pyflyby-1.9.1}/etc/pyflyby/std.py +0 -0
  103. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/emacs/pyflyby.el +0 -0
  104. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby/__init__.py +0 -0
  105. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby/__main__.py +0 -0
  106. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby/_cmdline.py +0 -0
  107. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby/_comms.py +0 -0
  108. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby/_dbg.py +0 -0
  109. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby/_docxref.py +0 -0
  110. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby/_file.py +0 -0
  111. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby/_flags.py +0 -0
  112. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby/_format.py +0 -0
  113. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby/_idents.py +0 -0
  114. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby/_importdb.py +0 -0
  115. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby/_imports2s.py +0 -0
  116. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby/_interactive.py +0 -0
  117. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby/_livepatch.py +0 -0
  118. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby/_log.py +0 -0
  119. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby/autoimport.py +0 -0
  120. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby/importdb.py +0 -0
  121. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby.egg-info/SOURCES.txt +0 -0
  122. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby.egg-info/dependency_links.txt +0 -0
  123. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby.egg-info/entry_points.txt +0 -0
  124. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby.egg-info/requires.txt +0 -0
  125. {pyflyby-1.9.0 → pyflyby-1.9.1}/lib/python/pyflyby.egg-info/top_level.txt +0 -0
  126. {pyflyby-1.9.0 → pyflyby-1.9.1}/libexec/pyflyby/colordiff +0 -0
  127. {pyflyby-1.9.0 → pyflyby-1.9.1}/libexec/pyflyby/diff-colorize +0 -0
  128. {pyflyby-1.9.0 → pyflyby-1.9.1}/setup.cfg +0 -0
  129. {pyflyby-1.9.0 → pyflyby-1.9.1}/setup.py +0 -0
  130. {pyflyby-1.9.0 → pyflyby-1.9.1}/tests/__init__.py +0 -0
  131. {pyflyby-1.9.0 → pyflyby-1.9.1}/tests/test_0testconfig.py +0 -0
  132. {pyflyby-1.9.0 → pyflyby-1.9.1}/tests/test_autoimp.py +0 -0
  133. {pyflyby-1.9.0 → pyflyby-1.9.1}/tests/test_cmdline.py +0 -0
  134. {pyflyby-1.9.0 → pyflyby-1.9.1}/tests/test_docxref.py +0 -0
  135. {pyflyby-1.9.0 → pyflyby-1.9.1}/tests/test_file.py +0 -0
  136. {pyflyby-1.9.0 → pyflyby-1.9.1}/tests/test_flags.py +0 -0
  137. {pyflyby-1.9.0 → pyflyby-1.9.1}/tests/test_format.py +0 -0
  138. {pyflyby-1.9.0 → pyflyby-1.9.1}/tests/test_idents.py +0 -0
  139. {pyflyby-1.9.0 → pyflyby-1.9.1}/tests/test_importclns.py +0 -0
  140. {pyflyby-1.9.0 → pyflyby-1.9.1}/tests/test_importdb.py +0 -0
  141. {pyflyby-1.9.0 → pyflyby-1.9.1}/tests/test_imports2s.py +0 -0
  142. {pyflyby-1.9.0 → pyflyby-1.9.1}/tests/test_jupyterlab_pyflyby.py +0 -0
  143. {pyflyby-1.9.0 → pyflyby-1.9.1}/tests/test_livepatch.py +0 -0
  144. {pyflyby-1.9.0 → pyflyby-1.9.1}/tests/test_parse.py +0 -0
  145. {pyflyby-1.9.0 → pyflyby-1.9.1}/tests/test_py.py +0 -0
  146. {pyflyby-1.9.0 → pyflyby-1.9.1}/tests/test_util.py +0 -0
  147. {pyflyby-1.9.0 → pyflyby-1.9.1}/tests/tests_sorts.py +0 -0
  148. {pyflyby-1.9.0 → pyflyby-1.9.1}/tests/xrefs.py +0 -0
  149. {pyflyby-1.9.0 → pyflyby-1.9.1}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyflyby
3
- Version: 1.9.0
3
+ Version: 1.9.1
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
@@ -22,7 +22,7 @@ from pyflyby._importdb import ImportDB
22
22
  from pyflyby._importstmt import Import
23
23
  from pyflyby._log import logger
24
24
  from pyflyby._modules import ModuleHandle
25
- from pyflyby._parse import PythonBlock, infer_compile_mode
25
+ from pyflyby._parse import PythonBlock, infer_compile_mode, _is_ast_str
26
26
 
27
27
  if sys.version_info >= (3, 12):
28
28
  ATTRIBUTE_NAME = "value"
@@ -34,6 +34,11 @@ if sys.version_info > (3, 11):
34
34
  else:
35
35
  LOAD_SHIFT = 0
36
36
 
37
+ if sys.version_info > (3, 11):
38
+ LOAD_SHIFT = 1
39
+ else:
40
+ LOAD_SHIFT = 0
41
+
37
42
  NoneType = type(None)
38
43
  EllipsisType = type(Ellipsis)
39
44
 
@@ -580,7 +585,7 @@ class _MissingImportFinder(object):
580
585
  if not isinstance(node.value, ast.List):
581
586
  logger.warning("Don't know how to handle __all__ as (%s)" % node.value)
582
587
  return
583
- if not all(isinstance(e, ast.Str) for e in node.value.elts):
588
+ if not all(_is_ast_str(e) for e in node.value.elts):
584
589
  logger.warning("Don't know how to handle __all__ with list elements other than str")
585
590
  return
586
591
  for e in node.value.elts:
@@ -588,7 +593,11 @@ class _MissingImportFinder(object):
588
593
 
589
594
  def visit_ClassDef(self, node):
590
595
  logger.debug("visit_ClassDef(%r)", node)
591
- assert node._fields == ('name', 'bases', 'keywords', 'body', 'decorator_list')
596
+ if sys.version_info > (3,12):
597
+ # we don't visit type_params, so autoimport won't work yet for type annotations
598
+ assert node._fields == ('name', 'bases', 'keywords', 'body', 'decorator_list', 'type_params'), node._fields
599
+ else:
600
+ assert node._fields == ('name', 'bases', 'keywords', 'body', 'decorator_list'), node._fields
592
601
  self.visit(node.bases)
593
602
  self.visit(node.decorator_list)
594
603
  # The class's name is only visible to others (not to the body to the
@@ -619,7 +628,11 @@ class _MissingImportFinder(object):
619
628
  # scope.
620
629
  # - Store the name in the current scope (but not visibly to
621
630
  # args/decorator_list).
622
- assert node._fields == ('name', 'args', 'body', 'decorator_list', 'returns', 'type_comment'), node._fields
631
+ if sys.version_info > (3, 12):
632
+ # we don't visit type_params, so autoimport won't work yet for type annotations
633
+ assert node._fields == ('name', 'args', 'body', 'decorator_list', 'returns', 'type_comment', 'type_params'), node._fields
634
+ else:
635
+ assert node._fields == ('name', 'args', 'body', 'decorator_list', 'returns', 'type_comment'), node._fields
623
636
  with self._NewScopeCtx(include_class_scopes=True):
624
637
  self.visit(node.decorator_list)
625
638
  self.visit(node.args)
@@ -215,8 +215,8 @@ class ImportSet(object):
215
215
  """
216
216
  :return:
217
217
  (mapping from name to __future__ imports,
218
- mapping from name to non-'from' imports,
219
- mapping from name to 'from' imports)
218
+ mapping from name to non-'from' imports,
219
+ mapping from name to 'from' imports)
220
220
  """
221
221
  ftr_imports = defaultdict(set)
222
222
  pkg_imports = defaultdict(set)
@@ -15,17 +15,16 @@ from pyflyby._parse import PythonStatement
15
15
  from pyflyby._util import (Inf, cached_attribute, cmp,
16
16
  longest_common_prefix)
17
17
 
18
- from black import format_str, FileMode as Mode
19
- from black.files import find_pyproject_toml, parse_pyproject_toml
20
- from black.mode import TargetVersion
21
18
 
19
+ from typing import Dict, Tuple, Optional
22
20
 
23
- def read_black_config():
24
- """Read the black configuration from ``pyproject.toml``
25
21
 
26
22
 
27
- """
28
- pyproject_path = find_pyproject_toml('.')
23
+ def read_black_config() -> Dict:
24
+ """Read the black configuration from ``pyproject.toml``"""
25
+ from black.files import find_pyproject_toml, parse_pyproject_toml
26
+
27
+ pyproject_path = find_pyproject_toml((".",))
29
28
 
30
29
  raw_config = parse_pyproject_toml(pyproject_path) if pyproject_path else {}
31
30
 
@@ -348,24 +347,31 @@ class Import(object):
348
347
  return NotImplemented
349
348
  return self._data < other._data
350
349
 
351
- def _validate_alias(arg):
350
+ def _validate_alias(arg) -> Tuple[str, Optional[str]]:
352
351
  """
353
352
  Ensure each alias is a tuple (str, None|str), and return it.
354
353
 
355
354
  """
356
355
  assert isinstance(arg, tuple)
357
- a0,a1 = arg
356
+ # Pyright does not seem to be able to infer the length from a
357
+ # the unpacking.
358
+ assert len(arg) == 2
359
+ a0, a1 = arg
358
360
  assert isinstance(a0, str)
359
361
  assert isinstance(a1, (str, type(None)))
360
362
  return arg
361
363
 
362
364
  @total_ordering
363
- class ImportStatement(object):
365
+ class ImportStatement:
364
366
  """
365
367
  Token-level representation of an import statement containing multiple
366
368
  imports from a single module. Corresponds to an ``ast.ImportFrom`` or
367
369
  ``ast.Import``.
368
370
  """
371
+
372
+ aliases : Tuple[Tuple[str, Optional[str]],...]
373
+ fromname : Optional[str]
374
+
369
375
  def __new__(cls, arg):
370
376
  if isinstance(arg, cls):
371
377
  return arg
@@ -381,10 +387,10 @@ class ImportStatement(object):
381
387
  raise TypeError
382
388
 
383
389
  @classmethod
384
- def from_parts(cls, fromname, aliases):
385
- assert isinstance(aliases, list)
390
+ def from_parts(cls, fromname:Optional[str], aliases:Tuple[Tuple[str, Optional[str]],...]):
391
+ assert isinstance(aliases, tuple)
386
392
  assert len(aliases) > 0
387
-
393
+
388
394
  self = object.__new__(cls)
389
395
  self.fromname = fromname
390
396
  self.aliases = tuple(_validate_alias(a) for a in aliases)
@@ -440,7 +446,7 @@ class ImportStatement(object):
440
446
  raise NonImportStatementError(
441
447
  'Expected ImportStatement, got {node}'.format(node=node)
442
448
  )
443
- aliases = [ (alias.name, alias.asname) for alias in node.names ]
449
+ aliases = tuple( (alias.name, alias.asname) for alias in node.names )
444
450
  return cls.from_parts(fromname, aliases)
445
451
 
446
452
  @classmethod
@@ -463,7 +469,7 @@ class ImportStatement(object):
463
469
  raise ValueError(
464
470
  "Inconsistent module names %r" % (sorted(module_names),))
465
471
  fromname = list(module_names)[0]
466
- aliases = [ imp.split[1:] for imp in imports ]
472
+ aliases = tuple(imp.split[1:] for imp in imports)
467
473
  return cls.from_parts(fromname, aliases)
468
474
 
469
475
  @cached_attribute
@@ -529,12 +535,15 @@ class ImportStatement(object):
529
535
  return res
530
536
 
531
537
  @staticmethod
532
- def run_black(src_contents: str, params) -> str:
538
+ def run_black(src_contents: str, params:FormatParams) -> str:
533
539
  """Run the black formatter for the Python source code given as a string
534
540
 
535
541
  This is adapted from https://github.com/akaihola/darker
536
542
 
537
543
  """
544
+ from black import format_str, FileMode
545
+ from black.mode import TargetVersion
546
+
538
547
  black_config = read_black_config()
539
548
  mode = dict()
540
549
  if "line_length" in black_config:
@@ -568,14 +577,12 @@ class ImportStatement(object):
568
577
  # ``--skip-string-normalization``, but the parameter for
569
578
  # ``black.Mode`` needs to be the opposite boolean of
570
579
  # ``skip-string-normalization``, hence the inverse boolean
571
- mode["string_normalization"] = not black_config[
572
- "skip_string_normalization"
573
- ]
580
+ mode["string_normalization"] = not black_config["skip_string_normalization"]
574
581
 
575
582
  # The custom handling of empty and all-whitespace files below will be unnecessary if
576
583
  # https://github.com/psf/black/pull/2484 lands in Black.
577
584
  contents_for_black = src_contents
578
- return format_str(contents_for_black, mode=Mode(**mode))
585
+ return format_str(contents_for_black, mode=FileMode(**mode))
579
586
 
580
587
  @property
581
588
  def _data(self):
@@ -125,6 +125,8 @@ class ModuleHandle(object):
125
125
  A handle to a module.
126
126
  """
127
127
 
128
+ name: DottedIdentifier
129
+
128
130
  def __new__(cls, arg):
129
131
  if isinstance(arg, cls):
130
132
  return arg
@@ -206,14 +208,8 @@ class ModuleHandle(object):
206
208
  if self.parent and not self.parent.exists:
207
209
  return False
208
210
 
209
- # pkgutil.find_loader returns None for unimported Python 3
210
- # namespace packages, so prefer importlib
211
- try:
212
- import importlib.util
213
- find = importlib.util.find_spec
214
- except ImportError:
215
- import pkgutil
216
- find = pkgutil.find_loader
211
+ import importlib.util
212
+ find = importlib.util.find_spec
217
213
 
218
214
  try:
219
215
  pkg = find(name)
@@ -241,6 +237,7 @@ class ModuleHandle(object):
241
237
  # module, which may be undesirable.
242
238
  import pkgutil
243
239
  try:
240
+ #TODO: deprecated and will be removed in 3.14
244
241
  loader = pkgutil.get_loader(str(self.name))
245
242
  except ImportError:
246
243
  return None
@@ -303,7 +300,7 @@ class ModuleHandle(object):
303
300
  Enumerate the importable submodules of this module.
304
301
 
305
302
  >>> ModuleHandle("email").submodules # doctest:+ELLIPSIS
306
- (..., 'email.encoders', ..., 'email.mime', ...)
303
+ (..., ModuleHandle('email.encoders'), ..., ModuleHandle('email.mime'), ...)
307
304
 
308
305
  :rtype:
309
306
  ``tuple`` of `ModuleHandle` s
@@ -366,6 +363,7 @@ class ModuleHandle(object):
366
363
 
367
364
  # If __all__ is defined, try to use it
368
365
  all_is_good = False # pun intended
366
+ all_members = []
369
367
  if "__all__" in members:
370
368
  # Iterate through the nodes and reconstruct the
371
369
  # value of __all__
@@ -418,7 +416,7 @@ class ModuleHandle(object):
418
416
  members = [n for n in members if not n.startswith("_")]
419
417
 
420
418
  # Filter out artificially added "deep" members.
421
- members = [(n, None) for n in members if "." not in n]
419
+ members = tuple([(n, None) for n in members if "." not in n])
422
420
  if not members:
423
421
  return None
424
422
  return ImportSet(
@@ -482,7 +480,11 @@ class ModuleHandle(object):
482
480
  result = module.module
483
481
  except Exception as e:
484
482
  raise ImportError(e)
485
- for part, prefix in zip(identifier, prefixes(identifier))[1:]:
483
+ # TODO: as far as I can tell the code here is never reached, or haven't
484
+ # been in quite some time as the line below was invalid on Python 3 since 2011
485
+ # zip(...)[...] fails as zip is not indexable.
486
+ # the only place that seem to be using this method is XrefScanner.
487
+ for part, prefix in list(zip(identifier, prefixes(identifier)))[1:]:
486
488
  try:
487
489
  result = getattr(result, str(part))
488
490
  except Exception:
@@ -260,7 +260,10 @@ def _test_parse_string_literal(text, flags):
260
260
  body = module_node.body
261
261
  if not _is_ast_str_or_byte(body):
262
262
  return None
263
- return body.s
263
+ if sys.version_info < (3 ,9):
264
+ return body.s
265
+ else:
266
+ return body.value
264
267
 
265
268
 
266
269
  AstNodeContext = namedtuple("AstNodeContext", "parent field index")
@@ -275,6 +275,7 @@ Examples
275
275
 
276
276
 
277
277
  from functools import total_ordering
278
+ from typing import Any
278
279
 
279
280
  from pyflyby._util import cmp
280
281
 
@@ -377,7 +378,7 @@ FLAGS = CompilerFlags(["absolute_import", "with_statement", "division",
377
378
  "print_function"])
378
379
 
379
380
 
380
- def _get_argspec(arg, _recurse=False):
381
+ def _get_argspec(arg:Any) -> inspect.FullArgSpec:
381
382
  from inspect import getfullargspec as getargspec, FullArgSpec as ArgSpec
382
383
  if isinstance(arg, FunctionType):
383
384
  return getargspec(arg)
@@ -394,8 +395,6 @@ def _get_argspec(arg, _recurse=False):
394
395
  else:
395
396
  argspec = _get_argspec(arg.__init__)
396
397
  return ArgSpec(argspec.args[1:], *argspec[1:])
397
- elif _recurse and hasattr(arg, '__call__'):
398
- return _get_argspec(arg.__call__, _recurse=False)
399
398
  elif callable(arg):
400
399
  # Unknown, probably a built-in method.
401
400
  return ArgSpec([], "args", "kwargs", None, [], None, {})
@@ -458,8 +457,16 @@ def _requires_parens_as_function(function_name:str):
458
457
  return True
459
458
 
460
459
 
461
- def _format_call_spec(function_name, argspec):
462
- callspec = inspect.formatargspec(*argspec)
460
+ def _format_call_spec(function_name:str, obj:Any)-> str:
461
+ # using signature() is not strictly identical
462
+ # as it might look at __text_signature__ and/or respect possitional only
463
+ # forward slash:
464
+ # >>> def foo(a, /, b)
465
+ # whcih formatargspec did not do.
466
+ # argspec = _get_argspec(obj)
467
+ # old_callspect = inspect.formatargspec(*argspec)
468
+ # assert old_callspect == callspec , f"{old_callspect} !={callspec}"
469
+ callspec = str(inspect.signature(obj))
463
470
  if _requires_parens_as_function(function_name):
464
471
  return "(%s)%s" % (function_name, callspec)
465
472
  else:
@@ -483,11 +490,13 @@ except ImportError:
483
490
  return "'" + s.replace("'", "'\"'\"'") + "'"
484
491
 
485
492
 
486
- def _build_function_usage_string(function_name, argspec, prefix):
493
+ def _build_function_usage_string(function_name:str, obj:Any, prefix:str):
494
+ argspec = _get_argspec(obj)
495
+
487
496
  usage = []
488
497
  # TODO: colorize
489
498
  usage.append("Python signature:")
490
- usage.append(" >"+">> " + _format_call_spec(function_name, argspec))
499
+ usage.append(" >"+">> " + _format_call_spec(function_name, obj))
491
500
  usage.append("")
492
501
  usage.append("Command-line signature:")
493
502
  keywords = argspec.varkw
@@ -902,11 +911,10 @@ def _get_help(expr, verbosity=1):
902
911
  # TODO: colorize headers
903
912
  result = ""
904
913
  obj = expr.value
905
- name = str(expr.source)
914
+ name:str = str(expr.source)
906
915
  if callable(obj):
907
- argspec = _get_argspec(obj)
908
916
  prefix = os.path.basename(sys.orig_argv[0]) + " "
909
- result += _build_function_usage_string(name, argspec, prefix)
917
+ result += _build_function_usage_string(name, obj, prefix)
910
918
  if verbosity == 0:
911
919
  include_filename = False
912
920
  include_doc = False
@@ -7,60 +7,38 @@
7
7
  from contextlib import contextmanager
8
8
  import inspect
9
9
  import os
10
- from six import reraise
11
10
  import sys
12
11
  import types
13
12
 
13
+ # There used to be a custom caching_attribute implementation
14
+ # this now uses functools's cached_property which is understood by
15
+ # various static analysis tools.
16
+ from functools import cached_property as cached_attribute # noqa: F401
17
+
14
18
  # Python 2/3 compatibility
15
19
  DictProxyType = type(object.__dict__)
16
20
 
17
-
18
- def memoize(function):
19
- cache = {}
20
- def wrapped_fn(*args, **kwargs):
21
- cache_key = (args, tuple(sorted(kwargs.items())))
22
- try:
23
- return cache[cache_key]
24
- except KeyError:
25
- result = function(*args, **kwargs)
26
- cache[cache_key] = result
27
- return result
28
- wrapped_fn.cache = cache
29
- return wrapped_fn
21
+ if sys.version_info > (3,9):
22
+ from functools import cache as memoize
23
+ else:
24
+ def memoize(function):
25
+ cache = {}
26
+ def wrapped_fn(*args, **kwargs):
27
+ cache_key = (args, tuple(sorted(kwargs.items())))
28
+ try:
29
+ return cache[cache_key]
30
+ except KeyError:
31
+ result = function(*args, **kwargs)
32
+ cache[cache_key] = result
33
+ return result
34
+ wrapped_fn.cache = cache
35
+ return wrapped_fn
30
36
 
31
37
 
32
38
  class WrappedAttributeError(Exception):
33
39
  pass
34
40
 
35
41
 
36
- class cached_attribute(object):
37
- """Computes attribute value and caches it in instance.
38
-
39
- Example::
40
-
41
- class MyClass(object):
42
- @cached_attribute
43
- def myMethod(self):
44
- # ...
45
-
46
- Use "del inst.myMethod" to clear cache."""
47
- # http://code.activestate.com/recipes/276643/
48
-
49
- def __init__(self, method, name=None):
50
- self.method = method
51
- self.name = name or method.__name__
52
-
53
- def __get__(self, inst, cls):
54
- if inst is None:
55
- return self
56
- try:
57
- result = self.method(inst)
58
- except AttributeError as e:
59
- reraise(WrappedAttributeError, WrappedAttributeError(str(e)), sys.exc_info()[2])
60
- setattr(inst, self.name, result)
61
- return result
62
-
63
-
64
42
  def stable_unique(items):
65
43
  """
66
44
  Return a copy of ``items`` without duplicates. The order of other items is
@@ -5,4 +5,4 @@
5
5
 
6
6
 
7
7
 
8
- __version__ = '1.9.0'
8
+ __version__ = '1.9.1'
@@ -22,7 +22,7 @@ from pyflyby._importdb import ImportDB
22
22
  from pyflyby._importstmt import Import
23
23
  from pyflyby._log import logger
24
24
  from pyflyby._modules import ModuleHandle
25
- from pyflyby._parse import PythonBlock, infer_compile_mode
25
+ from pyflyby._parse import PythonBlock, infer_compile_mode, _is_ast_str
26
26
 
27
27
  if sys.version_info >= (3, 12):
28
28
  ATTRIBUTE_NAME = "value"
@@ -34,6 +34,11 @@ if sys.version_info > (3, 11):
34
34
  else:
35
35
  LOAD_SHIFT = 0
36
36
 
37
+ if sys.version_info > (3, 11):
38
+ LOAD_SHIFT = 1
39
+ else:
40
+ LOAD_SHIFT = 0
41
+
37
42
  NoneType = type(None)
38
43
  EllipsisType = type(Ellipsis)
39
44
 
@@ -580,7 +585,7 @@ class _MissingImportFinder(object):
580
585
  if not isinstance(node.value, ast.List):
581
586
  logger.warning("Don't know how to handle __all__ as (%s)" % node.value)
582
587
  return
583
- if not all(isinstance(e, ast.Str) for e in node.value.elts):
588
+ if not all(_is_ast_str(e) for e in node.value.elts):
584
589
  logger.warning("Don't know how to handle __all__ with list elements other than str")
585
590
  return
586
591
  for e in node.value.elts:
@@ -588,7 +593,11 @@ class _MissingImportFinder(object):
588
593
 
589
594
  def visit_ClassDef(self, node):
590
595
  logger.debug("visit_ClassDef(%r)", node)
591
- assert node._fields == ('name', 'bases', 'keywords', 'body', 'decorator_list')
596
+ if sys.version_info > (3,12):
597
+ # we don't visit type_params, so autoimport won't work yet for type annotations
598
+ assert node._fields == ('name', 'bases', 'keywords', 'body', 'decorator_list', 'type_params'), node._fields
599
+ else:
600
+ assert node._fields == ('name', 'bases', 'keywords', 'body', 'decorator_list'), node._fields
592
601
  self.visit(node.bases)
593
602
  self.visit(node.decorator_list)
594
603
  # The class's name is only visible to others (not to the body to the
@@ -619,7 +628,11 @@ class _MissingImportFinder(object):
619
628
  # scope.
620
629
  # - Store the name in the current scope (but not visibly to
621
630
  # args/decorator_list).
622
- assert node._fields == ('name', 'args', 'body', 'decorator_list', 'returns', 'type_comment'), node._fields
631
+ if sys.version_info > (3, 12):
632
+ # we don't visit type_params, so autoimport won't work yet for type annotations
633
+ assert node._fields == ('name', 'args', 'body', 'decorator_list', 'returns', 'type_comment', 'type_params'), node._fields
634
+ else:
635
+ assert node._fields == ('name', 'args', 'body', 'decorator_list', 'returns', 'type_comment'), node._fields
623
636
  with self._NewScopeCtx(include_class_scopes=True):
624
637
  self.visit(node.decorator_list)
625
638
  self.visit(node.args)
@@ -215,8 +215,8 @@ class ImportSet(object):
215
215
  """
216
216
  :return:
217
217
  (mapping from name to __future__ imports,
218
- mapping from name to non-'from' imports,
219
- mapping from name to 'from' imports)
218
+ mapping from name to non-'from' imports,
219
+ mapping from name to 'from' imports)
220
220
  """
221
221
  ftr_imports = defaultdict(set)
222
222
  pkg_imports = defaultdict(set)
@@ -15,17 +15,16 @@ from pyflyby._parse import PythonStatement
15
15
  from pyflyby._util import (Inf, cached_attribute, cmp,
16
16
  longest_common_prefix)
17
17
 
18
- from black import format_str, FileMode as Mode
19
- from black.files import find_pyproject_toml, parse_pyproject_toml
20
- from black.mode import TargetVersion
21
18
 
19
+ from typing import Dict, Tuple, Optional
22
20
 
23
- def read_black_config():
24
- """Read the black configuration from ``pyproject.toml``
25
21
 
26
22
 
27
- """
28
- pyproject_path = find_pyproject_toml('.')
23
+ def read_black_config() -> Dict:
24
+ """Read the black configuration from ``pyproject.toml``"""
25
+ from black.files import find_pyproject_toml, parse_pyproject_toml
26
+
27
+ pyproject_path = find_pyproject_toml((".",))
29
28
 
30
29
  raw_config = parse_pyproject_toml(pyproject_path) if pyproject_path else {}
31
30
 
@@ -348,24 +347,31 @@ class Import(object):
348
347
  return NotImplemented
349
348
  return self._data < other._data
350
349
 
351
- def _validate_alias(arg):
350
+ def _validate_alias(arg) -> Tuple[str, Optional[str]]:
352
351
  """
353
352
  Ensure each alias is a tuple (str, None|str), and return it.
354
353
 
355
354
  """
356
355
  assert isinstance(arg, tuple)
357
- a0,a1 = arg
356
+ # Pyright does not seem to be able to infer the length from a
357
+ # the unpacking.
358
+ assert len(arg) == 2
359
+ a0, a1 = arg
358
360
  assert isinstance(a0, str)
359
361
  assert isinstance(a1, (str, type(None)))
360
362
  return arg
361
363
 
362
364
  @total_ordering
363
- class ImportStatement(object):
365
+ class ImportStatement:
364
366
  """
365
367
  Token-level representation of an import statement containing multiple
366
368
  imports from a single module. Corresponds to an ``ast.ImportFrom`` or
367
369
  ``ast.Import``.
368
370
  """
371
+
372
+ aliases : Tuple[Tuple[str, Optional[str]],...]
373
+ fromname : Optional[str]
374
+
369
375
  def __new__(cls, arg):
370
376
  if isinstance(arg, cls):
371
377
  return arg
@@ -381,10 +387,10 @@ class ImportStatement(object):
381
387
  raise TypeError
382
388
 
383
389
  @classmethod
384
- def from_parts(cls, fromname, aliases):
385
- assert isinstance(aliases, list)
390
+ def from_parts(cls, fromname:Optional[str], aliases:Tuple[Tuple[str, Optional[str]],...]):
391
+ assert isinstance(aliases, tuple)
386
392
  assert len(aliases) > 0
387
-
393
+
388
394
  self = object.__new__(cls)
389
395
  self.fromname = fromname
390
396
  self.aliases = tuple(_validate_alias(a) for a in aliases)
@@ -440,7 +446,7 @@ class ImportStatement(object):
440
446
  raise NonImportStatementError(
441
447
  'Expected ImportStatement, got {node}'.format(node=node)
442
448
  )
443
- aliases = [ (alias.name, alias.asname) for alias in node.names ]
449
+ aliases = tuple( (alias.name, alias.asname) for alias in node.names )
444
450
  return cls.from_parts(fromname, aliases)
445
451
 
446
452
  @classmethod
@@ -463,7 +469,7 @@ class ImportStatement(object):
463
469
  raise ValueError(
464
470
  "Inconsistent module names %r" % (sorted(module_names),))
465
471
  fromname = list(module_names)[0]
466
- aliases = [ imp.split[1:] for imp in imports ]
472
+ aliases = tuple(imp.split[1:] for imp in imports)
467
473
  return cls.from_parts(fromname, aliases)
468
474
 
469
475
  @cached_attribute
@@ -529,12 +535,15 @@ class ImportStatement(object):
529
535
  return res
530
536
 
531
537
  @staticmethod
532
- def run_black(src_contents: str, params) -> str:
538
+ def run_black(src_contents: str, params:FormatParams) -> str:
533
539
  """Run the black formatter for the Python source code given as a string
534
540
 
535
541
  This is adapted from https://github.com/akaihola/darker
536
542
 
537
543
  """
544
+ from black import format_str, FileMode
545
+ from black.mode import TargetVersion
546
+
538
547
  black_config = read_black_config()
539
548
  mode = dict()
540
549
  if "line_length" in black_config:
@@ -568,14 +577,12 @@ class ImportStatement(object):
568
577
  # ``--skip-string-normalization``, but the parameter for
569
578
  # ``black.Mode`` needs to be the opposite boolean of
570
579
  # ``skip-string-normalization``, hence the inverse boolean
571
- mode["string_normalization"] = not black_config[
572
- "skip_string_normalization"
573
- ]
580
+ mode["string_normalization"] = not black_config["skip_string_normalization"]
574
581
 
575
582
  # The custom handling of empty and all-whitespace files below will be unnecessary if
576
583
  # https://github.com/psf/black/pull/2484 lands in Black.
577
584
  contents_for_black = src_contents
578
- return format_str(contents_for_black, mode=Mode(**mode))
585
+ return format_str(contents_for_black, mode=FileMode(**mode))
579
586
 
580
587
  @property
581
588
  def _data(self):