Coverage for rulekit/main.py: 81%

89 statements  

« 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 

12 

13import jpype.imports 

14 

15from rulekit._logging import _RuleKitJavaLoggerConfig 

16 

17__RULEKIT_RELEASE_VERSION__ = "2.1.24" 

18__VERSION__ = f"{__RULEKIT_RELEASE_VERSION__}.1" 

19 

20 

21class JRE_Type(Enum): # pylint: disable=invalid-name 

22 """:meta private:""" 

23 

24 OPEN_JDK = "open_jdk" 

25 ORACLE = "oracle" 

26 

27 

28class RuleKit: 

29 """Class used for initializing RuleKit. It starts JVM underhood and setups it 

30 with jars. 

31 

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. 

36 

37 

38 Attributes 

39 ---------- 

40 version : str 

41 version of RuleKit jar used by wrapper (not equal to python package version). 

42 """ 

43 

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 

52 

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 

66 

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. 

74 

75 This method configure and starts JVM and load RuleKit jar file. 

76 

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. 

80 

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. 

93 

94 Raises 

95 ------ 

96 Exception 

97 If failed to load RuleKit jar file. 

98 """ 

99 if RuleKit.initialized: 

100 return 

101 RuleKit._setup_logger() 

102 

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. 

124 

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 

133 

134 @staticmethod 

135 def _setup_logger(): 

136 logging.basicConfig() 

137 RuleKit._logger = logging.getLogger("RuleKit") 

138 

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 

153 

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) 

167 

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. 

176 

177 

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 ) 

186 

187 @staticmethod 

188 def get_java_logger_config() -> Optional[_RuleKitJavaLoggerConfig]: 

189 """Returns the Java logger configuration configured using 

190 `configure_java_logger` method 

191 

192 Returns: 

193 Optional[_RuleKitJavaLoggerConfig]: Java logger configuration 

194 """ 

195 return RuleKit._java_logger_config