|
1 | 1 | # -*- coding: utf-8 -*- |
2 | 2 | import functools |
| 3 | +import inspect |
3 | 4 | import sys |
4 | 5 |
|
5 | 6 |
|
@@ -31,6 +32,64 @@ def get_fqn(obj): |
31 | 32 | return '.'.join(filter(None, path)) |
32 | 33 |
|
33 | 34 |
|
| 35 | +def get_caller_fqn(): |
| 36 | + """ |
| 37 | + Use this method inside the function/method to get the fully qualified name (FQN) of its caller. |
| 38 | + Works for methods and functions including inner ones. |
| 39 | +
|
| 40 | + E.g. consider module 'src.check_fqn': |
| 41 | + def parent(): |
| 42 | + internal() |
| 43 | +
|
| 44 | + def internal(): |
| 45 | + get_caller_fqn() |
| 46 | +
|
| 47 | + parent() # Returns FQN of the method that called 'internal()': 'src.check_fqn.parent' |
| 48 | +
|
| 49 | + For complex nested inner functions FQN will represent a stack trace of calls (only for Python3): |
| 50 | + def inside(): |
| 51 | + def child_method(): |
| 52 | + return get_caller_fqn() |
| 53 | +
|
| 54 | + def outer_method(): |
| 55 | + return child_method() |
| 56 | +
|
| 57 | + class Caller: |
| 58 | + def parent_with_inner(self): |
| 59 | + def inner_parent(): |
| 60 | + return outer_method() |
| 61 | + return inner_parent() |
| 62 | +
|
| 63 | + return Caller().parent_with_inner() |
| 64 | +
|
| 65 | + inside() # Returns 'src.check_fqn.inside.Caller.parent_with_inner.inner_parent.outer_method' |
| 66 | + """ |
| 67 | + stack_depth = len(inspect.stack()) |
| 68 | + depth = 2 # 0: current method, 1: method that uses this function, 2: actual caller |
| 69 | + function_name = None |
| 70 | + while depth < stack_depth: |
| 71 | + caller_frame = inspect.stack()[depth] |
| 72 | + try: |
| 73 | + frame, caller_function = caller_frame.frame, caller_frame.function |
| 74 | + except AttributeError: # old Python versions |
| 75 | + frame, caller_function = caller_frame[0], caller_frame[3] |
| 76 | + # If caller is a function |
| 77 | + try: |
| 78 | + return get_fqn(frame.f_globals[caller_function]) |
| 79 | + except KeyError: |
| 80 | + pass |
| 81 | + # If caller is a class method |
| 82 | + function_name = '.'.join(filter(None, [caller_function, function_name])) # Handle nested inner calls |
| 83 | + try: |
| 84 | + return '{}.{}'.format(get_fqn(frame.f_locals['self'].__class__), function_name) |
| 85 | + except KeyError: |
| 86 | + pass |
| 87 | + # We are called from internal method we can't obtain reference to, e.g. list comprehension or nested inner |
| 88 | + # functions -> try to go one level up the stack |
| 89 | + depth += 1 |
| 90 | + return '' |
| 91 | + |
| 92 | + |
34 | 93 | class Decorator(object): |
35 | 94 | """ |
36 | 95 | A base class to easily create decorators. |
|
0 commit comments