# Source code for pygrass.messages

    """
    @package grass.pygrass.messages

    @brief PyGRASS message interface

    Fast and exit-safe interface to GRASS C-library message functions

    (C) 2013 by the GRASS Development Team
    This program is free software under the GNU General Public
    License (>=v2). Read the file COPYING that comes with GRASS
    for details.

    @author Soeren Gebbert
    """
    import sys
    from multiprocessing import Process, Lock, Pipe

    import grass.lib.gis as libgis

    from grass.exceptions import FatalError

    def message_server(lock, conn):
        """The GRASS message server function designed to be a target for
        multiprocessing.Process

        :param lock: A multiprocessing.Lock
        :param conn: A multiprocessing.Pipe

        This function will use the G_* message C-functions from grass.lib.gis
        to provide an interface to the GRASS C-library messaging system.

        The data that is send through the pipe must provide an
        identifier string to specify which C-function should be called.

        The following identifiers are supported:

        - "INFO"       Prints an info message, see G_message() for details
        - "IMPORTANT"  Prints an important info message,
                       see G_important_message() for details
        - "VERBOSE"    Prints a verbose message if the verbosity level is
                       set accordingly, see G_verbose_message() for details
        - "WARNING"    Prints a warning message, see G_warning() for details
        - "ERROR"      Prints a message with a leading "ERROR: " string,
                       see G_important_message() for details
        - "PERCENT"    Prints a percent value based on three integer values: n, d and s
                       see G_percent() for details
        - "STOP"       Stops the server function and closes the pipe
        - "FATAL"      Calls G_fatal_error(), this functions is only for
                       testing purpose

        The that is end through the pipe must be a list of values:

        - Messages: ["INFO|VERBOSE|WARNING|ERROR|FATAL", "MESSAGE"]
        - Debug:    ["DEBUG", level, "MESSAGE"]
        - Percent:  ["PERCENT", n, d, s]

        """
        libgis.G_debug(1, "Start messenger server")

        while True:
            # Avoid busy waiting
            conn.poll(None)
            data = conn.recv()
            message_type = data[0]

            # Only one process is allowed to write to stderr
            lock.acquire()

            # Stop the pipe and the infinite loop
            if message_type == "STOP":
                conn.close()
                lock.release()
                libgis.G_debug(1, "Stop messenger server")
                sys.exit()

            message = data[1]
            # libgis limitation
            if isinstance(message, type(" ")):
                if len(message) >= 2000:
                    message = message[:1999]

            if message_type == "PERCENT":
                n = int(data[1])
                d = int(data[2])
                s = int(data[3])
                libgis.G_percent(n, d, s)
            elif message_type == "DEBUG":
                level = data[1]
                message = data[2]
                libgis.G_debug(level, message)
            elif message_type == "VERBOSE":
                libgis.G_verbose_message(message)
            elif message_type == "INFO":
                libgis.G_message(message)
            elif message_type == "IMPORTANT":
                libgis.G_important_message(message)
            elif message_type == "WARNING":
                libgis.G_warning(message)
            elif message_type == "ERROR":
                libgis.G_important_message("ERROR: %s" % message)
            # This is for testing only
            elif message_type == "FATAL":
                libgis.G_fatal_error(message)

            lock.release()

    class Messenger(object):
        """Fast and exit-safe interface to GRASS C-library message functions

        This class implements a fast and exit-safe interface to the GRASS
        C-library message functions like: G_message(), G_warning(),
        G_important_message(), G_verbose_message(), G_percent() and G_debug().

        Note:

        The C-library message functions a called via ctypes in a subprocess
        using a pipe (multiprocessing.Pipe) to transfer the text messages.
        Hence, the process that uses the Messenger interface will not be
        exited, if a G_fatal_error() was invoked in the subprocess.
        In this case the Messenger object will simply start a new subprocess
        and restarts the pipeline.

        Usage:

        >>> msgr = Messenger()
        >>> msgr.debug(0, "debug 0")
        >>> msgr.verbose("verbose message")
        >>> msgr.message("message")
        >>> msgr.important("important message")
        >>> msgr.percent(1, 1, 1)
        >>> msgr.warning("Ohh")
        >>> msgr.error("Ohh no")

        >>> msgr = Messenger()
        >>> msgr.fatal("Ohh no no no!")
        Traceback (most recent call last):
          File "__init__.py", line 239, in fatal
            sys.exit(1)
        SystemExit: 1

        >>> msgr = Messenger(raise_on_error=True)
        >>> msgr.fatal("Ohh no no no!")
        Traceback (most recent call last):
          File "__init__.py", line 241, in fatal
            raise FatalError(message)
        grass.exceptions.FatalError: Ohh no no no!

        >>> msgr = Messenger(raise_on_error=True)
        >>> msgr.set_raise_on_error(False)
        >>> msgr.fatal("Ohh no no no!")
        Traceback (most recent call last):
          File "__init__.py", line 239, in fatal
            sys.exit(1)
        SystemExit: 1

        >>> msgr = Messenger(raise_on_error=False)
        >>> msgr.set_raise_on_error(True)
        >>> msgr.fatal("Ohh no no no!")
        Traceback (most recent call last):
          File "__init__.py", line 241, in fatal
            raise FatalError(message)
        grass.exceptions.FatalError: Ohh no no no!

        """

        def __init__(self, raise_on_error=False):
            self.client_conn = None
            self.server_conn = None
            self.server = None
            self.raise_on_error = raise_on_error
            self.start_server()

        def start_server(self):
            """Start the messenger server and open the pipe"""
            self.client_conn, self.server_conn = Pipe()
            self.lock = Lock()
            self.server = Process(target=message_server, args=(self.lock, self.server_conn))
            self.server.daemon = True
            self.server.start()

        def _check_restart_server(self):
            """Restart the server if it was terminated"""
            if self.server.is_alive() is True:
                return
            self.client_conn.close()
            self.server_conn.close()
            self.start_server()
            self.warning("Needed to restart the messenger server")

        def message(self, message):
            """Send a message to stderr

            :param message: the text of message
            :type message: str

               G_message() will be called in the messenger server process
            """
            self._check_restart_server()
            self.client_conn.send(["INFO", message])

        def verbose(self, message):
            """Send a verbose message to stderr

            :param message: the text of message
            :type message: str

               G_verbose_message() will be called in the messenger server process
            """
            self._check_restart_server()
            self.client_conn.send(["VERBOSE", message])

        def important(self, message):
            """Send an important message to stderr

            :param message: the text of message
            :type message: str

               G_important_message() will be called in the messenger server process
            """
            self._check_restart_server()
            self.client_conn.send(["IMPORTANT", message])

        def warning(self, message):
            """Send a warning message to stderr

            :param message: the text of message
            :type message: str

               G_warning() will be called in the messenger server process
            """
            self._check_restart_server()
            self.client_conn.send(["WARNING", message])

        def error(self, message):
            """Send an error message to stderr

            :param message: the text of message
            :type message: str

               G_important_message() with an additional "ERROR:" string at
               the start will be called in the messenger server process
            """
            self._check_restart_server()
            self.client_conn.send(["ERROR", message])

        def fatal(self, message):
            """Send an error message to stderr, call sys.exit(1) or raise FatalError

            :param message: the text of message
            :type message: str

               This function emulates the behavior of G_fatal_error(). It prints
               an error message to stderr and calls sys.exit(1). If raise_on_error
               is set True while creating the messenger object, a FatalError
               exception will be raised instead of calling sys.exit(1).
            """
            self._check_restart_server()
            self.client_conn.send(["ERROR", message])
            self.stop()

            if self.raise_on_error is True:
                raise FatalError(message)
            else:
                sys.exit(1)

        def debug(self, level, message):
            """Send a debug message to stderr

            :param message: the text of message
            :type message: str

               G_debug() will be called in the messenger server process
            """
            self._check_restart_server()
            self.client_conn.send(["DEBUG", level, message])

        def percent(self, n, d, s):
            """Send a percentage to stderr

            :param message: the text of message
            :type message: str

               G_percent() will be called in the messenger server process
            """
            self._check_restart_server()
            self.client_conn.send(["PERCENT", n, d, s])

        def stop(self):
            """Stop the messenger server and close the pipe"""
            if self.server is not None and self.server.is_alive():
                self.client_conn.send(
                    [
                        "STOP",
                    ]
                )
                self.server.join(5)
                self.server.terminate()
            if self.client_conn is not None:
                self.client_conn.close()

        def set_raise_on_error(self, raise_on_error=True):
            """Set the fatal error behavior

            :param raise_on_error: if True a FatalError exception will be
                                   raised instead of calling sys.exit(1)
            :type raise_on_error: bool

            - If raise_on_error == True, a FatalError exception will be raised
              if fatal() is called
            - If raise_on_error == False, sys.exit(1) will be invoked if
              fatal() is called

            """
            self.raise_on_error = raise_on_error

        def get_raise_on_error(self):
            """Get the fatal error behavior

            :returns: True if a FatalError exception will be raised or False if
                      sys.exit(1) will be called in case of invoking fatal()
            """
            return self.raise_on_error

        def test_fatal_error(self, message):
            """Force the messenger server to call G_fatal_error()"""
            import time

            self._check_restart_server()
            self.client_conn.send(["FATAL", message])
            time.sleep(1)

    def get_msgr(
        _instance=[
            None,
        ],
        *args,
        **kwargs,
    ):
        """Return a Messenger instance.

           :returns: the Messenger instance.

        >>> msgr0 = get_msgr()
        >>> msgr1 = get_msgr()
        >>> msgr2 = Messenger()
        >>> msgr0 is msgr1
        True
        >>> msgr0 is msgr2
        False
        """
        if not _instance[0]:
            _instance[0] = Messenger(*args, **kwargs)
        return _instance[0]

    if __name__ == "__main__":
        import doctest

        doctest.testmod()

Help Index | Topics Index | Keywords Index | Full Index

© 2003-2023 GRASS Development Team, GRASS GIS 8.3.0dev Reference Manual