pyflyby 1.9.9__tar.gz → 1.9.11__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 (161) hide show
  1. {pyflyby-1.9.9/lib/python/pyflyby.egg-info → pyflyby-1.9.11}/PKG-INFO +1 -1
  2. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/__init__.py +2 -0
  3. {pyflyby-1.9.9/lib/python → pyflyby-1.9.11/bin}/pyflyby/_dbg.py +21 -1
  4. {pyflyby-1.9.9/lib/python → pyflyby-1.9.11/bin}/pyflyby/_dynimp.py +3 -2
  5. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/_interactive.py +0 -5
  6. pyflyby-1.9.11/bin/pyflyby/_saveframe_reader.py +471 -0
  7. {pyflyby-1.9.9/lib/python → pyflyby-1.9.11/bin}/pyflyby/_version.py +1 -1
  8. {pyflyby-1.9.9 → pyflyby-1.9.11}/etc/pyflyby/common.py +1 -1
  9. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/__init__.py +2 -0
  10. {pyflyby-1.9.9/bin → pyflyby-1.9.11/lib/python}/pyflyby/_dbg.py +21 -1
  11. {pyflyby-1.9.9/bin → pyflyby-1.9.11/lib/python}/pyflyby/_dynimp.py +3 -2
  12. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/_interactive.py +0 -5
  13. pyflyby-1.9.11/lib/python/pyflyby/_saveframe_reader.py +471 -0
  14. {pyflyby-1.9.9/bin → pyflyby-1.9.11/lib/python}/pyflyby/_version.py +1 -1
  15. {pyflyby-1.9.9 → pyflyby-1.9.11/lib/python/pyflyby.egg-info}/PKG-INFO +1 -1
  16. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby.egg-info/SOURCES.txt +3 -0
  17. pyflyby-1.9.11/tests/test_saveframe_reader.py +780 -0
  18. {pyflyby-1.9.9 → pyflyby-1.9.11}/.pyflyby +0 -0
  19. {pyflyby-1.9.9 → pyflyby-1.9.11}/LICENSE.txt +0 -0
  20. {pyflyby-1.9.9 → pyflyby-1.9.11}/MANIFEST.in +0 -0
  21. {pyflyby-1.9.9 → pyflyby-1.9.11}/README.rst +0 -0
  22. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/autoipython +0 -0
  23. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/autopython +0 -0
  24. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/collect-exports +0 -0
  25. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/collect-imports +0 -0
  26. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/create-imports +0 -0
  27. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/find-import +0 -0
  28. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/list-bad-xrefs +0 -0
  29. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/prune-broken-imports +0 -0
  30. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/py +0 -0
  31. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/__main__.py +0 -0
  32. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/_autoimp.py +0 -0
  33. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/_cmdline.py +0 -0
  34. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/_comms.py +0 -0
  35. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/_docxref.py +0 -0
  36. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/_file.py +0 -0
  37. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/_flags.py +0 -0
  38. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/_format.py +0 -0
  39. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/_idents.py +0 -0
  40. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/_import_sorting.py +0 -0
  41. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/_importclns.py +0 -0
  42. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/_importdb.py +0 -0
  43. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/_imports2s.py +0 -0
  44. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/_importstmt.py +0 -0
  45. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/_livepatch.py +0 -0
  46. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/_log.py +0 -0
  47. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/_modules.py +0 -0
  48. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/_parse.py +0 -0
  49. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/_py.py +0 -0
  50. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/_saveframe.py +0 -0
  51. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/_util.py +0 -0
  52. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/autoimport.py +0 -0
  53. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby/importdb.py +0 -0
  54. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/pyflyby-diff +0 -0
  55. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/reformat-imports +0 -0
  56. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/replace-star-imports +0 -0
  57. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/saveframe +0 -0
  58. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/tidy-imports +0 -0
  59. {pyflyby-1.9.9 → pyflyby-1.9.11}/bin/transform-imports +0 -0
  60. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/LICENSE.txt +0 -0
  61. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/Makefile +0 -0
  62. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/TODO.txt +0 -0
  63. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/__init__.py +0 -0
  64. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/api/api.rst +0 -0
  65. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/api/autoimp.rst +0 -0
  66. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/api/cmdline.rst +0 -0
  67. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/api/comms.rst +0 -0
  68. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/api/dbg.rst +0 -0
  69. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/api/file.rst +0 -0
  70. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/api/flags.rst +0 -0
  71. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/api/format.rst +0 -0
  72. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/api/idents.rst +0 -0
  73. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/api/importclns.rst +0 -0
  74. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/api/importdb.rst +0 -0
  75. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/api/imports2s.rst +0 -0
  76. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/api/importstmt.rst +0 -0
  77. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/api/interactive.rst +0 -0
  78. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/api/livepatch.rst +0 -0
  79. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/api/log.rst +0 -0
  80. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/api/modules.rst +0 -0
  81. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/api/parse.rst +0 -0
  82. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/api/py.rst +0 -0
  83. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/api/util.rst +0 -0
  84. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/cli/autoipython.rst +0 -0
  85. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/cli/cli.rst +0 -0
  86. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/cli/collect_exports.rst +0 -0
  87. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/cli/collect_imports.rst +0 -0
  88. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/cli/find_import.rst +0 -0
  89. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/cli/prune_broken_imports.rst +0 -0
  90. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/cli/py.rst +0 -0
  91. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/cli/pyflyby_diff.rst +0 -0
  92. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/cli/reformat_imports.rst +0 -0
  93. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/cli/replace_star_imports.rst +0 -0
  94. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/cli/tidy_imports.rst +0 -0
  95. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/cli/transform_imports.rst +0 -0
  96. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/conf.py +0 -0
  97. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/index.rst +0 -0
  98. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/make.bat +0 -0
  99. {pyflyby-1.9.9 → pyflyby-1.9.11}/doc/testing.txt +0 -0
  100. {pyflyby-1.9.9 → pyflyby-1.9.11}/etc/pyflyby/canonical.py +0 -0
  101. {pyflyby-1.9.9 → pyflyby-1.9.11}/etc/pyflyby/forget.py +0 -0
  102. {pyflyby-1.9.9 → pyflyby-1.9.11}/etc/pyflyby/mandatory.py +0 -0
  103. {pyflyby-1.9.9 → pyflyby-1.9.11}/etc/pyflyby/numpy.py +0 -0
  104. {pyflyby-1.9.9 → pyflyby-1.9.11}/etc/pyflyby/std.py +0 -0
  105. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/emacs/pyflyby.el +0 -0
  106. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/__main__.py +0 -0
  107. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/_autoimp.py +0 -0
  108. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/_cmdline.py +0 -0
  109. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/_comms.py +0 -0
  110. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/_docxref.py +0 -0
  111. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/_file.py +0 -0
  112. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/_flags.py +0 -0
  113. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/_format.py +0 -0
  114. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/_idents.py +0 -0
  115. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/_import_sorting.py +0 -0
  116. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/_importclns.py +0 -0
  117. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/_importdb.py +0 -0
  118. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/_imports2s.py +0 -0
  119. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/_importstmt.py +0 -0
  120. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/_livepatch.py +0 -0
  121. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/_log.py +0 -0
  122. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/_modules.py +0 -0
  123. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/_parse.py +0 -0
  124. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/_py.py +0 -0
  125. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/_saveframe.py +0 -0
  126. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/_util.py +0 -0
  127. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/autoimport.py +0 -0
  128. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby/importdb.py +0 -0
  129. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby.egg-info/dependency_links.txt +0 -0
  130. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby.egg-info/entry_points.txt +0 -0
  131. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby.egg-info/requires.txt +0 -0
  132. {pyflyby-1.9.9 → pyflyby-1.9.11}/lib/python/pyflyby.egg-info/top_level.txt +0 -0
  133. {pyflyby-1.9.9 → pyflyby-1.9.11}/libexec/pyflyby/colordiff +0 -0
  134. {pyflyby-1.9.9 → pyflyby-1.9.11}/libexec/pyflyby/diff-colorize +0 -0
  135. {pyflyby-1.9.9 → pyflyby-1.9.11}/pyproject.toml +0 -0
  136. {pyflyby-1.9.9 → pyflyby-1.9.11}/setup.cfg +0 -0
  137. {pyflyby-1.9.9 → pyflyby-1.9.11}/setup.py +0 -0
  138. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/__init__.py +0 -0
  139. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/test_0testconfig.py +0 -0
  140. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/test_autoimp.py +0 -0
  141. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/test_cmdline.py +0 -0
  142. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/test_docxref.py +0 -0
  143. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/test_file.py +0 -0
  144. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/test_flags.py +0 -0
  145. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/test_format.py +0 -0
  146. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/test_idents.py +0 -0
  147. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/test_importclns.py +0 -0
  148. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/test_importdb.py +0 -0
  149. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/test_imports2s.py +0 -0
  150. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/test_importstmt.py +0 -0
  151. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/test_interactive.py +0 -0
  152. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/test_jupyterlab_pyflyby.py +0 -0
  153. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/test_livepatch.py +0 -0
  154. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/test_modules.py +0 -0
  155. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/test_parse.py +0 -0
  156. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/test_py.py +0 -0
  157. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/test_saveframe.py +0 -0
  158. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/test_util.py +0 -0
  159. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/tests_sorts.py +0 -0
  160. {pyflyby-1.9.9 → pyflyby-1.9.11}/tests/xrefs.py +0 -0
  161. {pyflyby-1.9.9 → pyflyby-1.9.11}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyflyby
3
- Version: 1.9.9
3
+ Version: 1.9.11
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
@@ -33,6 +33,8 @@ from pyflyby._livepatch import livepatch, xreload
33
33
  from pyflyby._log import logger
34
34
  from pyflyby._parse import PythonBlock, PythonStatement
35
35
  from pyflyby._saveframe import saveframe
36
+ from pyflyby._saveframe_reader \
37
+ import SaveframeReader
36
38
  from pyflyby._version import __version__
37
39
 
38
40
  # Deprecated:
@@ -332,7 +332,27 @@ def _debug_exception(*exc_info, **kwargs):
332
332
  # keep the process waiting for debugger to attach.
333
333
  pdb.postloop = _prompt_continue_waiting_for_debugger
334
334
  print_verbose_tb(*exc_info)
335
- 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)
336
356
 
337
357
 
338
358
  def _debug_code(arg, globals=None, locals=None, auto_import=True, tty="/dev/tty"):
@@ -111,8 +111,9 @@ def _add_import(ip, names: str, code: str) -> None:
111
111
  private version of add_import
112
112
  """
113
113
  assert ip is not None
114
- mang = PYFLYBY_LAZY_LOAD_PREFIX + names.replace(",", "_").replace(" ", "_")
115
- a: FrozenSet[Import] = ImportSet(f"{mang} import {names}")._importset
114
+ module = PYFLYBY_LAZY_LOAD_PREFIX.split()[1]
115
+ mang = module + names.replace(",", "_").replace(" ", "_")
116
+ a: FrozenSet[Import] = ImportSet(f"from {mang} import {names}")._importset
116
117
  b: FrozenSet[Import] = ip._auto_importer.db.known_imports._importset
117
118
  s_import: FrozenSet[Import] = a | b
118
119
 
@@ -1156,11 +1156,6 @@ def UpdateIPythonStdioCtx():
1156
1156
  module = sys.modules["IPython.utils.io"]
1157
1157
  container = module
1158
1158
  IOStream = module.IOStream
1159
- elif "IPython.genutils" in sys.modules:
1160
- # Tested with IPython 0.10.
1161
- module = sys.modules["IPython.genutils"]
1162
- container = module.Term
1163
- IOStream = module.IOStream
1164
1159
  else:
1165
1160
  # IPython version too old or too new?
1166
1161
  # For now just silently do nothing.
@@ -0,0 +1,471 @@
1
+ """
2
+ pyflyby/_saveframe_reader.py
3
+
4
+ This module provides the ``SaveframeReader`` class, which is used to read data
5
+ saved by the ``saveframe`` utility.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ import pickle
12
+
13
+ from pyflyby._saveframe import ExceptionInfo, FrameMetadata
14
+
15
+ class SaveframeReader:
16
+ """
17
+ A class for reading data saved by the ``saveframe`` utility.
18
+
19
+ The ``saveframe`` utility saves data as a pickled Python dictionary.
20
+ Reading this raw data and extracting values of specific variables or metadata
21
+ fields can be complex.
22
+
23
+ The ``SaveframeReader`` class provides an easy and efficient way to read this
24
+ raw data and extract specific items. This class has a user-friendly ``repr``
25
+ for visualizing the data and provides various helpful methods to extract
26
+ different items.
27
+
28
+ **Usage Example:**
29
+
30
+ **Creating an instance**
31
+
32
+ First, create an instance of this class by passing the path of the file that
33
+ contains the ``saveframe`` data.
34
+
35
+ ::
36
+
37
+ >> from pyflyby import SaveframeReader
38
+ >> reader = SaveframeReader('/path/to/file')
39
+
40
+ **Extracting all available metadata fields**
41
+
42
+ To extract all available metadata fields, use the ``SaveframeReader.metadata``
43
+ property. Example:
44
+
45
+ ::
46
+
47
+ >> reader.metadata
48
+ ['frame_index', 'filename', 'lineno', 'function_name', 'function_qualname',
49
+ 'function_object', 'module_name', 'code', 'frame_identifier',
50
+ 'exception_string', 'exception_full_string', 'exception_class_name',
51
+ 'exception_class_qualname', 'exception_object', 'traceback']
52
+
53
+ **Extracting all stored local variables**
54
+
55
+ To extract the names of all local variables stored in the frames, use the
56
+ ``SaveframeReader.variables`` property. Example:
57
+
58
+ ::
59
+
60
+ >> reader.variables
61
+ {
62
+ 1: ['var1', 'var2', ...],
63
+ 2: ['var5', 'var8', 'var9', ...],
64
+ ...
65
+ }
66
+
67
+ **Extracting the value of a specific metadata field**
68
+
69
+ To extract the value of a specific metadata field, use the
70
+ `SaveframeReader.get_metadata` method. Example:
71
+
72
+ ::
73
+
74
+ >> reader.get_metadata("filename")
75
+ {1: '/dir1/mod1.py', 2: '/dir2/mod2.py', ...}
76
+
77
+ >> reader.get_metadata("filename", frame_idx=2)
78
+ '/dir2/mod2.py'
79
+
80
+ >> reader.get_metadata("exception_string")
81
+ "Error is raised"
82
+
83
+ **Extracting the value of specific local variables**
84
+
85
+ To extract the value of specific local variable(s), use the
86
+ `SaveframeReader.get_variables` method. Example:
87
+
88
+ ::
89
+
90
+ >> reader.get_variables('var1')
91
+ {2: var1_value2, 4: var1_value4}
92
+
93
+ >> reader.get_variables('var1', frame_idx=4)
94
+ var1_value4
95
+
96
+ >> reader.get_variables('var2')
97
+ var2_value3
98
+
99
+ >> reader.get_variables(['var1', 'var3'])
100
+ {2: {'var1': var1_value2, 'var3': var3_value2},
101
+ 4: {'var1': var1_value4}, 5: {'var3': var3_value5}}
102
+
103
+ >> reader.get_variables(['var1', 'var3'], frame_idx=2)
104
+ {'var1': var1_value2, 'var3': var3_value2}
105
+
106
+ Raw data can be extracted using ``SaveframeReader.data`` property.
107
+ """
108
+
109
+ def __init__(self, filename):
110
+ """
111
+ Initializes the ``SaveframeReader`` class.
112
+
113
+ :param filename:
114
+ The file path where the ``saveframe`` data is stored.
115
+ """
116
+ self._filename = filename
117
+ with open(filename, 'rb') as f:
118
+ self._data = pickle.load(f)
119
+ if not isinstance(self._data, dict):
120
+ raise ValueError(
121
+ f"The data in the file '{filename}' is of type "
122
+ f"'{type(self._data).__name__}', which is not valid saveframe "
123
+ "data.")
124
+
125
+
126
+ @property
127
+ def filename(self):
128
+ """
129
+ The file path where the ``saveframe`` data is stored.
130
+ """
131
+ return self._filename
132
+
133
+
134
+ @property
135
+ def data(self):
136
+ """
137
+ Returns the raw ``saveframe`` data as a Python dictionary.
138
+ """
139
+ return self._data
140
+
141
+
142
+ @property
143
+ def metadata(self):
144
+ """
145
+ Returns a list of all metadata items present in the data.
146
+
147
+ This includes both frame metadata and exception metadata. The returned
148
+ list contains the names of all metadata fields. For example:
149
+ ['frame_index', 'filename', ..., 'exception_object', 'traceback'].
150
+
151
+ To obtain the value of a specific metadata field, use the
152
+ `SaveframeReader.get_metadata` method.
153
+ """
154
+ metadata = []
155
+ metadata.extend([field for field in FrameMetadata.__dataclass_fields__])
156
+ metadata.extend([field for field in ExceptionInfo.__dataclass_fields__])
157
+ return metadata
158
+
159
+
160
+ @property
161
+ def variables(self):
162
+ """
163
+ Returns the local variables present in each frame.
164
+
165
+ The returned value is a dictionary where the keys are frame indices and
166
+ the values are lists of local variable names in those frames. For example:
167
+
168
+ ::
169
+
170
+ {
171
+ 1: ['variable1', 'variable2', ...],
172
+ 2: ['variable5', 'variable6', 'variable8'],
173
+ ...
174
+ }
175
+
176
+ To obtain the value of specific variable(s), use the
177
+ `SaveframeReader.get_variables` method.
178
+ """
179
+ frame_idx_to_variables_map = {}
180
+ for key_item in self._data:
181
+ if not isinstance(key_item, int):
182
+ continue
183
+ frame_idx_to_variables_map[key_item] = list(
184
+ self._data[key_item]['variables'].keys())
185
+ return frame_idx_to_variables_map
186
+
187
+
188
+ def get_metadata(self, metadata, *, frame_idx=None):
189
+ """
190
+ Retrieve the value of a specific metadata field.
191
+
192
+ **Example usage:**
193
+
194
+ ::
195
+
196
+ >> reader = SaveframeReader("/path/to/file")
197
+
198
+ >> reader.get_metadata("filename")
199
+ {1: '/dir1/mod1.py', 2: '/dir2/mod2.py', ...}
200
+
201
+ >> reader.get_metadata("filename", frame_idx=2)
202
+ '/dir2/mod2.py'
203
+
204
+ >> reader.get_metadata("exception_string")
205
+ "Error is raised"
206
+
207
+ :param metadata:
208
+ The metadata field for which to get the value.
209
+ :param frame_idx:
210
+ The index of the frame from which to get the metadata value. Default is
211
+ None, which means metadata from all frames is returned. This parameter
212
+ is only supported for frame metadata, not exception metadata.
213
+ :return:
214
+ - If ``frame_idx`` is None (default):
215
+ - If ``metadata`` is a frame metadata field, a dictionary is returned
216
+ with the frame index as the key and the metadata value as the value.
217
+ - If ``metadata`` is an exception metadata field, the value of the
218
+ metadata is returned.
219
+ - If ``frame_idx`` is specified:
220
+ - If ``metadata`` is a frame metadata field, the metadata value for
221
+ the specified frame is returned.
222
+ - If ``metadata`` is an exception metadata field, an error is raised.
223
+ """
224
+ # Sanity checks.
225
+ all_metadata_entries = self.metadata
226
+ if metadata not in all_metadata_entries:
227
+ raise ValueError(
228
+ f"Invalid metadata requested: {metadata!a}. Allowed metadata "
229
+ f"entries are: {all_metadata_entries}.")
230
+ exception_metadata = ([field for field in ExceptionInfo.__dataclass_fields__])
231
+ # Handle exception metadata.
232
+ if metadata in exception_metadata:
233
+ if frame_idx:
234
+ raise ValueError(
235
+ "'frame_idx' is not supported for querying exception "
236
+ f"metadata: {metadata!a}.")
237
+ return self._data[metadata]
238
+ # frame_idx is not passed.
239
+ if frame_idx is None:
240
+ frame_idx_to_metadata_value_map = {}
241
+ for key_item in self._data:
242
+ if key_item in exception_metadata:
243
+ continue
244
+ metadata_value = self._data[key_item][metadata]
245
+ # Unpickle the 'function_object' metadata value.
246
+ if metadata == "function_object":
247
+ try:
248
+ if not isinstance(metadata_value, str):
249
+ metadata_value = pickle.loads(metadata_value)
250
+ except Exception as err:
251
+ logging.warning("Can't unpickle the 'function_object' "
252
+ "value for frame: %a. Error: %s",
253
+ key_item, err)
254
+ metadata_value = (
255
+ f"Can't unpickle the 'function_object'. Error: {err}")
256
+ frame_idx_to_metadata_value_map[key_item] = metadata_value
257
+ return frame_idx_to_metadata_value_map
258
+
259
+ # frame_idx is passed.
260
+ if not isinstance(frame_idx, int):
261
+ raise TypeError(
262
+ "'frame_idx' must be of type 'int', not "
263
+ f"'{type(frame_idx).__name__}'.")
264
+ try:
265
+ metadata_value = self._data[frame_idx][metadata]
266
+ if metadata == "function_object":
267
+ try:
268
+ if not isinstance(metadata_value, str):
269
+ metadata_value = pickle.loads(metadata_value)
270
+ except Exception as err:
271
+ logging.warning("Can't unpickle the 'function_object' "
272
+ "value for frame: %a. Error: %s",
273
+ frame_idx, err)
274
+ return metadata_value
275
+ except KeyError:
276
+ allowed_frame_idx = list(
277
+ set(self._data.keys()) - set(exception_metadata))
278
+ raise ValueError(
279
+ f"Invalid value for 'frame_idx': '{frame_idx}'. Allowed values "
280
+ f"are: {allowed_frame_idx}.")
281
+
282
+
283
+ def get_variables(self, variables, *, frame_idx=None):
284
+ """
285
+ Retrieve the value of local variable(s) from specific frames.
286
+
287
+ **Example usage:**
288
+
289
+ ::
290
+
291
+ >> reader = SaveframeReader('/path/to/file')
292
+
293
+ >> reader.get_variables('var1')
294
+ {2: var1_value2, 4: var1_value4}
295
+
296
+ >> reader.get_variables('var1', frame_idx=4)
297
+ var1_value4
298
+
299
+ >> reader.get_variables(('var1',), frame_idx=4)
300
+ {'var1': var1_value4}
301
+
302
+ >> reader.get_variables('var2')
303
+ var2_value3 # 'var2' is only present in frame 3
304
+
305
+ >> reader.get_variables(['var1', 'var3'])
306
+ {2: {'var1': var1_value2, 'var3': var3_value2},
307
+ 4: {'var1': var1_value4}, 5: {'var3': var3_value5}}
308
+
309
+ >> reader.get_variables(['var1', 'var3'], frame_idx=2)
310
+ {'var1': var1_value2, 'var3': var3_value2}
311
+
312
+ :param variables:
313
+ One or more variable names for which to retrieve the values. You can
314
+ pass a single variable name as a string or a list / tuple of variable
315
+ names.
316
+
317
+ :param frame_idx:
318
+ The index of the frame from which to retrieve the value(s) of the
319
+ variable(s). Default is None, which means values from all frames are
320
+ returned.
321
+ :return:
322
+ - If ``frame_idx`` is None (default):
323
+ - For a single variable:
324
+ - A dictionary with frame indices as keys and variable values
325
+ as values.
326
+ - If the variable is present in only one frame, the value is
327
+ returned directly.
328
+ - For a list / tuple of variables:
329
+ - A dictionary with frame indices as keys and dictionaries as
330
+ values, where each inner dictionary contains the queried
331
+ variables and their values for that frame.
332
+ - If the queried variables are present in only one frame, a
333
+ dictionary of those variables and their values is returned.
334
+ - If ``frame_idx`` is specified:
335
+ - For a single variable:
336
+ - The value of the variable in the specified frame.
337
+ - If the variable is not present in that frame, an error is raised.
338
+ - For a list / tuple of variables:
339
+ - A dictionary with the variable names as keys and their values
340
+ as values, for the specified frame.
341
+ - If none of the queried variables are present in that frame,
342
+ an error is raised.
343
+ """
344
+ # Boolean to denote if variables are passed as a list or tuple.
345
+ variables_passed_as_list_or_tuple = False
346
+ # Sanity checks.
347
+ if isinstance(variables, (list, tuple)):
348
+ variables_passed_as_list_or_tuple = True
349
+ for variable in variables:
350
+ if not isinstance(variable, str):
351
+ raise TypeError(
352
+ f"Invalid type for variable name: {type(variable).__name__}. "
353
+ "Expected string type instead.")
354
+ elif isinstance(variables, str):
355
+ variables = (variables,)
356
+ else:
357
+ raise TypeError(
358
+ f"'variables' must either be a string or a list/tuple. "
359
+ f"Got '{type(variables).__name__}'.")
360
+ if len(variables) == 0:
361
+ raise ValueError("No 'variables' passed.")
362
+
363
+ def _get_variable_value_on_unpickle_error(err):
364
+ """
365
+ Get variable's value when it fails to unpickle due to error ``err``.
366
+ """
367
+ return f"Can't un-pickle the variable. Error: {err}"
368
+
369
+ # frame_idx is not passed.
370
+ if frame_idx is None:
371
+ frame_idx_to_variables_map = {}
372
+ for key_item in self._data:
373
+ if not isinstance(key_item, int):
374
+ continue
375
+ variables_map = self._data[key_item]['variables']
376
+ for variable in variables:
377
+ try:
378
+ variable_value = variables_map[variable]
379
+ except KeyError:
380
+ continue
381
+ try:
382
+ variable_value = pickle.loads(variable_value)
383
+ except Exception as err:
384
+ logging.warning(
385
+ "Can't un-pickle the value of variable %a for frame "
386
+ "%a. Error: %s", variable, key_item, err)
387
+ variable_value = _get_variable_value_on_unpickle_error(err)
388
+ if len(variables) == 1 and not variables_passed_as_list_or_tuple:
389
+ # Single variable is queried.
390
+ frame_idx_to_variables_map[key_item] = variable_value
391
+ else:
392
+ # Multiple variables are queried. The result would be
393
+ # a dict where keys would be the frame indices and values
394
+ # would the dicts containing the queried variables and
395
+ # their values for that frame.
396
+ if not key_item in frame_idx_to_variables_map:
397
+ frame_idx_to_variables_map[key_item] = {}
398
+ frame_idx_to_variables_map[key_item][variable] = variable_value
399
+ if not frame_idx_to_variables_map:
400
+ raise ValueError(f"Local variable(s) {variables} not found in "
401
+ "any of the saved frames.")
402
+ # If there is only 1 frame in the result, return the value directly.
403
+ if len(frame_idx_to_variables_map) == 1:
404
+ return frame_idx_to_variables_map.popitem()[1]
405
+ return frame_idx_to_variables_map
406
+
407
+ # frame_idx is passed.
408
+ if not isinstance(frame_idx, int):
409
+ raise TypeError(
410
+ "'frame_idx' must be of type 'int', not "
411
+ f"'{type(frame_idx).__name__}'.")
412
+ try:
413
+ variables_map = self._data[frame_idx]['variables']
414
+ except KeyError:
415
+ allowed_frame_idx = list(
416
+ set(self._data.keys()) - set(self.metadata))
417
+ raise ValueError(
418
+ f"Invalid value for 'frame_idx': '{frame_idx}'. Allowed values "
419
+ f"are: {allowed_frame_idx}.")
420
+ variable_key_to_value_map = {}
421
+ for variable in variables:
422
+ try:
423
+ variable_value = variables_map[variable]
424
+ except KeyError:
425
+ continue
426
+ try:
427
+ variable_value = pickle.loads(variable_value)
428
+ except Exception as err:
429
+ logging.warning(
430
+ "Can't un-pickle the value of variable %a for frame "
431
+ "%a. Error: %s", variable, frame_idx, err)
432
+ if len(variables) > 1:
433
+ variable_value = _get_variable_value_on_unpickle_error(err)
434
+ if len(variables) == 1 and not variables_passed_as_list_or_tuple:
435
+ # Single variable is queried. Directly return the value.
436
+ return variable_value
437
+ variable_key_to_value_map[variable] = variable_value
438
+ if not variable_key_to_value_map:
439
+ raise ValueError(f"Local variable(s) {variables} not found in "
440
+ f"frame {frame_idx}")
441
+ return variable_key_to_value_map
442
+
443
+
444
+ def __str__(self):
445
+ frames_info = []
446
+ for frame_idx, frame_data in self._data.items():
447
+ if isinstance(frame_idx, int):
448
+ frame_info = (
449
+ f"Frame {frame_idx}:\n"
450
+ f" Filename: '{frame_data.get('filename')}'\n"
451
+ f" Line Number: {frame_data.get('lineno')}\n"
452
+ f" Function: {frame_data.get('function_qualname')}\n"
453
+ f" Module: {frame_data.get('module_name')}\n"
454
+ f" Frame ID: '{frame_data.get('frame_identifier')}'\n"
455
+ f" Code: {frame_data.get('code')}\n"
456
+ f" Variables: {list(frame_data.get('variables', {}).keys())}\n"
457
+ )
458
+ frames_info.append(frame_info)
459
+
460
+ exception_info = (
461
+ f"Exception:\n"
462
+ f" Full String: {self._data.get('exception_full_string')}\n"
463
+ f" String: {self._data.get('exception_string')}\n"
464
+ f" Class Name: {self._data.get('exception_class_name')}\n"
465
+ f" Qualified Name: {self._data.get('exception_class_qualname')}\n"
466
+ )
467
+
468
+ return "Frames:\n" + "\n".join(frames_info) + "\n" + exception_info
469
+
470
+ def __repr__(self):
471
+ return f"{self.__class__.__name__}(\nfilename: {self._filename!a} \n\n{str(self)})"
@@ -4,4 +4,4 @@
4
4
  # http://creativecommons.org/publicdomain/zero/1.0/
5
5
 
6
6
 
7
- __version__ = "1.9.9"
7
+ __version__ = "1.9.11"
@@ -11,7 +11,7 @@ import perl
11
11
  import pexpect
12
12
  import pstats
13
13
  import pyflyby
14
- from pyflyby import saveframe, xreload
14
+ from pyflyby import SaveframeReader, saveframe, xreload
15
15
  import pylab
16
16
  import pyodbc
17
17
  import pysvn
@@ -33,6 +33,8 @@ from pyflyby._livepatch import livepatch, xreload
33
33
  from pyflyby._log import logger
34
34
  from pyflyby._parse import PythonBlock, PythonStatement
35
35
  from pyflyby._saveframe import saveframe
36
+ from pyflyby._saveframe_reader \
37
+ import SaveframeReader
36
38
  from pyflyby._version import __version__
37
39
 
38
40
  # Deprecated:
@@ -332,7 +332,27 @@ def _debug_exception(*exc_info, **kwargs):
332
332
  # keep the process waiting for debugger to attach.
333
333
  pdb.postloop = _prompt_continue_waiting_for_debugger
334
334
  print_verbose_tb(*exc_info)
335
- 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)
336
356
 
337
357
 
338
358
  def _debug_code(arg, globals=None, locals=None, auto_import=True, tty="/dev/tty"):
@@ -111,8 +111,9 @@ def _add_import(ip, names: str, code: str) -> None:
111
111
  private version of add_import
112
112
  """
113
113
  assert ip is not None
114
- mang = PYFLYBY_LAZY_LOAD_PREFIX + names.replace(",", "_").replace(" ", "_")
115
- a: FrozenSet[Import] = ImportSet(f"{mang} import {names}")._importset
114
+ module = PYFLYBY_LAZY_LOAD_PREFIX.split()[1]
115
+ mang = module + names.replace(",", "_").replace(" ", "_")
116
+ a: FrozenSet[Import] = ImportSet(f"from {mang} import {names}")._importset
116
117
  b: FrozenSet[Import] = ip._auto_importer.db.known_imports._importset
117
118
  s_import: FrozenSet[Import] = a | b
118
119
 
@@ -1156,11 +1156,6 @@ def UpdateIPythonStdioCtx():
1156
1156
  module = sys.modules["IPython.utils.io"]
1157
1157
  container = module
1158
1158
  IOStream = module.IOStream
1159
- elif "IPython.genutils" in sys.modules:
1160
- # Tested with IPython 0.10.
1161
- module = sys.modules["IPython.genutils"]
1162
- container = module.Term
1163
- IOStream = module.IOStream
1164
1159
  else:
1165
1160
  # IPython version too old or too new?
1166
1161
  # For now just silently do nothing.