__init__.py 11 KB

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