Completed
Push — master ( af1fbe...dff617 )
by Ionel Cristian
29s
created

CallPrinter.__call__()   D

Complexity

Conditions 9

Size

Total Lines 75

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 1 Features 0
Metric Value
cc 9
c 6
b 1
f 0
dl 0
loc 75
rs 4

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
from __future__ import absolute_import
2
3
import ast
4
import os
5
import sys
6
import threading
7
from collections import defaultdict
8
from itertools import chain
9
10
from colorama import AnsiToWin32
11
from colorama import Back
12
from colorama import Fore
13
from colorama import Style
14
from six import string_types
15
16
from .util import Fields
17
18
EVENT_COLORS = {
19
    'reset': Style.RESET_ALL,
20
    'normal': Style.NORMAL,
21
    'filename': '',
22
    'colon': Style.BRIGHT + Fore.BLACK,
23
    'lineno': Style.RESET_ALL,
24
    'kind': Fore.CYAN,
25
    'continuation': Style.BRIGHT + Fore.BLUE,
26
    'call': Style.BRIGHT + Fore.BLUE,
27
    'return': Style.BRIGHT + Fore.GREEN,
28
    'exception': Style.BRIGHT + Fore.RED,
29
    'detail': Style.NORMAL,
30
    'vars': Style.RESET_ALL + Fore.MAGENTA,
31
    'vars-name': Style.BRIGHT,
32
    'internal-failure': Style.BRIGHT + Back.RED + Fore.RED,
33
    'internal-detail': Fore.WHITE,
34
    'source-failure': Style.BRIGHT + Back.YELLOW + Fore.YELLOW,
35
    'source-detail': Fore.WHITE,
36
}
37
CODE_COLORS = {
38
    'call': Fore.RESET + Style.BRIGHT,
39
    'line': Fore.RESET,
40
    'return': Fore.YELLOW,
41
    'exception': Fore.RED,
42
}
43
NO_COLORS = {key: '' for key in chain(CODE_COLORS, EVENT_COLORS)}
44
MISSING = type('MISSING', (), {'__repr__': lambda _: '?'})()
45
46
47
class Action(object):
48
    def __call__(self, event):
49
        raise NotImplementedError()
50
51
52
class Debugger(Fields.klass.kwargs, Action):
53
    """
54
    An action that starts ``pdb``.
55
    """
56
    def __init__(self, klass=lambda **kwargs: __import__('pdb').Pdb(**kwargs), **kwargs):
57
        self.klass = klass
58
        self.kwargs = kwargs
59
60
    def __call__(self, event):
61
        """
62
        Runs a ``pdb.set_trace`` at the matching frame.
63
        """
64
        self.klass(**self.kwargs).set_trace(event.frame)
65
66
67
class Manhole(Action):
68
    def __init__(self, **options):
69
        self.options = options
70
71
    def __call__(self, event):
72
        import manhole
73
        inst = manhole.install(strict=False, thread=False, **self.options)
74
        inst.handle_oneshot()
75
76
77
DEFAULT_STREAM = sys.stderr
78
79
80
class ColorStreamAction(Fields.stream.force_colors.filename_alignment.thread_alignment.repr_limit, Action):
81
    _stream_cache = {}
82
    _stream = None
83
    _tty = None
84
85
    def __init__(self,
86
                 stream=None,
87
                 force_colors=False,
88
                 filename_alignment=40,
89
                 thread_alignment=12,
90
                 repr_limit=1024):
91
        self.force_colors = force_colors
92
        self.stream = DEFAULT_STREAM if stream is None else stream
93
        self.filename_alignment = filename_alignment
94
        self.thread_alignment = thread_alignment
95
        self.repr_limit = repr_limit
96
97
    @property
98
    def stream(self):
99
        return self._stream
100
101
    @stream.setter
102
    def stream(self, value):
103
        if isinstance(value, string_types):
104
            if value in self._stream_cache:
105
                value = self._stream_cache[value]
106
            else:
107
                value = self._stream_cache[value] = open(value, 'a', buffering=0)
108
109
        isatty = getattr(value, 'isatty', None)
110
        if self.force_colors or (isatty and isatty() and os.name != 'java'):
111
            self._stream = AnsiToWin32(value, strip=False)
112
            self._tty = True
113
            self.event_colors = EVENT_COLORS
114
            self.code_colors = CODE_COLORS
115
        else:
116
            self._tty = False
117
            self._stream = value
118
            self.event_colors = NO_COLORS
119
            self.code_colors = NO_COLORS
120
121
    def _safe_repr(self, obj):
122
        limit = self.repr_limit
123
124
        try:
125
            s = repr(obj)
126
            s = s.replace('\n', r'\n')
127
            if len(s) > limit:
128
                cutoff = limit // 2
129
                return "{} {continuation}[...]{reset} {}".format(s[:cutoff], s[-cutoff:], **self.event_colors)
130
            else:
131
                return s
132
        except Exception as exc:
133
            return "{internal-failure}!!! FAILED REPR: {internal-detail}{!r}{reset}".format(exc, **self.event_colors)
134
135
136
class CodePrinter(ColorStreamAction):
137
    """
138
    An action that just prints the code being executed.
139
140
    Args:
141
        stream (file-like): Stream to write to. Default: ``sys.stderr``.
142
        filename_alignment (int): Default size for the filename column (files are right-aligned). Default: ``40``.
143
        force_colors (bool): Force coloring. Default: ``False``.
144
        repr_limit (bool): Limit length of ``repr()`` output. Default: ``512``.
145
    """
146
    def _safe_source(self, event):
147
        try:
148
            lines = event._raw_fullsource.rstrip().splitlines()
149
            if lines:
150
                return lines
151
            else:
152
                return "{source-failure}??? NO SOURCE: {source-detail}" \
153
                       "Source code string for module {!r} is empty.".format(event.module, **self.event_colors),
154
            return lines
155
        except Exception as exc:
156
            return "{source-failure}??? NO SOURCE: {source-detail}{!r}".format(exc, **self.event_colors),
157
158
    def _format_filename(self, event):
159
        filename = event.filename or "<???>"
160
        if len(filename) > self.filename_alignment:
161
            filename = '[...]{}'.format(filename[5 - self.filename_alignment:])
162
        return filename
163
164
    def __call__(self, event, sep=os.path.sep, join=os.path.join):
165
        """
166
        Handle event and print filename, line number and source code. If event.kind is a `return` or `exception` also
167
        prints values.
168
        """
169
170
        # context = event.tracer
171
        # alignment = context.filename_alignment = max(
172
        #     getattr(context, 'filename_alignment', 5),
173
        #     len(filename)
174
        # )
175
        lines = self._safe_source(event)
176
        thread_name = threading.current_thread().name if event.tracer.threading_support else ''
177
        thread_align = self.thread_alignment if event.tracer.threading_support else ''
178
179
        self.stream.write(
180
            "{thread:{thread_align}}{filename}{:>{align}}{colon}:{lineno}{:<5} {kind}{:9} {code}{}{reset}\n".format(
181
                self._format_filename(event),
182
                event.lineno,
183
                event.kind,
184
                lines[0],
185
                thread=thread_name, thread_align=thread_align,
186
                align=self.filename_alignment,
187
                code=self.code_colors[event.kind],
188
                **self.event_colors
189
            ))
190
        for line in lines[1:]:
191
            self.stream.write("{thread:{thread_align}}{:>{align}}       {kind}{:9} {code}{}{reset}\n".format(
192
                "",
193
                r"   |",
194
                line,
195
                thread=thread_name, thread_align=thread_align,
196
                align=self.filename_alignment,
197
                code=self.code_colors[event.kind],
198
                **self.event_colors
199
            ))
200
201
        if event.kind in ('return', 'exception'):
202
            self.stream.write(
203
                "{thread:{thread_align}}{:>{align}}       {continuation}{:9} {color}{} "
204
                "value: {detail}{}{reset}\n".format(
205
                    "",
206
                    "...",
207
                    event.kind,
208
                    self._safe_repr(event.arg),
209
                    thread=thread_name, thread_align=thread_align,
210
                    align=self.filename_alignment,
211
                    color=self.event_colors[event.kind],
212
                    **self.event_colors
213
                ))
214
215
216
class CallPrinter(CodePrinter):
217
    """
218
    An action that just prints the code being executed, but unlike :obj:`hunter.CodePrinter` it indents based on
219
    callstack depth and it also shows ``repr()`` of function arguments.
220
221
    Args:
222
        stream (file-like): Stream to write to. Default: ``sys.stderr``.
223
        filename_alignment (int): Default size for the filename column (files are right-aligned). Default: ``40``.
224
        force_colors (bool): Force coloring. Default: ``False``.
225
        repr_limit (bool): Limit length of ``repr()`` output. Default: ``512``.
226
227
    .. versionadded:: 1.2.0
228
229
    .. note::
230
231
        This will be the default action in `hunter 2.0`.
232
    """
233
234
    def __init__(self, **options):
235
        super(CallPrinter, self).__init__(**options)
236
        self.locals = defaultdict(list)
237
238
    def __call__(self, event, sep=os.path.sep, join=os.path.join):
239
        """
240
        Handle event and print filename, line number and source code. If event.kind is a `return` or `exception` also
241
        prints values.
242
        """
243
        filename = self._format_filename(event)
244
        ident = event.module, event.function
245
        thread = threading.current_thread()
246
        thread_name = thread.name if event.tracer.threading_support else ''
247
        thread_align = self.thread_alignment if event.tracer.threading_support else ''
248
        stack = self.locals[thread.ident]
249
250
        if event.kind == 'call':
251
            code = event.code
252
            stack.append(ident)
253
            self.stream.write(
254
                "{thread:{thread_align}}{filename}{:>{align}}{colon}:{lineno}{:<5} {kind}{:9} {}{call}=>{normal} "
255
                "{}({}{call}{normal}){reset}\n".format(
256
                    filename,
257
                    event.lineno,
258
                    event.kind,
259
                    '   ' * (len(stack) - 1),
260
                    event.function,
261
                    ', '.join('{vars}{vars-name}{0}{vars}={reset}{1}'.format(
262
                        var,
263
                        self._safe_repr(event.locals.get(var, MISSING)),
264
                        **self.event_colors
265
                    ) for var in code.co_varnames[:code.co_argcount]),
266
                    thread=thread_name, thread_align=thread_align,
267
                    align=self.filename_alignment,
268
                    **self.event_colors
269
                ))
270
        elif event.kind == 'exception':
271
            self.stream.write(
272
                "{thread:{thread_align}}{filename}{:>{align}}{colon}:{lineno}{:<5} {kind}{:9} "
273
                "{exception}{} !{normal} {}: {reset}{}\n".format(
274
                    filename,
275
                    event.lineno,
276
                    event.kind,
277
                    '   ' * (len(stack) - 1),
278
                    event.function,
279
                    self._safe_repr(event.arg),
280
                    thread=thread_name, thread_align=thread_align,
281
                    align=self.filename_alignment,
282
                    **self.event_colors
283
                ))
284
285
        elif event.kind == 'return':
286
            self.stream.write(
287
                "{thread:{thread_align}}{filename}{:>{align}}{colon}:{lineno}{:<5} {kind}{:9} "
288
                "{return}{}<={normal} {}: {reset}{}\n".format(
289
                    filename,
290
                    event.lineno,
291
                    event.kind,
292
                    '   ' * (len(stack) - 1),
293
                    event.function,
294
                    self._safe_repr(event.arg),
295
                    thread=thread_name, thread_align=thread_align,
296
                    align=self.filename_alignment,
297
                    **self.event_colors
298
                ))
299
            if stack and stack[-1] == ident:
300
                stack.pop()
301
        else:
302
            self.stream.write(
303
                "{thread:{thread_align}}{filename}{:>{align}}{colon}:{lineno}{:<5} {kind}{:9} {reset}{}{}\n".format(
304
                    filename,
305
                    event.lineno,
306
                    event.kind,
307
                    '   ' * len(stack),
308
                    event.source.strip(),
309
                    thread=thread_name, thread_align=thread_align,
310
                    align=self.filename_alignment,
311
                    code=self.code_colors[event.kind],
312
                    **self.event_colors
313
                ))
314
315
316
class VarsPrinter(Fields.names.globals.stream.filename_alignment, ColorStreamAction):
317
    """
318
    An action that prints local variables and optionally global variables visible from the current executing frame.
319
320
    Args:
321
        *names (strings): Names to evaluate. Expressions can be used (will only try to evaluate if all the variables are
322
            present on the frame.
323
        globals (bool): Allow access to globals. Default: ``False`` (only looks at locals).
324
        stream (file-like): Stream to write to. Default: ``sys.stderr``.
325
        filename_alignment (int): Default size for the filename column (files are right-aligned). Default: ``40``.
326
        force_colors (bool): Force coloring. Default: ``False``.
327
        repr_limit (bool): Limit length of ``repr()`` output. Default: ``512``.
328
    """
329
330
    def __init__(self, *names, **options):
331
        if not names:
332
            raise TypeError("VarsPrinter requires at least one variable name/expression.")
333
        self.names = {
334
            name: set(self._iter_symbols(name))
335
            for name in names
336
        }
337
        self.globals = options.pop('globals', False)
338
        super(VarsPrinter, self).__init__(**options)
339
340
    @staticmethod
341
    def _iter_symbols(code):
342
        """
343
        Iterate all the variable names in the given expression.
344
345
        Example:
346
347
        * ``self.foobar`` yields ``self``
348
        * ``self[foobar]`` yields `self`` and ``foobar``
349
        """
350
        for node in ast.walk(ast.parse(code)):
351
            if isinstance(node, ast.Name):
352
                yield node.id
353
354
    def _safe_eval(self, code, event):
355
        """
356
        Try to evaluate the given code on the given frame. If failure occurs, returns some ugly string with exception.
357
        """
358
        try:
359
            return eval(code, event.globals if self.globals else {}, event.locals)
360
        except Exception as exc:
361
            return "{internal-failure}FAILED EVAL: {internal-detail}{!r}".format(exc, **self.event_colors)
362
363
    def __call__(self, event):
364
        """
365
        Handle event and print the specified variables.
366
        """
367
        first = True
368
        frame_symbols = set(event.locals)
369
        if self.globals:
370
            frame_symbols |= set(event.globals)
371
        thread_name = threading.current_thread().name if event.tracer.threading_support else ''
372
        thread_align = self.thread_alignment if event.tracer.threading_support else ''
373
374
        for code, symbols in self.names.items():
375
            try:
376
                obj = eval(code, event.globals if self.globals else {}, event.locals)
377
            except AttributeError:
378
                continue
379
            except Exception as exc:
380
                printout = "{internal-failure}FAILED EVAL: {internal-detail}{!r}".format(exc, **self.event_colors)
381
            else:
382
                printout = self._safe_repr(obj)
383
384
            if frame_symbols >= symbols:
385
                self.stream.write("{thread:{thread_align}}{:>{align}}       {vars}{:9} {vars-name}{} {vars}=> {reset}{}{reset}\n".format(
386
                    "",
387
                    "vars" if first else "...",
388
                    code,
389
                    printout,
390
                    thread=thread_name, thread_align=thread_align,
391
                    align=self.filename_alignment,
392
                    **self.event_colors
393
                ))
394
                first = False
395