1
- import ast
2
- import dis
3
- import inspect
4
1
import os
5
- import pdb
6
2
import sys
7
- from pathlib import Path
8
- from textwrap import dedent , indent
9
- from types import FrameType
10
- from typing import Generator , List , Optional , Tuple
11
3
12
- from .ansi import isatty , sformat
4
+ from .ansi import sformat
13
5
from .prettier import PrettyFormat , env_true
14
6
from .timer import Timer
7
+ from .utils import isatty
15
8
16
9
__all__ = 'Debug' , 'debug'
17
- CWD = Path ('.' ).resolve ()
10
+ MYPY = False
11
+ if MYPY :
12
+ import ast
13
+ from types import FrameType
14
+ from typing import Generator , List , Optional , Tuple
18
15
19
16
20
17
pformat = PrettyFormat (
@@ -78,7 +75,7 @@ class DebugOutput:
78
75
arg_class = DebugArgument
79
76
__slots__ = 'filename' , 'lineno' , 'frame' , 'arguments' , 'warning'
80
77
81
- def __init__ (self , * , filename : str , lineno : int , frame : str , arguments : List [DebugArgument ], warning = None ):
78
+ def __init__ (self , * , filename : str , lineno : int , frame : str , arguments : ' List[DebugArgument]' , warning = None ):
82
79
self .filename = filename
83
80
self .lineno = lineno
84
81
self .frame = frame
@@ -110,22 +107,9 @@ def __repr__(self) -> str:
110
107
111
108
class Debug :
112
109
output_class = DebugOutput
113
- complex_nodes = (
114
- ast .Call ,
115
- ast .Attribute ,
116
- ast .Subscript ,
117
- ast .IfExp ,
118
- ast .BoolOp ,
119
- ast .BinOp ,
120
- ast .Compare ,
121
- ast .DictComp ,
122
- ast .ListComp ,
123
- ast .SetComp ,
124
- ast .GeneratorExp ,
125
- )
126
110
127
111
def __init__ (
128
- self , * , warnings : Optional [bool ] = None , highlight : Optional [bool ] = None , frame_context_length : int = 50
112
+ self , * , warnings : ' Optional[bool]' = None , highlight : ' Optional[bool]' = None , frame_context_length : int = 50
129
113
):
130
114
self ._show_warnings = self ._env_bool (warnings , 'PY_DEVTOOLS_WARNINGS' , True )
131
115
self ._highlight = self ._env_bool (highlight , 'PY_DEVTOOLS_HIGHLIGHT' , None )
@@ -149,6 +133,8 @@ def format(self, *args, **kwargs) -> DebugOutput:
149
133
return self ._process (args , kwargs , 'format' )
150
134
151
135
def breakpoint (self ):
136
+ import pdb
137
+
152
138
pdb .Pdb (skip = ['devtools.*' ]).set_trace ()
153
139
154
140
def timer (self , name = None , * , verbose = True , file = None , dp = 3 ) -> Timer :
@@ -160,7 +146,7 @@ def _process(self, args, kwargs, func_name: str) -> DebugOutput:
160
146
"""
161
147
# HELP: any errors other than ValueError from _getframe? If so please submit an issue
162
148
try :
163
- call_frame : FrameType = sys ._getframe (2 )
149
+ call_frame : ' FrameType' = sys ._getframe (2 )
164
150
except ValueError :
165
151
# "If [ValueError] is deeper than the call stack, ValueError is raised"
166
152
return self .output_class (
@@ -175,15 +161,20 @@ def _process(self, args, kwargs, func_name: str) -> DebugOutput:
175
161
function = call_frame .f_code .co_name
176
162
if filename .startswith ('/' ):
177
163
# make the path relative
164
+ from pathlib import Path
165
+
166
+ cwd = Path ('.' ).resolve ()
178
167
try :
179
- filename = str (Path (filename ).relative_to (CWD ))
168
+ filename = str (Path (filename ).relative_to (cwd ))
180
169
except ValueError :
181
170
# happens if filename path is not within CWD
182
171
pass
183
172
184
173
lineno = call_frame .f_lineno
185
174
warning = None
186
175
176
+ import inspect
177
+
187
178
try :
188
179
file_lines , _ = inspect .findsource (call_frame )
189
180
except OSError :
@@ -214,7 +205,23 @@ def _args_inspection_failed(self, args, kwargs):
214
205
for name , value in kwargs .items ():
215
206
yield self .output_class .arg_class (value , name = name )
216
207
217
- def _process_args (self , func_ast , code_lines , args , kwargs ) -> Generator [DebugArgument , None , None ]: # noqa: C901
208
+ def _process_args (self , func_ast , code_lines , args , kwargs ) -> 'Generator[DebugArgument, None, None]' : # noqa: C901
209
+ import ast
210
+
211
+ complex_nodes = (
212
+ ast .Call ,
213
+ ast .Attribute ,
214
+ ast .Subscript ,
215
+ ast .IfExp ,
216
+ ast .BoolOp ,
217
+ ast .BinOp ,
218
+ ast .Compare ,
219
+ ast .DictComp ,
220
+ ast .ListComp ,
221
+ ast .SetComp ,
222
+ ast .GeneratorExp ,
223
+ )
224
+
218
225
arg_offsets = list (self ._get_offsets (func_ast ))
219
226
for i , arg in enumerate (args ):
220
227
try :
@@ -226,7 +233,7 @@ def _process_args(self, func_ast, code_lines, args, kwargs) -> Generator[DebugAr
226
233
227
234
if isinstance (ast_node , ast .Name ):
228
235
yield self .output_class .arg_class (arg , name = ast_node .id )
229
- elif isinstance (ast_node , self . complex_nodes ):
236
+ elif isinstance (ast_node , complex_nodes ):
230
237
# TODO replace this hack with astor when it get's round to a new release
231
238
start_line , start_col = arg_offsets [i ]
232
239
@@ -252,12 +259,14 @@ def _process_args(self, func_ast, code_lines, args, kwargs) -> Generator[DebugAr
252
259
yield self .output_class .arg_class (value , name = name , variable = kw_arg_names .get (name ))
253
260
254
261
def _parse_code (
255
- self , filename : str , file_lines : List [str ], first_line : int , last_line : int
256
- ) -> Tuple [ast .AST , List [str ]]:
262
+ self , filename : str , file_lines : ' List[str]' , first_line : int , last_line : int
263
+ ) -> ' Tuple[ast.AST, List[str]]' :
257
264
"""
258
265
All we're trying to do here is build an AST of the function call statement. However numerous ugly interfaces,
259
266
lack on introspection support and changes between python versions make this extremely hard.
260
267
"""
268
+ from textwrap import dedent
269
+ import ast
261
270
262
271
def get_code (_last_line : int ) -> str :
263
272
lines = file_lines [first_line - 1 : _last_line ]
@@ -295,10 +304,12 @@ def get_code(_last_line: int) -> str:
295
304
return func_ast , code_lines
296
305
297
306
@staticmethod # noqa: C901
298
- def _statement_range (call_frame : FrameType , func_name : str ) -> Tuple [int , int ]: # noqa: C901
307
+ def _statement_range (call_frame : ' FrameType' , func_name : str ) -> ' Tuple[int, int]' : # noqa: C901
299
308
"""
300
309
Try to find the start and end of a frame statement.
301
310
"""
311
+ import dis
312
+
302
313
# dis.disassemble(call_frame.f_code, call_frame.f_lasti)
303
314
# pprint([i for i in dis.get_instructions(call_frame.f_code)])
304
315
@@ -343,15 +354,20 @@ def _statement_range(call_frame: FrameType, func_name: str) -> Tuple[int, int]:
343
354
return first_line , last_line
344
355
345
356
@staticmethod
346
- def _wrap_parse (code , filename ) :
357
+ def _wrap_parse (code : str , filename : str ) -> 'ast.Call' :
347
358
"""
348
359
async wrapper is required to avoid await calls raising a SyntaxError
349
360
"""
361
+ import ast
362
+ from textwrap import indent
363
+
350
364
code = 'async def wrapper():\n ' + indent (code , ' ' )
351
365
return ast .parse (code , filename = filename ).body [0 ].body [0 ].value
352
366
353
367
@staticmethod
354
368
def _get_offsets (func_ast ):
369
+ import ast
370
+
355
371
for arg in func_ast .args :
356
372
start_line , start_col = arg .lineno - 2 , arg .col_offset - 1
357
373
0 commit comments