|
| 1 | +# Black-box ABM Calibration Kit (Black-it) |
| 2 | +# Copyright (C) 2021-2023 Banca d'Italia |
| 3 | +# |
| 4 | +# This program is free software: you can redistribute it and/or modify |
| 5 | +# it under the terms of the GNU Affero General Public License as |
| 6 | +# published by the Free Software Foundation, either version 3 of the |
| 7 | +# License, or (at your option) any later version. |
| 8 | +# |
| 9 | +# This program is distributed in the hope that it will be useful, |
| 10 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 | +# GNU Affero General Public License for more details. |
| 13 | +# |
| 14 | +# You should have received a copy of the GNU Affero General Public License |
| 15 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 16 | + |
| 17 | +""" |
| 18 | +Python module to handle extras dependencies loading and import errors. |
| 19 | +
|
| 20 | +This is a private module of the library. There should be no point in using it directly from client code. |
| 21 | +""" |
| 22 | + |
| 23 | +import sys |
| 24 | +from typing import Optional |
| 25 | + |
| 26 | +# known extras and their dependencies |
| 27 | +_GPY_PACKAGE_NAME = "GPy" |
| 28 | +_GP_SAMPLER_EXTRA_NAME = "gp-sampler" |
| 29 | + |
| 30 | +_XGBOOST_PACKAGE_NAME = "xgboost" |
| 31 | +_XGBOOST_SAMPLER_EXTRA_NAME = "xgboost-sampler" |
| 32 | + |
| 33 | + |
| 34 | +class DependencyNotInstalled(Exception): |
| 35 | + """Library exception for when a required dependency is not installed.""" |
| 36 | + |
| 37 | + def __init__(self, component_name: str, package_name: str, extra_name: str) -> None: |
| 38 | + """Initialize the exception object.""" |
| 39 | + message = ( |
| 40 | + f"Cannot import package '{package_name}', required by component {component_name}. " |
| 41 | + f"To solve the issue, you can install the extra '{extra_name}': pip install black-it[{extra_name}]" |
| 42 | + ) |
| 43 | + super().__init__(message) |
| 44 | + |
| 45 | + |
| 46 | +class GPyNotSupportedOnPy311Exception(Exception): |
| 47 | + """Specific exception class for import error of GPy on Python 3.11.""" |
| 48 | + |
| 49 | + __ERROR_MSG = ( |
| 50 | + f"The GaussianProcessSampler depends on '{_GPY_PACKAGE_NAME}', which is not supported on Python 3.11; " |
| 51 | + f"see https://github.com/bancaditalia/black-it/issues/36" |
| 52 | + ) |
| 53 | + |
| 54 | + def __init__(self) -> None: |
| 55 | + """Initialize the exception object.""" |
| 56 | + super().__init__(self.__ERROR_MSG) |
| 57 | + |
| 58 | + |
| 59 | +def _check_import_error_else_raise_exception( |
| 60 | + import_error: Optional[ImportError], |
| 61 | + component_name: str, |
| 62 | + package_name: str, |
| 63 | + black_it_extra_name: str, |
| 64 | +) -> None: |
| 65 | + """ |
| 66 | + Check an import error; raise the DependencyNotInstalled exception with a useful message. |
| 67 | +
|
| 68 | + Args: |
| 69 | + import_error: the ImportError object generated by the failed attempt. If None, then no error occurred. |
| 70 | + component_name: the component for which the dependency is needed |
| 71 | + package_name: the Python package name of the dependency |
| 72 | + black_it_extra_name: the name of the black-it extra to install to solve the issue. |
| 73 | + """ |
| 74 | + if import_error is None: |
| 75 | + # nothing to do. |
| 76 | + return |
| 77 | + |
| 78 | + # an import error happened; we need to raise error to the caller |
| 79 | + raise DependencyNotInstalled(component_name, package_name, black_it_extra_name) |
| 80 | + |
| 81 | + |
| 82 | +def _check_gpy_import_error_else_raise_exception( |
| 83 | + import_error: Optional[ImportError], |
| 84 | + component_name: str, |
| 85 | + package_name: str, |
| 86 | + black_it_extra_name: str, |
| 87 | +) -> None: |
| 88 | + """ |
| 89 | + Check GPy import error and if an error occurred, raise erorr with a useful error message. |
| 90 | +
|
| 91 | + We need to handle two cases: |
| 92 | +
|
| 93 | + - the user is using Python 3.11: the GPy package cannot be installed there; |
| 94 | + see https://github.com/SheffieldML/GPy/issues/998 |
| 95 | + - the user did not install the 'gp-sampler' extra. |
| 96 | +
|
| 97 | + Args: |
| 98 | + import_error: the ImportError object generated by the failed attempt. If None, then no error occurred. |
| 99 | + component_name: the component for which the dependency is needed |
| 100 | + package_name: the Python package name of the dependency |
| 101 | + black_it_extra_name: the name of the black-it extra to install to solve the issue. |
| 102 | + """ |
| 103 | + if import_error is None: |
| 104 | + # nothing to do. |
| 105 | + return |
| 106 | + |
| 107 | + if sys.version_info == (3, 11): |
| 108 | + raise GPyNotSupportedOnPy311Exception() |
| 109 | + |
| 110 | + _check_import_error_else_raise_exception( |
| 111 | + import_error, component_name, package_name, black_it_extra_name |
| 112 | + ) |
0 commit comments