Usage of decorated functions as item callback
Version of Dear PyGui
Version: 1.11.1 and 2.0.0 on Python 3.12 Operating System: MacOS 15.3.1
My Issue/Question
Are there any restrictions on using decorated functions as item callbacks?
I am trying to use decorated functions as item callbacks. These functions are decorated to catch exceptions in order to log them and show an error message to the user.
Decorated functions called from a callback seem to lack the function parameters, as I get the following error message:
Function foo raised an exception: foo() missing 3 required positional arguments: 'sender', 'app_data', and 'user_data'
Calling the decorated functions directly, i.e., not via a callback, works as expected. It does not matter whether the function is a class member or not.
To Reproduce
Steps to reproduce the behavior:
- Start the sample code from below.
- The decorated functions are called directly and print the expected output.
- Click on any of the two buttons to call any of the functions.
- The callback fails and prints an error message in the terminal.
Expected behavior
The decorated functions called via a callback produce the same output as those being called directly.
Standalone, minimal, complete and verifiable example
import functools
import logging
import dearpygui.dearpygui as dpg
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
def log(logger: logging.Logger):
def log_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
logger.info(f"Calling function {func.__name__}")
try:
return func(*args, **kwargs)
except Exception as e:
logger.error(f"Function {func.__name__} raised exception {e}")
return wrapper
return log_decorator
class Bob:
def __init__(self):
pass
@log(logger)
def foo(self, sender, app_data, user_data):
print(f"Sender {sender}")
print(f"App Data {app_data}")
print(f"user_data {user_data}")
@log(logger)
def foo(sender, app_data, user_data):
print(f"Sender {sender}")
print(f"App Data {app_data}")
print(f"user_data {user_data}")
if __name__ == "__main__":
# Calling decorated functions works as expected.
bob = Bob()
print(foo(None, None, "Called foo"))
print(bob.foo(None, None, "Called Bob.foo"))
dpg.create_context()
dpg.create_viewport(title='Custom Title', width=600, height=300)
with dpg.window(label="Example Window"):
# Calling decorated functions via callback fails with error:
# Function foo raised exception foo() missing 3 required positional arguments: 'sender', 'app_data', and 'user_data'
dpg.add_button(
label="Call decorated function 'foo'", callback=foo, user_data="Called foo via callback"
)
dpg.add_button(
label="Call decorated function 'bob.foo'", callback=bob.foo, user_data="Called bob.foo via callback"
)
dpg.setup_dearpygui()
dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()
I see it works by using lambda.
callback=lambda s, a, u: foo(s, a, u)
callback=lambda s, a, u: bob.foo(s, a, u)
... And here's why: when DPG invokes the callback, it checks the callback's signature to see if it needs to pass sender, app_data, and user_data. All three arguments are optional, meaning that the callback may have only 2 of them or none of them -- DPG will call it anyway. When you use a decorated function, DPG sees the wrapper, which does not have those arguments in its signature. That's why DPG does not pass them, too.
With the lambda, DPG is seeing sender/app_data/user_data in the signature of the lambda.
You could try to add those arguments to wrapper, but in this case the callback will need to have all three of them, too. Or you could dynamically build args for the callee in your wrapper (i.e. see which of the three arguments the callback actually accepts).
Awesome! Thank you for the quick replies with workarounds and explanation. 🙂