i2mint / i2

Python Mint creation, manipulation, and use
Apache License 2.0
2 stars 1 forks source link

Bug with jupyter tab-completion when using `double_up_as_factory` #28

Closed thorwhalen closed 2 years ago

thorwhalen commented 2 years ago

Note: Posted on this stackoverflow question

My question is: What is happening here between jupyter and functools.wraps that produces the strangest of effects described below? How should I write my decorator "correctly" (besides the solution I show at the end) to avoid such effects?

The double_up_as_factory decorator below is meant to transform other decorator functions so that they can be used both as parameter-less decorators (e.g. @decorator) or "factories" (e.g. @decorator(...)).

from functools import partial, wraps

def _double_up_as_factory(wrapped=None, *args, __decorator_func=None, **kwargs):
    if wrapped is None:  # then we want a factory
        return partial(__decorator_func, **kwargs)
    else:
        return __decorator_func(wrapped, *args, **kwargs)

def double_up_as_factory(decorator_func):
    return wraps(decorator_func)(
        partial(_double_up_as_factory, __decorator_func=decorator_func)
    )

The above is a stripped down version of my actual function, but it works. Below is the minimum to produce the error, but not representative of an actual use (I can post that, but don't think it's necessary).

from i2.deco import double_up_as_factory

def foo(f=None, *, x=1):
    return f(x)

# This creates no problem (as long as the block below is commented out)
bar = double_up_as_factory(foo)

# Using double_up_as_factory with @ creates the problem
@double_up_as_factory
def same_foo(f=None, *, x):
    return f(x)

To test, in a notebook, do:

from PACKAGE import MODULE_WHERE_YOU_PUT_THE_ABOVE_CODE as m

m.[TRY_TAB_COMPLETING]

The first time I (after a kernel restart) I get the expected auto-suggestions, but the second time I try, I get nothing.

This problem

And the plot thickens: If I copy the code for wraps from functools within the module I'm defining double_up_as_factory in:

from functools import WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES, update_wrapper

def wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES):
    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)

the problem disappears.

I'm stumped as to why such minor differences would affect jupyter's tab-completion abilities!