Optional dependencies#
Often our packages contain features that require extra packages. If a feature is not regarded as core functionality, we’ll make it an optional feature. This allows us to keep required dependencies at minimum, since the more dependencies we add, the higher the chance that our users will encounter problems during installation.
If a function has optional dependencies, you can use the @requires decorator. Alternatively, you might use the check_installed function. Examples below.
@requires#
Decorate a function with @requires and pass a list of the packages.
from ploomber_core.dependencies import requires
@requires(["some_package"])
def some_optional_functionality(x, y):
import some_package
return some_package.sum(x, y)
Since some_package is not installed, calling the function raises an error (the function is not executed):
some_optional_functionality()
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[2], line 1
----> 1 some_optional_functionality()
File ~/checkouts/readthedocs.org/user_builds/ploomber-core/checkouts/latest/src/ploomber_core/dependencies.py:34, in requires.<locals>.decorator.<locals>.wrapper(*args, **kwargs)
32 @wraps(f)
33 def wrapper(*args, **kwargs):
---> 34 check_installed(
35 pkgs=pkgs,
36 name=name or f.__name__,
37 extra_msg=extra_msg,
38 pip_names=pip_names,
39 )
40 return f(*args, **kwargs)
File ~/checkouts/readthedocs.org/user_builds/ploomber-core/checkouts/latest/src/ploomber_core/dependencies.py:83, in check_installed(pkgs, name, extra_msg, pip_names)
76 if any(is_pkg_missing):
77 missing_pkgs = [
78 name
79 for name, is_missing in zip(pip_names or pkgs, is_pkg_missing)
80 if is_missing
81 ]
---> 83 raise ModuleNotFoundError(
84 _make_requires_error_message(missing_pkgs, name, extra_msg)
85 )
ModuleNotFoundError: 'some_package' is required to use 'some_optional_functionality'. Install with: pip install 'some_package'
Note
We should only use the @requires decorator when certain parts of the project require extra packages. If the extra package is only required in a tutorial then we should include it in the installation step in the tutorial itself,
Customizing the error message#
If you need to provide more details in the error message (for example, if the package has particular installation instructions):
@requires(["some_package"], extra_msg="Some other relevant information")
def some_optional_functionality(x, y):
import some_package
return some_package.sum(x, y)
some_optional_functionality()
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[4], line 1
----> 1 some_optional_functionality()
File ~/checkouts/readthedocs.org/user_builds/ploomber-core/checkouts/latest/src/ploomber_core/dependencies.py:34, in requires.<locals>.decorator.<locals>.wrapper(*args, **kwargs)
32 @wraps(f)
33 def wrapper(*args, **kwargs):
---> 34 check_installed(
35 pkgs=pkgs,
36 name=name or f.__name__,
37 extra_msg=extra_msg,
38 pip_names=pip_names,
39 )
40 return f(*args, **kwargs)
File ~/checkouts/readthedocs.org/user_builds/ploomber-core/checkouts/latest/src/ploomber_core/dependencies.py:83, in check_installed(pkgs, name, extra_msg, pip_names)
76 if any(is_pkg_missing):
77 missing_pkgs = [
78 name
79 for name, is_missing in zip(pip_names or pkgs, is_pkg_missing)
80 if is_missing
81 ]
---> 83 raise ModuleNotFoundError(
84 _make_requires_error_message(missing_pkgs, name, extra_msg)
85 )
ModuleNotFoundError: 'some_package' is required to use 'some_optional_functionality'. Install with: pip install 'some_package'
Some other relevant information
Ensuring accurate pip names#
Sometimes the name of the package does not match the name of the module installed. For example, you install scikit-learn with:
pip install scikit-learn
But import it with:
import sklearn
In such cases, you can pass the pip names:
@requires(["sklean"], pip_names=["scikit-learn"])
def some_optional_functionality(x, y):
import sklearn
return sklearn.stuff(x, y)
some_optional_functionality()
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[6], line 1
----> 1 some_optional_functionality()
File ~/checkouts/readthedocs.org/user_builds/ploomber-core/checkouts/latest/src/ploomber_core/dependencies.py:34, in requires.<locals>.decorator.<locals>.wrapper(*args, **kwargs)
32 @wraps(f)
33 def wrapper(*args, **kwargs):
---> 34 check_installed(
35 pkgs=pkgs,
36 name=name or f.__name__,
37 extra_msg=extra_msg,
38 pip_names=pip_names,
39 )
40 return f(*args, **kwargs)
File ~/checkouts/readthedocs.org/user_builds/ploomber-core/checkouts/latest/src/ploomber_core/dependencies.py:83, in check_installed(pkgs, name, extra_msg, pip_names)
76 if any(is_pkg_missing):
77 missing_pkgs = [
78 name
79 for name, is_missing in zip(pip_names or pkgs, is_pkg_missing)
80 if is_missing
81 ]
---> 83 raise ModuleNotFoundError(
84 _make_requires_error_message(missing_pkgs, name, extra_msg)
85 )
ModuleNotFoundError: 'scikit-learn' is required to use 'some_optional_functionality'. Install with: pip install 'scikit-learn'
Classes#
Classes are supported too, decorate the __init__ method, and pass the name of the class:
class SomeClass:
@requires(["some_package"], name="SomeClass")
def __init__(self):
pass
obj = SomeClass()
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[8], line 1
----> 1 obj = SomeClass()
File ~/checkouts/readthedocs.org/user_builds/ploomber-core/checkouts/latest/src/ploomber_core/dependencies.py:34, in requires.<locals>.decorator.<locals>.wrapper(*args, **kwargs)
32 @wraps(f)
33 def wrapper(*args, **kwargs):
---> 34 check_installed(
35 pkgs=pkgs,
36 name=name or f.__name__,
37 extra_msg=extra_msg,
38 pip_names=pip_names,
39 )
40 return f(*args, **kwargs)
File ~/checkouts/readthedocs.org/user_builds/ploomber-core/checkouts/latest/src/ploomber_core/dependencies.py:83, in check_installed(pkgs, name, extra_msg, pip_names)
76 if any(is_pkg_missing):
77 missing_pkgs = [
78 name
79 for name, is_missing in zip(pip_names or pkgs, is_pkg_missing)
80 if is_missing
81 ]
---> 83 raise ModuleNotFoundError(
84 _make_requires_error_message(missing_pkgs, name, extra_msg)
85 )
ModuleNotFoundError: 'some_package' is required to use 'SomeClass'. Install with: pip install 'some_package'
check_installed#
Added in version 0.2.7.
In cases where you cannot decorate a function, you can use check_installed, which takes the same arguments. The only difference is that name is mandatory.
from ploomber_core.dependencies import check_installed
check_installed(["package"], "some feature")
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[10], line 1
----> 1 check_installed(["package"], "some feature")
File ~/checkouts/readthedocs.org/user_builds/ploomber-core/checkouts/latest/src/ploomber_core/dependencies.py:83, in check_installed(pkgs, name, extra_msg, pip_names)
76 if any(is_pkg_missing):
77 missing_pkgs = [
78 name
79 for name, is_missing in zip(pip_names or pkgs, is_pkg_missing)
80 if is_missing
81 ]
---> 83 raise ModuleNotFoundError(
84 _make_requires_error_message(missing_pkgs, name, extra_msg)
85 )
ModuleNotFoundError: 'package' is required to use 'some feature'. Install with: pip install 'package'