1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
|
"""
The code in this module is a backport of cPython changes in Pdb
that were introduced in Python 3.13 by gh-83151: Make closure work on pdb
https://github.com/python/cpython/pull/111094.
This file should be removed once IPython drops supports for Python 3.12.
The only changes are:
- reformatting by darker (black) formatter
- addition of type-ignore comments to satisfy mypy
Copyright (c) 2001 Python Software Foundation; All Rights Reserved
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
--------------------------------------------
1. This LICENSE AGREEMENT is between the Python Software Foundation
("PSF"), and the Individual or Organization ("Licensee") accessing and
otherwise using this software ("Python") in source or binary form and
its associated documentation.
2. Subject to the terms and conditions of this License Agreement, PSF hereby
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
analyze, test, perform and/or display publicly, prepare derivative works,
distribute, and otherwise use Python alone or in any derivative version,
provided, however, that PSF's License Agreement and PSF's notice of copyright,
i.e., "Copyright (c) 2001 Python Software Foundation; All Rights Reserved"
are retained in Python alone or in any derivative version prepared by Licensee.
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python.
4. PSF is making Python available to Licensee on an "AS IS"
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. Nothing in this License Agreement shall be deemed to create any
relationship of agency, partnership, or joint venture between PSF and
Licensee. This License Agreement does not grant permission to use PSF
trademarks or trade name in a trademark sense to endorse or promote
products or services of Licensee, or any third party.
8. By copying, installing or otherwise using Python, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
"""
import sys
import types
import codeop
import textwrap
from types import CodeType
class PdbClosureBackport:
def _exec_in_closure(self, source, globals, locals): # type: ignore[no-untyped-def]
"""Run source code in closure so code object created within source
can find variables in locals correctly
returns True if the source is executed, False otherwise
"""
# Determine if the source should be executed in closure. Only when the
# source compiled to multiple code objects, we should use this feature.
# Otherwise, we can just raise an exception and normal exec will be used.
code = compile(source, "<string>", "exec")
if not any(isinstance(const, CodeType) for const in code.co_consts):
return False
# locals could be a proxy which does not support pop
# copy it first to avoid modifying the original locals
locals_copy = dict(locals)
locals_copy["__pdb_eval__"] = {"result": None, "write_back": {}}
# If the source is an expression, we need to print its value
try:
compile(source, "<string>", "eval")
except SyntaxError:
pass
else:
source = "__pdb_eval__['result'] = " + source
# Add write-back to update the locals
source = (
"try:\n"
+ textwrap.indent(source, " ")
+ "\n"
+ "finally:\n"
+ " __pdb_eval__['write_back'] = locals()"
)
# Build a closure source code with freevars from locals like:
# def __pdb_outer():
# var = None
# def __pdb_scope(): # This is the code object we want to execute
# nonlocal var
# <source>
# return __pdb_scope.__code__
source_with_closure = (
"def __pdb_outer():\n"
+ "\n".join(f" {var} = None" for var in locals_copy)
+ "\n"
+ " def __pdb_scope():\n"
+ "\n".join(f" nonlocal {var}" for var in locals_copy)
+ "\n"
+ textwrap.indent(source, " ")
+ "\n"
+ " return __pdb_scope.__code__"
)
# Get the code object of __pdb_scope()
# The exec fills locals_copy with the __pdb_outer() function and we can call
# that to get the code object of __pdb_scope()
ns = {}
try:
exec(source_with_closure, {}, ns)
except Exception:
return False
code = ns["__pdb_outer"]()
cells = tuple(types.CellType(locals_copy.get(var)) for var in code.co_freevars)
try:
exec(code, globals, locals_copy, closure=cells)
except Exception:
return False
# get the data we need from the statement
pdb_eval = locals_copy["__pdb_eval__"]
# __pdb_eval__ should not be updated back to locals
pdb_eval["write_back"].pop("__pdb_eval__")
# Write all local variables back to locals
locals.update(pdb_eval["write_back"])
eval_result = pdb_eval["result"]
if eval_result is not None:
print(repr(eval_result))
return True
def default(self, line): # type: ignore[no-untyped-def]
if line[:1] == "!":
line = line[1:].strip()
locals = self.curframe_locals
globals = self.curframe.f_globals
try:
buffer = line
if (
code := codeop.compile_command(line + "\n", "<stdin>", "single")
) is None:
# Multi-line mode
with self._disable_command_completion():
buffer = line
continue_prompt = "... "
while (
code := codeop.compile_command(buffer, "<stdin>", "single")
) is None:
if self.use_rawinput:
try:
line = input(continue_prompt)
except (EOFError, KeyboardInterrupt):
self.lastcmd = ""
print("\n")
return
else:
self.stdout.write(continue_prompt)
self.stdout.flush()
line = self.stdin.readline()
if not len(line):
self.lastcmd = ""
self.stdout.write("\n")
self.stdout.flush()
return
else:
line = line.rstrip("\r\n")
buffer += "\n" + line
save_stdout = sys.stdout
save_stdin = sys.stdin
save_displayhook = sys.displayhook
try:
sys.stdin = self.stdin
sys.stdout = self.stdout
sys.displayhook = self.displayhook
if not self._exec_in_closure(buffer, globals, locals):
exec(code, globals, locals)
finally:
sys.stdout = save_stdout
sys.stdin = save_stdin
sys.displayhook = save_displayhook
except:
self._error_exc()
|