Coverage for rulekit/main.py: 81%
89 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-07 11:26 +0000
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-07 11:26 +0000
1"""Contains classes for initializing RuleKit Java backend
2"""
3import logging
4import os
5import re
6import zipfile
7from enum import Enum
8from subprocess import PIPE
9from subprocess import Popen
10from subprocess import STDOUT
11from typing import Optional
13import jpype.imports
15from rulekit._logging import _RuleKitJavaLoggerConfig
17__RULEKIT_RELEASE_VERSION__ = "2.1.24"
18__VERSION__ = f"{__RULEKIT_RELEASE_VERSION__}.1"
21class JRE_Type(Enum): # pylint: disable=invalid-name
22 """:meta private:"""
24 OPEN_JDK = "open_jdk"
25 ORACLE = "oracle"
28class RuleKit:
29 """Class used for initializing RuleKit. It starts JVM underhood and setups it
30 with jars.
32 .. note:: Since version 1.7.0 there is no need to manually initialize RuleKit.
33 You may just skip the **RuleKit.init()** line. However in certain scenarios when
34 you want use a custom RuleKit jar file or modify Java VM parameters, this class
35 can be used.
38 Attributes
39 ----------
40 version : str
41 version of RuleKit jar used by wrapper (not equal to python package version).
42 """
44 version: str
45 _logger: logging.Logger = None
46 _jar_dir_path: str
47 _class_path: str
48 _rulekit_jar_file_path: str
49 _jre_type: JRE_Type
50 _java_logger_config: Optional[_RuleKitJavaLoggerConfig] = None
51 initialized: bool = False
53 @staticmethod
54 def _detect_jre_type():
55 try:
56 with Popen(["java", "-version"], stderr=STDOUT, stdout=PIPE) as output:
57 output = str(output.communicate()[0])
58 if "openjdk" in output:
59 RuleKit._jre_type = JRE_Type.OPEN_JDK
60 else:
61 RuleKit._jre_type = JRE_Type.ORACLE
62 except FileNotFoundError as error:
63 raise RuntimeError(
64 "RuletKit requires java JRE to be installed (version 1.8.0 recommended)"
65 ) from error
67 @staticmethod
68 def init(
69 initial_heap_size: int = None,
70 max_heap_size: int = None,
71 rulekit_jar_file_path: str = None,
72 ):
73 """Initialize package.
75 This method configure and starts JVM and load RuleKit jar file.
77 .. note:: Since version 1.7.0 it don't have to be called before using any
78 operator class. However in certain scenarios when you want use a custom RuleKit
79 jar file or modify Java VM parameters, this method can be used.
81 Parameters
82 ----------
83 initial_heap_size : int
84 JVM initial heap size in mb
85 max_heap_size : int
86 JVM max heap size in mb
87 rulekit_jar_file_path : str
88 Path to the RuleKit jar file. This parameters.
89 .. note::
90 You probably don't need to use this parameter unless you want to use
91 your own custom version of RuleKit jar file. Otherwise leave it as it
92 is and the package will use the official RuleKit release jar file.
94 Raises
95 ------
96 Exception
97 If failed to load RuleKit jar file.
98 """
99 if RuleKit.initialized:
100 return
101 RuleKit._setup_logger()
103 RuleKit._detect_jre_type()
104 current_path: str = os.path.dirname(os.path.realpath(__file__))
105 RuleKit._jar_dir_path = f"{current_path}/jar"
106 class_path_separator = os.pathsep
107 try:
108 jar_file_name: str = "rulekit-" + f"{__RULEKIT_RELEASE_VERSION__}-all.jar"
109 RuleKit._rulekit_jar_file_path = os.path.join(
110 RuleKit._jar_dir_path, jar_file_name
111 )
112 jars_paths: list[str] = [RuleKit._rulekit_jar_file_path]
113 if rulekit_jar_file_path is not None:
114 jars_paths.remove(RuleKit._rulekit_jar_file_path)
115 jars_paths.append(rulekit_jar_file_path)
116 RuleKit._rulekit_jar_file_path = rulekit_jar_file_path
117 RuleKit._class_path = f"{str.join(class_path_separator, jars_paths)}"
118 except IndexError as error:
119 RuleKit._logger.error("Failed to load jar files")
120 raise RuntimeError(
121 f"""\n
122Failed to load RuleKit jar file. Check if valid rulekit jar file is present in
123"{RuleKit._jar_dir_path}" directory.
125If you're running this package for the first time you need to download RuleKit jar
126file by running:
127 python -m rulekit download_jar
128 """
129 ) from error
130 RuleKit._read_versions()
131 RuleKit._launch_jvm(initial_heap_size, max_heap_size)
132 RuleKit.initialized = True
134 @staticmethod
135 def _setup_logger():
136 logging.basicConfig()
137 RuleKit._logger = logging.getLogger("RuleKit")
139 @staticmethod
140 def _read_versions():
141 with zipfile.ZipFile(RuleKit._rulekit_jar_file_path, "r") as jar_archive:
142 try:
143 manifest_file_content: str = jar_archive.read(
144 "META-INF/MANIFEST.MF"
145 ).decode("utf-8")
146 RuleKit.version = re.findall(
147 r"Implementation-Version: \S+\r", manifest_file_content
148 )[0].split(" ")[1]
149 except Exception as error:
150 RuleKit._logger.error("Failed to read RuleKit versions from jar file")
151 RuleKit._logger.error(error)
152 raise error
154 @staticmethod
155 def _launch_jvm(initial_heap_size: int, max_heap_size: int):
156 if jpype.isJVMStarted():
157 RuleKit._logger.info("JVM already running")
158 else:
159 params = [
160 f"-Djava.class.path={RuleKit._class_path}",
161 ]
162 if initial_heap_size is not None:
163 params.append(f"-Xms{initial_heap_size}m")
164 if max_heap_size is not None:
165 params.append(f"-Xmx{max_heap_size}m")
166 jpype.startJVM(jpype.getDefaultJVMPath(), *params, convertStrings=False)
168 @staticmethod
169 def configure_java_logger(
170 log_file_path: str,
171 verbosity_level: int = 1,
172 ):
173 """Enable Java debug logging. You probably don't need to use this
174 method unless you want too deep dive into the process of rules inductions
175 or your're debugging some issues.
178 Args:
179 log_file_path (str): Path to the file where logs will be stored
180 verbosity_level (int, optional): Verbosity level.
181 Minimum value is 1, maximum value is 2, default value is 1.
182 """
183 RuleKit._java_logger_config = _RuleKitJavaLoggerConfig(
184 verbosity_level=verbosity_level, log_file_path=log_file_path
185 )
187 @staticmethod
188 def get_java_logger_config() -> Optional[_RuleKitJavaLoggerConfig]:
189 """Returns the Java logger configuration configured using
190 `configure_java_logger` method
192 Returns:
193 Optional[_RuleKitJavaLoggerConfig]: Java logger configuration
194 """
195 return RuleKit._java_logger_config