Source code for pypeit.pypmsgs

"""
Module for terminal and file logging.

.. todo::
    Why not use pythons native logging package?

"""
import datetime
import sys
import os
import getpass
import glob
import textwrap
import inspect
import io

# Imported for versioning
import scipy
import numpy
import astropy
import pypeit

from pypeit.core.qa import close_qa

#pypeit_logger = None

# Alphabetical list of developers
developers = ['ema', 'joe', 'milvang', 'rcooke', 'thsyu', 'xavier']


[docs]class PypeItError(Exception): pass
[docs]class PypeItBitMaskError(PypeItError): pass
[docs]class PypeItDataModelError(PypeItError): pass
[docs]class Messages: """ Create coloured text for messages printed to screen. For further details on colours see the following example: http://ascii-table.com/ansi-escape-sequences.php Parameters ---------- log : str or file-like object,optional Name of saved log file (no log will be saved if log==""). If None, no log is saved. verbosity : int Level of verbosity. Options are - 0 = No output - 1 = Minimal output - 2 = All output (default) colors : bool If true, the screen output will have colors, otherwise normal screen output will be displayed """ def __init__(self, log=None, verbosity=None, colors=True): # Initialize other variables self._defverb = 1 try: user = getpass.getuser() except ModuleNotFoundError: # there appears to be a bug in getpass in windows systems where the pwd module doesn't load user = os.getlogin() if user in developers: self._defverb = 2 self._verbosity = self._defverb if verbosity is None else verbosity # TODO: Why are these two necessary? It would seem better to # provide Messages with member functions that can operate on # sciexp and pypeit_file instead of having them kept within the # object itself... self.sciexp = None self.pypeit_file = None self.qa_path = None # Initialize the log self._log_to_stderr = self._verbosity != 0 self._log = None self._initialize_log_file(log=log) # Use colors? self._start = None self._end = None self._black_CL = None self._yellow_CL = None self._blue_CL = None self._green_CL = None self._red_CL = None self._white_RD = None self._white_GR = None self._white_BK = None self._white_BL = None self._black_YL = None self._yellow_BK = None self.disablecolors() if colors: self.enablecolors()
[docs] def _cleancolors(self, msg): cols = [self._end, self._start, self._black_CL, self._yellow_CL, self._blue_CL, self._green_CL, self._red_CL, self._white_RD, self._white_GR, self._white_BK, self._white_BL, self._black_YL, self._yellow_BK] for i in cols: msg = msg.replace(i, '') return msg
[docs] def _devmsg(self): if self._verbosity == 2: info = inspect.getouterframes(inspect.currentframe())[3] devmsg = self._start + self._blue_CL + info[1].split('/')[-1] + ' ' + str(info[2]) \ + ' ' + info[3] + '()' + self._end + ' - ' else: devmsg = '' return devmsg
[docs] def _print(self, premsg, msg, last=True, printDevMsg=True): """ Print to standard error and the log file """ devmsg = self._devmsg() if printDevMsg else '' _msg = premsg+devmsg+msg if self._log_to_stderr != 0: print(_msg, file=sys.stderr) if self._log: clean_msg = self._cleancolors(_msg) self._log.write(clean_msg+'\n' if last else clean_msg)
[docs] def _initialize_log_file(self, log=None): """ Expects self._log is already None. """ if log is None: return self._log = log if isinstance(log, io.IOBase) else open(log, 'w') self._log.write("------------------------------------------------------\n\n") self._log.write("This log was generated with version {0:s} of PypeIt\n\n".format( pypeit.__version__)) self._log.write("You are using scipy version={:s}\n".format(scipy.__version__)) self._log.write("You are using numpy version={:s}\n".format(numpy.__version__)) self._log.write("You are using astropy version={:s}\n\n".format(astropy.__version__)) self._log.write("------------------------------------------------------\n\n")
[docs] def reset(self, log=None, verbosity=None, colors=True, log_to_stderr=None): """ Reinitialize the object. Needed so that there can be a default object for all modules, but also a dynamically defined log file. """ # Initialize other variables self._verbosity = self._defverb if verbosity is None else verbosity if log_to_stderr is None: self._log_to_stderr = self._verbosity != 0 else: self._log_to_stderr = log_to_stderr self.reset_log_file(log) self.disablecolors() if colors: self.enablecolors()
[docs] def reset_log_file(self, log): if self._log: self._log.close() self._log = None self._initialize_log_file(log=log)
[docs] def close(self): ''' Close the log file before the code exits ''' close_qa(self.pypeit_file, self.qa_path) return self.reset_log_file(None)
[docs] def error(self, msg, cls='PypeItError'): """ Print an error message """ premsg = '\n'+self._start + self._white_RD + '[ERROR] ::' + self._end + ' ' self._print(premsg, msg) # Close QA plots close_qa(self.pypeit_file, self.qa_path) raise eval(cls)(msg)
[docs] def info(self, msg): """ Print an information message """ premsg = self._start + self._green_CL + '[INFO] ::' + self._end + ' ' self._print(premsg, msg)
[docs] def info_update(self, msg, last=False): """ Print an information message that needs to be updated """ premsg = '\r' + self._start + self._green_CL + '[INFO] ::' + self._end + ' ' self._print(premsg, msg, last=last)
[docs] def test(self, msg): """ Print a test message """ if self._verbosity == 2: premsg = self._start + self._white_BL + '[TEST] ::' + self._end + ' ' self._print(premsg, msg)
[docs] def warn(self, msg): """ Print a warning message """ premsg = self._start + self._red_CL + '[WARNING] ::' + self._end + ' ' self._print(premsg, msg)
[docs] def bug(self, msg): """ Print a bug message """ premsg = self._start + self._white_BK + '[BUG] ::' + self._end + ' ' self._print(premsg, msg)
[docs] def work(self, msg): """ Print a work in progress message """ if self._verbosity == 2: premsgp = self._start + self._black_CL + '[WORK IN ]::' + self._end + '\n' premsgs = self._start + self._yellow_CL + '[PROGRESS]::' + self._end + ' ' self._print(premsgp+premsgs, msg)
[docs] def pypeitpar_text(self, msglist): """ Prepare a text string with the pypeit par formatting. Parameters ---------- msglist: list A list containing the pypeit parameter strings. The last element of the list must be the argument and the variable. For example, to print: .. code-block:: ini [sensfunc] [[UVIS]] polycorrect = False you should set ``msglist = ['sensfunc', 'UVIS', 'polycorrect = False']``. Returns ------- parstring : str The parameter string """ parstring = '\n' premsg = ' ' for ll, lin in enumerate(msglist): thismsg = ll*' ' if ll == len(msglist)-1: thismsg += lin else: thismsg += (ll+1) * '[' + lin + (ll+1) * ']' parstring += premsg + thismsg + '\n' return parstring
[docs] def pypeitpar(self, msglist): """ Print a message with the pypeit par formatting. Parameters ---------- msglist: list A list containing the pypeit parameter strings. The last element of the list must be the argument and the variable. For example, to print: .. code-block:: ini [sensfunc] [[UVIS]] polycorrect = False you should set ``msglist = ['sensfunc', 'UVIS', 'polycorrect = False']``. """ premsg = ' ' for ll, lin in enumerate(msglist): thismsg = ll*' ' if ll == len(msglist)-1: thismsg += lin else: thismsg += (ll+1) * '[' + lin + (ll+1) * ']' self._print(premsg, thismsg, printDevMsg=False)
[docs] def prindent(self, msg): """ Print an indent """ premsg = ' ' self._print(premsg, msg)
[docs] def input(self): """ Return a text string to be used to display input required from the user """ premsg = self._start + self._blue_CL + '[INPUT] ::' + self._end + ' ' return premsg
[docs] @staticmethod def newline(): """ Return a text string containing a newline to be used with messages """ return '\n '
[docs] @staticmethod def indent(): """ Return a text string containing an indent to be used with messages """ return ' '
# Set the colors
[docs] def enablecolors(self): """ Enable colored output text """ # Start and end coloured text self._start = '\x1B[' self._end = '\x1B[' + '0m' # Clear Backgrounds self._black_CL = '1;30m' self._yellow_CL = '1;33m' self._blue_CL = '1;34m' self._green_CL = '1;32m' self._red_CL = '1;31m' # Coloured Backgrounds self._white_RD = '1;37;41m' self._white_GR = '1;37;42m' self._white_BK = '1;37;40m' self._white_BL = '1;37;44m' self._black_YL = '1;37;43m' self._yellow_BK = '1;33;40m'
[docs] def disablecolors(self): """ Disable colored output text """ # Start and end coloured text self._start = '' self._end = '' # Clear Backgrounds self._black_CL = '' self._yellow_CL = '' self._blue_CL = '' self._green_CL = '' self._red_CL = '' # Coloured Backgrounds self._white_RD = '' self._white_GR = '' self._white_BK = '' self._white_BL = '' self._black_YL = '' self._yellow_BK = ''
[docs] def set_logfile_and_verbosity(self, scriptname, verbosity): """ Set the logfile name and verbosity level for a script run. PypeIt scripts (with the exception of run_pypeit) default to verbosity level = 1. For certain scripts, having a more verbose output (with an accompanying log file) would be helpful for debugging purposes. This function provides the ability to set the ``msgs`` verbosity and create a log file for those certain scripts. Log filenames have the form scriptname_YYYYMMDD_HHMM.log to differentiate between different runs of the script. Timestamp is UT. Args: scriptname (:obj:`str`, optional): The name of the calling script for use in the logfile verbosity (:obj:`int`, optional): The requested verbosity, passed in from the argument parser. Verbosity level between 0 [none] and 2 [all] """ # Create a UT timestamp (to the minute) for the log filename timestamp = datetime.datetime.utcnow().strftime("%Y%m%d-%H%M") # Create a logfile only if verbosity == 2 logname = f"{scriptname}_{timestamp}.log" if verbosity == 2 else None # Set the verbosity in msgs self.reset(log=logname, verbosity=verbosity)