80
80
import signal
81
81
import inspect
82
82
import tokenize
83
+ import functools
83
84
import traceback
84
85
import linecache
85
86
87
+ from typing import Union
88
+
86
89
87
90
class Restart (Exception ):
88
91
"""Causes a debugger to be restarted for the debugged python program."""
@@ -128,6 +131,77 @@ def __repr__(self):
128
131
return self
129
132
130
133
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
+
131
205
# Interaction prompt line will separate file and call info from code
132
206
# text using value of line_prefix string. A newline and arrow may
133
207
# be to your liking. You can set it once pdb is imported using the
@@ -1538,49 +1612,26 @@ def lookupmodule(self, filename):
1538
1612
return fullname
1539
1613
return None
1540
1614
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).
1542
1621
self ._wait_for_mainpyfile = True
1543
1622
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.
1565
1629
import __main__
1566
1630
__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 )
1571
1634
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 )
1584
1635
1585
1636
# Collect all command help into docstring, if not run with -OO
1586
1637
@@ -1669,6 +1720,7 @@ def help():
1669
1720
To let the script run up to a given line X in the debugged file, use
1670
1721
"-c 'until X'"."""
1671
1722
1723
+
1672
1724
def main ():
1673
1725
import getopt
1674
1726
@@ -1678,28 +1730,19 @@ def main():
1678
1730
print (_usage )
1679
1731
sys .exit (2 )
1680
1732
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 ()
1696
1736
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' ]]
1698
1738
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
1703
1746
1704
1747
# Note on saving/restoring sys.argv: it's a good idea when sys.argv was
1705
1748
# modified by the script being debugged. It's a bad idea when it was
@@ -1709,15 +1752,12 @@ def main():
1709
1752
pdb .rcLines .extend (commands )
1710
1753
while True :
1711
1754
try :
1712
- if run_as_module :
1713
- pdb ._runmodule (mainpyfile )
1714
- else :
1715
- pdb ._runscript (mainpyfile )
1755
+ pdb ._run (target )
1716
1756
if pdb ._user_requested_quit :
1717
1757
break
1718
1758
print ("The program finished and will be restarted" )
1719
1759
except Restart :
1720
- print ("Restarting" , mainpyfile , "with arguments:" )
1760
+ print ("Restarting" , target , "with arguments:" )
1721
1761
print ("\t " + " " .join (sys .argv [1 :]))
1722
1762
except SystemExit :
1723
1763
# In most cases SystemExit does not warrant a post-mortem session.
@@ -1732,7 +1772,7 @@ def main():
1732
1772
print ("Running 'cont' or 'step' will restart the program" )
1733
1773
t = sys .exc_info ()[2 ]
1734
1774
pdb .interaction (None , t )
1735
- print ("Post mortem debugger finished. The " + mainpyfile +
1775
+ print ("Post mortem debugger finished. The " + target +
1736
1776
" will be restarted" )
1737
1777
1738
1778
0 commit comments