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.
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?
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.