Skip to content

Commit

Permalink
feat: improve JVM exceptions (#1037)
Browse files Browse the repository at this point in the history
This PR improves the compatibility JVM error messages.
  • Loading branch information
zepfred authored Aug 13, 2024
1 parent 189b894 commit df74d42
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 11 deletions.
3 changes: 2 additions & 1 deletion python/jpyinterpreter/src/main/python/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""
This module acts as an interface to the Python bytecode to Java bytecode interpreter
"""
from .jvm_setup import init, set_class_output_directory, get_path
from .jvm_setup import init, set_class_output_directory, get_path, ensure_valid_jvm, get_default_jvm_path, \
InvalidJVMVersionError
from .annotations import JavaAnnotation, AnnotationValueSupplier, add_class_annotation, add_java_interface
from .conversions import (convert_to_java_python_like_object, unwrap_python_like_object,
update_python_object_from_java, is_c_native, add_python_java_type_mapping)
Expand Down
43 changes: 35 additions & 8 deletions python/jpyinterpreter/src/main/python/jvm_setup.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import pathlib
import jpype
import jpype.imports
import importlib.resources
import os
import locale
import os
import pathlib
from typing import List, ContextManager

import jpype
import jpype.imports


def _normalize_path(path):
"""Normalize a path by ensuring it is a string.
Expand Down Expand Up @@ -72,8 +73,10 @@ def init(*args, path: List[str] = None, include_translator_jars: bool = True,

jpype.startJVM(*args, *extra_jvm_args, classpath=path, convertStrings=True) # noqa

ensure_valid_jvm()

if class_output_path is not None:
from ai.timefold.jpyinterpreter import InterpreterStartupOptions # noqa
from ai.timefold.jpyinterpreter import InterpreterStartupOptions # noqa
InterpreterStartupOptions.classOutputRootPath = class_output_path

import ai.timefold.jpyinterpreter.CPythonBackedPythonInterpreter as CPythonBackedPythonInterpreter
Expand Down Expand Up @@ -260,7 +263,6 @@ def apply(self, code_object, function_globals, closure, name):
return out



@jpype.JImplements('ai.timefold.jpyinterpreter.util.function.PentaFunction', deferred=True)
class ImportModule:
@jpype.JOverride()
Expand All @@ -276,6 +278,31 @@ def apply(self, module_name, globals_map, locals_map, from_list, level):
)


class InvalidJVMVersionError(Exception):
pass


def ensure_valid_jvm(runtime=None):
if runtime is None:
import java.lang.Runtime as runtime
try:
version = runtime.version().feature()
if version < 17:
raise InvalidJVMVersionError(
f"Timefold Solver for Python requires JVM (java) version 17 or later. Your JVM version {version} is not supported. Maybe use sdkman (https://sdkman.io) to install a more modern version of Java.")
except AttributeError:
raise InvalidJVMVersionError(
f"Timefold Solver for Python requires JVM (java) version 17 or later. Your JVM version is not supported. Maybe use sdkman (https://sdkman.io) to install a more modern version of Java.")


def get_default_jvm_path(jvm_getter=jpype.getDefaultJVMPath):
try:
return jvm_getter()
except jpype.JVMNotFoundException:
raise InvalidJVMVersionError(
f"Timefold Solver for Python requires JVM (java) version 17 or later. You have none installed. Maybe use sdkman (https://sdkman.io) to install a more modern version of Java.")


def ensure_init():
"""Start the JVM if it isn't started; does nothing otherwise
Expand All @@ -284,7 +311,7 @@ def ensure_init():
:return: None
"""
if jpype.isJVMStarted(): # noqa
if jpype.isJVMStarted(): # noqa
return
else:
init()
Expand All @@ -293,5 +320,5 @@ def ensure_init():
def set_class_output_directory(path: pathlib.Path):
ensure_init()

from ai.timefold.jpyinterpreter import PythonBytecodeToJavaBytecodeTranslator # noqa
from ai.timefold.jpyinterpreter import PythonBytecodeToJavaBytecodeTranslator # noqa
PythonBytecodeToJavaBytecodeTranslator.classOutputRootPath = path
44 changes: 44 additions & 0 deletions python/jpyinterpreter/tests/test_jvm_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import pytest
import jpyinterpreter
import jpype

class Java17Runtime:
def version(self):
class Version:
def feature(self):
return 17
return Version()

class Java10Runtime:
def version(self):
class Version:
def feature(self):
return 10
return Version()

class Java9Runtime:
def version(self):
class Version:
def major(self):
return 17
return Version()

class Java8Runtime:
pass

def test_jvm_setup():
jpyinterpreter.ensure_valid_jvm(Java17Runtime())
with pytest.raises(jpyinterpreter.InvalidJVMVersionError):
jpyinterpreter.ensure_valid_jvm(Java8Runtime())
with pytest.raises(jpyinterpreter.InvalidJVMVersionError):
jpyinterpreter.ensure_valid_jvm(Java9Runtime())
with pytest.raises(jpyinterpreter.InvalidJVMVersionError):
jpyinterpreter.ensure_valid_jvm(Java10Runtime())

def jvm_not_found():
raise jpype.JVMNotFoundException()

def test_jvm_get_default_jvm_path():
jpyinterpreter.get_default_jvm_path()
with pytest.raises(jpyinterpreter.InvalidJVMVersionError):
jpyinterpreter.get_default_jvm_path(jvm_not_found)
5 changes: 3 additions & 2 deletions python/python-core/src/main/python/_timefold_java_interop.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,18 @@ def init(*args, path: list[str] = None, include_timefold_jars: bool = True) -> N
If the Timefold dependencies should be added to `path`.
Defaults to True.
"""
from _jpyinterpreter import init
from _jpyinterpreter import init, get_default_jvm_path

if jpype.isJVMStarted(): # noqa
raise RuntimeError('JVM already started. Maybe call init before timefold.solver.types imports?')

if path is None:
include_timefold_jars = True
path = []
if include_timefold_jars:
path = path + extract_timefold_jars()
if len(args) == 0:
args = [jpype.getDefaultJVMPath()]
args = [get_default_jvm_path()]
init(*args, path=path, include_translator_jars=False)

from ai.timefold.solver.python.logging import PythonDelegateAppender
Expand Down

0 comments on commit df74d42

Please sign in to comment.