__init__.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. # -*- coding: utf-8 -*-
  2. """!@package grass.pygrass.massages
  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 logging
  12. import sys
  13. import grass.lib.gis as libgis
  14. from multiprocessing import Process, Lock, Pipe
  15. class FatalError(Exception):
  16. """!This error will be raised in case raise_on_error was set True
  17. when creating the messenger object.
  18. """
  19. def __init__(self, msg):
  20. self.value = msg
  21. def __str__(self):
  22. return self.value
  23. def message_server(lock, conn):
  24. """!The GRASS message server function designed to be a target for
  25. multiprocessing.Process
  26. @param lock A multiprocessing.Lock
  27. @param conn A multiprocessing.Pipe
  28. This function will use the G_* message C-functions from grass.lib.gis
  29. to provide an interface to the GRASS C-library messaging system.
  30. The data that is send through the pipe must provide an
  31. identifier string to specify which C-function should be called.
  32. The following identifers are supported:
  33. - "INFO" Prints an info message, see G_message() for details
  34. - "IMPORTANT" Prints an important info message,
  35. see G_important_message() for details
  36. - "VERBOSE" Prints a verbose message if the verbosity level is
  37. set accordingly, see G_verbose_message() for details
  38. - "WARNING" Prints a warning message, see G_warning() for details
  39. - "ERROR" Prints a message with a leading "ERROR: " string,
  40. see G_important_message() for details
  41. - "PERCENT" Prints a percent value based on three integer values: n, d and s
  42. see G_percent() for details
  43. - "STOP" Stops the server function and closes the pipe
  44. - "FATAL" Calls G_fatal_error(), this functions is only for
  45. testing purpose
  46. The that is end through the pipe must be a list of values:
  47. - Messages: ["INFO|VERBOSE|WARNING|ERROR|FATAL", "MESSAGE"]
  48. - Debug: ["DEBUG", level, "MESSAGE"]
  49. - Percent: ["PERCENT", n, d, s]
  50. """
  51. libgis.G_debug(1, "Start messenger server")
  52. while True:
  53. # Avoid busy waiting
  54. conn.poll(None)
  55. data = conn.recv()
  56. message_type = data[0]
  57. # Only one process is allowed to write to stderr
  58. lock.acquire()
  59. # Stop the pipe and the infinite loop
  60. if message_type == "STOP":
  61. conn.close()
  62. lock.release()
  63. libgis.G_debug(1, "Stop messenger server")
  64. sys.exit()
  65. message = data[1]
  66. if message_type == "PERCENT":
  67. n = int(data[1])
  68. d = int(data[2])
  69. s = int(data[3])
  70. libgis.G_percent(n, d, s)
  71. elif message_type == "DEBUG":
  72. level = data[1]
  73. message = data[2]
  74. libgis.G_debug(level, message)
  75. elif message_type == "VERBOSE":
  76. libgis.G_verbose_message(message)
  77. elif message_type == "INFO":
  78. libgis.G_message(message)
  79. elif message_type == "IMPORTANT":
  80. libgis.G_important_message(message)
  81. elif message_type == "WARNING":
  82. libgis.G_warning(message)
  83. elif message_type == "ERROR":
  84. libgis.G_important_message("ERROR: %s"%message)
  85. # This is for testing only
  86. elif message_type == "FATAL":
  87. libgis.G_fatal_error(message)
  88. lock.release()
  89. class Messenger(object):
  90. """!Fast and exit-safe interface to GRASS C-library message functions
  91. This class implements a fast and exit-safe interface to the GRASS
  92. C-library message functions like: G_message(), G_warning(),
  93. G_important_message(), G_verbose_message(), G_percent() and G_debug().
  94. Note:
  95. The C-library message functions a called via ctypes in a subprocess
  96. using a pipe (multiprocessing.Pipe) to transfer the text messages.
  97. Hence, the process that uses the Messenger interface will not be
  98. exited, if a G_fatal_error() was invoked in the subprocess.
  99. In this case the Messenger object will simply start a new subprocess
  100. and restarts the pipeline.
  101. Usage:
  102. @code
  103. >>> msgr = Messenger()
  104. >>> msgr.debug(0, "debug 0")
  105. >>> msgr.verbose("verbose message")
  106. >>> msgr.message("message")
  107. >>> msgr.important("important message")
  108. >>> msgr.percent(1, 1, 1)
  109. >>> msgr.warning("Ohh")
  110. >>> msgr.error("Ohh no")
  111. D0/0: debug 0
  112. message
  113. important message
  114. 100%
  115. WARNING: Ohh
  116. ERROR: Ohh no
  117. >>> msgr = Messenger()
  118. >>> msgr.fatal("Ohh no no no!")
  119. Traceback (most recent call last):
  120. File "__init__.py", line 239, in fatal
  121. sys.exit(1)
  122. SystemExit: 1
  123. >>> msgr = Messenger(raise_on_error=True)
  124. >>> msgr.fatal("Ohh no no no!")
  125. Traceback (most recent call last):
  126. File "__init__.py", line 241, in fatal
  127. raise FatalError(message)
  128. FatalError: Ohh no no no!
  129. @endcode
  130. """
  131. def __init__(self, raise_on_error=False):
  132. self.client_conn = None
  133. self.server_conn = None
  134. self.server = None
  135. self.raise_on_error = raise_on_error
  136. self.start_server()
  137. def __del__(self):
  138. self.stop()
  139. def start_server(self):
  140. self.client_conn, self.server_conn = Pipe()
  141. self.lock = Lock()
  142. self.server = Process(target=message_server, args=(self.lock,
  143. 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. """
  149. if self.server.is_alive() is True:
  150. return
  151. self.client_conn.close()
  152. self.server_conn.close()
  153. self.start_server()
  154. self.warning("Needed to restart the messenger server")
  155. def message(self, message):
  156. """!Send a message to stderr
  157. G_message() will be called in the messenger server process
  158. """
  159. self._check_restart_server()
  160. self.client_conn.send(["INFO", message])
  161. def verbose(self, message):
  162. """!Send a verbose message to stderr
  163. G_verbose_message() will be called in the messenger server process
  164. """
  165. self._check_restart_server()
  166. self.client_conn.send(["VERBOSE", message])
  167. def important(self, message):
  168. """!Send an important message to stderr
  169. G_important_message() will be called in the messenger server process
  170. """
  171. self._check_restart_server()
  172. self.client_conn.send(["IMPORTANT", message])
  173. def warning(self, message):
  174. """!Send a warning message to stderr
  175. G_warning() will be called in the messenger server process
  176. """
  177. self._check_restart_server()
  178. self.client_conn.send(["WARNING", message])
  179. def error(self, message):
  180. """!Send an error message to stderr
  181. G_important_message() with an additional "ERROR:" string at
  182. the start will be called in the messenger server process
  183. """
  184. self._check_restart_server()
  185. self.client_conn.send(["ERROR", message])
  186. def fatal(self, message):
  187. """!Send an error message to stderr, call sys.exit(1) or raise FatalError
  188. This function emulates the behavior of G_fatal_error(). It prints
  189. an error message to stderr and calls sys.exit(1). If raise_on_error
  190. is set True while creating the messenger object, a FatalError
  191. exception will be raised instead of calling sys.exit(1).
  192. """
  193. self._check_restart_server()
  194. self.client_conn.send(["ERROR", message])
  195. self.stop()
  196. if self.raise_on_error is True:
  197. raise FatalError(message)
  198. else:
  199. sys.exit(1)
  200. def debug(self, level, message):
  201. """!Send a debug message to stderr
  202. G_debug() will be called in the messenger server process
  203. """
  204. self._check_restart_server()
  205. self.client_conn.send(["DEBUG", level, message])
  206. def percent(self, n, d, s):
  207. """!Send a percentage to stderr
  208. G_percent() will be called in the messenger server process
  209. """
  210. self._check_restart_server()
  211. self.client_conn.send(["PERCENT", n, d, s])
  212. def stop(self):
  213. """!Stop the messenger server and close the pipe
  214. """
  215. if self.server is not None and self.server.is_alive():
  216. self.client_conn.send(["STOP",])
  217. self.server.join(5)
  218. self.server.terminate()
  219. if self.client_conn is not None:
  220. self.client_conn.close()
  221. def test_fatal_error(self, message):
  222. """!Force the messenger server to call G_fatal_error()
  223. """
  224. import time
  225. self._check_restart_server()
  226. self.client_conn.send(["FATAL", message])
  227. time.sleep(1)
  228. if __name__ == "__main__":
  229. import doctest
  230. doctest.testmod()