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'