blob: 27d11fd4fa9553e21077bd4a93c523dbcb1d32cb [file] [log] [blame]
Andrew Svetlovf74ef452017-12-15 07:04:38 +02001import functools
2import inspect
3import reprlib
Yury Selivanov989b9e02018-05-28 16:27:34 -04004import sys
Andrew Svetlovf74ef452017-12-15 07:04:38 +02005import traceback
6
7from . import constants
8
9
10def _get_function_source(func):
11 func = inspect.unwrap(func)
12 if inspect.isfunction(func):
13 code = func.__code__
14 return (code.co_filename, code.co_firstlineno)
15 if isinstance(func, functools.partial):
16 return _get_function_source(func.func)
17 if isinstance(func, functools.partialmethod):
18 return _get_function_source(func.func)
19 return None
20
21
22def _format_callback_source(func, args):
23 func_repr = _format_callback(func, args, None)
24 source = _get_function_source(func)
25 if source:
26 func_repr += f' at {source[0]}:{source[1]}'
27 return func_repr
28
29
30def _format_args_and_kwargs(args, kwargs):
31 """Format function arguments and keyword arguments.
32
33 Special case for a single parameter: ('hello',) is formatted as ('hello').
34 """
35 # use reprlib to limit the length of the output
36 items = []
37 if args:
38 items.extend(reprlib.repr(arg) for arg in args)
39 if kwargs:
40 items.extend(f'{k}={reprlib.repr(v)}' for k, v in kwargs.items())
41 return '({})'.format(', '.join(items))
42
43
44def _format_callback(func, args, kwargs, suffix=''):
45 if isinstance(func, functools.partial):
46 suffix = _format_args_and_kwargs(args, kwargs) + suffix
47 return _format_callback(func.func, func.args, func.keywords, suffix)
48
Yury Selivanov989b9e02018-05-28 16:27:34 -040049 if hasattr(func, '__qualname__') and func.__qualname__:
50 func_repr = func.__qualname__
51 elif hasattr(func, '__name__') and func.__name__:
52 func_repr = func.__name__
Andrew Svetlovf74ef452017-12-15 07:04:38 +020053 else:
54 func_repr = repr(func)
55
56 func_repr += _format_args_and_kwargs(args, kwargs)
57 if suffix:
58 func_repr += suffix
59 return func_repr
60
61
62def extract_stack(f=None, limit=None):
63 """Replacement for traceback.extract_stack() that only does the
64 necessary work for asyncio debug mode.
65 """
66 if f is None:
67 f = sys._getframe().f_back
68 if limit is None:
69 # Limit the amount of work to a reasonable amount, as extract_stack()
70 # can be called for each coroutine and future in debug mode.
71 limit = constants.DEBUG_STACK_DEPTH
72 stack = traceback.StackSummary.extract(traceback.walk_stack(f),
73 limit=limit,
74 lookup_lines=False)
75 stack.reverse()
76 return stack