Skip to content

Commit 8528be2

Browse files
author
Thierry RAMORASOAVINA
committed
Detect "unhappy" installation states
- Warn when the version tuple (major, minor, patch) of Khiops does not match the Khiops Python library one - Fix the types of the returned objects in `_build_status_message` - Detect when the library is installed by something else than conda in a conda environment - Detect when the conda execution environment does not match the installation one
1 parent 645223c commit 8528be2

File tree

1 file changed

+98
-28
lines changed

1 file changed

+98
-28
lines changed

khiops/core/internals/runner.py

Lines changed: 98 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import uuid
2323
import warnings
2424
from abc import ABC, abstractmethod
25+
from importlib.metadata import PackageNotFoundError, files
2526
from pathlib import Path
2627

2728
import khiops
@@ -218,6 +219,29 @@ def _check_executable(bin_path):
218219
)
219220

220221

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+
221245
class KhiopsRunner(ABC):
222246
"""Abstract Khiops Python runner to be re-implemented"""
223247

@@ -294,7 +318,7 @@ def root_temp_dir(self, dir_path):
294318
)
295319
else:
296320
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)
298322
self._root_temp_dir = dir_path
299323

300324
def create_temp_file(self, prefix, suffix):
@@ -397,46 +421,85 @@ def _build_status_message(self):
397421
Returns
398422
-------
399423
tuple
400-
A 2-tuple containing:
424+
A 3-tuple containing in this order :
401425
- The status message
402-
- A list of warning messages
426+
- A list of error messages (str)
427+
- A list of warning messages (WarningMessage)
403428
"""
404-
# Capture the status of the the samples dir
429+
# Capture the status of the samples dir
405430
warning_list = []
406431
with warnings.catch_warnings(record=True) as caught_warnings:
407432
samples_dir_path = self.samples_dir
408433
if caught_warnings is not None:
409434
warning_list += caught_warnings
410435

436+
package_dir = Path(__file__).parents[2]
437+
411438
status_msg = "Khiops Python library settings\n"
412439
status_msg += f"version : {khiops.__version__}\n"
413440
status_msg += f"runner class : {self.__class__.__name__}\n"
414441
status_msg += f"root temp dir : {self.root_temp_dir}\n"
415442
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
418473

419474
def print_status(self):
420475
"""Prints the status of the runner to stdout"""
421476
# 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()
427479

428480
# Print status details
429481
print(status_msg, end="")
430482

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
437501
else:
438-
print("")
439-
return 0
502+
return 1
440503

441504
@abstractmethod
442505
def _initialize_khiops_version(self):
@@ -955,21 +1018,28 @@ def _initialize_khiops_version(self):
9551018

9561019
self._khiops_version = KhiopsVersion(khiops_version_str)
9571020

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
9591024
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+
):
9611030
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"
9641034
"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.",
9671037
stacklevel=3,
9681038
)
9691039

9701040
def _build_status_message(self):
9711041
# 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()
9731043

9741044
# Build the messages for install type and mpi
9751045
install_type_msg = _infer_khiops_installation_method()
@@ -997,10 +1067,10 @@ def _build_status_message(self):
9971067
if return_code == 0:
9981068
status_msg += stdout
9991069
else:
1000-
warning_list.append(stderr)
1070+
errors_list.append(stderr)
10011071
status_msg += "\n"
10021072

1003-
return status_msg, warning_list
1073+
return status_msg, errors_list, warnings_list
10041074

10051075
def _get_khiops_version(self):
10061076
# Initialize the first time it is called

0 commit comments

Comments
 (0)