"""
Requirements Utilities
======================
Define the requirements utilities objects.
"""
from __future__ import annotations
import functools
import shutil
import subprocess
from colour.hints import (
Any,
Callable,
Literal,
)
from colour.utilities import CanonicalMapping
__author__ = "Colour Developers"
__copyright__ = "Copyright 2013 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"
__all__ = [
"is_ctlrender_installed",
"is_matplotlib_installed",
"is_networkx_installed",
"is_opencolorio_installed",
"is_openimageio_installed",
"is_pandas_installed",
"is_pydot_installed",
"is_tqdm_installed",
"is_trimesh_installed",
"is_xxhash_installed",
"REQUIREMENTS_TO_CALLABLE",
"required",
]
[docs]
def is_ctlrender_installed(raise_exception: bool = False) -> bool:
"""
Return whether *ctlrender* is installed and available.
Parameters
----------
raise_exception
Whether to raise an exception if *ctlrender* is unavailable.
Returns
-------
:class:`bool`
Whether *ctlrender* is installed.
Raises
------
:class:`ImportError`
If *ctlrender* is not installed.
"""
try: # pragma: no cover
stdout = subprocess.run(
["ctlrender", "-help"], # noqa: S603, S607
capture_output=True,
check=False,
).stdout.decode("utf-8")
if "transforms an image using one or more CTL scripts" not in stdout:
raise FileNotFoundError # noqa: TRY301
return True
except FileNotFoundError as error: # pragma: no cover
if raise_exception:
raise FileNotFoundError(
'"ctlrender" related API features are not available: '
f'"{error}".\nSee the installation guide for more information: '
"https://www.colour-science.org/installation-guide/"
) from error
return False
[docs]
def is_matplotlib_installed(raise_exception: bool = False) -> bool:
"""
Return whether *Matplotlib* is installed and available.
Parameters
----------
raise_exception
Whether to raise an exception if *Matplotlib* is unavailable.
Returns
-------
:class:`bool`
Whether *Matplotlib* is installed.
Raises
------
:class:`ImportError`
If *Matplotlib* is not installed.
"""
try: # pragma: no cover
import matplotlib as mpl # noqa: F401
return True
except ImportError as error: # pragma: no cover
if raise_exception:
raise ImportError(
'"Matplotlib" related API features are not available: '
f'"{error}".\nSee the installation guide for more information: '
"https://www.colour-science.org/installation-guide/"
) from error
return False
[docs]
def is_networkx_installed(raise_exception: bool = False) -> bool:
"""
Return whether *NetworkX* is installed and available.
Parameters
----------
raise_exception
Whether to raise an exception if *NetworkX* is unavailable.
Returns
-------
:class:`bool`
Whether *NetworkX* is installed.
Raises
------
:class:`ImportError`
If *NetworkX* is not installed.
"""
try: # pragma: no cover
import networkx as nx # noqa: F401
return True
except ImportError as error: # pragma: no cover
if raise_exception:
raise ImportError(
'"NetworkX" related API features, e.g., the automatic colour '
f'conversion graph, are not available: "{error}".\nPlease refer '
"to the installation guide for more information: "
"https://www.colour-science.org/installation-guide/"
) from error
return False
[docs]
def is_opencolorio_installed(raise_exception: bool = False) -> bool:
"""
Return whether *OpenColorIO* is installed and available.
Parameters
----------
raise_exception
Whether to raise an exception if *OpenColorIO* is unavailable.
Returns
-------
:class:`bool`
Whether *OpenColorIO* is installed.
Raises
------
:class:`ImportError`
If *OpenColorIO* is not installed.
"""
try: # pragma: no cover
import PyOpenColorIO # noqa: F401
return True
except ImportError as error: # pragma: no cover
if raise_exception:
raise ImportError(
'"OpenColorIO" related API features are not available: '
f'"{error}".\nSee the installation guide for more information: '
"https://www.colour-science.org/installation-guide/"
) from error
return False
[docs]
def is_openimageio_installed(raise_exception: bool = False) -> bool:
"""
Return whether *OpenImageIO* is installed and available.
Parameters
----------
raise_exception
Whether to raise an exception if *OpenImageIO* is unavailable.
Returns
-------
:class:`bool`
Whether *OpenImageIO* is installed.
Raises
------
:class:`ImportError`
If *OpenImageIO* is not installed.
"""
try: # pragma: no cover
import OpenImageIO # noqa: F401
return True
except ImportError as error: # pragma: no cover
if raise_exception:
raise ImportError(
'"OpenImageIO" related API features are not available: '
f'"{error}".\nSee the installation guide for more information: '
"https://www.colour-science.org/installation-guide/"
) from error
return False
[docs]
def is_pandas_installed(raise_exception: bool = False) -> bool:
"""
Return whether *Pandas* is installed and available.
Parameters
----------
raise_exception
Whether to raise an exception if *Pandas* is unavailable.
Returns
-------
:class:`bool`
Whether *Pandas* is installed.
Raises
------
:class:`ImportError`
If *Pandas* is not installed.
"""
try: # pragma: no cover
import pandas # noqa: F401, ICN001
return True
except ImportError as error: # pragma: no cover
if raise_exception:
raise ImportError(
f'"Pandas" related API features are not available: "{error}".\n'
"See the installation guide for more information: "
"https://www.colour-science.org/installation-guide/"
) from error
return False
[docs]
def is_pydot_installed(raise_exception: bool = False) -> bool:
"""
Return whether *Pydot* is installed and available. The presence of
*Graphviz* will also be tested.
Parameters
----------
raise_exception
Whether to raise an exception if *Pydot* is unavailable.
Returns
-------
:class:`bool`
Whether *Pydot* is installed.
Raises
------
:class:`ImportError`
If *Pydot* is not installed.
"""
try: # pragma: no cover
import pydot # noqa: F401
except ImportError as error: # pragma: no cover
if raise_exception:
raise ImportError(
'"Pydot" related API features are not available: '
f'"{error}".\nSee the installation guide for more information: '
"https://www.colour-science.org/installation-guide/"
) from error
if shutil.which("fdp") is not None:
return True
else:
if raise_exception:
raise RuntimeError(
'"Graphviz" is not installed, "Pydot" related API features '
"are not available!"
"\nSee the installation guide for more information: "
"https://www.colour-science.org/installation-guide/"
)
return False
[docs]
def is_tqdm_installed(raise_exception: bool = False) -> bool:
"""
Return whether *tqdm* is installed and available.
Parameters
----------
raise_exception
Whether to raise an exception if *tqdm* is unavailable.
Returns
-------
:class:`bool`
Whether *tqdm* is installed.
Raises
------
:class:`ImportError`
If *tqdm* is not installed.
"""
try: # pragma: no cover
import tqdm # noqa: F401
return True
except ImportError as error: # pragma: no cover
if raise_exception:
raise ImportError(
f'"tqdm" related API features are not available: "{error}".\n'
"See the installation guide for more information: "
"https://www.colour-science.org/installation-guide/"
) from error
return False
[docs]
def is_trimesh_installed(raise_exception: bool = False) -> bool:
"""
Return whether *Trimesh* is installed and available.
Parameters
----------
raise_exception
Whether to raise an exception if *Trimesh* is unavailable.
Returns
-------
:class:`bool`
Whether *Trimesh* is installed.
Raises
------
:class:`ImportError`
If *Trimesh* is not installed.
"""
try: # pragma: no cover
import trimesh # noqa: F401
return True
except ImportError as error: # pragma: no cover
if raise_exception:
raise ImportError(
'"Trimesh" related API features are not available: '
f'"{error}".\nSee the installation guide for more information: '
"https://www.colour-science.org/installation-guide/"
) from error
return False
[docs]
def is_xxhash_installed(raise_exception: bool = False) -> bool:
"""
Return whether *xxhash* is installed and available.
Parameters
----------
raise_exception
Whether to raise an exception if *xxhash* is unavailable.
Returns
-------
:class:`bool`
Whether *xxhash* is installed.
Raises
------
:class:`ImportError`
If *xxhash* is not installed.
"""
try: # pragma: no cover
import xxhash # noqa: F401
return True
except ImportError as error: # pragma: no cover
if raise_exception:
raise ImportError(
'"xxhash" related API features are not available: '
f'"{error}".\nSee the installation guide for more information: '
"https://www.colour-science.org/installation-guide/"
) from error
return False
REQUIREMENTS_TO_CALLABLE: CanonicalMapping = CanonicalMapping(
{
"ctlrender": is_ctlrender_installed,
"Matplotlib": is_matplotlib_installed,
"NetworkX": is_networkx_installed,
"OpenColorIO": is_opencolorio_installed,
"OpenImageIO": is_openimageio_installed,
"Pandas": is_pandas_installed,
"Pydot": is_pydot_installed,
"tqdm": is_tqdm_installed,
"trimesh": is_trimesh_installed,
"xxhash": is_xxhash_installed,
}
)
"""
Mapping of requirements to their respective callables.
"""
[docs]
def required(
*requirements: Literal[
"ctlrender",
"Matplotlib",
"NetworkX",
"OpenColorIO",
"OpenImageIO",
"Pandas",
"Pydot",
"tqdm",
"trimesh",
"xxhash",
],
) -> Callable:
"""
Decorate a function to check whether various ancillary package requirements
are satisfied.
Other Parameters
----------------
requirements
Requirements to check whether they are satisfied.
Returns
-------
Callable
"""
def wrapper(function: Callable) -> Callable:
"""Wrap given function wrapper."""
@functools.wraps(function)
def wrapped(*args: Any, **kwargs: Any) -> Any:
"""Wrap given function."""
for requirement in requirements:
REQUIREMENTS_TO_CALLABLE[requirement](raise_exception=True)
return function(*args, **kwargs)
return wrapped
return wrapper