__init__.py 11 KB

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