diff --git a/docs/releasehistory.md b/docs/releasehistory.md index d6d0e22d1..a05afe7eb 100644 --- a/docs/releasehistory.md +++ b/docs/releasehistory.md @@ -14,6 +14,9 @@ Releases follow the `major.minor.micro` scheme recommended by [PEP440](https://w ### Bug fixes - [PR #1417](https://github.com/openforcefield/openff-toolkit/pull/1417): Ensure the properties dict is copied when a `Molecule` is. +### Improved documentation and warnings +- [PR #1426](https://github.com/openforcefield/openff-toolkit/pull/1426): A warning about OpenEye Toolkits being unavailable is only emitted when they are installed but the license file is not found. + ## 0.11.1 Minor release forbidding loading radicals ## Behavior changes diff --git a/openff/toolkit/utils/openeye_wrapper.py b/openff/toolkit/utils/openeye_wrapper.py index 0b59ec61d..3cd82908b 100644 --- a/openff/toolkit/utils/openeye_wrapper.py +++ b/openff/toolkit/utils/openeye_wrapper.py @@ -13,7 +13,7 @@ import tempfile from collections import defaultdict from functools import wraps -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional import numpy as np from cachetools import LRUCache, cached @@ -70,16 +70,19 @@ class OpenEyeToolkitWrapper(base_wrapper.ToolkitWrapper): _toolkit_name = "OpenEye Toolkit" _toolkit_installation_instructions = ( - "The OpenEye toolkit requires a (free for academics) license, and can be " - "found at: " - "https://docs.eyesopen.com/toolkits/python/quickstart-python/install.html" + "The OpenEye Toolkits can be installed via " + "`conda install openeye-toolkits -c openeye`" + ) + _toolkit_license_instructions = ( + "The OpenEye Toolkits require a (free for academics) license, see " + "https://docs.eyesopen.com/toolkits/python/quickstart-python/license.html" ) # This could belong to ToolkitWrapper, although it seems strange # to carry that data for open-source toolkits - _is_licensed = None + _is_licensed: Optional[bool] = None # Only for OpenEye is there potentially a difference between # being available and installed - _is_installed = None + _is_installed: Optional[bool] = None _license_functions = { "oechem": "OEChemIsLicensed", "oequacpac": "OEQuacPacIsLicensed", @@ -140,24 +143,27 @@ def __init__(self): # check if the toolkit can be loaded if not self.is_available(): - msg = ( - f"The required toolkit {self._toolkit_name} is not " - f"available. {self._toolkit_installation_instructions}" - ) if self._is_installed is False: - raise ToolkitUnavailableException(msg) + raise ToolkitUnavailableException( + "OpenEye Toolkits are not installed." + + self._toolkit_installation_instructions + ) if self._is_licensed is False: - raise LicenseError(msg) + raise LicenseError( + "The OpenEye Toolkits are found to be installed but not licensed and " + + "therefore will not be used.\n" + + self._toolkit_license_instructions + ) from openeye import __version__ as openeye_version self._toolkit_version = openeye_version @classmethod - def _check_licenses(cls): + def _check_licenses(cls) -> bool: """Check license of all known OpenEye tools. Returns True if any are found - to be licensed, False if any are not.""" - for (tool, license_func) in cls._license_functions.items(): + to be licensed, False if all are not.""" + for tool, license_func in cls._license_functions.items(): try: module = importlib.import_module("openeye." + tool) except (ImportError, ModuleNotFoundError): @@ -192,6 +198,11 @@ def is_available(cls): importlib.import_module("openeye." + tool) except (ImportError, ModuleNotFoundError): cls._is_installed = False + if cls._is_installed: + if cls._is_licensed: + cls._is_available = True + else: + cls._is_available = False cls._is_available = cls._is_installed and cls._is_licensed return cls._is_available diff --git a/openff/toolkit/utils/toolkit_registry.py b/openff/toolkit/utils/toolkit_registry.py index 19e3fa0f1..cb1fe1a08 100644 --- a/openff/toolkit/utils/toolkit_registry.py +++ b/openff/toolkit/utils/toolkit_registry.py @@ -12,6 +12,7 @@ from openff.toolkit.utils.builtin_wrapper import BuiltInToolkitWrapper from openff.toolkit.utils.exceptions import ( InvalidToolkitError, + LicenseError, ToolkitUnavailableException, ) from openff.toolkit.utils.openeye_wrapper import OpenEyeToolkitWrapper @@ -162,24 +163,19 @@ def register_toolkit(self, toolkit_wrapper, exception_if_unavailable=True): if isinstance(toolkit_wrapper, type): try: toolkit_wrapper = toolkit_wrapper() + + # This exception can be raised by OpenEyeToolkitWrapper + except LicenseError as license_exception: + if exception_if_unavailable: + raise ToolkitUnavailableException(license_exception.msg) + else: + logger.warning(license_exception) except ToolkitUnavailableException: msg = "Unable to load toolkit '{}'. ".format( toolkit_wrapper._toolkit_name ) if exception_if_unavailable: raise ToolkitUnavailableException(msg) - else: - if "OpenEye" in msg: - msg += ( - "The Open Force Field Toolkit does not require the OpenEye Toolkits, and can " - "use RDKit/AmberTools instead. However, if you have a valid license for the " - "OpenEye Toolkits, consider installing them for faster performance and additional " - "file format support: " - "https://docs.eyesopen.com/toolkits/python/quickstart-python/linuxosx.html " - "OpenEye offers free Toolkit licenses for academics: " - "https://www.eyesopen.com/academic-licensing" - ) - logger.warning(f"Warning: {msg}") return # Add toolkit to the registry.