diff --git a/schainpy/VERSION b/schainpy/VERSION index 994ffd0..0d7b7c6 100644 --- a/schainpy/VERSION +++ b/schainpy/VERSION @@ -23,4 +23,7 @@ VERSIONS: -controller_api.py: Safe access to ControllerThead 2.1.3.3: --Colored Button Icons were added to GUI \ No newline at end of file +-Colored Button Icons were added to GUI + +2.1.4: +-Sending error notifications to signal chain administrator \ No newline at end of file diff --git a/schainpy/__init__.py b/schainpy/__init__.py index 76b27b6..54bdb1d 100644 --- a/schainpy/__init__.py +++ b/schainpy/__init__.py @@ -4,4 +4,4 @@ Created on Feb 7, 2012 @author $Author$ @version $Id$ ''' -__version__ = "2.1.3.3" \ No newline at end of file +__version__ = "2.1.4" \ No newline at end of file diff --git a/schainpy/admin.py b/schainpy/admin.py new file mode 100644 index 0000000..f6eb8bc --- /dev/null +++ b/schainpy/admin.py @@ -0,0 +1,346 @@ +"""The admin module contains all administrative classes relating to the schain python api. + +The main role of this module is to send some reports. It contains a +notification class and a standard error handing class. + +$Id: admin.py 3966 2015-12-01 14:32:29Z miguel.urco $ +""" +import os +import smtplib +import ConfigParser +import StringIO + +from email.mime.text import MIMEText +from email.mime.application import MIMEApplication +from email.mime.multipart import MIMEMultipart + +class SchainConfigure(): + + __DEFAULT_SENDER_EMAIL = "notifier-schain@jro.igp.gob.pe" + __DEFAULT_ADMINISTRATOR_EMAIL = "miguel.urco@jro.igp.gob.pe" + __DEFAULT_EMAIL_SERVER = "jro-zimbra.igp.gob.pe" + + __SCHAIN_ADMINISTRATOR_EMAIL = "CONTACT" + __SCHAIN_EMAIL_SERVER = "MAILSERVER" + __SCHAIN_SENDER_EMAIL = "MAILSERVER_ACCOUNT" + + def __init__(self, initFile = None): + + # Set configuration file + if (initFile == None): + self.__confFilePath = "/etc/schain.conf" + else: + self.__confFilePath = initFile + + # open configuration file + try: + self.__confFile = open(self.__confFilePath, "r") + except IOError: + # can't read from file - use all hard-coded values + self.__initFromHardCode() + return + + # create Parser using standard module ConfigParser + self.__parser = ConfigParser.ConfigParser() + + # read conf file into a StringIO with "[madrigal]\n" section heading prepended + strConfFile = StringIO.StringIO("[schain]\n" + self.__confFile.read()) + + # parse StringIO configuration file + self.__parser.readfp(strConfFile) + + # read information from configuration file + self.__readConfFile() + + # close conf file + self.__confFile.close() + + + def __initFromHardCode(self): + + self.__sender_email = self.__DEFAULT_SENDER_EMAIL + self.__admin_email = self.__DEFAULT_ADMINISTRATOR_EMAIL + self.__email_server = self.__DEFAULT_EMAIL_SERVER + + def __readConfFile(self): + """__readConfFile is a private helper function that reads information from the parsed config file. + + Inputs: None + + Returns: Void. + + Affects: Initializes class member variables that are found in the config file. + + Exceptions: MadrigalError thrown if any key not found. + """ + + # get the sender email + try: + self.__sender_email = self.__parser.get("schain", self.__SCHAIN_SENDER_EMAIL) + except: + self.__sender_email = self.__DEFAULT_SENDER_EMAIL + + # get the administrator email + try: + self.__admin_email = self.__parser.get("schain", self.__SCHAIN_ADMINISTRATOR_EMAIL) + except: + self.__admin_email = self.__DEFAULT_ADMINISTRATOR_EMAIL + + # get the server email + try: + self.__email_server = self.__parser.get("schain", self.__SCHAIN_EMAIL_SERVER) + except: + self.__email_server = self.__DEFAULT_EMAIL_SERVER + + def getEmailServer(self): + + return self.__email_server + + def getSenderEmail(self): + + return self.__sender_email + + def getAdminEmail(self): + + return self.__admin_email + +class SchainNotify: + """SchainNotify is an object used to send messages to an administrator about a Schain software. + + This object provides functions needed to send messages to an administrator about a Schain , for now + only sendAlert, which sends an email to the site administrator found is ADMIN_EMAIL + + Usage example: + + import schainpy.admin + + try: + + adminObj = schainpy.admin.SchainNotify() + adminObj.sendAlert('This is important!', 'Important Message') + + except schainpy.admin.SchainError, e: + + print e.getExceptionStr() + + + Non-standard Python modules used: + None + + Exceptions thrown: None - Note that SchainNotify tries every trick it knows to avoid + throwing exceptions, since this is the class that will generally be called when there is a problem. + + Change history: + + Written by "Miguel Urco":mailto:miguel.urco@jro.igp.gob.pe Dec. 1, 2015 + """ + + #constants + + def __init__(self): + """__init__ initializes SchainNotify by getting some basic information from SchainDB and SchainSite. + + Note that SchainNotify tries every trick it knows to avoid throwing exceptions, since + this is the class that will generally be called when there is a problem. + + Inputs: Existing SchainDB object, by default = None. + + Returns: void + + Affects: Initializes self.__binDir. + + Exceptions: None. + """ + + # note that the main configuration file is unavailable + # the best that can be done is send an email to root using localhost mailserver + confObj = SchainConfigure() + + self.__emailFromAddress = confObj.getSenderEmail() + self.__emailToAddress = confObj.getAdminEmail() + self.__emailServer = confObj.getEmailServer() + + def sendEmail(self, email_from, email_to, subject='Error running ...', message="", subtitle="", filename="", html_format=True): + + msg = MIMEMultipart() + msg['Subject'] = subject + msg['From'] = "(Python SChain API): " + email_from + msg['Reply-to'] = email_from + msg['To'] = email_to + + # That is what u see if dont have an email reader: + msg.preamble = 'SChainPy' + + if html_format: + message = "

%s

" %subject + "

" + subtitle.replace("\n", "

\n") + "

" + message.replace("\n", "
\n") + message = "\n" + message + '' + + # This is the textual part: + part = MIMEText(message, "html") + else: + message = subject + "\n" + subtitle + "\n" + message + part = MIMEText(message) + + msg.attach(part) + + if os.path.isfile(filename): + # This is the binary part(The Attachment): + part = MIMEApplication(open(filename,"rb").read()) + part.add_header('Content-Disposition', + 'attachment', + filename=os.path.basename(filename)) + msg.attach(part) + + # Create an instance in SMTP server + smtp = smtplib.SMTP(self.__emailServer) + # Start the server: +# smtp.ehlo() +# smtp.login(email_from, email_from_pass) + + # Send the email + smtp.sendmail(msg['From'], msg['To'], msg.as_string()) + smtp.quit() + + + def sendAlert(self, message, subject = "", subtitle="", filename=""): + """sendAlert sends an email with the given message and optional title. + + Inputs: message (string), and optional title (string) + + Returns: void + + Affects: none + + Exceptions: None. + """ + print "***** Sending alert to %s *****" %self.__emailToAddress + # set up message + + self.sendEmail(email_from=self.__emailFromAddress, + email_to=self.__emailToAddress, + subject=subject, + message=message, + subtitle=subtitle, + filename=filename) + + print "***** Your system administrator has been notified *****" + + + def notify(self, email, message, subject = "", subtitle="", filename=""): + """notify sends an email with the given message and title to email. + + Inputs: email (string), message (string), and subject (string) + + Returns: void + + Affects: none + + Exceptions: None. + """ + + print "Notifying to %s ..." %email + + self.sendEmail(email_from=self.__emailFromAddress, + email_to=email, + subject=subject, + message=message, + subtitle=subtitle, + filename=filename) + + print "***** Your system administrator has been notified *****" + +class SchainError: + """SchainError is an exception class that is thrown for all known errors in using Schain Py lib. + + Usage example: + + import sys, traceback + import schainpy.admin + + try: + + test = open('ImportantFile.txt', 'r') + + except: + + raise schainpy.admin.SchainError('ImportantFile.txt not opened!', + traceback.format_exception(sys.exc_info()[0], + sys.exc_info()[1], + sys.exc_info()[2])) + """ + + + def __init__(self, strInterpretation, exceptionList): + """ __init__ gathers the interpretation string along with all information from sys.exc_info(). + + Inputs: strIntepretation - A string representing the programmer's interpretation of + why the exception occurred + + exceptionList - a list of strings completely describing the exception. + Generated by traceback.format_exception(sys.exc_info()[0], + sys.exc_info()[1], + sys.exc_info()[2]) + + Returns: Void. + + Affects: Initializes class member variables _strInterp, _strExcList. + + Exceptions: None. + """ + + self._strInterp = strInterpretation + self._strExcList = exceptionList + + + def getExceptionStr(self): + """ getExceptionStr returns a formatted string ready for printing completely describing the exception. + + Inputs: None + + Returns: A formatted string ready for printing completely describing the exception. + + Affects: None + + Exceptions: None. + """ + excStr = 'The following Schain Python exception has occurred:\n' + excStr = excStr + self._strInterp + '\n\n' + + if self._strExcList != None: + for item in self._strExcList: + excStr = excStr + str(item) + '\n' + + return excStr + + def __str__(self): + return(self.getExceptionStr()) + + + def getExceptionHtml(self): + """ getExceptionHtml returns an Html formatted string completely describing the exception. + + Inputs: None + + Returns: A formatted string ready for printing completely describing the exception. + + Affects: None + + Exceptions: None. + """ + + excStr = '
The following Schain Python exception has occurred:\n
' + excStr = excStr + self._strInterp + '\n
\n' + + if self._strExcList != None: + for item in self._strExcList: + excStr = excStr + str(item) + '\n
' + + return excStr + +if __name__ == '__main__': + + test = SchainNotify() + + test.sendAlert('This is a message from the python module SchainNotify', 'Test from SchainNotify') + + print 'Hopefully message sent - check.' diff --git a/schainpy/controller.py b/schainpy/controller.py index e3d0f60..cc9b5dd 100644 --- a/schainpy/controller.py +++ b/schainpy/controller.py @@ -2,18 +2,18 @@ Created on September , 2012 @author: ''' -from xml.etree.ElementTree import ElementTree, Element, SubElement, tostring -from xml.dom import minidom - -from model import * -from time import sleep import sys import ast import traceback +import schainpy +import schainpy.admin -SCHAIN_MAIL = "miguel.urco@jro.igp.gob.pe" -EMAIL_SERVER = "jro.igp.gob.pe" +from xml.etree.ElementTree import ElementTree, Element, SubElement, tostring +from xml.dom import minidom + +from schainpy.model import * +from time import sleep def prettify(elem): """Return a pretty-printed XML string for the Element. @@ -325,9 +325,9 @@ class OperationConf(): return parmConfObj - def makeXml(self, upElement): + def makeXml(self, procUnitElement): - opElement = SubElement(upElement, self.ELEMENTNAME) + opElement = SubElement(procUnitElement, self.ELEMENTNAME) opElement.set('id', str(self.id)) opElement.set('name', self.name) opElement.set('type', self.type) @@ -539,16 +539,16 @@ class ProcUnitConf(): return opConfObj - def makeXml(self, procUnitElement): + def makeXml(self, projectElement): - upElement = SubElement(procUnitElement, self.ELEMENTNAME) - upElement.set('id', str(self.id)) - upElement.set('name', self.name) - upElement.set('datatype', self.datatype) - upElement.set('inputId', str(self.inputId)) + procUnitElement = SubElement(projectElement, self.ELEMENTNAME) + procUnitElement.set('id', str(self.id)) + procUnitElement.set('name', self.name) + procUnitElement.set('datatype', self.datatype) + procUnitElement.set('inputId', str(self.inputId)) for opConfObj in self.opConfObjList: - opConfObj.makeXml(upElement) + opConfObj.makeXml(procUnitElement) def readXml(self, upElement): @@ -757,12 +757,53 @@ class ReadUnitConf(ProcUnitConf): return opObj +# def makeXml(self, projectElement): +# +# procUnitElement = SubElement(projectElement, self.ELEMENTNAME) +# procUnitElement.set('id', str(self.id)) +# procUnitElement.set('name', self.name) +# procUnitElement.set('datatype', self.datatype) +# procUnitElement.set('inputId', str(self.inputId)) +# +# for opConfObj in self.opConfObjList: +# opConfObj.makeXml(procUnitElement) + + def readXml(self, upElement): + + self.id = upElement.get('id') + self.name = upElement.get('name') + self.datatype = upElement.get('datatype') + self.inputId = upElement.get('inputId') + + if self.ELEMENTNAME == "ReadUnit": + self.datatype = self.datatype.replace("Reader", "") + + if self.inputId == 'None': + self.inputId = '0' + + self.opConfObjList = [] + + opElementList = upElement.getiterator(OperationConf().getElementName()) + + for opElement in opElementList: + opConfObj = OperationConf() + opConfObj.readXml(opElement) + self.opConfObjList.append(opConfObj) + + if opConfObj.name == 'run': + self.path = opConfObj.getParameterValue('path') + self.startDate = opConfObj.getParameterValue('startDate') + self.endDate = opConfObj.getParameterValue('endDate') + self.startTime = opConfObj.getParameterValue('startTime') + self.endTime = opConfObj.getParameterValue('endTime') + class Project(): id = None name = None description = None -# readUnitConfObjList = None + filename = None + procUnitConfObjDict = None ELEMENTNAME = 'Project' @@ -898,14 +939,26 @@ class Project(): def writeXml(self, filename): - self.makeXml() + if not os.access(os.path.dirname(filename), os.W_OK): + return 0 - #print prettify(self.projectElement) + if os.path.isfile(filename) and not(os.access(filename, os.W_OK)): + return 0 + + self.makeXml() ElementTree(self.projectElement).write(filename, method='xml') + + self.filename = filename + + return 1 def readXml(self, filename): + if not os.path.isfile(filename): + print "%s does not exist" %filename + return 0 + self.projectElement = None self.procUnitConfObjDict = {} @@ -938,7 +991,9 @@ class Project(): procUnitConfObj.parentId = self.id self.procUnitConfObjDict[procUnitConfObj.getId()] = procUnitConfObj - + + return 1 + def printattr(self): print "Project[%s]: name = %s, description = %s" %(self.id, @@ -1010,7 +1065,7 @@ class Project(): print print "*"*60 - print " Starting SIGNAL CHAIN PROCESSING " + print " Starting SIGNAL CHAIN PROCESSING v%s " %schainpy.__version__ print "*"*60 print @@ -1026,28 +1081,46 @@ class Project(): procUnitConfObj = self.procUnitConfObjDict[procKey] - message = "" try: sts = procUnitConfObj.run() is_ok = is_ok or sts except: - print "***** Error running %s *****" %procUnitConfObj.name - sleep(1) + print "***** Error occurred in %s *****" %(procUnitConfObj.name) + + sleep(0.5) err = traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]) - for thisLine in err: - message += thisLine + import socket + + subject = "SChain v%s: Error running %s\n" %(schainpy.__version__, procUnitConfObj.name) + + subtitle = "%s: %s\n" %(procUnitConfObj.getElementName() ,procUnitConfObj.name) + subtitle += "Hostname: %s\n" %socket.gethostbyname(socket.gethostname()) + subtitle += "Working directory: %s\n" %os.path.abspath("./") + subtitle += "Configuration file: %s\n" %self.filename + + readUnitConfObj = self.getReadUnitObj() + if readUnitConfObj: + subtitle += "Data path: %s\n" %readUnitConfObj.path + subtitle += "Data type: %s\n" %readUnitConfObj.datatype + subtitle += "Start date: %s\n" %readUnitConfObj.startDate + subtitle += "End date: %s\n" %readUnitConfObj.endDate + subtitle += "Start time: %s\n" %readUnitConfObj.startTime + subtitle += "End time: %s\n" %readUnitConfObj.endTime + + message = "".join(err) sys.stderr.write(message) -# print "*"*60 -# print message -# print "*"*60 + + adminObj = schainpy.admin.SchainNotify() + adminObj.sendAlert(message=message, + subject=subject, + subtitle=subtitle, + filename=self.filename) -# self.sendReport(message) - sleep(0.1) is_ok = False break @@ -1075,23 +1148,6 @@ class Project(): self.createObjects() self.connectObjects() self.run() - - def sendReport(self, message, subject="Error occurred in Signal Chain", email=SCHAIN_MAIL): - - import smtplib - - print subject - print "Sending report to %s ..." %email - - message = 'From: (Python Signal Chain API) ' + email + '\n' + \ - 'To: ' + email + '\n' + \ - 'Subject: ' + str(subject) + '\n' + \ - 'Content-type: text/html\n\n' + message - - server = smtplib.SMTP(EMAIL_SERVER) - server.sendmail(email.split(',')[0], - email.split(','), message) - server.quit() if __name__ == '__main__': diff --git a/schainpy/controller_api.py b/schainpy/controller_api.py index 39c871b..d44d9e6 100644 --- a/schainpy/controller_api.py +++ b/schainpy/controller_api.py @@ -1,8 +1,5 @@ import threading -from PyQt4 import QtCore -from PyQt4.QtCore import SIGNAL - from schainpy.controller import Project class ControllerThread(threading.Thread, Project): @@ -74,68 +71,71 @@ class ControllerThread(threading.Thread, Project): def isFinished(self): return not self.is_alive() - -class ControllerQThread(QtCore.QThread, Project): - - def __init__(self, filename): - - QtCore.QThread.__init__(self) - Project.__init__(self) - - self.filename = filename - - self.lock = threading.Lock() - self.control = {'stop':False, 'pause':False} - - def __del__(self): - - self.control['stop'] = True - self.wait() - - def stop(self): - - self.lock.acquire() - - self.control['stop'] = True - - self.lock.release() - - def pause(self): - - self.lock.acquire() - - self.control['pause'] = not(self.control['pause']) - paused = self.control['pause'] - - self.lock.release() - - return paused - - def isPaused(self): - - self.lock.acquire() - paused = self.control['pause'] - self.lock.release() - - return paused - - def isStopped(self): - - self.lock.acquire() - stopped = self.control['stop'] - self.lock.release() - - return stopped - - def run(self): - - self.control['stop'] = False - self.control['pause'] = False - - self.readXml(self.filename) - self.createObjects() - self.connectObjects() - self.emit( SIGNAL( "jobStarted( PyQt_PyObject )" ), 1) - Project.run(self) - self.emit( SIGNAL( "jobFinished( PyQt_PyObject )" ), 1) - \ No newline at end of file + +# from PyQt4 import QtCore +# from PyQt4.QtCore import SIGNAL +# +# class ControllerQThread(QtCore.QThread, Project): +# +# def __init__(self, filename): +# +# QtCore.QThread.__init__(self) +# Project.__init__(self) +# +# self.filename = filename +# +# self.lock = threading.Lock() +# self.control = {'stop':False, 'pause':False} +# +# def __del__(self): +# +# self.control['stop'] = True +# self.wait() +# +# def stop(self): +# +# self.lock.acquire() +# +# self.control['stop'] = True +# +# self.lock.release() +# +# def pause(self): +# +# self.lock.acquire() +# +# self.control['pause'] = not(self.control['pause']) +# paused = self.control['pause'] +# +# self.lock.release() +# +# return paused +# +# def isPaused(self): +# +# self.lock.acquire() +# paused = self.control['pause'] +# self.lock.release() +# +# return paused +# +# def isStopped(self): +# +# self.lock.acquire() +# stopped = self.control['stop'] +# self.lock.release() +# +# return stopped +# +# def run(self): +# +# self.control['stop'] = False +# self.control['pause'] = False +# +# self.readXml(self.filename) +# self.createObjects() +# self.connectObjects() +# self.emit( SIGNAL( "jobStarted( PyQt_PyObject )" ), 1) +# Project.run(self) +# self.emit( SIGNAL( "jobFinished( PyQt_PyObject )" ), 1) +# \ No newline at end of file diff --git a/schainpy/schain.conf.template b/schainpy/schain.conf.template new file mode 100644 index 0000000..d9fced0 --- /dev/null +++ b/schainpy/schain.conf.template @@ -0,0 +1,5 @@ +#Copy this file to /etc/schain.conf +[schain] +CONTACT = miguel.urco@jro.igp.gob.pe +MAILSERVER = jro-zimbra.igp.gob.pe +MALSERVER_ACCOUNT = notifier-schain@jro.igp.gob.pe \ No newline at end of file diff --git a/setup.py b/setup.py index 1a70eb1..21fd8de 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,9 @@ setup(name="schainpy", 'schainpy.gui.viewer.windows'}, py_modules=['schainpy.serializer.DataTranslate', 'schainpy.serializer.JROSerializer'], - package_data={'schainpy.gui.figures': ['*.png','*.jpg']}, + package_data={'schainpy': ['*.cfg'], + 'schainpy.gui.figures': ['*.png','*.jpg'] + }, include_package_data=True, scripts =['schainpy/gui/schainGUI'], install_requires=["numpy >= 1.6.0",