pyflyby 1.9.4__tar.gz → 1.9.6__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 (160) hide show
  1. {pyflyby-1.9.4/lib/python/pyflyby.egg-info → pyflyby-1.9.6}/PKG-INFO +31 -4
  2. {pyflyby-1.9.4 → pyflyby-1.9.6}/README.rst +30 -0
  3. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/__init__.py +1 -0
  4. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_autoimp.py +51 -5
  5. {pyflyby-1.9.4/lib/python → pyflyby-1.9.6/bin}/pyflyby/_cmdline.py +6 -3
  6. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_dbg.py +12 -11
  7. pyflyby-1.9.6/bin/pyflyby/_dynimp.py +152 -0
  8. {pyflyby-1.9.4/lib/python → pyflyby-1.9.6/bin}/pyflyby/_file.py +37 -20
  9. {pyflyby-1.9.4/lib/python → pyflyby-1.9.6/bin}/pyflyby/_importclns.py +30 -14
  10. {pyflyby-1.9.4/lib/python → pyflyby-1.9.6/bin}/pyflyby/_importdb.py +133 -45
  11. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_interactive.py +23 -146
  12. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_modules.py +13 -15
  13. {pyflyby-1.9.4/lib/python → pyflyby-1.9.6/bin}/pyflyby/_parse.py +20 -12
  14. {pyflyby-1.9.4/lib/python → pyflyby-1.9.6/bin}/pyflyby/_py.py +16 -5
  15. {pyflyby-1.9.4/lib/python → pyflyby-1.9.6/bin}/pyflyby/_version.py +1 -1
  16. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/__init__.py +1 -0
  17. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_autoimp.py +51 -5
  18. {pyflyby-1.9.4/bin → pyflyby-1.9.6/lib/python}/pyflyby/_cmdline.py +6 -3
  19. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_dbg.py +12 -11
  20. pyflyby-1.9.6/lib/python/pyflyby/_dynimp.py +152 -0
  21. {pyflyby-1.9.4/bin → pyflyby-1.9.6/lib/python}/pyflyby/_file.py +37 -20
  22. {pyflyby-1.9.4/bin → pyflyby-1.9.6/lib/python}/pyflyby/_importclns.py +30 -14
  23. {pyflyby-1.9.4/bin → pyflyby-1.9.6/lib/python}/pyflyby/_importdb.py +133 -45
  24. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_interactive.py +23 -146
  25. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_modules.py +13 -15
  26. {pyflyby-1.9.4/bin → pyflyby-1.9.6/lib/python}/pyflyby/_parse.py +20 -12
  27. {pyflyby-1.9.4/bin → pyflyby-1.9.6/lib/python}/pyflyby/_py.py +16 -5
  28. {pyflyby-1.9.4/bin → pyflyby-1.9.6/lib/python}/pyflyby/_version.py +1 -1
  29. {pyflyby-1.9.4 → pyflyby-1.9.6/lib/python/pyflyby.egg-info}/PKG-INFO +31 -4
  30. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby.egg-info/SOURCES.txt +3 -5
  31. pyflyby-1.9.6/lib/python/pyflyby.egg-info/requires.txt +6 -0
  32. pyflyby-1.9.6/pyproject.toml +26 -0
  33. {pyflyby-1.9.4 → pyflyby-1.9.6}/setup.py +6 -1
  34. {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_autoimp.py +64 -5
  35. {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_cmdline.py +46 -14
  36. {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_importclns.py +6 -0
  37. {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_interactive.py +29 -17
  38. {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_livepatch.py +1 -1
  39. {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_parse.py +12 -1
  40. {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_py.py +7 -19
  41. pyflyby-1.9.4/bin/pyflyby/.DS_Store +0 -0
  42. pyflyby-1.9.4/doc/.DS_Store +0 -0
  43. pyflyby-1.9.4/lib/.DS_Store +0 -0
  44. pyflyby-1.9.4/lib/python/.DS_Store +0 -0
  45. pyflyby-1.9.4/lib/python/pyflyby/.DS_Store +0 -0
  46. pyflyby-1.9.4/lib/python/pyflyby.egg-info/requires.txt +0 -3
  47. {pyflyby-1.9.4 → pyflyby-1.9.6}/.pyflyby +0 -0
  48. {pyflyby-1.9.4 → pyflyby-1.9.6}/LICENSE.txt +0 -0
  49. {pyflyby-1.9.4 → pyflyby-1.9.6}/MANIFEST.in +0 -0
  50. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/autoipython +0 -0
  51. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/autopython +0 -0
  52. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/collect-exports +0 -0
  53. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/collect-imports +0 -0
  54. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/create-imports +0 -0
  55. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/find-import +0 -0
  56. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/list-bad-xrefs +0 -0
  57. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/prune-broken-imports +0 -0
  58. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/py +0 -0
  59. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/__main__.py +0 -0
  60. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_comms.py +0 -0
  61. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_docxref.py +0 -0
  62. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_flags.py +0 -0
  63. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_format.py +0 -0
  64. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_idents.py +0 -0
  65. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_import_sorting.py +0 -0
  66. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_imports2s.py +0 -0
  67. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_importstmt.py +0 -0
  68. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_livepatch.py +0 -0
  69. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_log.py +0 -0
  70. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/_util.py +0 -0
  71. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/autoimport.py +0 -0
  72. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby/importdb.py +0 -0
  73. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/pyflyby-diff +0 -0
  74. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/reformat-imports +0 -0
  75. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/replace-star-imports +0 -0
  76. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/tidy-imports +0 -0
  77. {pyflyby-1.9.4 → pyflyby-1.9.6}/bin/transform-imports +0 -0
  78. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/LICENSE.txt +0 -0
  79. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/Makefile +0 -0
  80. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/TODO.txt +0 -0
  81. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/__init__.py +0 -0
  82. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/api.rst +0 -0
  83. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/autoimp.rst +0 -0
  84. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/cmdline.rst +0 -0
  85. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/comms.rst +0 -0
  86. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/dbg.rst +0 -0
  87. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/file.rst +0 -0
  88. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/flags.rst +0 -0
  89. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/format.rst +0 -0
  90. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/idents.rst +0 -0
  91. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/importclns.rst +0 -0
  92. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/importdb.rst +0 -0
  93. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/imports2s.rst +0 -0
  94. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/importstmt.rst +0 -0
  95. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/interactive.rst +0 -0
  96. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/livepatch.rst +0 -0
  97. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/log.rst +0 -0
  98. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/modules.rst +0 -0
  99. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/parse.rst +0 -0
  100. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/py.rst +0 -0
  101. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/api/util.rst +0 -0
  102. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/autoipython.rst +0 -0
  103. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/cli.rst +0 -0
  104. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/collect_exports.rst +0 -0
  105. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/collect_imports.rst +0 -0
  106. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/find_import.rst +0 -0
  107. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/prune_broken_imports.rst +0 -0
  108. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/py.rst +0 -0
  109. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/pyflyby_diff.rst +0 -0
  110. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/reformat_imports.rst +0 -0
  111. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/replace_star_imports.rst +0 -0
  112. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/tidy_imports.rst +0 -0
  113. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/cli/transform_imports.rst +0 -0
  114. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/conf.py +0 -0
  115. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/index.rst +0 -0
  116. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/make.bat +0 -0
  117. {pyflyby-1.9.4 → pyflyby-1.9.6}/doc/testing.txt +0 -0
  118. {pyflyby-1.9.4 → pyflyby-1.9.6}/etc/pyflyby/canonical.py +0 -0
  119. {pyflyby-1.9.4 → pyflyby-1.9.6}/etc/pyflyby/common.py +0 -0
  120. {pyflyby-1.9.4 → pyflyby-1.9.6}/etc/pyflyby/forget.py +0 -0
  121. {pyflyby-1.9.4 → pyflyby-1.9.6}/etc/pyflyby/mandatory.py +0 -0
  122. {pyflyby-1.9.4 → pyflyby-1.9.6}/etc/pyflyby/numpy.py +0 -0
  123. {pyflyby-1.9.4 → pyflyby-1.9.6}/etc/pyflyby/std.py +0 -0
  124. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/emacs/pyflyby.el +0 -0
  125. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/__main__.py +0 -0
  126. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_comms.py +0 -0
  127. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_docxref.py +0 -0
  128. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_flags.py +0 -0
  129. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_format.py +0 -0
  130. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_idents.py +0 -0
  131. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_import_sorting.py +0 -0
  132. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_imports2s.py +0 -0
  133. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_importstmt.py +0 -0
  134. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_livepatch.py +0 -0
  135. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_log.py +0 -0
  136. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/_util.py +0 -0
  137. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/autoimport.py +0 -0
  138. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby/importdb.py +0 -0
  139. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby.egg-info/dependency_links.txt +0 -0
  140. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby.egg-info/entry_points.txt +0 -0
  141. {pyflyby-1.9.4 → pyflyby-1.9.6}/lib/python/pyflyby.egg-info/top_level.txt +0 -0
  142. {pyflyby-1.9.4 → pyflyby-1.9.6}/libexec/pyflyby/colordiff +0 -0
  143. {pyflyby-1.9.4 → pyflyby-1.9.6}/libexec/pyflyby/diff-colorize +0 -0
  144. {pyflyby-1.9.4 → pyflyby-1.9.6}/setup.cfg +0 -0
  145. {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/__init__.py +0 -0
  146. {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_0testconfig.py +0 -0
  147. {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_docxref.py +0 -0
  148. {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_file.py +0 -0
  149. {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_flags.py +0 -0
  150. {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_format.py +0 -0
  151. {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_idents.py +0 -0
  152. {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_importdb.py +0 -0
  153. {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_imports2s.py +0 -0
  154. {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_importstmt.py +0 -0
  155. {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_jupyterlab_pyflyby.py +0 -0
  156. {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_modules.py +0 -0
  157. {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/test_util.py +0 -0
  158. {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/tests_sorts.py +0 -0
  159. {pyflyby-1.9.4 → pyflyby-1.9.6}/tests/xrefs.py +0 -0
  160. {pyflyby-1.9.4 → pyflyby-1.9.6}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyflyby
3
- Version: 1.9.4
3
+ Version: 1.9.6
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
@@ -18,9 +18,6 @@ Classifier: License :: OSI Approved :: MIT License
18
18
  Classifier: Programming Language :: Python
19
19
  Requires-Python: >3.8, <4
20
20
  License-File: LICENSE.txt
21
- Requires-Dist: six
22
- Requires-Dist: toml
23
- Requires-Dist: black
24
21
 
25
22
  #########
26
23
  Pyflyby
@@ -105,6 +102,36 @@ or
105
102
  [PYFLYBY] from base64 import b64decode
106
103
  Out[1]: 'hello'
107
104
 
105
+ Auto importer lazy variables
106
+ ----------------------------
107
+
108
+ It is possible to use the autoimporter to lazily define variables.
109
+
110
+ To use, put the following in your IPython startup files
111
+ (``~/.ipython/profile_default/startup/autoimp.py``), or in your IPython
112
+ configuration file:
113
+
114
+ .. code:: python
115
+
116
+
117
+ from pyflyby import add_import
118
+
119
+ add_import("foo", "foo = 1")
120
+
121
+ add_import(
122
+ "df, data as dd",
123
+ '''
124
+ import pandas as pd
125
+ data = [1,2,3]
126
+ df = pd.DataFrame(data)
127
+ ''')
128
+
129
+
130
+ You can add the keyword ``strict=False`` to not fail if not in IPython or of the
131
+ pyflyby extensions is not loaded.
132
+
133
+
134
+
108
135
 
109
136
  Quick start: ``py`` command-line multi-tool
110
137
  ===========================================
@@ -81,6 +81,36 @@ or
81
81
  [PYFLYBY] from base64 import b64decode
82
82
  Out[1]: 'hello'
83
83
 
84
+ Auto importer lazy variables
85
+ ----------------------------
86
+
87
+ It is possible to use the autoimporter to lazily define variables.
88
+
89
+ To use, put the following in your IPython startup files
90
+ (``~/.ipython/profile_default/startup/autoimp.py``), or in your IPython
91
+ configuration file:
92
+
93
+ .. code:: python
94
+
95
+
96
+ from pyflyby import add_import
97
+
98
+ add_import("foo", "foo = 1")
99
+
100
+ add_import(
101
+ "df, data as dd",
102
+ '''
103
+ import pandas as pd
104
+ data = [1,2,3]
105
+ df = pd.DataFrame(data)
106
+ ''')
107
+
108
+
109
+ You can add the keyword ``strict=False`` to not fail if not in IPython or of the
110
+ pyflyby extensions is not loaded.
111
+
112
+
113
+
84
114
 
85
115
  Quick start: ``py`` command-line multi-tool
86
116
  ===========================================
@@ -6,6 +6,7 @@
6
6
 
7
7
  from pyflyby._autoimp import (auto_eval, auto_import,
8
8
  find_missing_imports)
9
+ from pyflyby._dynimp import add_import
9
10
  from pyflyby._dbg import (add_debug_functions_to_builtins,
10
11
  attach_debugger, debug_on_exception,
11
12
  debug_statement, debugger,
@@ -19,7 +19,7 @@ from pyflyby._importstmt import Import
19
19
  from pyflyby._log import logger
20
20
  from pyflyby._modules import ModuleHandle
21
21
  from pyflyby._parse import (PythonBlock, _is_ast_str,
22
- infer_compile_mode)
22
+ infer_compile_mode, MatchAs)
23
23
 
24
24
  from six import reraise
25
25
  import sys
@@ -491,8 +491,11 @@ class _MissingImportFinder(object):
491
491
  logger.debug(
492
492
  "_MissingImportFinder has no method %r, using generic_visit", method
493
493
  )
494
-
495
- visitor = getattr(self, method, self.generic_visit)
494
+ if hasattr(self, method):
495
+ visitor = getattr(self, method)
496
+ else:
497
+ logger.debug("No method `%s`, using `generic_visit`", method)
498
+ visitor = self.generic_visit
496
499
  return visitor(node)
497
500
  else:
498
501
  raise TypeError("unexpected %s" % (type(node).__name__,))
@@ -827,6 +830,47 @@ class _MissingImportFinder(object):
827
830
  self._visit_StoreImport(node, modulename)
828
831
  self.generic_visit(node)
829
832
 
833
+ if sys.version_info >= (3,10):
834
+ def visit_match_case(self, node:ast.match_case):
835
+ logger.debug("visit_match_case(%r)", node)
836
+ return self.generic_visit(node)
837
+
838
+ def visit_Match(self, node:ast.Match):
839
+ logger.debug("visit_Match(%r)", node)
840
+ return self.generic_visit(node)
841
+
842
+ def visit_MatchMapping(self, node:ast.MatchMapping):
843
+ logger.debug("visit_MatchMapping(%r)", node)
844
+ return self.generic_visit(node)
845
+
846
+ def visit_MatchAs(self, node:MatchAs):
847
+ logger.debug("visit_MatchAs(%r)", node)
848
+ if node.name is None:
849
+ return
850
+ isinstance(node.name, str), node.name
851
+ return self._visit_Store(node.name)
852
+
853
+ def visit_Call(self, node:ast.Call):
854
+ logger.debug("visit_Call(%r)", node)
855
+ return self.generic_visit(node)
856
+
857
+ def visit_Pass(self, node:ast.Pass):
858
+ logger.debug("visit_Pass(%r)", node)
859
+ return self.generic_visit(node)
860
+
861
+ def visit_Constant(self, node:ast.Constant):
862
+ logger.debug("visit_Constant(%r)", node)
863
+ return self.generic_visit(node)
864
+
865
+ def visit_Module(self, node:ast.Module):
866
+ logger.debug("visit_Module(%r)", node)
867
+ return self.generic_visit(node)
868
+
869
+ def visit_Expr(self, node:ast.Expr):
870
+ logger.debug("visit_Expr(%r)", node)
871
+ return self.generic_visit(node)
872
+
873
+
830
874
  def visit_Name(self, node):
831
875
  logger.debug("visit_Name(%r)", node.id)
832
876
  self._visit_fullname(node.id, node.ctx)
@@ -884,7 +928,7 @@ class _MissingImportFinder(object):
884
928
  value = _UseChecker(name, imp, self._lineno)
885
929
  self._visit_Store(name, value)
886
930
 
887
- def _visit_Store(self, fullname, value=None):
931
+ def _visit_Store(self, fullname:str, value=None):
888
932
  logger.debug("_visit_Store(%r)", fullname)
889
933
  if fullname is None:
890
934
  return
@@ -1860,7 +1904,7 @@ def auto_import_symbol(fullname, namespaces, db=None, autoimported=None, post_im
1860
1904
  return True
1861
1905
 
1862
1906
 
1863
- def auto_import(arg, namespaces, db=None, autoimported=None, post_import_hook=None):
1907
+ def auto_import(arg, namespaces, db=None, autoimported=None, post_import_hook=None, *, extra_db=None):
1864
1908
  """
1865
1909
  Parse ``arg`` for symbols that need to be imported and automatically import
1866
1910
  them.
@@ -1914,6 +1958,8 @@ def auto_import(arg, namespaces, db=None, autoimported=None, post_import_hook=No
1914
1958
  if autoimported is None:
1915
1959
  autoimported = {}
1916
1960
  db = ImportDB.interpret_arg(db, target_filename=filename)
1961
+ if extra_db:
1962
+ db = db|extra_db
1917
1963
  ok = True
1918
1964
  for fullname in fullnames:
1919
1965
  ok &= auto_import_symbol(fullname, namespaces, db, autoimported, post_import_hook=post_import_hook)
@@ -4,10 +4,10 @@
4
4
 
5
5
 
6
6
 
7
+ from builtins import input
7
8
  import optparse
8
9
  import os
9
10
  import signal
10
- from builtins import input
11
11
  import sys
12
12
  from textwrap import dedent
13
13
  import traceback
@@ -285,7 +285,8 @@ def parse_args(
285
285
  def _default_on_error(filename):
286
286
  raise SystemExit("bad filename %s" % (filename,))
287
287
 
288
- def filename_args(args, on_error=_default_on_error):
288
+
289
+ def filename_args(args: List[str], on_error=_default_on_error):
289
290
  """
290
291
  Return list of filenames given command-line arguments.
291
292
 
@@ -293,7 +294,9 @@ def filename_args(args, on_error=_default_on_error):
293
294
  ``list`` of `Filename`
294
295
  """
295
296
  if args:
296
- return expand_py_files_from_args(args, on_error)
297
+ for a in args:
298
+ assert isinstance(a, str)
299
+ return expand_py_files_from_args([Filename(f) for f in args], on_error)
297
300
  elif not os.isatty(0):
298
301
  return [Filename.STDIN]
299
302
  else:
@@ -909,23 +909,24 @@ def enable_faulthandler():
909
909
  faulthandler.enable()
910
910
 
911
911
 
912
- def add_debug_functions_to_builtins():
913
- '''
912
+ def add_debug_functions_to_builtins(*, add_deprecated: bool):
913
+ """
914
914
  Install debugger(), etc. in the builtin global namespace.
915
- '''
915
+ """
916
916
  functions_to_add = [
917
917
  'debugger',
918
918
  'debug_on_exception',
919
919
  'print_traceback',
920
920
  ]
921
- # DEPRECATED: In the future, the following will not be added to builtins.
922
- # Use debugger() instead.
923
- functions_to_add += [
924
- 'breakpoint',
925
- 'debug_exception',
926
- 'debug_statement',
927
- 'waitpoint',
928
- ]
921
+ if add_deprecated:
922
+ # DEPRECATED: In the future, the following will not be added to builtins.
923
+ # Use debugger() instead.
924
+ functions_to_add += [
925
+ "breakpoint",
926
+ "debug_exception",
927
+ "debug_statement",
928
+ "waitpoint",
929
+ ]
929
930
  for name in functions_to_add:
930
931
  setattr(builtins, name, globals()[name])
931
932
 
@@ -0,0 +1,152 @@
1
+ """
2
+ Virtual module to create dynamic import at runtime.
3
+
4
+ It is sometime desirable to have auto import which are define only during
5
+ a session and never exist on a on-disk file.
6
+
7
+ This is injects a Dict module loader as well as a dictionary registry of in
8
+ memory module.
9
+
10
+ This is mostly use in IPython for lazy variable initialisation without having
11
+ to use proxy objects.
12
+
13
+ To use, put the following in your IPython startup files
14
+ (``~/.ipython/profile_default/startup/autoimp.py`), or in your IPython
15
+ configuration file:
16
+
17
+
18
+ .. code:: python
19
+
20
+ from pyflyby._dynimp import add_import
21
+
22
+ add_import("foo", "foo = 1")
23
+
24
+ add_import(
25
+ "df, data",
26
+ '''
27
+ import pandas as pd
28
+ data = [1,2,3]
29
+ df = pd.DataFrame(data)
30
+ ''',
31
+ )
32
+
33
+ Now at the IPython prompt, if the pyflyby extension is loaded (either because
34
+ you started using the ``py`` cli, or some configuration options like ``ipython
35
+ --TerminalIPythonApp.extra_extensions=pyflyby``. When trying to use an undefined
36
+ variable like ``foo``, ``df`` or ``data``, the corresponding module will be
37
+ executed and the relevant variable imported.
38
+
39
+
40
+ """
41
+ import importlib.abc
42
+ import importlib.util
43
+ import sys
44
+
45
+ from textwrap import dedent
46
+ from typing import FrozenSet
47
+
48
+ from pyflyby._importclns import ImportSet, Import
49
+
50
+ module_dict = {}
51
+
52
+
53
+ def add_import(names: str, code: str, *, strict: bool = True):
54
+ """
55
+ Add a runtime generated import module
56
+
57
+ Parameters
58
+ ----------
59
+ names: str
60
+ name, or comma separated list variable names that should be created by
61
+ executing and importing `code`.
62
+ code: str
63
+ potentially multiline string that will be turned into a module,
64
+ executed and from which variables listed in names can be imported.
65
+ strict: bool
66
+ Raise in case of problem loading IPython of if pyflyby extension not installed.
67
+ otherwise just ignore error
68
+
69
+
70
+
71
+ Examples
72
+ --------
73
+
74
+ >>> add_import('pd, df', '''
75
+ ... import pandas a pd
76
+ ...
77
+ ... df = pd.DataFrame([[1,2], [3,4]])
78
+ ... ''', strict=False) # don't fail doctest
79
+
80
+ """
81
+ try:
82
+ ip = _raise_if_problem()
83
+ except Exception:
84
+ if strict:
85
+ raise
86
+ else:
87
+ return
88
+ return _add_import(ip, names, code)
89
+
90
+
91
+ def _raise_if_problem():
92
+ try:
93
+ import IPython
94
+ except ModuleNotFoundError as e:
95
+ raise ImportError("Dynamic autoimport requires IPython to be installed") from e
96
+
97
+ ip = IPython.get_ipython()
98
+ if ip is None:
99
+ raise ImportError("Dynamic autoimport only work from within IPython")
100
+
101
+ if not hasattr(ip, "_auto_importer"):
102
+ raise ValueError(
103
+ "IPython needs to be loaded with pyflyby extension for lazy variable to work"
104
+ )
105
+ return ip
106
+
107
+
108
+ def _add_import(ip, names: str, code: str) -> None:
109
+ """
110
+ private version of add_import
111
+ """
112
+ assert ip is not None
113
+ mang = "pyflyby_autoimport_" + names.replace(",", "_").replace(" ", "_")
114
+ a: FrozenSet[Import] = ImportSet(f"from {mang} import {names}")._importset
115
+ b: FrozenSet[Import] = ip._auto_importer.db.known_imports._importset
116
+ s_import: FrozenSet[Import] = a | b
117
+
118
+ ip._auto_importer.db.known_imports = ImportSet._from_imports(list(s_import))
119
+ module_dict[mang] = dedent(code)
120
+
121
+ class DictLoader(importlib.abc.Loader):
122
+ """
123
+ A dict based loader for in-memory module definition.
124
+ """
125
+ def __init__(self, module_name, module_code):
126
+ self.module_name = module_name
127
+ self.module_code = module_code
128
+
129
+ def create_module(self, spec):
130
+ return None # Use default module creation semantics
131
+
132
+ def exec_module(self, module):
133
+ """
134
+ we exec module code directly in memory
135
+ """
136
+ exec(self.module_code, module.__dict__)
137
+
138
+
139
+ class DictFinder(importlib.abc.MetaPathFinder):
140
+ """
141
+ A meta path finder for abode DictLoader
142
+ """
143
+ def find_spec(self, fullname, path, target=None):
144
+ if fullname in module_dict:
145
+ module_code = module_dict[fullname]
146
+ loader = DictLoader(fullname, module_code)
147
+ return importlib.util.spec_from_loader(fullname, loader)
148
+ return None
149
+
150
+
151
+ def inject():
152
+ sys.meta_path.insert(0, DictFinder())
@@ -3,15 +3,20 @@
3
3
  # License: MIT http://opensource.org/licenses/MIT
4
4
  from __future__ import annotations
5
5
 
6
- from functools import total_ordering, cached_property
6
+ from functools import cached_property, total_ordering
7
7
  import io
8
8
  import os
9
9
  import re
10
10
  import sys
11
- from typing import Optional, Tuple, ClassVar
11
+ from typing import ClassVar, List, Optional, Tuple, Union
12
12
 
13
13
  from pyflyby._util import cmp, memoize
14
14
 
15
+ if sys.version_info < (3,10):
16
+ NoneType = type(None)
17
+ else:
18
+ from types import NoneType
19
+
15
20
 
16
21
  class UnsafeFilenameError(ValueError):
17
22
  pass
@@ -33,24 +38,27 @@ class Filename(object):
33
38
 
34
39
  def __new__(cls, arg):
35
40
  if isinstance(arg, cls):
36
- return arg
41
+ # TODO make this assert False
42
+ return cls._from_filename(arg._filename)
37
43
  if isinstance(arg, str):
38
44
  return cls._from_filename(arg)
39
45
  raise TypeError
40
46
 
41
47
  @classmethod
42
- def _from_filename(cls, filename):
48
+ def _from_filename(cls, filename: str):
43
49
  if not isinstance(filename, str):
44
50
  raise TypeError
45
- filename = str(filename)
51
+ filename = os.path.abspath(filename)
46
52
  if not filename:
47
53
  raise UnsafeFilenameError("(empty string)")
48
- if re.search("[^a-zA-Z0-9_=+{}/.,~@-]", filename):
49
- raise UnsafeFilenameError(filename)
54
+ # we only allow filename with given character set
55
+ match = re.search("[^a-zA-Z0-9_=+{}/.,~@-]", filename)
56
+ if match:
57
+ raise UnsafeFilenameError((filename, match))
50
58
  if re.search("(^|/)~", filename):
51
59
  raise UnsafeFilenameError(filename)
52
60
  self = object.__new__(cls)
53
- self._filename = os.path.abspath(filename)
61
+ self._filename = filename
54
62
  return self
55
63
 
56
64
  def __str__(self):
@@ -373,6 +381,8 @@ class FileText:
373
381
  :rtype:
374
382
  ``FileText``
375
383
  """
384
+ if isinstance(filename, str):
385
+ filename = Filename(filename)
376
386
  if isinstance(arg, cls):
377
387
  if filename is startpos is None:
378
388
  return arg
@@ -387,8 +397,8 @@ class FileText:
387
397
  else:
388
398
  raise TypeError("%s: unexpected %s"
389
399
  % (cls.__name__, type(arg).__name__))
390
- if filename is not None:
391
- filename = Filename(filename)
400
+
401
+ assert isinstance(filename, (Filename, NoneType))
392
402
  startpos = FilePos(startpos)
393
403
  self.filename = filename
394
404
  self.startpos = startpos
@@ -439,9 +449,9 @@ class FileText:
439
449
  def from_filename(cls, filename):
440
450
  return cls.from_lines(Filename(filename))
441
451
 
442
- def alter(self, filename=None, startpos=None):
452
+ def alter(self, filename: Optional[Filename] = None, startpos=None):
443
453
  if filename is not None:
444
- filename = Filename(filename)
454
+ assert isinstance(filename, Filename)
445
455
  else:
446
456
  filename = self.filename
447
457
  if startpos is not None:
@@ -652,8 +662,8 @@ class FileText:
652
662
  return h
653
663
 
654
664
 
655
- def read_file(filename):
656
- filename = Filename(filename)
665
+ def read_file(filename: Filename) -> FileText:
666
+ assert isinstance(filename, Filename)
657
667
  if filename == Filename.STDIN:
658
668
  data = sys.stdin.read()
659
669
  else:
@@ -661,14 +671,15 @@ def read_file(filename):
661
671
  data = f.read()
662
672
  return FileText(data, filename=filename)
663
673
 
664
- def write_file(filename, data):
665
- filename = Filename(filename)
674
+
675
+ def write_file(filename: Filename, data):
676
+ assert isinstance(filename, Filename)
666
677
  data = FileText(data)
667
678
  with open(str(filename), 'w') as f:
668
679
  f.write(data.joined)
669
680
 
670
- def atomic_write_file(filename, data):
671
- filename = Filename(filename)
681
+ def atomic_write_file(filename: Filename, data):
682
+ assert isinstance(filename, Filename)
672
683
  data = FileText(data)
673
684
  temp_filename = Filename("%s.tmp.%s" % (filename, os.getpid(),))
674
685
  write_file(temp_filename, data)
@@ -680,7 +691,10 @@ def atomic_write_file(filename, data):
680
691
  pass
681
692
  os.rename(str(temp_filename), str(filename))
682
693
 
683
- def expand_py_files_from_args(pathnames, on_error=lambda filename: None):
694
+
695
+ def expand_py_files_from_args(
696
+ pathnames: Union[List[Filename], Filename], on_error=lambda filename: None
697
+ ):
684
698
  """
685
699
  Enumerate ``*.py`` files, recursively.
686
700
 
@@ -698,8 +712,11 @@ def expand_py_files_from_args(pathnames, on_error=lambda filename: None):
698
712
  ``list`` of `Filename` s
699
713
  """
700
714
  if not isinstance(pathnames, (tuple, list)):
715
+ # July 2024 DeprecationWarning
716
+ # this seem to be used only internally, maybe deprecate not passing a list.
701
717
  pathnames = [pathnames]
702
- pathnames = [Filename(f) for f in pathnames]
718
+ for f in pathnames:
719
+ assert isinstance(f, Filename)
703
720
  result = []
704
721
  # Check for problematic arguments. Note that we intentionally only do
705
722
  # this for directly specified arguments, not for recursively traversed
@@ -4,7 +4,7 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
-
7
+ import sys
8
8
 
9
9
  from collections import defaultdict
10
10
  from functools import total_ordering
@@ -18,8 +18,18 @@ from pyflyby._parse import PythonBlock
18
18
  from pyflyby._util import (cached_attribute, cmp, partition,
19
19
  stable_unique)
20
20
 
21
- from typing import (ClassVar, Dict, FrozenSet, List,
22
- Sequence, Union)
21
+ from typing import (
22
+ ClassVar,
23
+ Dict,
24
+ FrozenSet,
25
+ Sequence,
26
+ Union,
27
+ )
28
+
29
+ if sys.version_info < (3, 12):
30
+ from typing_extensions import Self
31
+ else:
32
+ from typing import Self
23
33
 
24
34
 
25
35
  class NoSuchImportError(ValueError):
@@ -30,7 +40,7 @@ class ConflictingImportsError(ValueError):
30
40
  pass
31
41
 
32
42
  @total_ordering
33
- class ImportSet(object):
43
+ class ImportSet:
34
44
  r"""
35
45
  Representation of a set of imports organized into import statements.
36
46
 
@@ -49,8 +59,8 @@ class ImportSet(object):
49
59
  An ``ImportSet`` is an immutable data structure.
50
60
  """
51
61
 
52
- _EMPTY : ClassVar[ImportSet]
53
- _importset : FrozenSet[Import]
62
+ _EMPTY: ClassVar[ImportSet]
63
+ _importset: FrozenSet[Import]
54
64
 
55
65
  def __new__(cls, arg, ignore_nonimports=False, ignore_shadowed=False):
56
66
  """
@@ -80,8 +90,11 @@ class ImportSet(object):
80
90
  ignore_nonimports=ignore_nonimports,
81
91
  ignore_shadowed=ignore_shadowed)
82
92
 
93
+ def __or__(self: Self, other: Self) -> Self:
94
+ return type(self)._from_imports(list(self._importset | other._importset))
95
+
83
96
  @classmethod
84
- def _from_imports(cls, imports:List[Import], ignore_shadowed:bool=False):
97
+ def _from_imports(cls, imports: Sequence[Import], ignore_shadowed: bool = False):
85
98
  """
86
99
  :type imports:
87
100
  Sequence of `Import` s
@@ -114,7 +127,7 @@ class ImportSet(object):
114
127
  return self
115
128
 
116
129
  @classmethod
117
- def _from_args(cls, args, ignore_nonimports:bool=False, ignore_shadowed=False):
130
+ def _from_args(cls, args, ignore_nonimports:bool=False, ignore_shadowed=False) -> Self:
118
131
  """
119
132
  :type args:
120
133
  ``tuple`` or ``list`` of `ImportStatement` s, `PythonStatement` s,
@@ -133,7 +146,7 @@ class ImportSet(object):
133
146
  # more often.
134
147
  args = [a for a in args if a]
135
148
  if not args:
136
- return cls._EMPTY
149
+ return cls._EMPTY # type: ignore[return-value]
137
150
  # If we only got one ``ImportSet``, just return it.
138
151
  if len(args) == 1 and type(args[0]) is cls and not ignore_shadowed:
139
152
  return args[0]
@@ -543,9 +556,12 @@ class ImportMap(object):
543
556
  if not len(arg):
544
557
  return cls._EMPTY
545
558
  return cls._from_map(arg)
546
- else:
547
- raise TypeError("ImportMap: expected a dict, not a %s"
548
- % (type(arg).__name__,))
559
+ raise TypeError("ImportMap: expected a dict, not a %s" % (type(arg).__name__,))
560
+
561
+ def __or__(self, other):
562
+ assert isinstance(other, ImportMap)
563
+ assert set(self._data.keys()).intersection(other._data.keys()) == set(), set(self._data.keys()).intersection(other._data.keys())
564
+ return self._merge([self, other])
549
565
 
550
566
  @classmethod
551
567
  def _from_map(cls, arg):
@@ -562,8 +578,8 @@ class ImportMap(object):
562
578
  if not maps:
563
579
  return cls._EMPTY
564
580
  data = {}
565
- for map in maps:
566
- data.update(map._data)
581
+ for map_ in maps:
582
+ data.update(map_._data)
567
583
  return cls(data)
568
584
 
569
585
  def __getitem__(self, k):