8080import signal
8181import inspect
8282import tokenize
83+ import functools
8384import traceback
8485import linecache
8586
87+ from typing import Union
88+
8689
8790class Restart (Exception ):
8891 """Causes a debugger to be restarted for the debugged python program."""
@@ -128,6 +131,77 @@ def __repr__(self):
128131 return self
129132
130133
134+ class ScriptTarget (str ):
135+ def __new__ (cls , val ):
136+ # Mutate self to be the "real path".
137+ res = super ().__new__ (cls , os .path .realpath (val ))
138+
139+ # Store the original path for error reporting.
140+ res .orig = val
141+
142+ return res
143+
144+ def check (self ):
145+ if not os .path .exists (self ):
146+ print ('Error:' , self .orig , 'does not exist' )
147+ sys .exit (1 )
148+
149+ # Replace pdb's dir with script's dir in front of module search path.
150+ sys .path [0 ] = os .path .dirname (self )
151+
152+ @property
153+ def filename (self ):
154+ return self
155+
156+ @property
157+ def namespace (self ):
158+ return dict (
159+ __name__ = '__main__' ,
160+ __file__ = self ,
161+ __builtins__ = __builtins__ ,
162+ )
163+
164+ @property
165+ def code (self ):
166+ with io .open (self ) as fp :
167+ return f"exec(compile({ fp .read ()!r} , { self !r} , 'exec'))"
168+
169+
170+ class ModuleTarget (str ):
171+ def check (self ):
172+ pass
173+
174+ @functools .cached_property
175+ def _details (self ):
176+ import runpy
177+ return runpy ._get_module_details (self )
178+
179+ @property
180+ def filename (self ):
181+ return self .code .co_filename
182+
183+ @property
184+ def code (self ):
185+ name , spec , code = self ._details
186+ return code
187+
188+ @property
189+ def _spec (self ):
190+ name , spec , code = self ._details
191+ return spec
192+
193+ @property
194+ def namespace (self ):
195+ return dict (
196+ __name__ = '__main__' ,
197+ __file__ = os .path .normcase (os .path .abspath (self .filename )),
198+ __package__ = self ._spec .parent ,
199+ __loader__ = self ._spec .loader ,
200+ __spec__ = self ._spec ,
201+ __builtins__ = __builtins__ ,
202+ )
203+
204+
131205# Interaction prompt line will separate file and call info from code
132206# text using value of line_prefix string. A newline and arrow may
133207# be to your liking. You can set it once pdb is imported using the
@@ -1538,49 +1612,26 @@ def lookupmodule(self, filename):
15381612 return fullname
15391613 return None
15401614
1541- def _runmodule (self , module_name ):
1615+ def _run (self , target : Union [ModuleTarget , ScriptTarget ]):
1616+ # When bdb sets tracing, a number of call and line events happen
1617+ # BEFORE debugger even reaches user's code (and the exact sequence of
1618+ # events depends on python version). Take special measures to
1619+ # avoid stopping before reaching the main script (see user_line and
1620+ # user_call for details).
15421621 self ._wait_for_mainpyfile = True
15431622 self ._user_requested_quit = False
1544- import runpy
1545- mod_name , mod_spec , code = runpy ._get_module_details (module_name )
1546- self .mainpyfile = self .canonic (code .co_filename )
1547- import __main__
1548- __main__ .__dict__ .clear ()
1549- __main__ .__dict__ .update ({
1550- "__name__" : "__main__" ,
1551- "__file__" : self .mainpyfile ,
1552- "__package__" : mod_spec .parent ,
1553- "__loader__" : mod_spec .loader ,
1554- "__spec__" : mod_spec ,
1555- "__builtins__" : __builtins__ ,
1556- })
1557- self .run (code )
1558-
1559- def _runscript (self , filename ):
1560- # The script has to run in __main__ namespace (or imports from
1561- # __main__ will break).
1562- #
1563- # So we clear up the __main__ and set several special variables
1564- # (this gets rid of pdb's globals and cleans old variables on restarts).
1623+
1624+ self .mainpyfile = self .canonic (target .filename )
1625+
1626+ # The target has to run in __main__ namespace (or imports from
1627+ # __main__ will break). Clear __main__ and replace with
1628+ # the target namespace.
15651629 import __main__
15661630 __main__ .__dict__ .clear ()
1567- __main__ .__dict__ .update ({"__name__" : "__main__" ,
1568- "__file__" : filename ,
1569- "__builtins__" : __builtins__ ,
1570- })
1631+ __main__ .__dict__ .update (target .namespace )
1632+
1633+ self .run (target .code )
15711634
1572- # When bdb sets tracing, a number of call and line events happens
1573- # BEFORE debugger even reaches user's code (and the exact sequence of
1574- # events depends on python version). So we take special measures to
1575- # avoid stopping before we reach the main script (see user_line and
1576- # user_call for details).
1577- self ._wait_for_mainpyfile = True
1578- self .mainpyfile = self .canonic (filename )
1579- self ._user_requested_quit = False
1580- with io .open_code (filename ) as fp :
1581- statement = "exec(compile(%r, %r, 'exec'))" % \
1582- (fp .read (), self .mainpyfile )
1583- self .run (statement )
15841635
15851636# Collect all command help into docstring, if not run with -OO
15861637
@@ -1669,6 +1720,7 @@ def help():
16691720To let the script run up to a given line X in the debugged file, use
16701721"-c 'until X'"."""
16711722
1723+
16721724def main ():
16731725 import getopt
16741726
@@ -1678,28 +1730,19 @@ def main():
16781730 print (_usage )
16791731 sys .exit (2 )
16801732
1681- commands = []
1682- run_as_module = False
1683- for opt , optarg in opts :
1684- if opt in ['-h' , '--help' ]:
1685- print (_usage )
1686- sys .exit ()
1687- elif opt in ['-c' , '--command' ]:
1688- commands .append (optarg )
1689- elif opt in ['-m' ]:
1690- run_as_module = True
1691-
1692- mainpyfile = args [0 ] # Get script filename
1693- if not run_as_module and not os .path .exists (mainpyfile ):
1694- print ('Error:' , mainpyfile , 'does not exist' )
1695- sys .exit (1 )
1733+ if any (opt in ['-h' , '--help' ] for opt , optarg in opts ):
1734+ print (_usage )
1735+ sys .exit ()
16961736
1697- sys . argv [:] = args # Hide "pdb.py" and pdb options from argument list
1737+ commands = [ optarg for opt , optarg in opts if opt in [ '-c' , '--command' ]]
16981738
1699- if not run_as_module :
1700- mainpyfile = os .path .realpath (mainpyfile )
1701- # Replace pdb's dir with script's dir in front of module search path.
1702- sys .path [0 ] = os .path .dirname (mainpyfile )
1739+ module_indicated = any (opt in ['-m' ] for opt , optarg in opts )
1740+ cls = ModuleTarget if module_indicated else ScriptTarget
1741+ target = cls (args [0 ])
1742+
1743+ target .check ()
1744+
1745+ sys .argv [:] = args # Hide "pdb.py" and pdb options from argument list
17031746
17041747 # Note on saving/restoring sys.argv: it's a good idea when sys.argv was
17051748 # modified by the script being debugged. It's a bad idea when it was
@@ -1709,15 +1752,12 @@ def main():
17091752 pdb .rcLines .extend (commands )
17101753 while True :
17111754 try :
1712- if run_as_module :
1713- pdb ._runmodule (mainpyfile )
1714- else :
1715- pdb ._runscript (mainpyfile )
1755+ pdb ._run (target )
17161756 if pdb ._user_requested_quit :
17171757 break
17181758 print ("The program finished and will be restarted" )
17191759 except Restart :
1720- print ("Restarting" , mainpyfile , "with arguments:" )
1760+ print ("Restarting" , target , "with arguments:" )
17211761 print ("\t " + " " .join (sys .argv [1 :]))
17221762 except SystemExit :
17231763 # In most cases SystemExit does not warrant a post-mortem session.
@@ -1732,7 +1772,7 @@ def main():
17321772 print ("Running 'cont' or 'step' will restart the program" )
17331773 t = sys .exc_info ()[2 ]
17341774 pdb .interaction (None , t )
1735- print ("Post mortem debugger finished. The " + mainpyfile +
1775+ print ("Post mortem debugger finished. The " + target +
17361776 " will be restarted" )
17371777
17381778
0 commit comments