Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] dash.callback should utilize functools.wraps #2742

Closed
shea-parkes opened this issue Feb 2, 2024 · 4 comments
Closed

[Feature Request] dash.callback should utilize functools.wraps #2742

shea-parkes opened this issue Feb 2, 2024 · 4 comments

Comments

@shea-parkes
Copy link

Thanks so much for your interest in Dash!

Before posting an issue here, please check the Dash community forum to see if the topic has already been discussed. The community forum is also great for implementation questions. When in doubt, please feel free to just post the issue here :)

Is your feature request related to a problem? Please describe.
Yes. We implemented a profiling solution for some of our longer running callback functions. During implementation, we assumed that mycallback.__wrapped__ would exist. It did not, so we had to rework how we defined our callbacks.

Describe the solution you'd like
Use the python standard library functools.wraps inside of dash.callback to have standard access to the decorated function.

Describe alternatives you've considered
Tried to look at dash internals to see if there was a convenient way to access the original function. The dash.callback internals are complex so we chose not to do any deep bindings to non-public APIs.

We could switch to defining our callback functions without decoration, and then using dash.callback(..)(myfunc) (i.e. don't use dash.callback as a decorator`). But that's also adding complexity to the solution.

Additional context
Given that dash.callback is intended to be a decorator, I think it is reasonable to want it to implement the standard functools.wraps.

However, this is y'all's solution, and you're already providing great value to us, so I understand if you feel differently. Thanks!

@shea-parkes
Copy link
Author

shea-parkes commented Feb 2, 2024

In case anyone else stumbles here someday, here's the code snippet of how we're handling this currently. Instead of dash.callback, we made mylib.callback and use it in the same way:

P = typing.ParamSpec("P")  # Params to dash.callback (e.g. dash.Outputs, dash.Inputs)
F = typing.TypeVar(  # Signature of our function being passed to dash.callback
    "F", bound=typing.Callable[..., typing.Any]
)

@functools.wraps(dash.callback)  # Just meta-fun, not the usage of `functools.wraps` I'm asking about...
def callback(
    *dash_io_args: P.args,
    **dash_io_kwargs: P.kwargs,
) -> typing.Callable[[F], F]:
    """wraps dash.callback to enable introspection to access the underlying function"""

    def our_decorator(f: F) -> F:
        """The inner closure that nests two decorators (functools.wraps and dash.callback)"""
        return functools.wraps(f)(dash.callback(*dash_io_args, **dash_io_kwargs)(f))

    return our_decorator

@shea-parkes
Copy link
Author

Upon further review, it appears that the reason we didn't see a mycallback.__wrapped__ was that the callback wrapping functionality stores the wrapped function elsewhere, but then just returns back the original function from the decorator. I'm double checking, but I think that this means this could all be shut down. Sorry for the inconvenience.

@shea-parkes
Copy link
Author

Indeed, dash.callback() returns the original function unaltered and unwrapped. It does create a wrapped version, but it stores that in its own internal ~namespace. So I'm closing this down. Sorry y'all.

@alexcjohnson
Copy link
Collaborator

Thanks @shea-parkes - yes up to v2.3 we returned the wrapper but this was fixed in v2.4.0 via #1839

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants