|
22 | 22 | import uuid
|
23 | 23 | import warnings
|
24 | 24 | from abc import ABC, abstractmethod
|
| 25 | +from importlib.metadata import PackageNotFoundError, files |
25 | 26 | from pathlib import Path
|
26 | 27 |
|
27 | 28 | import khiops
|
@@ -218,6 +219,29 @@ def _check_executable(bin_path):
|
218 | 219 | )
|
219 | 220 |
|
220 | 221 |
|
| 222 | +def _get_current_module_installer(): |
| 223 | + """Tells how the module was installed |
| 224 | + in order to detect installation incompatibilities |
| 225 | +
|
| 226 | + Returns |
| 227 | + str |
| 228 | + 'pip' |
| 229 | + 'conda' |
| 230 | + or 'unknown' |
| 231 | + """ |
| 232 | + |
| 233 | + try: |
| 234 | + # Each time a python module is installed a 'dist-info' folder is created |
| 235 | + # Normalized files can be found in this folder |
| 236 | + installer_files = [path for path in files("khiops") if path.name == "INSTALLER"] |
| 237 | + if len(installer_files) > 0: |
| 238 | + return installer_files[0].read_text() |
| 239 | + except PackageNotFoundError: |
| 240 | + # The python module is not installed via standard tools like conda, pip... |
| 241 | + pass |
| 242 | + return "unknown" |
| 243 | + |
| 244 | + |
221 | 245 | class KhiopsRunner(ABC):
|
222 | 246 | """Abstract Khiops Python runner to be re-implemented"""
|
223 | 247 |
|
@@ -294,7 +318,7 @@ def root_temp_dir(self, dir_path):
|
294 | 318 | )
|
295 | 319 | else:
|
296 | 320 | os.makedirs(real_dir_path)
|
297 |
| - # There are no checks for non local filesystems (no `else` statement) |
| 321 | + # There are no checks for non-local filesystems (no `else` statement) |
298 | 322 | self._root_temp_dir = dir_path
|
299 | 323 |
|
300 | 324 | def create_temp_file(self, prefix, suffix):
|
@@ -397,46 +421,85 @@ def _build_status_message(self):
|
397 | 421 | Returns
|
398 | 422 | -------
|
399 | 423 | tuple
|
400 |
| - A 2-tuple containing: |
| 424 | + A 3-tuple containing in this order : |
401 | 425 | - The status message
|
402 |
| - - A list of warning messages |
| 426 | + - A list of error messages (str) |
| 427 | + - A list of warning messages (WarningMessage) |
403 | 428 | """
|
404 |
| - # Capture the status of the the samples dir |
| 429 | + # Capture the status of the samples dir |
405 | 430 | warning_list = []
|
406 | 431 | with warnings.catch_warnings(record=True) as caught_warnings:
|
407 | 432 | samples_dir_path = self.samples_dir
|
408 | 433 | if caught_warnings is not None:
|
409 | 434 | warning_list += caught_warnings
|
410 | 435 |
|
| 436 | + package_dir = Path(__file__).parents[2] |
| 437 | + |
411 | 438 | status_msg = "Khiops Python library settings\n"
|
412 | 439 | status_msg += f"version : {khiops.__version__}\n"
|
413 | 440 | status_msg += f"runner class : {self.__class__.__name__}\n"
|
414 | 441 | status_msg += f"root temp dir : {self.root_temp_dir}\n"
|
415 | 442 | status_msg += f"sample datasets dir : {samples_dir_path}\n"
|
416 |
| - status_msg += f"package dir : {Path(__file__).parents[2]}\n" |
417 |
| - return status_msg, warning_list |
| 443 | + status_msg += f"package dir : {package_dir}\n" |
| 444 | + |
| 445 | + errors_list = [] |
| 446 | + |
| 447 | + # Detect known incompatible installations |
| 448 | + if "CONDA_PREFIX" in os.environ: |
| 449 | + # If a conda environment is detected it must match the module installation |
| 450 | + if not package_dir.as_posix().startswith(os.environ["CONDA_PREFIX"]): |
| 451 | + error = ( |
| 452 | + f"Khiops Python library installation path '{package_dir}' " |
| 453 | + f"does not match the current Conda environment " |
| 454 | + f"'{os.environ['CONDA_PREFIX']}'. " |
| 455 | + f"Please install the Khiops Python library " |
| 456 | + f"in the current Conda environment.\n" |
| 457 | + ) |
| 458 | + errors_list.append(error) |
| 459 | + # Ensure no mix between conda and pip is detected |
| 460 | + current_module_installer = _get_current_module_installer() |
| 461 | + if current_module_installer != "conda": |
| 462 | + error = ( |
| 463 | + f"Khiops Python library installation was installed by " |
| 464 | + f"'{current_module_installer}' " |
| 465 | + f"while running in the Conda environment " |
| 466 | + f"'{os.environ['CONDA_PREFIX']}'. " |
| 467 | + f"Please install the Khiops Python library " |
| 468 | + f"using a conda installer.\n" |
| 469 | + ) |
| 470 | + errors_list.append(error) |
| 471 | + |
| 472 | + return status_msg, errors_list, warning_list |
418 | 473 |
|
419 | 474 | def print_status(self):
|
420 | 475 | """Prints the status of the runner to stdout"""
|
421 | 476 | # Obtain the status_msg, errors and warnings
|
422 |
| - try: |
423 |
| - status_msg, warning_list = self._build_status_message() |
424 |
| - except (KhiopsEnvironmentError, KhiopsRuntimeError) as error: |
425 |
| - print(f"Khiops Python library status KO: {error}") |
426 |
| - return 1 |
| 477 | + |
| 478 | + status_msg, errors_list, warnings_list = self._build_status_message() |
427 | 479 |
|
428 | 480 | # Print status details
|
429 | 481 | print(status_msg, end="")
|
430 | 482 |
|
431 |
| - # Print status |
432 |
| - print("Khiops Python library status OK", end="") |
433 |
| - if warning_list: |
434 |
| - print(", with warnings:") |
435 |
| - for warning in warning_list: |
436 |
| - print(f"warning: {warning.message}") |
| 483 | + print("Installation issues detected...") |
| 484 | + print("---") |
| 485 | + |
| 486 | + # Print the errors (if any) |
| 487 | + if errors_list: |
| 488 | + print("Errors were detected and need to be fixed:") |
| 489 | + for error in errors_list: |
| 490 | + print(f"\tError: {error}\n") |
| 491 | + |
| 492 | + # Print the warnings (if any) |
| 493 | + if warnings_list: |
| 494 | + print("Warnings:") |
| 495 | + for warning in warnings_list: |
| 496 | + print(f"\tWarning: {warning.message}\n") |
| 497 | + |
| 498 | + if len(errors_list) == 0: |
| 499 | + print("None\n") |
| 500 | + return 0 |
437 | 501 | else:
|
438 |
| - print("") |
439 |
| - return 0 |
| 502 | + return 1 |
440 | 503 |
|
441 | 504 | @abstractmethod
|
442 | 505 | def _initialize_khiops_version(self):
|
@@ -955,21 +1018,28 @@ def _initialize_khiops_version(self):
|
955 | 1018 |
|
956 | 1019 | self._khiops_version = KhiopsVersion(khiops_version_str)
|
957 | 1020 |
|
958 |
| - # Warn if the khiops version is too far from the Khiops Python library version |
| 1021 | + # Warn if the khiops version does not match the Khiops Python library version |
| 1022 | + # Currently the check is very strict |
| 1023 | + # (major.minor.patch must be the same), it could be relaxed later |
959 | 1024 | compatible_khiops_version = khiops.get_compatible_khiops_version()
|
960 |
| - if self._khiops_version.major > compatible_khiops_version.major: |
| 1025 | + if ( |
| 1026 | + (self._khiops_version.major != compatible_khiops_version.major) |
| 1027 | + or (self._khiops_version.minor != compatible_khiops_version.minor) |
| 1028 | + or (self._khiops_version.patch != compatible_khiops_version.patch) |
| 1029 | + ): |
961 | 1030 | warnings.warn(
|
962 |
| - f"Khiops version '{self._khiops_version}' is ahead of " |
963 |
| - f"the Khiops Python library version '{khiops.__version__}'. " |
| 1031 | + f"Khiops version '{self._khiops_version}' does not match " |
| 1032 | + f"the Khiops Python library version '{khiops.__version__}' " |
| 1033 | + "(different major.minor.patch version).\n" |
964 | 1034 | "There may be compatibility errors and "
|
965 |
| - "we recommend you to update to the latest Khiops Python " |
966 |
| - "library version. See https://khiops.org for more information.", |
| 1035 | + "we recommend to update either Khiops or the Khiops Python library.\n" |
| 1036 | + "See https://khiops.org for more information.", |
967 | 1037 | stacklevel=3,
|
968 | 1038 | )
|
969 | 1039 |
|
970 | 1040 | def _build_status_message(self):
|
971 | 1041 | # Call the parent's method
|
972 |
| - status_msg, warning_list = super()._build_status_message() |
| 1042 | + status_msg, errors_list, warnings_list = super()._build_status_message() |
973 | 1043 |
|
974 | 1044 | # Build the messages for install type and mpi
|
975 | 1045 | install_type_msg = _infer_khiops_installation_method()
|
@@ -997,10 +1067,10 @@ def _build_status_message(self):
|
997 | 1067 | if return_code == 0:
|
998 | 1068 | status_msg += stdout
|
999 | 1069 | else:
|
1000 |
| - warning_list.append(stderr) |
| 1070 | + errors_list.append(stderr) |
1001 | 1071 | status_msg += "\n"
|
1002 | 1072 |
|
1003 |
| - return status_msg, warning_list |
| 1073 | + return status_msg, errors_list, warnings_list |
1004 | 1074 |
|
1005 | 1075 | def _get_khiops_version(self):
|
1006 | 1076 | # Initialize the first time it is called
|
|
0 commit comments