Refactor pdb executable targets (#112570)

Co-authored-by: Jason R. Coombs <jaraco@jaraco.com>
This commit is contained in:
Tian Gao 2024-03-29 09:02:01 -07:00 committed by GitHub
parent 54f7e14500
commit 0fa571dbcd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -82,13 +82,12 @@ import pprint
import signal import signal
import inspect import inspect
import tokenize import tokenize
import functools
import traceback import traceback
import linecache import linecache
from contextlib import contextmanager from contextlib import contextmanager
from rlcompleter import Completer from rlcompleter import Completer
from typing import Union from types import CodeType
class Restart(Exception): class Restart(Exception):
@ -156,52 +155,58 @@ class _rstr(str):
return self return self
class _ScriptTarget(str): class _ExecutableTarget:
def __new__(cls, val): filename: str
# Mutate self to be the "real path". code: CodeType | str
res = super().__new__(cls, os.path.realpath(val)) namespace: dict
# Store the original path for error reporting.
res.orig = val
return res class _ScriptTarget(_ExecutableTarget):
def __init__(self, target):
self._target = os.path.realpath(target)
def check(self): if not os.path.exists(self._target):
if not os.path.exists(self): print(f'Error: {target} does not exist')
print('Error:', self.orig, 'does not exist')
sys.exit(1) sys.exit(1)
if os.path.isdir(self): if os.path.isdir(self._target):
print('Error:', self.orig, 'is a directory') print(f'Error: {target} is a directory')
sys.exit(1) sys.exit(1)
# If safe_path(-P) is not set, sys.path[0] is the directory # If safe_path(-P) is not set, sys.path[0] is the directory
# of pdb, and we should replace it with the directory of the script # of pdb, and we should replace it with the directory of the script
if not sys.flags.safe_path: if not sys.flags.safe_path:
sys.path[0] = os.path.dirname(self) sys.path[0] = os.path.dirname(self._target)
def __repr__(self):
return self._target
@property @property
def filename(self): def filename(self):
return self return self._target
@property
def code(self):
# Open the file each time because the file may be modified
with io.open_code(self._target) as fp:
return f"exec(compile({fp.read()!r}, {self._target!r}, 'exec'))"
@property @property
def namespace(self): def namespace(self):
return dict( return dict(
__name__='__main__', __name__='__main__',
__file__=self, __file__=self._target,
__builtins__=__builtins__, __builtins__=__builtins__,
__spec__=None, __spec__=None,
) )
@property
def code(self):
with io.open_code(self) as fp:
return f"exec(compile({fp.read()!r}, {self!r}, 'exec'))"
class _ModuleTarget(_ExecutableTarget):
def __init__(self, target):
self._target = target
class _ModuleTarget(str): import runpy
def check(self):
try: try:
self._details _, self._spec, self._code = runpy._get_module_details(self._target)
except ImportError as e: except ImportError as e:
print(f"ImportError: {e}") print(f"ImportError: {e}")
sys.exit(1) sys.exit(1)
@ -209,24 +214,16 @@ class _ModuleTarget(str):
traceback.print_exc() traceback.print_exc()
sys.exit(1) sys.exit(1)
@functools.cached_property def __repr__(self):
def _details(self): return self._target
import runpy
return runpy._get_module_details(self)
@property @property
def filename(self): def filename(self):
return self.code.co_filename return self._code.co_filename
@property @property
def code(self): def code(self):
name, spec, code = self._details return self._code
return code
@property
def _spec(self):
name, spec, code = self._details
return spec
@property @property
def namespace(self): def namespace(self):
@ -2029,7 +2026,7 @@ class Pdb(bdb.Bdb, cmd.Cmd):
return fullname return fullname
return None return None
def _run(self, target: Union[_ModuleTarget, _ScriptTarget]): def _run(self, target: _ExecutableTarget):
# When bdb sets tracing, a number of call and line events happen # When bdb sets tracing, a number of call and line events happen
# BEFORE debugger even reaches user's code (and the exact sequence of # BEFORE debugger even reaches user's code (and the exact sequence of
# events depends on python version). Take special measures to # events depends on python version). Take special measures to
@ -2281,8 +2278,6 @@ def main():
file = opts.args.pop(0) file = opts.args.pop(0)
target = _ScriptTarget(file) target = _ScriptTarget(file)
target.check()
sys.argv[:] = [file] + opts.args # Hide "pdb.py" and pdb options from argument list sys.argv[:] = [file] + opts.args # Hide "pdb.py" and pdb options from argument list
# Note on saving/restoring sys.argv: it's a good idea when sys.argv was # Note on saving/restoring sys.argv: it's a good idea when sys.argv was
@ -2306,8 +2301,8 @@ def main():
print("Uncaught exception. Entering post mortem debugging") print("Uncaught exception. Entering post mortem debugging")
print("Running 'cont' or 'step' will restart the program") print("Running 'cont' or 'step' will restart the program")
pdb.interaction(None, e) pdb.interaction(None, e)
print("Post mortem debugger finished. The " + target + print(f"Post mortem debugger finished. The {target} will "
" will be restarted") "be restarted")
if pdb._user_requested_quit: if pdb._user_requested_quit:
break break
print("The program finished and will be restarted") print("The program finished and will be restarted")