RBerga06 / python-utils

Python Utilities
https://rberga06.github.io/python-utils/
GNU Affero General Public License v3.0
1 stars 0 forks source link

Add a `call_info` decorator for functions. #37

Closed RBerga06 closed 1 year ago

RBerga06 commented 1 year ago

Is your feature request related to a problem? Please describe. It would be very useful, especially when debugging, to be able to retrieve detailed information about a function at runtime (maybe even log every call).

Describe the solution you'd like A new debug decorator would be nice:

@debug
def foo(x: int, /, *, n: int = 3):
    return x + n * (x % (x // n))

foo(41)             # logs something like 'foo(41)'
foo(41, n=0)        # logs something like 'foo(41, n=0)'
calls = debug_get_calls(foo)  # or some other api
len(calls)  # 2
calls[0]  # CallInfo(...) or whatever it'll be called
calls[0].args  # (41, )
calls[0].kwargs  # {}
calls[0].result  # 44
calls[0].success # True
calls[1].success # False
calls[1].result  # ZeroDivisionError(...)
repr(calls[0])  # Something like '<CallInfo: foo(41, n=3) -> 44>'
repr(calls[1])  # Something like '<CallInfo: foo(41, n=0) -> <!> ZeroDivisionError <!>>' or similar

Describe alternatives you've considered N/A

Additional context N/A

RBerga06 commented 1 year ago

This is a very interesting and useful feature, however it probably requires a way to attach information to the function. Currently, the decorator API does not have this capability, because it's not always safe to setattr(...) something on unknown functions.

RBerga06 commented 1 year ago

Maybe debug isn't the right name... Probably call_info is better.

RBerga06 commented 1 year ago

Ok, so this is what we want:

@call_info()
def foo(throws: bool) -> None:
    if throws: raise

foo(False)
try:
    foo(True)  # this raises
except:
    pass

foo_calls = call_info.get(foo)
reveal_type(foo_calls)  # list[CallInfo]
call0 = foo_calls[0]
call1 = foo_calls[1]
call0.args      # (False, )
call1.args      # (True, )
call0.kwargs    # {}
call1.kwargs    # {}
call0.success   # True
call1.success   # False
call0.result    # None
call1.result    # RuntimeError
RBerga06 commented 1 year ago

We could also introduce a parameter to control logging behaviour:

@call_info(log=False)  # by default, log=True
def foo(throws: bool) -> None:
    if throws: raise