Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor OpenEye checks to only warn if installed and unlicensed #1426

Merged
merged 4 commits into from
Oct 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/releasehistory.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 26 additions & 15 deletions openff/toolkit/utils/openeye_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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

Expand Down
20 changes: 8 additions & 12 deletions openff/toolkit/utils/toolkit_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down