1# -*- coding: utf-8 -*- 2"""@package grass.pygrass.messages 3 4@brief PyGRASS message interface 5 6Fast and exit-safe interface to GRASS C-library message functions 7 8 9(C) 2013 by the GRASS Development Team 10This program is free software under the GNU General Public 11License (>=v2). Read the file COPYING that comes with GRASS 12for details. 13 14@author Soeren Gebbert 15""" 16import sys 17from multiprocessing import Process, Lock, Pipe 18 19import grass.lib.gis as libgis 20 21from grass.exceptions import FatalError 22 23 24def message_server(lock, conn): 25 """The GRASS message server function designed to be a target for 26 multiprocessing.Process 27 28 29 :param lock: A multiprocessing.Lock 30 :param conn: A multiprocessing.Pipe 31 32 This function will use the G_* message C-functions from grass.lib.gis 33 to provide an interface to the GRASS C-library messaging system. 34 35 The data that is send through the pipe must provide an 36 identifier string to specify which C-function should be called. 37 38 The following identifiers are supported: 39 40 - "INFO" Prints an info message, see G_message() for details 41 - "IMPORTANT" Prints an important info message, 42 see G_important_message() for details 43 - "VERBOSE" Prints a verbose message if the verbosity level is 44 set accordingly, see G_verbose_message() for details 45 - "WARNING" Prints a warning message, see G_warning() for details 46 - "ERROR" Prints a message with a leading "ERROR: " string, 47 see G_important_message() for details 48 - "PERCENT" Prints a percent value based on three integer values: n, d and s 49 see G_percent() for details 50 - "STOP" Stops the server function and closes the pipe 51 - "FATAL" Calls G_fatal_error(), this functions is only for 52 testing purpose 53 54 The that is end through the pipe must be a list of values: 55 56 - Messages: ["INFO|VERBOSE|WARNING|ERROR|FATAL", "MESSAGE"] 57 - Debug: ["DEBUG", level, "MESSAGE"] 58 - Percent: ["PERCENT", n, d, s] 59 60 """ 61 libgis.G_debug(1, "Start messenger server") 62 63 while True: 64 # Avoid busy waiting 65 conn.poll(None) 66 data = conn.recv() 67 message_type = data[0] 68 69 # Only one process is allowed to write to stderr 70 lock.acquire() 71 72 # Stop the pipe and the infinite loop 73 if message_type == "STOP": 74 conn.close() 75 lock.release() 76 libgis.G_debug(1, "Stop messenger server") 77 sys.exit() 78 79 message = data[1] 80 # libgis limitation 81 if isinstance(message, type(" ")): 82 if len(message) >= 2000: 83 message = message[:1999] 84 85 if message_type == "PERCENT": 86 n = int(data[1]) 87 d = int(data[2]) 88 s = int(data[3]) 89 libgis.G_percent(n, d, s) 90 elif message_type == "DEBUG": 91 level = data[1] 92 message = data[2] 93 libgis.G_debug(level, message) 94 elif message_type == "VERBOSE": 95 libgis.G_verbose_message(message) 96 elif message_type == "INFO": 97 libgis.G_message(message) 98 elif message_type == "IMPORTANT": 99 libgis.G_important_message(message) 100 elif message_type == "WARNING": 101 libgis.G_warning(message) 102 elif message_type == "ERROR": 103 libgis.G_important_message("ERROR: %s"%message) 104 # This is for testing only 105 elif message_type == "FATAL": 106 libgis.G_fatal_error(message) 107 108 lock.release() 109 110 111class Messenger(object): 112 """Fast and exit-safe interface to GRASS C-library message functions 113 114 This class implements a fast and exit-safe interface to the GRASS 115 C-library message functions like: G_message(), G_warning(), 116 G_important_message(), G_verbose_message(), G_percent() and G_debug(). 117 118 Note: 119 120 The C-library message functions a called via ctypes in a subprocess 121 using a pipe (multiprocessing.Pipe) to transfer the text messages. 122 Hence, the process that uses the Messenger interface will not be 123 exited, if a G_fatal_error() was invoked in the subprocess. 124 In this case the Messenger object will simply start a new subprocess 125 and restarts the pipeline. 126 127 128 Usage: 129 130 >>> msgr = Messenger() 131 >>> msgr.debug(0, "debug 0") 132 >>> msgr.verbose("verbose message") 133 >>> msgr.message("message") 134 >>> msgr.important("important message") 135 >>> msgr.percent(1, 1, 1) 136 >>> msgr.warning("Ohh") 137 >>> msgr.error("Ohh no") 138 139 >>> msgr = Messenger() 140 >>> msgr.fatal("Ohh no no no!") 141 Traceback (most recent call last): 142 File "__init__.py", line 239, in fatal 143 sys.exit(1) 144 SystemExit: 1 145 146 >>> msgr = Messenger(raise_on_error=True) 147 >>> msgr.fatal("Ohh no no no!") 148 Traceback (most recent call last): 149 File "__init__.py", line 241, in fatal 150 raise FatalError(message) 151 grass.exceptions.FatalError: Ohh no no no! 152 153 >>> msgr = Messenger(raise_on_error=True) 154 >>> msgr.set_raise_on_error(False) 155 >>> msgr.fatal("Ohh no no no!") 156 Traceback (most recent call last): 157 File "__init__.py", line 239, in fatal 158 sys.exit(1) 159 SystemExit: 1 160 161 >>> msgr = Messenger(raise_on_error=False) 162 >>> msgr.set_raise_on_error(True) 163 >>> msgr.fatal("Ohh no no no!") 164 Traceback (most recent call last): 165 File "__init__.py", line 241, in fatal 166 raise FatalError(message) 167 grass.exceptions.FatalError: Ohh no no no! 168 169 """ 170 def __init__(self, raise_on_error=False): 171 self.client_conn = None 172 self.server_conn = None 173 self.server = None 174 self.raise_on_error = raise_on_error 175 self.start_server() 176 177 def start_server(self): 178 """Start the messenger server and open the pipe 179 """ 180 self.client_conn, self.server_conn = Pipe() 181 self.lock = Lock() 182 self.server = Process(target=message_server, args=(self.lock, 183 self.server_conn)) 184 self.server.daemon = True 185 self.server.start() 186 187 def _check_restart_server(self): 188 """Restart the server if it was terminated 189 """ 190 if self.server.is_alive() is True: 191 return 192 self.client_conn.close() 193 self.server_conn.close() 194 self.start_server() 195 self.warning("Needed to restart the messenger server") 196 197 def message(self, message): 198 """Send a message to stderr 199 200 :param message: the text of message 201 :type message: str 202 203 G_message() will be called in the messenger server process 204 """ 205 self._check_restart_server() 206 self.client_conn.send(["INFO", message]) 207 208 def verbose(self, message): 209 """Send a verbose message to stderr 210 211 :param message: the text of message 212 :type message: str 213 214 G_verbose_message() will be called in the messenger server process 215 """ 216 self._check_restart_server() 217 self.client_conn.send(["VERBOSE", message]) 218 219 def important(self, message): 220 """Send an important message to stderr 221 222 :param message: the text of message 223 :type message: str 224 225 G_important_message() will be called in the messenger server process 226 """ 227 self._check_restart_server() 228 self.client_conn.send(["IMPORTANT", message]) 229 230 def warning(self, message): 231 """Send a warning message to stderr 232 233 :param message: the text of message 234 :type message: str 235 236 G_warning() will be called in the messenger server process 237 """ 238 self._check_restart_server() 239 self.client_conn.send(["WARNING", message]) 240 241 def error(self, message): 242 """Send an error message to stderr 243 244 :param message: the text of message 245 :type message: str 246 247 G_important_message() with an additional "ERROR:" string at 248 the start will be called in the messenger server process 249 """ 250 self._check_restart_server() 251 self.client_conn.send(["ERROR", message]) 252 253 def fatal(self, message): 254 """Send an error message to stderr, call sys.exit(1) or raise FatalError 255 256 :param message: the text of message 257 :type message: str 258 259 This function emulates the behavior of G_fatal_error(). It prints 260 an error message to stderr and calls sys.exit(1). If raise_on_error 261 is set True while creating the messenger object, a FatalError 262 exception will be raised instead of calling sys.exit(1). 263 """ 264 self._check_restart_server() 265 self.client_conn.send(["ERROR", message]) 266 self.stop() 267 268 if self.raise_on_error is True: 269 raise FatalError(message) 270 else: 271 sys.exit(1) 272 273 def debug(self, level, message): 274 """Send a debug message to stderr 275 276 :param message: the text of message 277 :type message: str 278 279 G_debug() will be called in the messenger server process 280 """ 281 self._check_restart_server() 282 self.client_conn.send(["DEBUG", level, message]) 283 284 def percent(self, n, d, s): 285 """Send a percentage to stderr 286 287 :param message: the text of message 288 :type message: str 289 290 291 G_percent() will be called in the messenger server process 292 """ 293 self._check_restart_server() 294 self.client_conn.send(["PERCENT", n, d, s]) 295 296 def stop(self): 297 """Stop the messenger server and close the pipe 298 """ 299 if self.server is not None and self.server.is_alive(): 300 self.client_conn.send(["STOP", ]) 301 self.server.join(5) 302 self.server.terminate() 303 if self.client_conn is not None: 304 self.client_conn.close() 305 306 def set_raise_on_error(self, raise_on_error=True): 307 """Set the fatal error behavior 308 309 :param raise_on_error: if True a FatalError exception will be 310 raised instead of calling sys.exit(1) 311 :type raise_on_error: bool 312 313 - If raise_on_error == True, a FatalError exception will be raised 314 if fatal() is called 315 - If raise_on_error == False, sys.exit(1) will be invoked if 316 fatal() is called 317 318 """ 319 self.raise_on_error = raise_on_error 320 321 def get_raise_on_error(self): 322 """Get the fatal error behavior 323 324 :returns: True if a FatalError exception will be raised or False if 325 sys.exit(1) will be called in case of invoking fatal() 326 """ 327 return self.raise_on_error 328 329 def test_fatal_error(self, message): 330 """Force the messenger server to call G_fatal_error() 331 """ 332 import time 333 self._check_restart_server() 334 self.client_conn.send(["FATAL", message]) 335 time.sleep(1) 336 337 338def get_msgr(_instance=[None, ], *args, **kwargs): 339 """Return a Messenger instance. 340 341 :returns: the Messenger instance. 342 343 >>> msgr0 = get_msgr() 344 >>> msgr1 = get_msgr() 345 >>> msgr2 = Messenger() 346 >>> msgr0 is msgr1 347 True 348 >>> msgr0 is msgr2 349 False 350 """ 351 if not _instance[0]: 352 _instance[0] = Messenger(*args, **kwargs) 353 return _instance[0] 354 355 356if __name__ == "__main__": 357 import doctest 358 doctest.testmod() 359