Search code examples
pythonmodulepackagedecoratorpython-decorators

How to properly use a decorator? (TypeError: wrapper() takes 0 positional arguments but 1 was given)


I'm trying to write a decorator that checks if specific packages are available before using a function.

In the example below, numpy should not throw an error but non_existent_test_package should inform the user that they need to install packages to use this functionality. The point of this is to reduce dependencies.

UPDATE based on @henry-harutyunyan suggestion


import numpy as np
import importlib

def check_available_packages(packages):
    if isinstance(packages,str):
        packages = [packages]
    packages = np.asarray(sorted(set(packages)))
    def wrapper(func):
        installed = list()
        for package in packages:
            try: 
                globals()[package] = importlib.import_module(package)
                installed.append(True)
            except ImportError:
                installed.append(False)
        installed = np.asarray(installed)
        assert np.all(installed), "Please install the following packages to use this functionality:\n{}".format(", ".join(map(lambda x: "'{}'".format(x), packages[~installed])))
        return func
    return wrapper

@check_available_packages(["numpy"])
def f():
    print("This worked")

@check_available_packages(["numpy", "non_existent_test_package"])
def f():
    print("This shouldn't work")

# ---------------------------------------------------------------------------
# AssertionError                            Traceback (most recent call last)
# <ipython-input-222-5e8224fb30bd> in <module>
#      23     print("This worked")
#      24 
# ---> 25 @check_available_packages(["numpy", "non_existent_test_package"])
#      26 def f():
#      27     print("This shouldn't work")

# <ipython-input-222-5e8224fb30bd> in wrapper(func)
#      15                 installed.append(False)
#      16         installed = np.asarray(installed)
# ---> 17         assert np.all(installed), "Please install the following packages to use this functionality:\n{}".format(", ".join(map(lambda x: "'{}'".format(x), packages[~installed])))
#      18         return func
#      19     return wrapper

# AssertionError: Please install the following packages to use this functionality:
# 'non_existent_test_package'

Now the decorator seems to be checking the packages exist at run time instead of when the function is actually called. How can I adjust this code?


Solution

  • This will work

    import numpy as np
    import importlib
    
    
    def check_available_packages(packages):
        if isinstance(packages, str):
            packages = [packages]
        packages = np.asarray(sorted(set(packages)))
    
        def decorator(func):
            def wrapper():
                installed = list()
                for package in packages:
                    try:
                        globals()[package] = importlib.import_module(package)
                        installed.append(True)
                    except ImportError:
                        installed.append(False)
                installed = np.asarray(installed)
                assert np.all(installed), "Please install the following packages to use this functionality:\n{}".format(
                    ", ".join(packages[~installed]))
                func()
    
            return wrapper
    
        return decorator
    
    
    @check_available_packages(["numpy"])
    def foo():
        print("This worked")
    
    
    @check_available_packages(["numpy", "non_existent_test_package"])
    def bar():
        print("This shouldn't work")
    
    
    foo()
    bar()
    

    The issue is that the wrapper() function you have is taking an argument, while by definition it doesn't need any. So passing _ in this statement wrapper(_) will do the job.

    _ is dummy, it can't be used, but it still is something. The IDEs won't complain about the unused variable either.

    To execute the decorator only when the function is called you need to use the decorator factory as above. See this reference for more details.