From a3dfdb3772ba32a775fbd9a3b609ad625de63ded Mon Sep 17 00:00:00 2001 From: nick Date: Wed, 11 Mar 2015 13:21:12 -0700 Subject: [PATCH 01/60] Updated python code to py3k. --- lib/gpi/GLObjects.py | 6 +-- lib/gpi/__init__.py | 2 +- lib/gpi/associate.py | 4 +- lib/gpi/canvasGraph.py | 34 ++++++------- lib/gpi/canvasScene.py | 2 +- lib/gpi/catalog.py | 16 +++--- lib/gpi/cmd.py | 6 +-- lib/gpi/config.py | 12 ++--- lib/gpi/defaultTypes.py | 2 +- lib/gpi/defines.py | 2 +- lib/gpi/docs.py | 10 ++-- lib/gpi/edge.py | 4 +- lib/gpi/functor.py | 4 +- lib/gpi/layoutWindow.py | 4 +- lib/gpi/library.py | 24 ++++----- lib/gpi/logger.py | 2 +- lib/gpi/macroNode.py | 6 +-- lib/gpi/mainWindow.py | 18 +++---- lib/gpi/make.py | 104 +++++++++++++++++++-------------------- lib/gpi/network.py | 2 +- lib/gpi/node.py | 20 ++++---- lib/gpi/nodeAPI.py | 14 +++--- lib/gpi/port.py | 6 +-- lib/gpi/qtapi.py | 16 +++--- lib/gpi/stateMachine.py | 4 +- lib/gpi/topsort.py | 28 +++++------ lib/gpi/widgets.py | 12 ++--- plugin/python_GPITYPE.py | 2 +- 28 files changed, 183 insertions(+), 183 deletions(-) diff --git a/lib/gpi/GLObjects.py b/lib/gpi/GLObjects.py index c1f3b950..626f3028 100644 --- a/lib/gpi/GLObjects.py +++ b/lib/gpi/GLObjects.py @@ -235,7 +235,7 @@ def applyShape(self): def run(self): if self._multiples is not None: self.applyLighting() - for i in xrange(self._multiples.shape[0]): + for i in range(self._multiples.shape[0]): self._position = self._multiples[i].tolist() self.applyTransforms() else: @@ -268,7 +268,7 @@ def applyShape(self): '''Grab external QT rendering command. ''' GL.glColor4d(*self._RGBA) - f = QtGui.QFont(unicode(self._font), self._ptsize) + f = QtGui.QFont(str(self._font), self._ptsize) p = self._position if self._glwdg is None: log.critical('Reference to GLWidget is not set! Aborting render.') @@ -394,7 +394,7 @@ def run(self): if self._multiples is not None: if self._endToEnd: - for i in xrange(self._multiples.shape[0]-1): + for i in range(self._multiples.shape[0]-1): self.setP1P2(self._multiples[i], self._multiples[i+1]) self.applyTransforms() else: diff --git a/lib/gpi/__init__.py b/lib/gpi/__init__.py index 3b7b62fb..b4716662 100644 --- a/lib/gpi/__init__.py +++ b/lib/gpi/__init__.py @@ -45,7 +45,7 @@ LIABILITY ACTIVITIES. ''' -print _version+' '+_copyright+'\n'+_disclaimer +print(_version+' '+_copyright+'\n'+_disclaimer) # transitioning tool from . import qtapi diff --git a/lib/gpi/associate.py b/lib/gpi/associate.py index 6e7c801c..7a5b6263 100644 --- a/lib/gpi/associate.py +++ b/lib/gpi/associate.py @@ -62,14 +62,14 @@ def isGPIAssociatedFile(fullpath): ''' if os.path.isfile(fullpath): bpath, ext = os.path.splitext(fullpath) - if str(ext).lower() in Bindings.keys(): + if str(ext).lower() in list(Bindings.keys()): return True return False def isGPIAssociatedExt(ext): '''Determine if the path exists, isfile, and valid ext. ''' - if str(ext).lower() in Bindings.keys(): + if str(ext).lower() in list(Bindings.keys()): return True return False diff --git a/lib/gpi/canvasGraph.py b/lib/gpi/canvasGraph.py index b793cac0..3a064b88 100644 --- a/lib/gpi/canvasGraph.py +++ b/lib/gpi/canvasGraph.py @@ -93,7 +93,7 @@ from .nodeQueue import GPINodeQueue from .port import Port, InPort from .stateMachine import GPI_FSM, GPIState -import topsort +from . import topsort from .logger import manager @@ -672,13 +672,13 @@ def inProcessingState(self): def printNodeState(self): allItems = self.getAllNodes() for node in allItems: - print "________________________" + print("________________________") node.printCurState() - print "node: " + str(node.name) - print "inDisabledState: " + str(node.inDisabledState()) - print "hasEventPending: " + str(node.hasEventPending()) - print "_nodeIF.reQueueIsSet: " + str(node._nodeIF.reQueueIsSet()) - print "________________________" + print("node: " + str(node.name)) + print("inDisabledState: " + str(node.inDisabledState())) + print("hasEventPending: " + str(node.hasEventPending())) + print("_nodeIF.reQueueIsSet: " + str(node._nodeIF.reQueueIsSet())) + print("________________________") def setPauseState(self, val): old_val = self.nodeQueue.isPaused() @@ -946,14 +946,14 @@ def getSelectedMacroNodes(self): return macros, enodes def getAllNodes(self): - allitems = self.scene().items()[:] # copy in case of user interrupt + allitems = list(self.scene().items())[:] # copy in case of user interrupt nodes = [item for item in allitems if isinstance(item, Node)] return(nodes) def getAllMacros(self): '''Get the MacroNode object class handle. ''' - allitems = self.scene().items()[:] # copy in case of user interrupt + allitems = list(self.scene().items())[:] # copy in case of user interrupt nodes = [item for item in allitems if isinstance(item, MacroNode)] return(nodes) @@ -965,12 +965,12 @@ def findNodeByNameAndLabel(self, name, lab): return node def getAllPorts(self): - allitems = self.scene().items()[:] # copy in case of user interrupt + allitems = list(self.scene().items())[:] # copy in case of user interrupt ports = [item for item in allitems if isinstance(item, Port)] return(ports) def getSelectedNodes(self): - sceneItems = self.scene().items()[:] # copy in case of user interrupt + sceneItems = list(self.scene().items())[:] # copy in case of user interrupt sceneItems = [ i for i in sceneItems if i.isSelected() and isinstance(i, Node)] return sceneItems @@ -1110,7 +1110,7 @@ def keyPressEvent(self, event): if len(snodes): # just skip if no nodes are selected snode = snodes[0] nodes = self.getLinearNodeHierarchy() - for i in xrange(len(nodes)): + for i in range(len(nodes)): if nodes[i] == snode: if i < len(nodes) - 1: self.scene().makeOnlyTheseNodesSelected( @@ -1136,11 +1136,11 @@ def keyPressEvent(self, event): elif key == QtCore.Qt.Key_Minus: self.scaleView(1 / 1.2) elif key == QtCore.Qt.Key_Enter: - print "Key_Enter" + print("Key_Enter") # mix up nodes elif key == QtCore.Qt.Key_M and modifiers == QtCore.Qt.ControlModifier: - for item in self.scene().items(): + for item in list(self.scene().items()): if isinstance(item, Node): item.setPos(-150 + QtCore.qrand() % 300, -150 + QtCore.qrand() % 300) @@ -1189,8 +1189,8 @@ def keyPressEvent(self, event): #print self.getAllPorts() #print self.getAllMacroNodes() #print self.serializeGraphData() - print self.getAllNodes() - print self.getAllMacroNodes() + print(self.getAllNodes()) + print(self.getAllMacroNodes()) # close all node windows elif key == QtCore.Qt.Key_X and modifiers == QtCore.Qt.ControlModifier: @@ -1984,7 +1984,7 @@ def serializeGraphData(self, selectedOnly=False, minusAvgPos=False): for node in nodes: graph_settings['nodes'].append(copy.deepcopy(node.getSettings())) - for nid, nodes in macroNodes.iteritems(): + for nid, nodes in macroNodes.items(): graph_settings['macroNodes'].append(nodes[0].macroParent().getSettings()) if minusAvgPos and (len(graph_settings['nodes']) + len(graph_settings['macroNodes'])): diff --git a/lib/gpi/canvasScene.py b/lib/gpi/canvasScene.py index 28dbbef6..d1c09e50 100644 --- a/lib/gpi/canvasScene.py +++ b/lib/gpi/canvasScene.py @@ -306,7 +306,7 @@ def mouseReleaseEvent(self, event): # CANVAS SCENE def unselectAllItems(self): # TODO: add Z level changes here too - for item in self.items(): + for item in list(self.items()): if item.isSelected(): item.setSelected(False) diff --git a/lib/gpi/catalog.py b/lib/gpi/catalog.py index 9d6d627b..cef0091a 100644 --- a/lib/gpi/catalog.py +++ b/lib/gpi/catalog.py @@ -75,20 +75,20 @@ def append(self, elem): self._db[elem.key()] = elem def keys(self): - return self._db.keys() + return list(self._db.keys()) def values(self): - return self._db.values() + return list(self._db.values()) def iteritems(self): - return self._db.iteritems() + return iter(self._db.items()) def d(self): return self._db def __str__(self): o = '\n' - for k,v in self._db.iteritems(): + for k,v in self._db.items(): o += k +': '+str(v) + '\n' return o @@ -97,7 +97,7 @@ def get(self, key): def find(self, attr, value): # return the first object with the given attribute and value - for k,v in self._db.iteritems(): + for k,v in self._db.items(): if hasattr(v, attr): if getattr(v, attr) == value: return v @@ -105,7 +105,7 @@ def find(self, attr, value): def list(self, attr, value): o = [] # return a list of objects with the given attribute and value - for k,v in self._db.iteritems(): + for k,v in self._db.items(): if hasattr(v, attr): if getattr(v, attr) == value: o.append(v) @@ -118,7 +118,7 @@ def intrafind(self, finder, key): # Return the first object with a True internal finder() result. # The finder() function is a method contained within the CatalogObj() # subclass that returns True or False given the passed key for comparison. - for k,v in self._db.iteritems(): + for k,v in self._db.items(): if hasattr(v, finder): if getattr(v, finder)(key): return v @@ -128,7 +128,7 @@ def intralist(self, finder, key): # The finder() function is a method contained within the CatalogObj() # subclass that returns True or False given the passed key for comparison. o = [] - for k,v in self._db.iteritems(): + for k,v in self._db.items(): if hasattr(v, finder): if getattr(v, finder)(key): o.append(v) diff --git a/lib/gpi/cmd.py b/lib/gpi/cmd.py index 1e3a1de9..9690b5d4 100644 --- a/lib/gpi/cmd.py +++ b/lib/gpi/cmd.py @@ -108,7 +108,7 @@ def parse(self, argv): self.dumpSpecs() if self._options.dumpConfig: - from config import Config + from .config import Config log.dialog('Config:\n'+str(Config)) sys.exit(0) @@ -118,7 +118,7 @@ def parse(self, argv): def dumpSpecs(self): with open('specs.txt', 'wb') as specsfile: specsfile.write('# GPI (v'+str(VERSION)+') system specifications file.\n') - for k,v in Specs.table().iteritems(): + for k,v in Specs.table().items(): msg = k+': '+str(v) + '\n' specsfile.write(msg) @@ -160,7 +160,7 @@ def stringNodeArg(self, key): log.error('\''+str(key)+'\' not found in string args.') def stringNodeLabels(self): - return self._sargs.keys() + return list(self._sargs.keys()) def storeStringNodeArgs(self): # merge any redundant labels with warnings diff --git a/lib/gpi/config.py b/lib/gpi/config.py index c4e51421..9188359c 100644 --- a/lib/gpi/config.py +++ b/lib/gpi/config.py @@ -26,7 +26,7 @@ import os import traceback -import ConfigParser +import configparser # gpi from .associate import Bindings, BindCatalogItem @@ -123,7 +123,7 @@ def __str__(self): # even though bindings are external print them here for convenience msg += 'ASSOCIATIONS:\n' - for v in sorted([str(x) for x in Bindings.values()]): + for v in sorted([str(x) for x in list(Bindings.values())]): msg += str(v) + '\n' # makefile modifications @@ -258,7 +258,7 @@ def generateConfigFile(self, overwrite=False): configfile.write('# GPI (v'+str(VERSION)+') configuration file.\n') configfile.write('# Uncomment an option to activate it.\n') - config = ConfigParser.RawConfigParser() + config = configparser.RawConfigParser() # Makefile mods configfile.write('\n[GENERAL]\n') @@ -317,7 +317,7 @@ def loadConfigFile(self): log.info("loadConfigFile(): config file " + str(self._c_configFileName) + " doesn't exist, skipping.") return - config = ConfigParser.ConfigParser() + config = configparser.ConfigParser() config.read(self._c_configFileName) # print parse-able info @@ -329,7 +329,7 @@ def loadConfigFile(self): aps = lambda x: [ ap(p) for p in x.split(':') ] # multi-dirs ch = config.has_option # if config has the option... cg = config.get - oh = os.environ.has_key # if the env has the option... + oh = lambda x: x in os.environ oe = os.environ if config.has_section('GENERAL'): @@ -413,7 +413,7 @@ def parseMultiOPTS(self, config, section, option, env_opt=None, warnOnENV=True): aps = lambda x: [ ap(p) for p in x.split(':') ] # multi-dirs ch = config.has_option # if config has the option... cg = config.get - oh = os.environ.has_key # if the env has the option... + oh = lambda x: x in os.environ oe = os.environ if ch(section, option): diff --git a/lib/gpi/defaultTypes.py b/lib/gpi/defaultTypes.py index f4bd9aa9..404421a2 100644 --- a/lib/gpi/defaultTypes.py +++ b/lib/gpi/defaultTypes.py @@ -120,7 +120,7 @@ def setTypeParms(self, **kwargs): """Use the kwargs dict to set user specified args to addPort() """ - for k, v in kwargs.iteritems(): + for k, v in kwargs.items(): setter = "set_"+k if hasattr(self, setter): getattr(self, setter)(v) diff --git a/lib/gpi/defines.py b/lib/gpi/defines.py index 3621edcb..8e24fded 100644 --- a/lib/gpi/defines.py +++ b/lib/gpi/defines.py @@ -52,7 +52,7 @@ def stw(s): def terminalBell(): #log.dialog('<< BELL >>') - print '\a' + print('\a') def GetHumanReadable_bytes(size, precision=2): # change size in bytes (int) to a string with nice display units diff --git a/lib/gpi/docs.py b/lib/gpi/docs.py index 3f03edcc..b5b72101 100755 --- a/lib/gpi/docs.py +++ b/lib/gpi/docs.py @@ -81,7 +81,7 @@ def scanGPIModules(self, ipath, recursion_depth=1): if item.valid(): self._known_GPI_nodes.append(item) else: - print "Failed to load: "+str(item) + print("Failed to load: "+str(item)) def __str__(self): @@ -92,7 +92,7 @@ def extractDocs(self): # look for ExternalNode cur_lib = '' cur_sub = '' - for item in sorted(self._known_GPI_nodes.values(), key=lambda x: x.key().lower()): + for item in sorted(list(self._known_GPI_nodes.values()), key=lambda x: x.key().lower()): if hasattr(item.mod, 'ExternalNode'): cur_doc = str(inspect.getdoc(getattr(item.mod, 'ExternalNode'))) @@ -120,7 +120,7 @@ def extractDocs(self): #self._docText += '\n'+80*'*'+'\n' else: - print str(item) + ' Doesnt have ExternalNode definition, skipping...' + print(str(item) + ' Doesnt have ExternalNode definition, skipping...') @@ -328,7 +328,7 @@ def formatFuncDoc(self, func): # NodeAPI with open('NodeAPI.md','w') as f: - print "Writing to NodeAPI.md..." + print("Writing to NodeAPI.md...") preamble = '''# Node API\n''' @@ -339,7 +339,7 @@ def formatFuncDoc(self, func): # CoreNodes with open('CoreNodes.md','w') as f: - print "Writing to CoreNodes.md..." + print("Writing to CoreNodes.md...") preamble = '''This is the core node library. The following node usage information was generated from comments written into diff --git a/lib/gpi/edge.py b/lib/gpi/edge.py index fbdc1627..0f656272 100644 --- a/lib/gpi/edge.py +++ b/lib/gpi/edge.py @@ -323,9 +323,9 @@ def paint(self, painter, option, widget): # EDGE a = math.atan2(ya, xa)*180.0/math.pi buf = self.source.getDataString() if self._beingHovered: - f = QtGui.QFont(u"times", 8) + f = QtGui.QFont("times", 8) else: - f = QtGui.QFont(u"times", 6) + f = QtGui.QFont("times", 6) fm = QtGui.QFontMetricsF(f) bw = fm.width(buf) bw2 = -bw*0.5 diff --git a/lib/gpi/functor.py b/lib/gpi/functor.py index 4e5583c2..647eb432 100644 --- a/lib/gpi/functor.py +++ b/lib/gpi/functor.py @@ -210,7 +210,7 @@ def applyQueuedData_setData(self): if o[0] == 'setData': # flag large NPY arrays for reconstruction if type(o[2]) is dict: - if o[2].has_key('951413'): + if '951413' in o[2]: self._largeNPYpresent = True continue self._node.setData(o[1], o[2]) @@ -225,7 +225,7 @@ def applyQueuedData_setData(self): # consolidate all outport data of type dict oportData = [ o for o in self._proxy if (o[0] == 'setData') and (type(o[2]) is dict) ] # take only dictionaries with the special key - oportData = [ o for o in oportData if o[2].has_key('951413') ] + oportData = [ o for o in oportData if '951413' in o[2] ] # consolidate all outports with large NPY arrays largeports = set([ o[1] for o in oportData ]) diff --git a/lib/gpi/layoutWindow.py b/lib/gpi/layoutWindow.py index 7999377a..b26c3dac 100644 --- a/lib/gpi/layoutWindow.py +++ b/lib/gpi/layoutWindow.py @@ -314,7 +314,7 @@ def loadSettings(self, s, nodeList): # wls: window layout settings if self._config == 3: curlabels = [lw.label() for lw in self.findChildren(LayoutWindow)] - for label, wls in s['layouts'].iteritems(): + for label, wls in s['layouts'].items(): if label not in curlabels: if label.startswith('top'): self.addTopColumn(label) @@ -323,7 +323,7 @@ def loadSettings(self, s, nodeList): # assign widgets to each layout # wls: window layout settings - for label, wls in s['layouts'].iteritems(): + for label, wls in s['layouts'].items(): log.debug("trying " + label) for lw in self.findChildren(LayoutWindow): diff --git a/lib/gpi/library.py b/lib/gpi/library.py index d19649cf..0b8b9276 100644 --- a/lib/gpi/library.py +++ b/lib/gpi/library.py @@ -426,11 +426,11 @@ def findNode_byPath(self, path): def findNode_byLibrary(self, name, second, third): key = third+'.'+second+'.'+name - if key in self._known_GPI_nodes.keys(): + if key in list(self._known_GPI_nodes.keys()): return self._known_GPI_nodes.get(key) def findNode_byKey(self, key): - if key in self._known_GPI_nodes.keys(): + if key in list(self._known_GPI_nodes.keys()): return self._known_GPI_nodes.get(key) def findNode_byClosestMatch(self, name, wdg_port_names): @@ -613,14 +613,14 @@ def regenerateLibMenus(self): def generateLibMenus(self): # default menu if no libraries are found - numnodes = len(self._known_GPI_nodes.keys()) + numnodes = len(list(self._known_GPI_nodes.keys())) if numnodes == 0: self._lib_menus['No Nodes Found'] = QtGui.QMenu('No Nodes Found') buf = 'Check your ~/.gpirc for the correct LIB_DIRS.' act = QtGui.QAction(buf, self._parent, triggered = self.openLIBDIRSHelp) self._lib_menus['No Nodes Found'].addAction(act) - for m in sorted(self._lib_menus.keys(), key=lambda x: x.lower()): + for m in sorted(list(self._lib_menus.keys()), key=lambda x: x.lower()): mm = self._lib_menus[m] mm.setTearOffEnabled(False) self._lib_menu.append(mm) @@ -630,7 +630,7 @@ def generateLibMenus(self): # NODE MENU # setup libs using node id. ex: core.mathematics.sum # the ids of - for k in sorted(self._known_GPI_nodes.keys(), key=lambda x: x.lower()): + for k in sorted(list(self._known_GPI_nodes.keys()), key=lambda x: x.lower()): node = self._known_GPI_nodes.get(k) if node.third not in self._lib_menus: #self._lib_menus[node.third] = QtGui.QMenu(node.third.capitalize()) @@ -653,10 +653,10 @@ def generateLibMenus(self): # NETWORK MENU - for sm in self._lib_second.values(): + for sm in list(self._lib_second.values()): sm.addSeparator() - for k in sorted(self._known_GPI_networks.keys(), key=lambda x: x.lower()): + for k in sorted(list(self._known_GPI_networks.keys()), key=lambda x: x.lower()): net = self._known_GPI_networks.get(k) if net.third not in self._lib_menus: #self._lib_menus[net.third] = QtGui.QMenu(net.third.capitalize()) @@ -675,7 +675,7 @@ def generateLibMenus(self): lambda who=s: self._parent.addNodeRun(who)) sm.addAction(a) - for m in sorted(self._lib_menus.keys(), key=lambda x: x.lower()): + for m in sorted(list(self._lib_menus.keys()), key=lambda x: x.lower()): mm = self._lib_menus[m] mm.setTearOffEnabled(True) self._lib_menu.append(mm) @@ -723,11 +723,11 @@ def generateNodeSearchActions(self, txt, menu, mousemenu): # search using txt string sortedMods = [] if len(txt) > 2: # match anywhere in name - for node in self._known_GPI_nodes.values(): + for node in list(self._known_GPI_nodes.values()): if node.name.lower().find(txt) > -1: sortedMods.append(node) else: # only match from start of name - for node in self._known_GPI_nodes.values(): + for node in list(self._known_GPI_nodes.values()): if node.name.lower().startswith(txt): sortedMods.append(node) sortedMods = sorted(sortedMods, key=lambda x: x.name.lower()) @@ -751,12 +751,12 @@ def generateNodeSearchActions(self, txt, menu, mousemenu): if True: sortedMods= [] if len(txt) > 2: - for net in self._known_GPI_networks.values(): + for net in list(self._known_GPI_networks.values()): if net.name.lower().find(txt) > -1: sortedMods.append(net) else: - for net in self._known_GPI_networks.values(): + for net in list(self._known_GPI_networks.values()): if net.name.lower().startswith(txt): sortedMods.append(net) sortedMods = sorted(sortedMods, key=lambda x: x.name.lower()) diff --git a/lib/gpi/logger.py b/lib/gpi/logger.py index 15e612b9..863267d9 100644 --- a/lib/gpi/logger.py +++ b/lib/gpi/logger.py @@ -80,7 +80,7 @@ def printb(self, msg, lev): lineno = str(inspect.currentframe().f_back.f_back.f_lineno) # the user input 'msg' is forced to be a string - print time.asctime(time.localtime()) + ' - ' + self._name + ':' + lineno + ' - ' + lev + ' - ' + str(msg) + print(time.asctime(time.localtime()) + ' - ' + self._name + ':' + lineno + ' - ' + lev + ' - ' + str(msg)) class GPILogManager(object): diff --git a/lib/gpi/macroNode.py b/lib/gpi/macroNode.py index a1e604df..1c5ba20e 100644 --- a/lib/gpi/macroNode.py +++ b/lib/gpi/macroNode.py @@ -297,7 +297,7 @@ def paint(self, painter, option, widget): # NODE # paint the node title painter.drawText(-5, -9, w, 20, (QtCore.Qt.AlignLeft | - QtCore.Qt.AlignVCenter), unicode(buf)) + QtCore.Qt.AlignVCenter), str(buf)) class MacroNodeEdge(QtGui.QGraphicsItem): @@ -403,7 +403,7 @@ def paint(self, painter, option, widget): m = math.sqrt(xa * xa + ya * ya) a = math.atan2(ya, xa) * 180.0 / math.pi buf = "Macro" - f = QtGui.QFont(u"times", 20) + f = QtGui.QFont("times", 20) fm = QtGui.QFontMetricsF(f) bw = fm.width(buf) bw2 = -bw * 0.5 @@ -568,7 +568,7 @@ def loadSettings(self, s, nodeList, pos): self._face.setPos(QtCore.QPointF(x, y)) rel = QtCore.QPointF(x, y) - for nid, epos in s['nodes_rel_pos'].iteritems(): + for nid, epos in s['nodes_rel_pos'].items(): enode = self.getNodeByID(nodeList, int(nid)) if enode: enode.setPos(rel + QtCore.QPointF(*epos)) diff --git a/lib/gpi/mainWindow.py b/lib/gpi/mainWindow.py index eed5e24f..6cbfa3b6 100644 --- a/lib/gpi/mainWindow.py +++ b/lib/gpi/mainWindow.py @@ -128,10 +128,10 @@ def __init__(self): self.createMenus() best_style = None - if u'Macintosh (aqua)' in QtGui.QStyleFactory.keys(): + if 'Macintosh (aqua)' in list(QtGui.QStyleFactory.keys()): log.debug("Choosing Mac aqua style.") best_style = 'Macintosh (aqua)' - elif u'Cleanlooks' in QtGui.QStyleFactory.keys(): + elif 'Cleanlooks' in list(QtGui.QStyleFactory.keys()): log.debug("Choosing Cleanlooks style.") best_style = 'Cleanlooks' if best_style: @@ -484,20 +484,20 @@ def setLoggerLevelMenuCheckbox(self, lev): self._loglevel_critical_act.setChecked(True) def printSysPath(self): - print "Current module search path (sys.path):" + print("Current module search path (sys.path):") for path in sys.path: - print path + print(path) def printSysModules(self): - print "Current modules loaded (sys.modules):" - for k in sorted(sys.modules.iterkeys()): + print("Current modules loaded (sys.modules):") + for k in sorted(sys.modules.keys()): v = sys.modules[k] - print k + " : " + str(v) + print(k + " : " + str(v)) if False: if k.lower().count('spiral'): - print "key: " + k + ", " + str(v) + print("key: " + k + ", " + str(v)) elif str(v).lower().count('spiral'): - print "key: " + k + ", " + str(v) + print("key: " + k + ", " + str(v)) def changeStyle(self, action): # UI style diff --git a/lib/gpi/make.py b/lib/gpi/make.py index febf6c64..3d3e4f7a 100755 --- a/lib/gpi/make.py +++ b/lib/gpi/make.py @@ -54,7 +54,7 @@ # gpi from gpi.config import Config -print("\n"+str(sys.version)+"\n") +print(("\n"+str(sys.version)+"\n")) # from: # http://stackoverflow.com/questions/287871/print-in-terminal-with-colors-using-python @@ -74,7 +74,7 @@ class Cl: def compile(mod_name, include_dirs=[], libraries=[], library_dirs=[], extra_compile_args=[], runtime_library_dirs=[]): - print "Making target: " + mod_name + print("Making target: " + mod_name) # do usual generic module setup # NOT: 'mod_name' must have an init @@ -97,11 +97,11 @@ def compile(mod_name, include_dirs=[], libraries=[], library_dirs=[], ext_modules=[Module1], script_args=["build_ext", "--inplace", "--force"]) except: - print sys.exc_info() - print "FAILED: " + mod_name + print(sys.exc_info()) + print("FAILED: " + mod_name) return 1 - print "SUCCESS: " + mod_name + print("SUCCESS: " + mod_name) return 0 @@ -174,47 +174,47 @@ def makePy(basename, ext, fmt=False): if fmt: try: import autopep8 - print "\nFound: autopep8 " + str(autopep8.__version__) + "..." - print "Reformatting Python script: " + "".join(target) + print("\nFound: autopep8 " + str(autopep8.__version__) + "...") + print("Reformatting Python script: " + "".join(target)) os.system('autopep8 -i ' + "".join(target)) except: - print "Failed to perform auto-formatting \ - with \'autopep8\'." + print("Failed to perform auto-formatting \ + with \'autopep8\'.") # PEP8 try: import pep8 - print "\nFound: pep8 " + str(pep8.__version__) + "..." - print "Checking Python script: " + "".join(target) - print "pep8 found these problems with your code, START" + Cl.WRN + print("\nFound: pep8 " + str(pep8.__version__) + "...") + print("Checking Python script: " + "".join(target)) + print("pep8 found these problems with your code, START" + Cl.WRN) os.system('pep8 --count --statistics --show-source ' + "".join(target)) - print Cl.ESC + "pep8 END" + print(Cl.ESC + "pep8 END") except: - print "Failed to perform check with \'pep8\'." + print("Failed to perform check with \'pep8\'.") # PYFLAKES try: import pyflakes - print "\nFound: pyflakes " + str(pyflakes.__version__) + "..." - print "Checking Python script: " + "".join(target) - print "pyflakes found these problems with your code, START" + Cl.FAIL + print("\nFound: pyflakes " + str(pyflakes.__version__) + "...") + print("Checking Python script: " + "".join(target)) + print("pyflakes found these problems with your code, START" + Cl.FAIL) os.system('pyflakes ' + "".join(target)) - print Cl.ESC + "pyflakes END" + print(Cl.ESC + "pyflakes END") except: - print "Failed to perform check with \'pyflakes\'." + print("Failed to perform check with \'pyflakes\'.") # FORCE COMPILE try: - print '\nAttemping py_compile...' + print('\nAttemping py_compile...') py_compile.compile(''.join(target), doraise=True) - print 'py_compile END' - print '\nSUCCESS: '+''.join(target) + print('py_compile END') + print('\nSUCCESS: '+''.join(target)) return 0 except: - print Cl.FAIL + str(traceback.format_exc()) + Cl.ESC - print 'py_compile END' - print '\nFAILED: '+''.join(target) + print(Cl.FAIL + str(traceback.format_exc()) + Cl.ESC) + print('py_compile END') + print('\nFAILED: '+''.join(target)) return 1 @@ -267,12 +267,12 @@ def makePy(basename, ext, fmt=False): # supersedes 'args' if present. if options.makeall: if options.makeall_rdepth < 0: - print Cl.FAIL + "ERROR: recursion depth is set to an invalid number." + Cl.ESC + print(Cl.FAIL + "ERROR: recursion depth is set to an invalid number." + Cl.ESC) sys.exit(-1) targets = targetWalk(options.makeall_rdepth) if targets is None: - print Cl.FAIL + "ERROR: no targets specified." + Cl.ESC + print(Cl.FAIL + "ERROR: no targets specified." + Cl.ESC) sys.exit(-1) # LIBRARIES, INCLUDES, ENV-VARS @@ -284,7 +284,7 @@ def makePy(basename, ext, fmt=False): # USER MAKE config if (len(Config.MAKE_CFLAGS) + len(Config.MAKE_LIBS) + len(Config.MAKE_INC_DIRS) + len(Config.MAKE_LIB_DIRS)) > 0: - print "Adding USER include dirs" + print("Adding USER include dirs") # add user libs libraries += Config.MAKE_LIBS include_dirs += Config.MAKE_INC_DIRS @@ -292,7 +292,7 @@ def makePy(basename, ext, fmt=False): extra_compile_args += Config.MAKE_CFLAGS # GPI library dirs - print "Adding GPI include dirs" + print("Adding GPI include dirs") # add libs from library paths found_libs = {} for flib in Config.GPI_LIBRARY_PATH: @@ -301,19 +301,19 @@ def makePy(basename, ext, fmt=False): p = os.path.dirname(usrdir) b = os.path.basename(usrdir) - if (b in found_libs.keys()) and not (p in found_libs.values()): - print Cl.FAIL + "ERROR: \'" + str(b) + "\' libraray conflict:"+Cl.ESC - print "\t "+os.path.join(found_libs[b],b) - print "\t "+os.path.join(p,b) + if (b in list(found_libs.keys())) and not (p in list(found_libs.values())): + print(Cl.FAIL + "ERROR: \'" + str(b) + "\' libraray conflict:"+Cl.ESC) + print("\t "+os.path.join(found_libs[b],b)) + print("\t "+os.path.join(p,b)) sys.exit(1) msg = "\tGPI_LIBRARY_PATH \'"+str(p)+"\' for lib \'"+str(b)+"\'" include_dirs += [os.path.dirname(usrdir)] found_libs[b] = p - print msg + print(msg) - if len(found_libs.keys()) == 0: - print Cl.WRN + "WARNING: No GPI libraries found!\n" + Cl.ESC + if len(list(found_libs.keys())) == 0: + print(Cl.WRN + "WARNING: No GPI libraries found!\n" + Cl.ESC) if options.preprocess: extra_compile_args.append('-E') @@ -323,24 +323,24 @@ def makePy(basename, ext, fmt=False): # debug pyfi arrays if options.debug: - print "Turning on PyFI Array Debug" + print("Turning on PyFI Array Debug") extra_compile_args += ['-DPYFI_ARRAY_DEBUG'] # Anaconda Python/Numpy - print "Adding Anaconda libs" + print("Adding Anaconda libs") include_dirs += [GPI_THIRD+'/anaconda/lib/python2.7/site-packages/numpy/core/include'] include_dirs += [GPI_THIRD+'/anaconda/include'] library_dirs += [GPI_THIRD+'/anaconda/lib'] # POSIX THREADS # this location is the same for Ubuntu and OSX - print "Adding POSIX-Threads lib" + print("Adding POSIX-Threads lib") libraries += ['pthread'] include_dirs += ['/usr/include'] library_dirs += ['/usr/lib'] # FFTW3 - print "Adding FFTW3 libs" + print("Adding FFTW3 libs") libraries += ['fftw3_threads', 'fftw3', 'fftw3f_threads', 'fftw3f'] include_dirs += [GPI_THIRD+'/fftw/include'] library_dirs += [GPI_THIRD+'/fftw/lib'] @@ -395,14 +395,14 @@ def makePy(basename, ext, fmt=False): # ASTYLE if options.format: try: - print "\nAstyle..." - print "Reformatting CPP Code: " + target['fn'] + target['ext'] + print("\nAstyle...") + print("Reformatting CPP Code: " + target['fn'] + target['ext']) os.system(GPI_BIN+'/astyle -A1 -S -w -c -k3 -b -H -U -C ' + target['fn'] + target['ext']) continue # don't proceed to compile except: - print "Failed to perform auto-formatting \ - with \'astyle\'." + print("Failed to perform auto-formatting \ + with \'astyle\'.") sys.exit(-1) mod_name = target['fn'].split("_PyMOD")[0] @@ -423,18 +423,18 @@ def makePy(basename, ext, fmt=False): # Py Summary if show_summary > 1: - print '\nSUMMARY (Py Compilations):\n\tSUCCESSES ('+Cl.OKGR+str(len(py_successes))+Cl.ESC+'):' + print('\nSUMMARY (Py Compilations):\n\tSUCCESSES ('+Cl.OKGR+str(len(py_successes))+Cl.ESC+'):') for i in py_successes: - print "\t\t" + i - print '\tFAILURES ('+Cl.FAIL+str(len(py_failures))+Cl.ESC+'):' + print("\t\t" + i) + print('\tFAILURES ('+Cl.FAIL+str(len(py_failures))+Cl.ESC+'):') for i in py_failures: - print "\t\t" + i + print("\t\t" + i) # CPP Summary if show_summary > 1: - print '\nSUMMARY (CPP Compilations):\n\tSUCCESSES ('+Cl.OKGR+str(len(successes))+Cl.ESC+'):' + print('\nSUMMARY (CPP Compilations):\n\tSUCCESSES ('+Cl.OKGR+str(len(successes))+Cl.ESC+'):') for i in successes: - print "\t\t" + i - print '\tFAILURES ('+Cl.FAIL+str(len(failures))+Cl.ESC+'):' + print("\t\t" + i) + print('\tFAILURES ('+Cl.FAIL+str(len(failures))+Cl.ESC+'):') for i in failures: - print "\t\t" + i + print("\t\t" + i) diff --git a/lib/gpi/network.py b/lib/gpi/network.py index dfad8ceb..53dcc4b8 100644 --- a/lib/gpi/network.py +++ b/lib/gpi/network.py @@ -129,7 +129,7 @@ def convert_incoming(self): msg += '\ttotal port mem: '+str(GetHumanReadable_bytes(int(self._contents['TOTAL_PMEM']))) + '\n' if 'PLATFORM' in self._contents: - for k,v in self._contents['PLATFORM'].iteritems(): + for k,v in self._contents['PLATFORM'].items(): msg += '\t'+k+': '+str(v)+'\n' log.dialog(msg) diff --git a/lib/gpi/node.py b/lib/gpi/node.py index f38c5953..d3275304 100644 --- a/lib/gpi/node.py +++ b/lib/gpi/node.py @@ -260,8 +260,8 @@ def __init__(self, CanvasBackend, nodeCatItem=None, nodeIF=None, nodeIFscroll=No # node name self.name = "Node" self._hierarchal_level = -1 - self.title_font = QtGui.QFont(u"times", 14) - self.progress_font = QtGui.QFont(u"times", 8) + self.title_font = QtGui.QFont("times", 14) + self.progress_font = QtGui.QFont("times", 8) self._progress_done = TimerPack() self._progress_done.setTimeoutSlot_interval(self.update) @@ -389,7 +389,7 @@ def reloadDone(self): def initStateMachine(self): # NODE # Set up intial state graph. self._machine = GPI_FSM('NODE') - self._switchSig.connect(self._machine.next) + self._switchSig.connect(self._machine.__next__) # node states self._idleState = GPIState('idle', self.idleRun, self._machine) @@ -947,9 +947,9 @@ def updateToolTip(self): # NODE if len(self._computeDuration): avg = self.avgWallTime() std = self.stdWallTime() - tip += u'\u03BC = ' + GetHumanReadable_time(avg) - tip += u', \u03C3 = ' + GetHumanReadable_time(std) - tip += u', n = ' + str(len(self._computeDuration)) + '\n' + tip += '\u03BC = ' + GetHumanReadable_time(avg) + tip += ', \u03C3 = ' + GetHumanReadable_time(std) + tip += ', n = ' + str(len(self._computeDuration)) + '\n' tip += 'Outport Mem: ' + GetHumanReadable_bytes( bytes_held) + pct_physmem @@ -1188,7 +1188,7 @@ def calculateForces(self): # Sum up all forces pushing this item away. xvel = 0.0 yvel = 0.0 - for item in self.scene().items(): + for item in list(self.scene().items()): if not isinstance(item, Node): continue @@ -1329,7 +1329,7 @@ def paint(self, painter, option, widget): # NODE if self._nodeIF: if self._nodeIF.label != '': buf += ": " + self._nodeIF.label - painter.drawText(-5, -9, w, 20, (QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter), unicode(buf)) + painter.drawText(-5, -9, w, 20, (QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter), str(buf)) # reloaded disp if self._reload_timer.isActive() and not self.progressON(): @@ -1372,7 +1372,7 @@ def drawProgress(self, painter, pdone): painter.setFont(self.progress_font) buf = str(int(pdone*100)) buf += '%' - painter.drawText(-7+self.getNodeWidth(), -9, 20, 20, (QtCore.Qt.AlignCenter | QtCore.Qt.AlignVCenter), unicode(buf)) + painter.drawText(-7+self.getNodeWidth(), -9, 20, 20, (QtCore.Qt.AlignCenter | QtCore.Qt.AlignVCenter), str(buf)) def drawReload(self, painter): # color @@ -1493,7 +1493,7 @@ def mouseReleaseEvent(self, event): # NODE # TODO: this should be moved to config elif Specs.inLinux(): editor = 'gedit' - if os.environ.has_key("EDITOR"): + if "EDITOR" in os.environ: editor = os.environ["EDITOR"] if self.getNodeDefinitionPath(): diff --git a/lib/gpi/nodeAPI.py b/lib/gpi/nodeAPI.py index 9cc4320e..a9027eca 100644 --- a/lib/gpi/nodeAPI.py +++ b/lib/gpi/nodeAPI.py @@ -37,8 +37,8 @@ from .logger import manager from .port import InPort, OutPort from .widgets import HidableGroupBox -import widgets as BUILTIN_WIDGETS -import syntax +from . import widgets as BUILTIN_WIDGETS +from . import syntax # start logger for this module @@ -169,7 +169,7 @@ def getWidgets(self): return self.parmList def getWidgetNames(self): - return self.parmDict.keys() + return list(self.parmDict.keys()) def starttime(self): self._startline = inspect.currentframe().f_back.f_lineno @@ -581,7 +581,7 @@ def modifyWidget_setter(self, src, kw, val): def modifyWidget_direct(self, pnumORtitle, **kwargs): src = self.getWidget(pnumORtitle) - for k, v in kwargs.iteritems(): + for k, v in kwargs.items(): if k != 'val': self.modifyWidget_setter(src, k, v) @@ -597,7 +597,7 @@ def modifyWidget_buffer(self, title, **kwargs): src = self.getWdgFromBuffer(title) try: - for k, v in kwargs.iteritems(): + for k, v in kwargs.items(): src['kwargs'][k] = v except: log.critical("modifyWidget_buffer() FAILED to modify buffered attribute") @@ -805,14 +805,14 @@ def getEvent(self): def portEvent(self): '''Specifically check for a port event.''' log.warn('The \'portEvent()\' function is deprecated, use \'portEvents()\' (its the plural form). '+str(self.node.getFullPath())) - if self.getEvent().has_key(GPI_PORT_EVENT): + if GPI_PORT_EVENT in self.getEvent(): return self.getEvent()[GPI_PORT_EVENT] return None def widgetEvent(self): '''Specifically check for a wdg event.''' log.warn('The \'widgetEvent()\' function is deprecated, use \'widgetEvents()\' (its the plural form). '+str(self.node.getFullPath())) - if self.getEvent().has_key(GPI_WIDGET_EVENT): + if GPI_WIDGET_EVENT in self.getEvent(): return self.getEvent()[GPI_WIDGET_EVENT] return None ############### DEPRECATED NODE API diff --git a/lib/gpi/port.py b/lib/gpi/port.py index 6096c0be..1fa38af4 100644 --- a/lib/gpi/port.py +++ b/lib/gpi/port.py @@ -209,13 +209,13 @@ def findOppositePorts(self): # for outports, find all appropriate inports if isinstance(self, OutPort): - for item in self.graph.scene().items(): + for item in list(self.graph.scene().items()): if isinstance(item, InPort): ports.append(item) # vica-versa for all inports if isinstance(self, InPort): - for item in self.graph.scene().items(): + for item in list(self.graph.scene().items()): if isinstance(item, OutPort): ports.append(item) @@ -301,7 +301,7 @@ def addEdge(self, edge): def detachEdge(self, edge): '''This is a little misleading, it only pops the edge from a port\'s edgelist''' - for i in xrange(len(self.edgeList)): + for i in range(len(self.edgeList)): if self.edgeList[i] == edge: return self.edgeList.pop(i) diff --git a/lib/gpi/qtapi.py b/lib/gpi/qtapi.py index d935f7dd..2f243423 100644 --- a/lib/gpi/qtapi.py +++ b/lib/gpi/qtapi.py @@ -34,18 +34,18 @@ # http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html _APIv2 = True if _APIv2: - sip.setapi(u'QDate', 2) - sip.setapi(u'QDateTime', 2) - sip.setapi(u'QString', 2) - sip.setapi(u'QTextStream', 2) - sip.setapi(u'QTime', 2) - sip.setapi(u'QUrl', 2) - sip.setapi(u'QVariant', 2) + sip.setapi('QDate', 2) + sip.setapi('QDateTime', 2) + sip.setapi('QString', 2) + sip.setapi('QTextStream', 2) + sip.setapi('QTime', 2) + sip.setapi('QUrl', 2) + sip.setapi('QVariant', 2) import PyQt4.QtCore as _QtCore QtCore = _QtCore def import_module(moduleName): - p = __import__('PyQt4', globals(), locals(), [moduleName], -1) + p = __import__('PyQt4', globals(), locals(), [moduleName], 0) return getattr(p, moduleName) Signal = QtCore.pyqtSignal diff --git a/lib/gpi/stateMachine.py b/lib/gpi/stateMachine.py index 0801da10..209aedb8 100644 --- a/lib/gpi/stateMachine.py +++ b/lib/gpi/stateMachine.py @@ -109,12 +109,12 @@ def next(self, dsig): if isinstance(dsig, str): sig = str(dsig) dsig = sig - elif isinstance(dsig, unicode): + elif isinstance(dsig, str): sig = str(dsig) dsig = sig elif isinstance(dsig, dict): if 'sig' in dsig: - if isinstance(dsig['sig'], str) or isinstance(dsig['sig'], unicode): + if isinstance(dsig['sig'], str) or isinstance(dsig['sig'], str): sig = str(dsig['sig']) dsig['sig'] = sig else: diff --git a/lib/gpi/topsort.py b/lib/gpi/topsort.py index d35d49ee..0f23a8bc 100755 --- a/lib/gpi/topsort.py +++ b/lib/gpi/topsort.py @@ -45,7 +45,7 @@ def topological_sort(items, partial_order): def add_node(graph, node): """Add a node to the graph if not already exists.""" - if not graph.has_key(node): + if node not in graph: graph[node] = [0] # 0 = number of arcs coming into this node. def add_arc(graph, fromnode, tonode): @@ -74,7 +74,7 @@ def add_arc(graph, fromnode, tonode): add_arc(graph, a, b) # Step 2 - find all roots (nodes with zero incoming arcs). - roots = [node for (node,nodeinfo) in graph.items() if nodeinfo[0] == 0] + roots = [node for (node,nodeinfo) in list(graph.items()) if nodeinfo[0] == 0] # step 3 - repeatedly emit a root and remove it from the graph. Removing # a node may convert some of the node's direct children into roots. @@ -97,7 +97,7 @@ def add_arc(graph, fromnode, tonode): if graph[child][0] == 0: roots.append(child) del graph[root] - if len(graph.items()) != 0: + if len(list(graph.items())) != 0: # There is a loop in the input. return None return sorted @@ -119,26 +119,26 @@ def topsort(connection_list): if __name__ == '__main__': - print "test topsort" - print topological_sort([1,2,3], [(1,2),(1,3),(3,2)]) # --> [1,3,2] - print topological_sort([1,2], [(2,1),(2,1)]) # --> [2,1] - print topological_sort([1,2], [(1,2),(2,1)]) # --> None - print topological_sort([0,1,2], [(0,1),(1,2),(2,1)]) # --> None + print("test topsort") + print(topological_sort([1,2,3], [(1,2),(1,3),(3,2)])) # --> [1,3,2] + print(topological_sort([1,2], [(2,1),(2,1)])) # --> [2,1] + print(topological_sort([1,2], [(1,2),(2,1)])) # --> None + print(topological_sort([0,1,2], [(0,1),(1,2),(2,1)])) # --> None # hashable type x = type y = int z = float - print topological_sort([x,y,z], [(x,y),(x,z),(z,y)]) # --> [1,3,2] + print(topological_sort([x,y,z], [(x,y),(x,z),(z,y)])) # --> [1,3,2] - print topsort( [(x,y),(x,z),(z,y)] ) + print(topsort( [(x,y),(x,z),(z,y)] )) - print "cyclic and acylic graphs" - print topsort( [(1,2),(1,3),(3,2),(5,6),(5,7),(7,6),(7,5)] ) + print("cyclic and acylic graphs") + print(topsort( [(1,2),(1,3),(3,2),(5,6),(5,7),(7,6),(7,5)] )) - print "multiple graphs" - print topsort( [(1,2),(1,3),(3,2),(5,6),(5,7),(7,6)] ) + print("multiple graphs") + print(topsort( [(1,2),(1,3),(3,2),(5,6),(5,7),(7,6)] )) diff --git a/lib/gpi/widgets.py b/lib/gpi/widgets.py index d7eb43ff..7edb6c56 100644 --- a/lib/gpi/widgets.py +++ b/lib/gpi/widgets.py @@ -36,7 +36,7 @@ from .defines import GPI_INT_MIN, GPI_INT_MAX from .defines import getKeyboardModifiers, printMouseEvent from .logger import manager -import syntax +from . import syntax # start logger for this module @@ -814,7 +814,7 @@ def getDataType(self): return 'FLOAT' if t == str: return 'STRING' - if t == long: + if t == int: return 'LONG' if t == list: return 'LIST' @@ -1557,7 +1557,7 @@ def __init__(self, title, parent=None): def annotationButton(self, value): if value: - for i in xrange(self.ann_box.count()): + for i in range(self.ann_box.count()): if self.ann_box.itemAt(i).widget().isChecked(): self.ann_type = str(self.ann_box.itemAt(i).widget().text()) @@ -2128,7 +2128,7 @@ def set_buttons(self, names): if len(names) != len(self.buttons): log.critical("set_buttons(): len not properly set.") - for i in xrange(len(self.buttons)): + for i in range(len(self.buttons)): self.buttons[i].setText(names[i]) # getters @@ -2206,7 +2206,7 @@ def set_buttons(self, names): if len(names) != len(self.buttons): log.critical("set_buttons(): len not properly set.") - for i in xrange(len(self.buttons)): + for i in range(len(self.buttons)): self.buttons[i].setText(names[i]) # getters @@ -2327,7 +2327,7 @@ def set_buttons(self, names): if len(names) != len(self.buttons): log.critical("set_buttons(): len not properly set.") - for i in xrange(len(self.buttons)): + for i in range(len(self.buttons)): self.buttons[i].setText(names[i]) # getters diff --git a/plugin/python_GPITYPE.py b/plugin/python_GPITYPE.py index ff9c14e3..2d55fd19 100644 --- a/plugin/python_GPITYPE.py +++ b/plugin/python_GPITYPE.py @@ -207,7 +207,7 @@ class LONG(GPIDefaultType): def __init__(self): super(LONG, self).__init__() - self._type = long # the class is implicitly this type + self._type = int # the class is implicitly this type self._range = None # useful for imposing widget settings def edgeTip(self, data): From 9a4ec9fb37b0125297f7147ae6409c1c4591ad89 Mon Sep 17 00:00:00 2001 From: nick Date: Wed, 11 Mar 2015 14:22:34 -0700 Subject: [PATCH 02/60] Ported PyFI to Python3. --- include/PyFI/PyFIArray_WrappedNUMPY.cpp | 4 +-- include/PyFI/PyFIMacros.h | 20 +++++++++++---- include/PyFI/PyFunctionIF.cpp | 16 ++++++++---- include/PyFI/embedded_script.py | 10 ++++---- include/PyFI/template_PyMOD.cpp | 4 ++- include/PyFI/template_test.py | 34 ++++++++++++------------- include/PyFI/test.py | 4 +-- lib/gpi/make.py | 3 ++- 8 files changed, 57 insertions(+), 38 deletions(-) diff --git a/include/PyFI/PyFIArray_WrappedNUMPY.cpp b/include/PyFI/PyFIArray_WrappedNUMPY.cpp index d18edafa..e08cc41d 100644 --- a/include/PyFI/PyFIArray_WrappedNUMPY.cpp +++ b/include/PyFI/PyFIArray_WrappedNUMPY.cpp @@ -116,8 +116,8 @@ void printArray(Array &in) { string code; code = "def func(in1):\n" - " print type(in1), in1.dtype, in1.shape\n" - " print in1\n"; + " print(type(in1), in1.dtype, in1.shape)\n" + " print(in1)\n"; PyCallable printer(code); printer.SetArg_Array(&in); printer.Run(); diff --git a/include/PyFI/PyFIMacros.h b/include/PyFI/PyFIMacros.h index cd836538..34d6d158 100644 --- a/include/PyFI/PyFIMacros.h +++ b/include/PyFI/PyFIMacros.h @@ -110,26 +110,36 @@ inline T __PYFI_itself(T arg) #define PYFI_FUNCDESC_TEMPLATE(_nam, _tmpl, _desc) { #_nam "_" #_tmpl, (PyCFunction) __PYFI_itself(_nam <_tmpl>), METH_VARARGS|METH_KEYWORDS, _desc } #define PYFI_FUNCDESC_TERM() {NULL, NULL, 0, NULL} +#define PYFI_METHOD_TABLE(_nam_str) static struct PyModuleDef __pyfimstruct = { PyModuleDef_HEAD_INIT, _nam_str, NULL, -1, Methods }; + +/* avoid this numpy macro return issue */ +void *numpy_import_array(void) +{ + import_array(); + return NULL; +} + /* PyMODINIT_FUNC stringifications */ #define STR_MOD_NAME1(_x) #_x #define STR_MOD_NAME(_x) STR_MOD_NAME1(_x) -#define MAKE_FN_NAME(_x) PyMODINIT_FUNC init ## _x (void) +#define MAKE_FN_NAME(_x) PyMODINIT_FUNC PyInit_ ## _x (void) #define FUNCTION_NAME(_x) MAKE_FN_NAME(_x) #define PYFI_MODINIT() \ FUNCTION_NAME(MOD_NAME) \ { \ PyObject *m; \ - m = Py_InitModule(STR_MOD_NAME(MOD_NAME), Methods); \ + m = PyModule_Create(&__pyfimstruct); \ if (m == NULL) \ - return; \ - import_array(); \ + return NULL; \ + numpy_import_array(); \ + return m; \ } /* further simplify mod delcarations */ #define PYFI_LIST_START_ PYFI_FUNCLIST = { -#define PYFI_LIST_END_ PYFI_FUNCDESC_TERM() }; PYFI_MODINIT(); +#define PYFI_LIST_END_ PYFI_FUNCDESC_TERM() }; PYFI_METHOD_TABLE(STR_MOD_NAME(MOD_NAME)); PYFI_MODINIT(); #define PYFI_DESC(_nam, _desc) PYFI_FUNCDESC(_nam, _desc), #define PYFI_T_DESC(_nam, _tmpl, _desc) PYFI_FUNCDESC_TEMPLATE(_nam, _tmpl, _desc), diff --git a/include/PyFI/PyFunctionIF.cpp b/include/PyFI/PyFunctionIF.cpp index 647d551c..c7eefa75 100644 --- a/include/PyFI/PyFunctionIF.cpp +++ b/include/PyFI/PyFunctionIF.cpp @@ -381,7 +381,7 @@ void Parm_STRING::Convert_In(void) } else { - local_val = string(PyString_AsString(pyobj_ptr)); + local_val = string(PyUnicode_AS_DATA(pyobj_ptr)); val = (void *)&local_val; } } @@ -389,7 +389,7 @@ void Parm_STRING::Convert_In(void) void Parm_STRING::Convert_Out(void) { /* new ref */ - pyobj_ptr = PyString_FromString((*(string *)val).c_str()); + pyobj_ptr = PyUnicode_FromString((*(string *)val).c_str()); } /**** DOUBLE ****/ @@ -1403,6 +1403,12 @@ class PyCallable delete *_arrays_itr; } + void *__import_array(void) + { + import_array(); /* required for using numpy arrays */ + return NULL; + } + /* 1) check if python has been initialized * 2) initialize numpy */ @@ -1412,7 +1418,7 @@ class PyCallable if (Py_IsInitialized() == 0) { Py_Initialize(); - import_array(); /* required for using numpy arrays */ + __import_array(); /* required for using numpy arrays */ } /* else @@ -1681,7 +1687,7 @@ class PyCallable } /* convert to python string */ - PyObject *pItem = PyString_FromString(in.c_str()); + PyObject *pItem = PyUnicode_FromString(in.c_str()); /* add to list */ if (PyList_Append(_pArgList, pItem) != _PYCALLABLE_SUCCESS) @@ -1845,7 +1851,7 @@ class PyCallable * calling function, not sure what the behavior will * be for this as it is. */ _PYFI_PYCALLABLE_ACQUIRE_GIL - string out = PyString_AsString(curVal); + string out = PyUnicode_AS_DATA(curVal); _PYFI_PYCALLABLE_RELEASE_GIL return(out); } diff --git a/include/PyFI/embedded_script.py b/include/PyFI/embedded_script.py index 6d0650b3..4830a601 100644 --- a/include/PyFI/embedded_script.py +++ b/include/PyFI/embedded_script.py @@ -26,10 +26,10 @@ import numpy as np def script(in1, in2, in3): - print "start script" - print "in1:", in1, type(in1) - print "in2:", in2, type(in2) - print "in array: ", in3, type(in3) - print "end script" + print("start script") + print(("in1:", in1, type(in1))) + print(("in2:", in2, type(in2))) + print(("in array: ", in3, type(in3))) + print("end script") return (0, 1, np.array([1,2,3],dtype=np.float32)) diff --git a/include/PyFI/template_PyMOD.cpp b/include/PyFI/template_PyMOD.cpp index 2f93fdda..c56dc56a 100644 --- a/include/PyFI/template_PyMOD.cpp +++ b/include/PyFI/template_PyMOD.cpp @@ -170,7 +170,9 @@ PYFI_FUNC(IFtest2) Array arr_copy(*arr); + cout << "Printing using PyFI" << endl; coutv(arr_copy); + cout << "Printing using Numpy" << endl; Numpy::printArray(arr_copy); //arr_copy(1000); @@ -222,7 +224,7 @@ PYFI_FUNC(IFtest2) coutv(*out); /* write your own function */ - PyCallable mycode("def func(in1):\n print \'in1\', in1\n return 1\n"); + PyCallable mycode("def func(in1):\n print(\'in1\', in1)\n return 1\n"); mycode.SetArg_Long(777); coutv(mycode.GetReturn_Long()); diff --git a/include/PyFI/template_test.py b/include/PyFI/template_test.py index 45508876..18d34e91 100755 --- a/include/PyFI/template_test.py +++ b/include/PyFI/template_test.py @@ -43,27 +43,27 @@ # PYCALLABLE -A = np.array(range(4), dtype=np.float32) +A = np.array(list(range(4)), dtype=np.float32) A.shape = [2,2] -print "A:", A -print "Ainv:", npl.pinv(A) +print("A:", A) +print("Ainv:", npl.pinv(A)) -x = np.array(range(24), dtype=np.float32) +x = np.array(list(range(24)), dtype=np.float32) x.shape = [4,3,2] -print x +print(x) -print "matmult (PYTHON)" -print "A: ", A -print "AdotA: ", np.dot(A,A) +print("matmult (PYTHON)") +print("A: ", A) +print("AdotA: ", np.dot(A,A)) st = time.time() aarr, c = temp.IFtest2(x) -print "IFtest time: ", time.time()-st +print("IFtest time: ", time.time()-st) -print c +print(c) -print "aarr:", aarr +print("aarr:", aarr) temp.math_double() @@ -76,16 +76,16 @@ # testing the PyFI interface. def test(thetest, name): if thetest: - print '\t-'+name+' passed' + print('\t-'+name+' passed') return 1 else: - print '\t-'+name+' failed' + print('\t-'+name+' failed') return 0 -print "\n\nInterface testing..." +print("\n\nInterface testing...") mi = 111 mf = 111.111 ms = 'hello from python' @@ -139,8 +139,8 @@ def test(thetest, name): cnt += test((kla == al).all(), 'mykwlarr') tot += 1 -print '\nTest Summary:' +print('\nTest Summary:') if cnt == tot: - print '\tSUCCESS: '+str(cnt)+'/'+str(tot) + print('\tSUCCESS: '+str(cnt)+'/'+str(tot)) else: - print '\tFAILED: '+str(cnt)+'/'+str(tot) + print('\tFAILED: '+str(cnt)+'/'+str(tot)) diff --git a/include/PyFI/test.py b/include/PyFI/test.py index 05285fb9..41bfb722 100755 --- a/include/PyFI/test.py +++ b/include/PyFI/test.py @@ -31,5 +31,5 @@ x = np.array([1,2,3,4], dtype=np.float32) y = bm.add_one(x) -print 'x: ', x -print 'y: ', y +print('x: ', x) +print('y: ', y) diff --git a/lib/gpi/make.py b/lib/gpi/make.py index 3d3e4f7a..e0e760f4 100755 --- a/lib/gpi/make.py +++ b/lib/gpi/make.py @@ -328,7 +328,8 @@ def makePy(basename, ext, fmt=False): # Anaconda Python/Numpy print("Adding Anaconda libs") - include_dirs += [GPI_THIRD+'/anaconda/lib/python2.7/site-packages/numpy/core/include'] + ver = sys.version_info + include_dirs += [GPI_THIRD+'/anaconda/lib/python'+str(ver.major)+'.'+str(ver.minor)+'/site-packages/numpy/core/include'] include_dirs += [GPI_THIRD+'/anaconda/include'] library_dirs += [GPI_THIRD+'/anaconda/lib'] From 2f7d6da020b533c1ae5723d564f12e970832e4f7 Mon Sep 17 00:00:00 2001 From: nick Date: Wed, 11 Mar 2015 14:56:32 -0700 Subject: [PATCH 03/60] Files must be opened with 'rb' to unpickle networks written in py2. --- lib/gpi/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gpi/network.py b/lib/gpi/network.py index 53dcc4b8..03adbc77 100644 --- a/lib/gpi/network.py +++ b/lib/gpi/network.py @@ -304,7 +304,7 @@ def determine_version(self, fname): # pickle is no longer used, then the other format will be checked # first, then pickle as a backup. try: - fptr = open(fname, "r") + fptr = open(fname, "rb") contents = pickle.load(fptr) fptr.close() except: From 313dc9e752c326f20094801721b58704ff76ac2e Mon Sep 17 00:00:00 2001 From: nick Date: Wed, 11 Mar 2015 14:57:18 -0700 Subject: [PATCH 04/60] Fixed '__next__' -> 'next', a 2to3 translation error. --- lib/gpi/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gpi/node.py b/lib/gpi/node.py index d3275304..54fcf712 100644 --- a/lib/gpi/node.py +++ b/lib/gpi/node.py @@ -389,7 +389,7 @@ def reloadDone(self): def initStateMachine(self): # NODE # Set up intial state graph. self._machine = GPI_FSM('NODE') - self._switchSig.connect(self._machine.__next__) + self._switchSig.connect(self._machine.next) # node states self._idleState = GPIState('idle', self.idleRun, self._machine) From 729db6fb2db21ce7da843ee1f1087c83701585e4 Mon Sep 17 00:00:00 2001 From: nick Date: Wed, 11 Mar 2015 15:06:00 -0700 Subject: [PATCH 05/60] VERSION file shouldn't be tracked. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 92bf8a3f..f75689c5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ *.so *.o *.DS_Store +VERSION build/ /node/ /local/ From ca8afe23daa4d83df3b6361f44850e2bde06d82a Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 17 Sep 2015 14:45:24 -0700 Subject: [PATCH 06/60] Fixed some 2to3. --- lib/gpi/__init__.py | 2 +- lib/gpi/canvasGraph.py | 14 ++++----- lib/gpi/catalog.py | 12 ++++---- lib/gpi/cmd.py | 2 +- lib/gpi/defaultTypes.py | 2 +- lib/gpi/docs.py | 4 +-- lib/gpi/layoutWindow.py | 4 +-- lib/gpi/logger.py | 2 +- lib/gpi/macroNode.py | 2 +- lib/gpi/mainWindow.py | 6 ++-- lib/gpi/make.py | 68 ++++++++++++++++++++--------------------- lib/gpi/network.py | 2 +- lib/gpi/node.py | 12 ++++---- lib/gpi/nodeAPI.py | 14 ++++----- lib/gpi/topsort.py | 16 +++++----- lib/gpi/widgets.py | 2 +- 16 files changed, 82 insertions(+), 82 deletions(-) diff --git a/lib/gpi/__init__.py b/lib/gpi/__init__.py index b4716662..293240fe 100644 --- a/lib/gpi/__init__.py +++ b/lib/gpi/__init__.py @@ -45,7 +45,7 @@ LIABILITY ACTIVITIES. ''' -print(_version+' '+_copyright+'\n'+_disclaimer) +print((_version+' '+_copyright+'\n'+_disclaimer)) # transitioning tool from . import qtapi diff --git a/lib/gpi/canvasGraph.py b/lib/gpi/canvasGraph.py index bf46eb8f..cbfbe3c4 100644 --- a/lib/gpi/canvasGraph.py +++ b/lib/gpi/canvasGraph.py @@ -689,10 +689,10 @@ def printNodeState(self): for node in allItems: print("________________________") node.printCurState() - print("node: " + str(node.name)) - print("inDisabledState: " + str(node.inDisabledState())) - print("hasEventPending: " + str(node.hasEventPending())) - print("_nodeIF.reQueueIsSet: " + str(node._nodeIF.reQueueIsSet())) + print(("node: " + str(node.name))) + print(("inDisabledState: " + str(node.inDisabledState()))) + print(("hasEventPending: " + str(node.hasEventPending()))) + print(("_nodeIF.reQueueIsSet: " + str(node._nodeIF.reQueueIsSet()))) print("________________________") def setPauseState(self, val): @@ -1216,8 +1216,8 @@ def keyPressEvent(self, event): #print self.getAllPorts() #print self.getAllMacroNodes() #print self.serializeGraphData() - print(self.getAllNodes()) - print(self.getAllMacroNodes()) + print((self.getAllNodes())) + print((self.getAllMacroNodes())) # close all node windows elif key == QtCore.Qt.Key_X and modifiers == QtCore.Qt.ControlModifier: @@ -2011,7 +2011,7 @@ def serializeGraphData(self, selectedOnly=False, minusAvgPos=False): for node in nodes: graph_settings['nodes'].append(copy.deepcopy(node.getSettings())) - for nid, nodes in macroNodes.items(): + for nid, nodes in list(macroNodes.items()): graph_settings['macroNodes'].append(nodes[0].macroParent().getSettings()) if minusAvgPos and (len(graph_settings['nodes']) + len(graph_settings['macroNodes'])): diff --git a/lib/gpi/catalog.py b/lib/gpi/catalog.py index cef0091a..0820fe6c 100644 --- a/lib/gpi/catalog.py +++ b/lib/gpi/catalog.py @@ -81,14 +81,14 @@ def values(self): return list(self._db.values()) def iteritems(self): - return iter(self._db.items()) + return iter(list(self._db.items())) def d(self): return self._db def __str__(self): o = '\n' - for k,v in self._db.items(): + for k,v in list(self._db.items()): o += k +': '+str(v) + '\n' return o @@ -97,7 +97,7 @@ def get(self, key): def find(self, attr, value): # return the first object with the given attribute and value - for k,v in self._db.items(): + for k,v in list(self._db.items()): if hasattr(v, attr): if getattr(v, attr) == value: return v @@ -105,7 +105,7 @@ def find(self, attr, value): def list(self, attr, value): o = [] # return a list of objects with the given attribute and value - for k,v in self._db.items(): + for k,v in list(self._db.items()): if hasattr(v, attr): if getattr(v, attr) == value: o.append(v) @@ -118,7 +118,7 @@ def intrafind(self, finder, key): # Return the first object with a True internal finder() result. # The finder() function is a method contained within the CatalogObj() # subclass that returns True or False given the passed key for comparison. - for k,v in self._db.items(): + for k,v in list(self._db.items()): if hasattr(v, finder): if getattr(v, finder)(key): return v @@ -128,7 +128,7 @@ def intralist(self, finder, key): # The finder() function is a method contained within the CatalogObj() # subclass that returns True or False given the passed key for comparison. o = [] - for k,v in self._db.items(): + for k,v in list(self._db.items()): if hasattr(v, finder): if getattr(v, finder)(key): o.append(v) diff --git a/lib/gpi/cmd.py b/lib/gpi/cmd.py index 9cb066f1..af001e71 100644 --- a/lib/gpi/cmd.py +++ b/lib/gpi/cmd.py @@ -145,7 +145,7 @@ def dumpDefines(self): def dumpSpecs(self): with open('specs.txt', 'wb') as specsfile: specsfile.write('# GPI (v'+str(VERSION)+') system specifications file.\n') - for k,v in Specs.table().items(): + for k,v in list(Specs.table().items()): msg = k+': '+str(v) + '\n' specsfile.write(msg) diff --git a/lib/gpi/defaultTypes.py b/lib/gpi/defaultTypes.py index 404421a2..b1942266 100644 --- a/lib/gpi/defaultTypes.py +++ b/lib/gpi/defaultTypes.py @@ -120,7 +120,7 @@ def setTypeParms(self, **kwargs): """Use the kwargs dict to set user specified args to addPort() """ - for k, v in kwargs.items(): + for k, v in list(kwargs.items()): setter = "set_"+k if hasattr(self, setter): getattr(self, setter)(v) diff --git a/lib/gpi/docs.py b/lib/gpi/docs.py index b5b72101..4f351991 100755 --- a/lib/gpi/docs.py +++ b/lib/gpi/docs.py @@ -81,7 +81,7 @@ def scanGPIModules(self, ipath, recursion_depth=1): if item.valid(): self._known_GPI_nodes.append(item) else: - print("Failed to load: "+str(item)) + print(("Failed to load: "+str(item))) def __str__(self): @@ -120,7 +120,7 @@ def extractDocs(self): #self._docText += '\n'+80*'*'+'\n' else: - print(str(item) + ' Doesnt have ExternalNode definition, skipping...') + print((str(item) + ' Doesnt have ExternalNode definition, skipping...')) diff --git a/lib/gpi/layoutWindow.py b/lib/gpi/layoutWindow.py index b26c3dac..36dd0410 100644 --- a/lib/gpi/layoutWindow.py +++ b/lib/gpi/layoutWindow.py @@ -314,7 +314,7 @@ def loadSettings(self, s, nodeList): # wls: window layout settings if self._config == 3: curlabels = [lw.label() for lw in self.findChildren(LayoutWindow)] - for label, wls in s['layouts'].items(): + for label, wls in list(s['layouts'].items()): if label not in curlabels: if label.startswith('top'): self.addTopColumn(label) @@ -323,7 +323,7 @@ def loadSettings(self, s, nodeList): # assign widgets to each layout # wls: window layout settings - for label, wls in s['layouts'].items(): + for label, wls in list(s['layouts'].items()): log.debug("trying " + label) for lw in self.findChildren(LayoutWindow): diff --git a/lib/gpi/logger.py b/lib/gpi/logger.py index 863267d9..b9d42a99 100644 --- a/lib/gpi/logger.py +++ b/lib/gpi/logger.py @@ -80,7 +80,7 @@ def printb(self, msg, lev): lineno = str(inspect.currentframe().f_back.f_back.f_lineno) # the user input 'msg' is forced to be a string - print(time.asctime(time.localtime()) + ' - ' + self._name + ':' + lineno + ' - ' + lev + ' - ' + str(msg)) + print((time.asctime(time.localtime()) + ' - ' + self._name + ':' + lineno + ' - ' + lev + ' - ' + str(msg))) class GPILogManager(object): diff --git a/lib/gpi/macroNode.py b/lib/gpi/macroNode.py index 8b3a6f1c..3a7f5668 100644 --- a/lib/gpi/macroNode.py +++ b/lib/gpi/macroNode.py @@ -587,7 +587,7 @@ def loadSettings(self, s, nodeList, pos): self._face.setPos(QtCore.QPointF(x, y)) rel = QtCore.QPointF(x, y) - for nid, epos in s['nodes_rel_pos'].items(): + for nid, epos in list(s['nodes_rel_pos'].items()): enode = self.getNodeByID(nodeList, int(nid)) if enode: enode.setPos(rel + QtCore.QPointF(*epos)) diff --git a/lib/gpi/mainWindow.py b/lib/gpi/mainWindow.py index e20eb874..6d5d4297 100644 --- a/lib/gpi/mainWindow.py +++ b/lib/gpi/mainWindow.py @@ -498,12 +498,12 @@ def printSysModules(self): print("Current modules loaded (sys.modules):") for k in sorted(sys.modules.keys()): v = sys.modules[k] - print(k + " : " + str(v)) + print((k + " : " + str(v))) if False: if k.lower().count('spiral'): - print("key: " + k + ", " + str(v)) + print(("key: " + k + ", " + str(v))) elif str(v).lower().count('spiral'): - print("key: " + k + ", " + str(v)) + print(("key: " + k + ", " + str(v))) def changeStyle(self, action): # UI style diff --git a/lib/gpi/make.py b/lib/gpi/make.py index c8481326..1f0d6445 100755 --- a/lib/gpi/make.py +++ b/lib/gpi/make.py @@ -82,7 +82,7 @@ class Cl: def compile(mod_name, include_dirs=[], libraries=[], library_dirs=[], extra_compile_args=[], runtime_library_dirs=[]): - print("Making target: " + mod_name) + print(("Making target: " + mod_name)) # do usual generic module setup # NOT: 'mod_name' must have an init @@ -105,11 +105,11 @@ def compile(mod_name, include_dirs=[], libraries=[], library_dirs=[], ext_modules=[Module1], script_args=["build_ext", "--inplace", "--force"]) except: - print(sys.exc_info()) - print("FAILED: " + mod_name) + print((sys.exc_info())) + print(("FAILED: " + mod_name)) return 1 - print("SUCCESS: " + mod_name) + print(("SUCCESS: " + mod_name)) return 0 @@ -182,8 +182,8 @@ def makePy(basename, ext, fmt=False): if fmt: try: import autopep8 - print("\nFound: autopep8 " + str(autopep8.__version__) + "...") - print("Reformatting Python script: " + "".join(target)) + print(("\nFound: autopep8 " + str(autopep8.__version__) + "...")) + print(("Reformatting Python script: " + "".join(target))) os.system('autopep8 -i ' + "".join(target)) except: print("Failed to perform auto-formatting \ @@ -192,23 +192,23 @@ def makePy(basename, ext, fmt=False): # PEP8 try: import pep8 - print("\nFound: pep8 " + str(pep8.__version__) + "...") - print("Checking Python script: " + "".join(target)) - print("pep8 found these problems with your code, START" + Cl.WRN) + print(("\nFound: pep8 " + str(pep8.__version__) + "...")) + print(("Checking Python script: " + "".join(target))) + print(("pep8 found these problems with your code, START" + Cl.WRN)) os.system('pep8 --count --statistics --show-source ' + "".join(target)) - print(Cl.ESC + "pep8 END") + print((Cl.ESC + "pep8 END")) except: print("Failed to perform check with \'pep8\'.") # PYFLAKES try: import pyflakes - print("\nFound: pyflakes " + str(pyflakes.__version__) + "...") - print("Checking Python script: " + "".join(target)) - print("pyflakes found these problems with your code, START" + Cl.FAIL) + print(("\nFound: pyflakes " + str(pyflakes.__version__) + "...")) + print(("Checking Python script: " + "".join(target))) + print(("pyflakes found these problems with your code, START" + Cl.FAIL)) os.system('pyflakes ' + "".join(target)) - print(Cl.ESC + "pyflakes END") + print((Cl.ESC + "pyflakes END")) except: print("Failed to perform check with \'pyflakes\'.") @@ -217,12 +217,12 @@ def makePy(basename, ext, fmt=False): print('\nAttemping py_compile...') py_compile.compile(''.join(target), doraise=True) print('py_compile END') - print('\nSUCCESS: '+''.join(target)) + print(('\nSUCCESS: '+''.join(target))) return 0 except: - print(Cl.FAIL + str(traceback.format_exc()) + Cl.ESC) + print((Cl.FAIL + str(traceback.format_exc()) + Cl.ESC)) print('py_compile END') - print('\nFAILED: '+''.join(target)) + print(('\nFAILED: '+''.join(target))) return 1 @@ -275,12 +275,12 @@ def makePy(basename, ext, fmt=False): # supersedes 'args' if present. if options.makeall: if options.makeall_rdepth < 0: - print(Cl.FAIL + "ERROR: recursion depth is set to an invalid number." + Cl.ESC) + print((Cl.FAIL + "ERROR: recursion depth is set to an invalid number." + Cl.ESC)) sys.exit(ERROR_INVALID_RECURSION_DEPTH) targets = targetWalk(options.makeall_rdepth) if targets is None: - print(Cl.FAIL + "ERROR: no targets specified." + Cl.ESC) + print((Cl.FAIL + "ERROR: no targets specified." + Cl.ESC)) sys.exit(ERROR_NO_VALID_TARGETS) # LIBRARIES, INCLUDES, ENV-VARS @@ -309,10 +309,10 @@ def makePy(basename, ext, fmt=False): p = os.path.dirname(usrdir) b = os.path.basename(usrdir) - if (b in found_libs.keys()) and not (p in found_libs.values()): - print(Cl.FAIL + "ERROR: \'" + str(b) + "\' libraray conflict:"+Cl.ESC) - print("\t "+os.path.join(found_libs[b],b)) - print("\t "+os.path.join(p,b)) + if (b in list(found_libs.keys())) and not (p in list(found_libs.values())): + print((Cl.FAIL + "ERROR: \'" + str(b) + "\' libraray conflict:"+Cl.ESC)) + print(("\t "+os.path.join(found_libs[b],b))) + print(("\t "+os.path.join(p,b))) sys.exit(ERROR_LIBRARY_CONFLICT) msg = "\tGPI_LIBRARY_PATH \'"+str(p)+"\' for lib \'"+str(b)+"\'" @@ -321,7 +321,7 @@ def makePy(basename, ext, fmt=False): print(msg) if len(list(found_libs.keys())) == 0: - print(Cl.WRN + "WARNING: No GPI libraries found!\n" + Cl.ESC) + print((Cl.WRN + "WARNING: No GPI libraries found!\n" + Cl.ESC)) if options.preprocess: extra_compile_args.append('-E') @@ -355,7 +355,7 @@ def makePy(basename, ext, fmt=False): library_dirs += [GPI_THIRD+'/fftw/lib'] # Eigen is headers-only - print "Adding Eigen libs" + print("Adding Eigen libs") include_dirs += [GPI_THIRD+'/eigen'] # The intel libs and extra compile flags are different between linux and OSX @@ -409,7 +409,7 @@ def makePy(basename, ext, fmt=False): if options.format: try: print("\nAstyle...") - print("Reformatting CPP Code: " + target['fn'] + target['ext']) + print(("Reformatting CPP Code: " + target['fn'] + target['ext'])) os.system(GPI_BIN+'/astyle -A1 -S -w -c -k3 -b -H -U -C ' + target['fn'] + target['ext']) continue # don't proceed to compile @@ -435,21 +435,21 @@ def makePy(basename, ext, fmt=False): # Py Summary if show_summary > 1: - print('\nSUMMARY (Py Compilations):\n\tSUCCESSES ('+Cl.OKGR+str(len(py_successes))+Cl.ESC+'):') + print(('\nSUMMARY (Py Compilations):\n\tSUCCESSES ('+Cl.OKGR+str(len(py_successes))+Cl.ESC+'):')) for i in py_successes: - print("\t\t" + i) - print('\tFAILURES ('+Cl.FAIL+str(len(py_failures))+Cl.ESC+'):') + print(("\t\t" + i)) + print(('\tFAILURES ('+Cl.FAIL+str(len(py_failures))+Cl.ESC+'):')) for i in py_failures: - print("\t\t" + i) + print(("\t\t" + i)) # CPP Summary if show_summary > 1: - print('\nSUMMARY (CPP Compilations):\n\tSUCCESSES ('+Cl.OKGR+str(len(successes))+Cl.ESC+'):') + print(('\nSUMMARY (CPP Compilations):\n\tSUCCESSES ('+Cl.OKGR+str(len(successes))+Cl.ESC+'):')) for i in successes: - print("\t\t" + i) - print('\tFAILURES ('+Cl.FAIL+str(len(failures))+Cl.ESC+'):') + print(("\t\t" + i)) + print(('\tFAILURES ('+Cl.FAIL+str(len(failures))+Cl.ESC+'):')) for i in failures: - print("\t\t" + i) + print(("\t\t" + i)) # ON FAILURE if (len(py_failures) + len(failures)) > 0: diff --git a/lib/gpi/network.py b/lib/gpi/network.py index f97301c1..2ba61400 100644 --- a/lib/gpi/network.py +++ b/lib/gpi/network.py @@ -132,7 +132,7 @@ def convert_incoming(self): msg += '\ttotal port mem: '+str(GetHumanReadable_bytes(int(self._contents['TOTAL_PMEM']))) + '\n' if 'PLATFORM' in self._contents: - for k,v in self._contents['PLATFORM'].items(): + for k,v in list(self._contents['PLATFORM'].items()): msg += '\t'+k+': '+str(v)+'\n' log.dialog(msg) diff --git a/lib/gpi/node.py b/lib/gpi/node.py index c74c308a..876ad64c 100644 --- a/lib/gpi/node.py +++ b/lib/gpi/node.py @@ -238,7 +238,7 @@ def fitPointSize(self, fontfamily, height): # find the point-size given height in pixels # this has to be done b/c setPixelSize() doesn't seem to work across # platforms. - for pt in xrange(1,self._MAX_ITER): + for pt in range(1,self._MAX_ITER): if QtGui.QFontMetricsF(QtGui.QFont(fontfamily,pt)).height() > height: return pt-1 @@ -1030,8 +1030,8 @@ def updateToolTip(self): # NODE if len(self._computeDuration): avg = self.avgWallTime() std = self.stdWallTime() - tip += '\u03BC = ' + GetHumanReadable_time(avg) - tip += ', \u03C3 = ' + GetHumanReadable_time(std) + tip += '\\u03BC = ' + GetHumanReadable_time(avg) + tip += ', \\u03C3 = ' + GetHumanReadable_time(std) tip += ', n = ' + str(len(self._computeDuration)) + '\n' tip += 'Outport Mem: ' + GetHumanReadable_bytes( @@ -1460,7 +1460,7 @@ def paint(self, painter, option, widget): # NODE painter.setPen(QtGui.QPen(QtCore.Qt.black, 0)) painter.setFont(self.title_font) buf = self.name - painter.drawText(-self._left_margin, -self._top_margin, w, self.getTitleSize()[1], (QtCore.Qt.AlignLeft), unicode(buf)) + painter.drawText(-self._left_margin, -self._top_margin, w, self.getTitleSize()[1], (QtCore.Qt.AlignLeft), str(buf)) # label buf = '' @@ -1472,7 +1472,7 @@ def paint(self, painter, option, widget): # NODE gr.setAlpha(175) painter.setPen(QtGui.QPen(gr, 0)) painter.setFont(self._label_font) - painter.drawText(self._label_inset-self._left_margin, -self._top_margin+th, w, self.getLabelSize()[1], (QtCore.Qt.AlignLeft), unicode(buf)) + painter.drawText(self._label_inset-self._left_margin, -self._top_margin+th, w, self.getLabelSize()[1], (QtCore.Qt.AlignLeft), str(buf)) # detail label (aka node text) if self._nodeIF: @@ -1493,7 +1493,7 @@ def paint(self, painter, option, widget): # NODE painter.drawText(self._detailLabel_inset-self._left_margin, -self._top_margin+th, w, self.getDetailLabelSize()[1], - (QtCore.Qt.AlignLeft), unicode(el_buf)) + (QtCore.Qt.AlignLeft), str(el_buf)) # reloaded disp if self._reload_timer.isActive() and not self.progressON(): diff --git a/lib/gpi/nodeAPI.py b/lib/gpi/nodeAPI.py index 1b20e8ee..1a9eaf58 100644 --- a/lib/gpi/nodeAPI.py +++ b/lib/gpi/nodeAPI.py @@ -133,7 +133,7 @@ def __init__(self, node): self.doc_text_win = QtGui.QTextEdit() self.doc_text_win.setPlainText(self.generateHelpText()) self.doc_text_win.setReadOnly(True) - doc_text_font = QtGui.QFont(u"Monospace", 14) + doc_text_font = QtGui.QFont("Monospace", 14) self.doc_text_win.setFont(doc_text_font) self.doc_text_win.setLineWrapMode(QtGui.QTextEdit.NoWrap) self.doc_text_win.setWindowTitle(node.getModuleName() + " Documentation") @@ -634,7 +634,7 @@ def modifyWidget_setter(self, src, kw, val): def modifyWidget_direct(self, pnumORtitle, **kwargs): src = self.getWidget(pnumORtitle) - for k, v in kwargs.items(): + for k, v in list(kwargs.items()): if k != 'val': self.modifyWidget_setter(src, k, v) @@ -650,7 +650,7 @@ def modifyWidget_buffer(self, title, **kwargs): src = self.getWdgFromBuffer(title) try: - for k, v in kwargs.items(): + for k, v in list(kwargs.items()): src['kwargs'][k] = v except: log.critical("modifyWidget_buffer() FAILED to modify buffered attribute") @@ -788,7 +788,7 @@ def setData(self, title, data): # log.debug("setData(): time: "+str(time.time() - start)+" sec") except: - print str(traceback.format_exc()) + print((str(traceback.format_exc()))) raise GPIError_nodeAPI_setData('self.setData(\''+stw(title)+'\',...) failed in the node definition, check the output name and data type().') def getData(self, title): @@ -956,7 +956,7 @@ def getVal(self, title): return self.getAttr(title, 'val') except: - print str(traceback.format_exc()) + print(str(traceback.format_exc())) raise GPIError_nodeAPI_getVal('self.getVal(\''+stw(title)+'\') failed in the node definition, check the widget name.') def getAttr(self, title, attr): @@ -977,7 +977,7 @@ def getAttr(self, title, attr): return self._getAttr_fromWdg(wdg, attr) except: - print str(traceback.format_exc()) + print(str(traceback.format_exc())) raise GPIError_nodeAPI_getAttr('self.getAttr(\''+stw(title)+'\',...) failed in the node definition, check widget name and attribute name.') def _getAttr_fromWdg(self, wdg, attr): @@ -1005,7 +1005,7 @@ def _getAttr_fromWdg(self, wdg, attr): except: #log.critical("_getAttr_fromWdg(): Likely the wrong input arg type.") #raise - print str(traceback.format_exc()) + print(str(traceback.format_exc())) raise GPIError_nodeAPI_getAttr('_getAttr() failed for widget \''+stw(title)+'\'') def validate(self): diff --git a/lib/gpi/topsort.py b/lib/gpi/topsort.py index 0f23a8bc..dbc642cf 100755 --- a/lib/gpi/topsort.py +++ b/lib/gpi/topsort.py @@ -120,25 +120,25 @@ def topsort(connection_list): if __name__ == '__main__': print("test topsort") - print(topological_sort([1,2,3], [(1,2),(1,3),(3,2)])) # --> [1,3,2] - print(topological_sort([1,2], [(2,1),(2,1)])) # --> [2,1] - print(topological_sort([1,2], [(1,2),(2,1)])) # --> None - print(topological_sort([0,1,2], [(0,1),(1,2),(2,1)])) # --> None + print((topological_sort([1,2,3], [(1,2),(1,3),(3,2)]))) # --> [1,3,2] + print((topological_sort([1,2], [(2,1),(2,1)]))) # --> [2,1] + print((topological_sort([1,2], [(1,2),(2,1)]))) # --> None + print((topological_sort([0,1,2], [(0,1),(1,2),(2,1)]))) # --> None # hashable type x = type y = int z = float - print(topological_sort([x,y,z], [(x,y),(x,z),(z,y)])) # --> [1,3,2] + print((topological_sort([x,y,z], [(x,y),(x,z),(z,y)]))) # --> [1,3,2] - print(topsort( [(x,y),(x,z),(z,y)] )) + print((topsort( [(x,y),(x,z),(z,y)] ))) print("cyclic and acylic graphs") - print(topsort( [(1,2),(1,3),(3,2),(5,6),(5,7),(7,6),(7,5)] )) + print((topsort( [(1,2),(1,3),(3,2),(5,6),(5,7),(7,6),(7,5)] ))) print("multiple graphs") - print(topsort( [(1,2),(1,3),(3,2),(5,6),(5,7),(7,6)] )) + print((topsort( [(1,2),(1,3),(3,2),(5,6),(5,7),(7,6)] ))) diff --git a/lib/gpi/widgets.py b/lib/gpi/widgets.py index 0bf7d558..71937cd9 100644 --- a/lib/gpi/widgets.py +++ b/lib/gpi/widgets.py @@ -39,7 +39,7 @@ from .defines import getKeyboardModifiers, printMouseEvent from .logger import manager from .sysspecs import Specs -import syntax +from . import syntax # start logger for this module From 68452eb1f187d78076b6520f18275e727c86f7d6 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 18 Sep 2015 12:25:16 -0700 Subject: [PATCH 07/60] Switched to dev-mode startup config. --- bin/gpi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/gpi b/bin/gpi index dea039cb..c00e62cd 100755 --- a/bin/gpi +++ b/bin/gpi @@ -1,5 +1,5 @@ -#!/opt/gpi/bin/GPILab #!/usr/bin/env python +#!/opt/anaconda1anaconda2anaconda3/bin/GPILab # ANACONDA # Copyright (C) 2014 Dignity Health # From 11a86174fa7814086216adb2c7c2e4d8fe0c8d45 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 18 Sep 2015 14:22:28 -0700 Subject: [PATCH 08/60] Replaced GPILab softlink with anaconda's python. --- bin/gpi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/gpi b/bin/gpi index c00e62cd..76b09e4f 100755 --- a/bin/gpi +++ b/bin/gpi @@ -1,5 +1,5 @@ #!/usr/bin/env python -#!/opt/anaconda1anaconda2anaconda3/bin/GPILab # ANACONDA +#!/opt/anaconda1anaconda2anaconda3/bin/python # ANACONDA # Copyright (C) 2014 Dignity Health # From 3f58c347ca72bcf669c4224efe7671ee16120b7c Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 18 Sep 2015 14:59:22 -0700 Subject: [PATCH 09/60] Added anaconda specific python for distro. --- bin/gpi_make | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/gpi_make b/bin/gpi_make index 30c7ebf8..c9249a83 100755 --- a/bin/gpi_make +++ b/bin/gpi_make @@ -1,4 +1,5 @@ #!/usr/bin/env python +#!/opt/anaconda1anaconda2anaconda3/bin/python # ANACONDA # Copyright (C) 2014 Dignity Health # From c7d1860439fe374cd0a2292085843f465cf17889 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 18 Sep 2015 19:56:13 -0700 Subject: [PATCH 10/60] Fixed GPI link issue for subsequent runs. --- launch/gpi.command | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/launch/gpi.command b/launch/gpi.command index fa44f520..cbbafb46 100755 --- a/launch/gpi.command +++ b/launch/gpi.command @@ -27,21 +27,22 @@ # The GPI launcher script. The .command suffix is used by OSX automator. # get user environment settings -# -preempt global settings with local user mods. -# -this will pickup the user's editor selection +# -this will pickup the user's visual editor if [ -f $HOME/.bashrc ]; then . $HOME/.bashrc fi PYTHON=/opt/anaconda1anaconda2anaconda3/bin/python # ANACONDA +GPI_LINK=/tmp/GPI +GPI_LAUNCH=/opt/anaconda1anaconda2anaconda3/bin/gpi_launch +# OSX if [ "$(uname)" == "Darwin" ]; then - ln -s $PYTHON /tmp/GPI - # run the main start script - /tmp/GPI /opt/anaconda1anaconda2anaconda3/bin/gpi_launch $@ + ln -f -s $PYTHON $GPI_LINK + $GPI_LINK $GPI_LAUNCH $@ fi # Linux if [ "$(uname)" == "Linux" ]; then - $PYTHON /opt/anaconda1anaconda2anaconda3/bin/gpi_launch -style cleanlooks $@ + $PYTHON $GPI_LAUNCH -style cleanlooks $@ fi From 604b00355c02444a35b37bfdc0ca080d9747566b Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 18 Sep 2015 20:04:23 -0700 Subject: [PATCH 11/60] Conda environment for py3k branch. Can't get qimage2ndarray see #76. To setup this env: $ conda env create -f gpi_py3k_env.yml --- gpi_py3k_env.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 gpi_py3k_env.yml diff --git a/gpi_py3k_env.yml b/gpi_py3k_env.yml new file mode 100644 index 00000000..670b1df2 --- /dev/null +++ b/gpi_py3k_env.yml @@ -0,0 +1,35 @@ +name: py3k +dependencies: +- astyle=2.05.1=0 +- eigen=3.2.5=0 +- fftw=3.3.4=0 +- freetype=2.5.2=2 +- gpi=0.6.0=py3k +- h5py=2.5.0=np19py35_3 +- hdf5=1.8.15.1=1 +- libpng=1.6.17=0 +- matplotlib=1.4.3=np19py35_3 +- numpy=1.9.2=py35_0 +- openssl=1.0.1k=1 +- pip=7.1.2=py35_0 +- psutil=3.2.0=py35_0 +- pyopengl=3.1.1a1=np19py35_0 +- pyparsing=2.0.3=py35_0 +- pyqt=4.11.3=py35_0 +- python=3.5.0=0 +- python-dateutil=2.4.2=py35_0 +- pytz=2015.4=py35_0 +- qt=4.8.6=3 +- readline=6.2=2 +- scipy=0.16.0=np19py35_1 +- setuptools=18.1=py35_0 +- sip=4.16.5=py35_0 +- six=1.9.0=py35_0 +- sqlite=3.8.4.1=1 +- tk=8.5.18=0 +- wheel=0.24.0=py35_0 +- xz=5.0.5=0 +- zlib=1.2.8=0 +- pip: + - qimage2ndarray==1.5 + From 1d49c1faab7834dee6e97480869b33eff5323a46 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 18 Sep 2015 21:50:02 -0700 Subject: [PATCH 12/60] Removed the need for patching these two. --- bin/gpi_launch | 10 ++++++---- bin/gpi_make | 11 ++++++----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/bin/gpi_launch b/bin/gpi_launch index 76b09e4f..cd1175e4 100755 --- a/bin/gpi_launch +++ b/bin/gpi_launch @@ -1,5 +1,4 @@ -#!/usr/bin/env python -#!/opt/anaconda1anaconda2anaconda3/bin/python # ANACONDA +#!/usr/bin/env python # DEV # Copyright (C) 2014 Dignity Health # @@ -27,8 +26,11 @@ import sys, os -# GPI_PREFIX = '/opt/anaconda1anaconda2anaconda3' # ANACONDA -GPI_PREFIX, _ = os.path.split(os.path.dirname(os.path.realpath(__file__))) +# Check for Anaconda PREFIX, or assume that THIS file location is the CWD. +GPI_PREFIX = '/opt/anaconda1anaconda2anaconda3' # ANACONDA +if GPI_PREFIX == '/opt/'+''.join(['anaconda'+str(i) for i in range(1,4)]): + GPI_PREFIX, _ = os.path.split(os.path.dirname(os.path.realpath(__file__))) + GPI_LIB_DIR = os.path.join(GPI_PREFIX, 'lib') if GPI_LIB_DIR not in sys.path: sys.path.insert(0, GPI_LIB_DIR) diff --git a/bin/gpi_make b/bin/gpi_make index c9249a83..95d0fc77 100755 --- a/bin/gpi_make +++ b/bin/gpi_make @@ -1,5 +1,4 @@ -#!/usr/bin/env python -#!/opt/anaconda1anaconda2anaconda3/bin/python # ANACONDA +#!/usr/bin/env python # DEV # Copyright (C) 2014 Dignity Health # @@ -27,8 +26,11 @@ import sys, os -# GPI_PREFIX = '/opt/anaconda1anaconda2anaconda3' # ANACONDA -GPI_PREFIX, _ = os.path.split(os.path.dirname(os.path.realpath(__file__))) +# Check for Anaconda PREFIX, or assume that THIS file location is the CWD. +GPI_PREFIX = '/opt/anaconda1anaconda2anaconda3' # ANACONDA +if GPI_PREFIX == '/opt/'+''.join(['anaconda'+str(i) for i in range(1,4)]): + GPI_PREFIX, _ = os.path.split(os.path.dirname(os.path.realpath(__file__))) + GPI_LIB_DIR = os.path.join(GPI_PREFIX, 'lib') if GPI_LIB_DIR not in sys.path: sys.path.insert(0, GPI_LIB_DIR) @@ -37,5 +39,4 @@ if GPI_LIB_DIR not in sys.path: from gpi import make if __name__ == '__main__': - # make.make() # ANACONDA make.make(GPI_PREFIX) From d688425df36d0d7ac6ca6958652acdea114a6aa0 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 18 Sep 2015 21:50:40 -0700 Subject: [PATCH 13/60] Fixed some py2 'print' statements --- lib/gpi/make.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/gpi/make.py b/lib/gpi/make.py index 189613e2..50261904 100755 --- a/lib/gpi/make.py +++ b/lib/gpi/make.py @@ -331,11 +331,11 @@ def make(GPI_PREFIX=None): # Anaconda environment includes # includes FFTW and eigen - print "Adding Anaconda lib and inc dirs..." + print("Adding Anaconda lib and inc dirs...") try: output = subprocess.check_output('conda info --json', shell=True) except subprocess.CalledProcessError as e: - print cmd, e.output + print(cmd, e.output) exit(e.returncode) conda_prefix = json.loads(output)['default_prefix'] include_dirs += [os.path.join(conda_prefix, 'include')] @@ -400,8 +400,8 @@ def make(GPI_PREFIX=None): # ASTYLE if options.format: try: - print "\nAstyle..." - print "Reformatting CPP Code: " + target['fn'] + target['ext'] + print("\nAstyle...") + print("Reformatting CPP Code: " + target['fn'] + target['ext']) # TODO: astyle might not be in the path os.system('astyle -A1 -S -w -c -k3 -b -H -U -C ' + target['fn'] + target['ext']) From 9cf1583efbca2b8fe8193bd73bee100b48087765 Mon Sep 17 00:00:00 2001 From: nick Date: Sat, 19 Sep 2015 00:08:13 -0700 Subject: [PATCH 14/60] Switched path to /Applications/GPI.app/Contents/Resources/anaconda/bin/gpi --- launch/GPI.app/Contents/Info.plist | 12 ++++++------ .../GPI.app/Contents/MacOS/Application Stub | Bin 14892 -> 38812 bytes .../German.lproj/ApplicationStub.nib | Bin 12615 -> 16829 bytes .../Italian.lproj/ApplicationStub.nib | Bin 12406 -> 12318 bytes .../Resources/ar.lproj/ApplicationStub.nib | Bin 12682 -> 16699 bytes .../Resources/ca.lproj/ApplicationStub.nib | Bin 12518 -> 12425 bytes .../Resources/da.lproj/ApplicationStub.nib | Bin 12416 -> 12332 bytes .../Resources/el.lproj/ApplicationStub.nib | Bin 12987 -> 12895 bytes .../Resources/he.lproj/ApplicationStub.nib | Bin 12547 -> 12407 bytes .../Resources/hr.lproj/ApplicationStub.nib | Bin 12484 -> 12345 bytes .../Resources/hu.lproj/ApplicationStub.nib | Bin 12909 -> 12799 bytes .../Resources/pt_PT.lproj/ApplicationStub.nib | Bin 12444 -> 12334 bytes .../Resources/ro.lproj/ApplicationStub.nib | Bin 12722 -> 12626 bytes .../Resources/ru.lproj/ApplicationStub.nib | Bin 12923 -> 12829 bytes .../Resources/sk.lproj/ApplicationStub.nib | Bin 12766 -> 12656 bytes .../Resources/th.lproj/ApplicationStub.nib | Bin 12696 -> 12545 bytes .../Resources/tr.lproj/ApplicationStub.nib | Bin 12694 -> 12536 bytes .../Resources/uk.lproj/ApplicationStub.nib | Bin 12820 -> 12704 bytes launch/GPI.app/Contents/document.wflow | 12 ++++++------ 19 files changed, 12 insertions(+), 12 deletions(-) diff --git a/launch/GPI.app/Contents/Info.plist b/launch/GPI.app/Contents/Info.plist index 638c5b61..d98c55d0 100644 --- a/launch/GPI.app/Contents/Info.plist +++ b/launch/GPI.app/Contents/Info.plist @@ -7,7 +7,7 @@ AMStayOpen BuildMachineOSBuild - 12E39 + 11E53 CFBundleDevelopmentRegion English CFBundleDocumentTypes @@ -46,21 +46,21 @@ CFBundleURLTypes CFBundleVersion - 346 + 339.2 DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild - 4F250 + 11E53 DTPlatformVersion GM DTSDKBuild - 12E39 + 11E53 DTSDKName DTXcode - 0440 + 0410 DTXcodeBuild - 4F250 + 11E53 LSMinimumSystemVersion 10.5 LSMinimumSystemVersionByArchitecture diff --git a/launch/GPI.app/Contents/MacOS/Application Stub b/launch/GPI.app/Contents/MacOS/Application Stub index 25001ca0cea0fc37ae62a8bff6b0a9ebef30f469..1d352b6ec2a07d51cba4c91b9195eb0ea9a2994f 100755 GIT binary patch literal 38812 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 IP9nsg0Zp^grT_o{ diff --git a/launch/GPI.app/Contents/Resources/German.lproj/ApplicationStub.nib b/launch/GPI.app/Contents/Resources/German.lproj/ApplicationStub.nib index 9a210d835a8823ee445245071d3e348c67f96e88..100dafe05869591e21ac3ec32e4e5d41ae798de9 100644 GIT binary patch literal 16829 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 WK0wm{@F(>2Kl)JSx$!al{(l3>QGP!F diff --git a/launch/GPI.app/Contents/Resources/Italian.lproj/ApplicationStub.nib b/launch/GPI.app/Contents/Resources/Italian.lproj/ApplicationStub.nib index 0a9f0bccb8d11a54a2fa066e00c8f657d13aff61..0cc4d82a27d25fbfa205ee0a1a52b4ea1173e46c 100644 GIT binary patch literal 12318 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 literal 12406 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 diff --git a/launch/GPI.app/Contents/Resources/ar.lproj/ApplicationStub.nib b/launch/GPI.app/Contents/Resources/ar.lproj/ApplicationStub.nib index b000c283263ab23ca1dd01a1e4a055f64ab99244..4687550763160e206223451f7012aefdcb9221f5 100644 GIT binary patch literal 16699 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 WZU_&N@{gi?pJcZ0v*NGt=>GvEk|{0# literal 12682 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 literal 12518 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 literal 12416 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 diff --git a/launch/GPI.app/Contents/Resources/el.lproj/ApplicationStub.nib b/launch/GPI.app/Contents/Resources/el.lproj/ApplicationStub.nib index 68c8a8b9b66f7511b27e493542fc5e241ebdfa42..777cc2a61044aa9955962bb6f51036d18325d922 100644 GIT binary patch literal 12895 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 Qaa`n;z7ct?`k9gWU%}CFc>n+a diff --git a/launch/GPI.app/Contents/Resources/he.lproj/ApplicationStub.nib b/launch/GPI.app/Contents/Resources/he.lproj/ApplicationStub.nib index 5e84ddd5b8b158901e5ce9cfb017faed3388b6ea..d74a77a80964d630e88def5da489e621bd590225 100644 GIT binary patch literal 12407 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 literal 12547 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 V{M(T9k745PWKI12&Ts$c{{;bMZR7v| literal 12484 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 b7%T}+2u=<@f#m-fCjOVKjsLyz9Gvri^5441 literal 12909 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 diff --git a/launch/GPI.app/Contents/Resources/pt_PT.lproj/ApplicationStub.nib b/launch/GPI.app/Contents/Resources/pt_PT.lproj/ApplicationStub.nib index df40ec20a508bf55fb91f70bd0f26f6cf471f06b..6e5292f3075cc6a1fe962248c67c7e396d40648e 100644 GIT binary patch literal 12334 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 diff --git a/launch/GPI.app/Contents/Resources/ro.lproj/ApplicationStub.nib b/launch/GPI.app/Contents/Resources/ro.lproj/ApplicationStub.nib index df64121096694179b26e62046e6d060495100254..62be6af10aa2d0b40ad7bf72033bd1cf773f7e4e 100644 GIT binary patch literal 12626 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 P?JM0@`&#$6NzeZW|Eq!( diff --git a/launch/GPI.app/Contents/Resources/ru.lproj/ApplicationStub.nib b/launch/GPI.app/Contents/Resources/ru.lproj/ApplicationStub.nib index 06b6918d4794f6cd0b246d3579e5348d561a7b44..a3fec4fcdbea774d519cd8478ee85a450652bd99 100644 GIT binary patch literal 12829 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�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 diff --git a/launch/GPI.app/Contents/Resources/th.lproj/ApplicationStub.nib b/launch/GPI.app/Contents/Resources/th.lproj/ApplicationStub.nib index 7f6887e945296d45a938b726729f366802f81b09..1f5a724dc88c2bd03b5e1c69096e9af72177cc5a 100644 GIT binary patch literal 12545 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 literal 12696 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 diff --git a/launch/GPI.app/Contents/Resources/tr.lproj/ApplicationStub.nib b/launch/GPI.app/Contents/Resources/tr.lproj/ApplicationStub.nib index fef396d076cee675ed44b39b378a8a5c1ef51ce1..64039bae73488142159d6eac06426d37db4c19da 100644 GIT binary patch literal 12536 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 A{{R30 diff --git a/launch/GPI.app/Contents/document.wflow b/launch/GPI.app/Contents/document.wflow index 3b274497..0e753f3c 100644 --- a/launch/GPI.app/Contents/document.wflow +++ b/launch/GPI.app/Contents/document.wflow @@ -3,9 +3,9 @@ AMApplicationBuild - 346 + 339.2 AMApplicationVersion - 2.3 + 2.2.4 AMDocumentVersion 2 actions @@ -25,7 +25,7 @@ AMActionVersion - 2.0.3 + 2.0.2 AMApplication Automator @@ -68,7 +68,7 @@ rm -f $TMPFILE # roll all args into another script for terminal.app echo "#!/bin/bash" > $TMPFILE -echo "/opt/gpi/launch/gpi.command $@" >> $TMPFILE +echo "/Applications/GPI.app/Contents/Resources/anaconda/bin/gpi $@" >> $TMPFILE chmod a+x $TMPFILE /usr/bin/open $TMPFILE CheckedForUserDefaultShell @@ -83,7 +83,7 @@ chmod a+x $TMPFILE BundleIdentifier com.apple.RunShellScript CFBundleVersion - 2.0.3 + 2.0.2 CanShowSelectedItemsWhenRun CanShowWhenRun @@ -183,7 +183,7 @@ chmod a+x $TMPFILE isViewVisible location - 352.500000:763.000000 + 410.500000:702.000000 nibPath /System/Library/Automator/Run Shell Script.action/Contents/Resources/English.lproj/main.nib From 4af1b717dec3c200c2418bcf41076edfb6d25a1a Mon Sep 17 00:00:00 2001 From: nick Date: Sat, 19 Sep 2015 11:11:41 -0700 Subject: [PATCH 15/60] Fixed prefix issue with conda build. -json was failing in the package build.sh with: TypeError: the JSON object must be str, not 'bytes' -the gpirc can get in the way of building a clean package so an 'ignore-gpirc' option was added. --- lib/gpi/make.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/lib/gpi/make.py b/lib/gpi/make.py index 50261904..4b60ca7f 100755 --- a/lib/gpi/make.py +++ b/lib/gpi/make.py @@ -37,7 +37,6 @@ $ ./make.py .py ''' import subprocess -import json from distutils.core import setup, Extension import os import sys @@ -251,6 +250,9 @@ def make(GPI_PREFIX=None): parser.add_option('--debug', dest='debug', default=False, action='store_true', help="Uses range checker for PyFI::Array calls.") + parser.add_option('--ignore-gpirc', dest='ignore_gpirc', default=False, + action='store_true', + help="Ignore the ~/.gpirc config.") parser.add_option( '-v', '--verbose', dest='verbose', default=False, action="store_true", @@ -285,20 +287,27 @@ def make(GPI_PREFIX=None): print((Cl.FAIL + "ERROR: no targets specified." + Cl.ESC)) sys.exit(ERROR_NO_VALID_TARGETS) + if options.ignore_gpirc: + print('Ignoring the ~/.gpirc...') + # USER MAKE config - if (len(Config.MAKE_CFLAGS) + len(Config.MAKE_LIBS) + len(Config.MAKE_INC_DIRS) + len(Config.MAKE_LIB_DIRS)) > 0: - print("Adding USER include dirs") - # add user libs - libraries += Config.MAKE_LIBS - include_dirs += Config.MAKE_INC_DIRS - library_dirs += Config.MAKE_LIB_DIRS - extra_compile_args += Config.MAKE_CFLAGS + if not options.ignore_gpirc: + if (len(Config.MAKE_CFLAGS) + len(Config.MAKE_LIBS) + len(Config.MAKE_INC_DIRS) + len(Config.MAKE_LIB_DIRS)) > 0: + print("Adding USER include dirs") + # add user libs + libraries += Config.MAKE_LIBS + include_dirs += Config.MAKE_INC_DIRS + library_dirs += Config.MAKE_LIB_DIRS + extra_compile_args += Config.MAKE_CFLAGS # GPI library dirs print("Adding GPI include dirs") # add libs from library paths found_libs = {} - for flib in Config.GPI_LIBRARY_PATH: + search_dirs = [os.path.realpath('.')] # CWD + if not options.ignore_gpirc: + search_dirs += Config.GPI_LIBRARY_PATH + for flib in search_dirs: if os.path.isdir(flib): # skip default config if dirs dont exist for usrdir in findLibraries(flib): p = os.path.dirname(usrdir) @@ -332,14 +341,8 @@ def make(GPI_PREFIX=None): # Anaconda environment includes # includes FFTW and eigen print("Adding Anaconda lib and inc dirs...") - try: - output = subprocess.check_output('conda info --json', shell=True) - except subprocess.CalledProcessError as e: - print(cmd, e.output) - exit(e.returncode) - conda_prefix = json.loads(output)['default_prefix'] - include_dirs += [os.path.join(conda_prefix, 'include')] - library_dirs += [os.path.join(conda_prefix, 'lib')] + include_dirs += [os.path.join(GPI_PREFIX, 'include')] + library_dirs += [os.path.join(GPI_PREFIX, 'lib')] include_dirs += [numpy.get_include()] libraries += ['fftw3_threads', 'fftw3', 'fftw3f_threads', 'fftw3f'] From 3e96f5d8f4ce53bf21c7b00ee46dea94dfe720c3 Mon Sep 17 00:00:00 2001 From: nick Date: Sat, 19 Sep 2015 11:44:10 -0700 Subject: [PATCH 16/60] Added local search of CWD for compilation, see #77. --- lib/gpi/make.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/gpi/make.py b/lib/gpi/make.py index 4b60ca7f..6e116911 100755 --- a/lib/gpi/make.py +++ b/lib/gpi/make.py @@ -218,6 +218,8 @@ def makePy(basename, ext, fmt=False): def make(GPI_PREFIX=None): + CWD = os.path.realpath('.') + # LIBRARIES, INCLUDES, ENV-VARS include_dirs = [] libraries = [] @@ -226,8 +228,7 @@ def make(GPI_PREFIX=None): runtime_library_dirs = [] if GPI_PREFIX is not None: - GPI_INC_DIR = os.path.join(GPI_PREFIX, 'include') - include_dirs.append(GPI_INC_DIR) + include_dirs.append(os.path.join(GPI_PREFIX, 'include')) parser = optparse.OptionParser() parser.add_option('--preprocess', dest='preprocess', default=False, @@ -304,9 +305,14 @@ def make(GPI_PREFIX=None): print("Adding GPI include dirs") # add libs from library paths found_libs = {} - search_dirs = [os.path.realpath('.')] # CWD + search_dirs = [] if not options.ignore_gpirc: search_dirs += Config.GPI_LIBRARY_PATH + else: + # resort to searching the CWD for libraries + # -if the make is being invoked on a PyMOD is reasonable to assume there + # is a library that contains this file potentially 2 levels up. + search_dirs = [CWD, os.path.realpath(CWD+'/../../')] for flib in search_dirs: if os.path.isdir(flib): # skip default config if dirs dont exist for usrdir in findLibraries(flib): From c6ecde408795dc9444ae6c7a1eceaf463a1abeed Mon Sep 17 00:00:00 2001 From: nick Date: Sat, 19 Sep 2015 15:23:33 -0700 Subject: [PATCH 17/60] Added the anaconda prefix to a few more locations. --- lib/gpi/config.py | 10 ++++++---- lib/gpi/defines.py | 3 ++- lib/gpi/docs.py | 5 ++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/gpi/config.py b/lib/gpi/config.py index 62745603..a482c3f9 100644 --- a/lib/gpi/config.py +++ b/lib/gpi/config.py @@ -24,6 +24,8 @@ # Brief: A module for configuring gpi thru the ~/.gpirc file +PREFIX='/opt/anaconda1anaconda2anaconda3' + import os import traceback import configparser @@ -53,7 +55,7 @@ GPI_NET_PATH_DEFAULT = USER_HOME GPI_DATA_PATH_DEFAULT = USER_HOME GPI_FOLLOW_CWD = True -GPI_LIBRARY_PATH_DEFAULT = ['/opt/anaconda1anaconda2anaconda3/lib/gpi/node-libs', USER_LIB_BASE_PATH_DEFAULT] # distro default +GPI_LIBRARY_PATH_DEFAULT = [PREFIX+'/lib/gpi/node-libs', USER_LIB_BASE_PATH_DEFAULT] # distro default ############################################################################### @@ -93,7 +95,7 @@ def __init__(self): self._c_gpi_lib_path = list(GPI_LIBRARY_PATH_DEFAULT) self._c_gpi_follow_cwd = GPI_FOLLOW_CWD - self._new_node_template_file = '/opt/gpi/lib/gpi/nodeTemplate_GPI.py' + self._new_node_template_file = PREFIX+'/lib/gpi/nodeTemplate_GPI.py' # make vars self._make_libs = [] @@ -272,10 +274,10 @@ def generateConfigFile(self, overwrite=False): configfile.write('\n[PATH]\n') configfile.write('# Add library paths for GPI nodes.\n') configfile.write('# Multiple paths are delimited with a \':\'.\n') - configfile.write('# (e.g. [default] LIB_DIRS = ~/gpi:/opt/anaconda1anaconda2anaconda3/gpi/node-libs/).\n') + configfile.write('# (e.g. [default] LIB_DIRS = ~/gpi:'+PREFIX+'/gpi/node-libs/).\n') configfile.write('\n# A list of directories where nodes can be found.\n') - configfile.write('# -To enable the exercises add \'/opt/gpi/doc/Training/exercises\'.\n') + configfile.write('# -To enable the exercises add \''+PREFIX+'/lib/gpi/doc/Training/exercises\'.\n') configfile.write('#LIB_DIRS = '+ ':'.join(GPI_LIBRARY_PATH_DEFAULT) + '\n') configfile.write('\n# Network file browser starts in this directory.\n') configfile.write('#NET_DIR = '+ GPI_NET_PATH_DEFAULT + '\n') diff --git a/lib/gpi/defines.py b/lib/gpi/defines.py index c11a6933..3a43440d 100644 --- a/lib/gpi/defines.py +++ b/lib/gpi/defines.py @@ -22,6 +22,7 @@ # MAKES NO WARRANTY AND HAS NOR LIABILITY ARISING FROM ANY USE OF THE # SOFTWARE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITIES. +PREFIX='/opt/anaconda1anaconda2anaconda3' import os import sys @@ -87,7 +88,7 @@ def ThisFilePath(): log.info('Set Recursion Limit: '+str(sys.getrecursionlimit())) # location of gpi documents in the packaged distro -GPI_DOCS_DIR = '/opt/gpi/doc' +GPI_DOCS_DIR = PREFIX+'/lib/gpi/doc' # Node and module paths, local bundle should be searched first. # The GPI_CWD only gives the path where gpi was invoked, not diff --git a/lib/gpi/docs.py b/lib/gpi/docs.py index 4f351991..22b6bdb0 100755 --- a/lib/gpi/docs.py +++ b/lib/gpi/docs.py @@ -24,15 +24,14 @@ # MAKES NO WARRANTY AND HAS NOR LIABILITY ARISING FROM ANY USE OF THE # SOFTWARE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITIES. +PREFIX='/opt/anaconda1anaconda2anaconda3' import os import sys import inspect -GPI_PKG='/opt/gpi/' +GPI_PKG=PREFIX GPI_FRAMEWORK=GPI_PKG+'lib/' -GPI_BIN=GPI_PKG+'bin/' -GPI_THIRD=GPI_PKG+'local/' sys.path.insert(0, GPI_FRAMEWORK) # gpi sys.path.insert(0, GPI_PKG) # plugin From a41a9bcb88d09b912e21e7fa4c752766c9f089e9 Mon Sep 17 00:00:00 2001 From: nick Date: Sat, 19 Sep 2015 23:04:43 -0700 Subject: [PATCH 18/60] Fixed network decoding from pickled py2 src. -this is a pretty hackish stop-gap; the network files were written with dict keys as py2-str which don't translate to py3-str. They can be read in as py3-bytes, but then the code can't reference the values unless each key is a byte. So for now, 'latin1' seems to work and will probably continue to work until we start using unicode. --- lib/gpi/network.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/gpi/network.py b/lib/gpi/network.py index 2ba61400..b96ba385 100644 --- a/lib/gpi/network.py +++ b/lib/gpi/network.py @@ -311,7 +311,7 @@ def determine_version(self, fname): # first, then pickle as a backup. try: fptr = open(fname, "rb") - contents = pickle.load(fptr) + contents = pickle.load(fptr, encoding="latin1") fptr.close() except: contents = None @@ -338,7 +338,6 @@ def determine_version(self, fname): # header section that is easier to read as a separate step. self._unpickled_contents = contents - if version != self._latest_net_version: log.warn('This network was saved in an older format, please re-save this network.') From 48db2ee6bfc0c109cf3b34559e92a6dc182ad62c Mon Sep 17 00:00:00 2001 From: nick Date: Sat, 19 Sep 2015 23:24:51 -0700 Subject: [PATCH 19/60] Fixed issue with writing networks -byte vs str. --- lib/gpi/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gpi/network.py b/lib/gpi/network.py index b96ba385..f7e7de04 100644 --- a/lib/gpi/network.py +++ b/lib/gpi/network.py @@ -101,7 +101,7 @@ def save(self): self.convert_outgoing() try: - fptr = open(self._fname, "w") + fptr = open(self._fname, "wb") pickle.dump(self._contents, fptr) fptr.close() log.dialog("Network saved.") From 2cdfefad684e037240a01b429d341773f6153173 Mon Sep 17 00:00:00 2001 From: nick Date: Sun, 20 Sep 2015 19:57:26 -0700 Subject: [PATCH 20/60] Fixed bug caused by changes in the new psutil API. --- lib/gpi/sysspecs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gpi/sysspecs.py b/lib/gpi/sysspecs.py index b441b67c..de799ec2 100644 --- a/lib/gpi/sysspecs.py +++ b/lib/gpi/sysspecs.py @@ -78,7 +78,7 @@ def __init__(self): # OS resource limits def numOpenFiles(self): - return self._proc.get_num_fds() + return self._proc.num_fds() def numOpenFilesLimit(self): # get the soft limit From ba1e340f8b46e3acdd56df6dfb04d303601c44f1 Mon Sep 17 00:00:00 2001 From: nick Date: Sun, 20 Sep 2015 20:02:39 -0700 Subject: [PATCH 21/60] Fixed unicode issue in mmap proxy case. --- lib/gpi/dataproxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gpi/dataproxy.py b/lib/gpi/dataproxy.py index 4219dab5..15439ca2 100644 --- a/lib/gpi/dataproxy.py +++ b/lib/gpi/dataproxy.py @@ -62,7 +62,7 @@ def getSHMF(self, nodeID, name='local'): '''return a unique shared mem handle for this gpi instance, node and port. ''' # make sure the user supplied string is a unique, consistent and valid filename - hsh = hashlib.md5(str(name)).hexdigest() + hsh = hashlib.md5(str(name).encode('utf8')).hexdigest() # add a little salt with the random int generator - this will just grow # the ports don't keep track of these file names for cleanup From 4c8647a85b92b0bc4a1990528a44b78dde4e01d9 Mon Sep 17 00:00:00 2001 From: ash Date: Tue, 22 Sep 2015 13:31:54 -0700 Subject: [PATCH 22/60] Bump version for p3k to 1.0.0 --- lib/gpi/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gpi/__init__.py b/lib/gpi/__init__.py index 293240fe..0e427514 100644 --- a/lib/gpi/__init__.py +++ b/lib/gpi/__init__.py @@ -26,8 +26,8 @@ # automatically into the gpi namespace. This way users only need to # import gpi, then use things like gpi.REQUIRED for ports etc... -VERSION = '0.5.0-n1' -RELEASE_DATE = '2015Feb16' +VERSION = '1.0.0' +RELEASE_DATE = '2015Sep22' # Print version info each time. _version = 'GPI '+VERSION+' ('+RELEASE_DATE+')' From fffb8fa682ab1eabc07eb44d7c75e3be80fdab4c Mon Sep 17 00:00:00 2001 From: ash Date: Tue, 22 Sep 2015 15:23:30 -0700 Subject: [PATCH 23/60] Fix bugs related to new node path --- lib/gpi/library.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/gpi/library.py b/lib/gpi/library.py index 11336d4e..f7b57709 100644 --- a/lib/gpi/library.py +++ b/lib/gpi/library.py @@ -406,6 +406,7 @@ def _createNewNode(self): # copy node template to this library, and open it up fullpath = self._new_node_path + new_node_created = False if os.path.exists(fullpath): log.warn("Didn't create new node at path: " + fullpath + " (file already exists)") @@ -457,11 +458,11 @@ def _setQTLabelElided(self, label, text): def _newNodeNameEdited(self): new_name = self._get_new_node_name() current_path = self._new_node_path - if current_path != NOPATH_MESSAGE: + if current_path != '': path, old_name = os.path.split(current_path) fullpath = os.path.join(path, new_name) self._new_node_path = fullpath - self._setQTLabelElided(self._new_node_path_label, fullpath) + self._setQTLabelElided(self._new_node_path_field, fullpath) # This slot is called whenever a list item is clicked. This is used to # update the path and set the enabled/disabled state of the create node @@ -470,17 +471,20 @@ def _listItemClicked(self, item): idx, label = self._new_node_list_index if idx == 0: self._create_button.setDisabled(True) - self._new_node_path_label.setText(NOPATH_MESSAGE) + self._new_node_path_field.setText(NOPATH_MESSAGE) + self._new_node_path = '' elif idx == 1: if item.text() == '..': self._create_button.setDisabled(True) - self._new_node_path_label.setText(NOPATH_MESSAGE) + self._new_node_path_field.setText(NOPATH_MESSAGE) + self._new_node_path = '' else: for k in self._known_GPI_nodes.keys(): node = self._known_GPI_nodes.get(k) if node.thrd_sec == '.'.join((label, item.text())): fullpath = os.path.join(node.path, self._get_new_node_name()) - self._setQTLabelElided(self._new_node_path_label, fullpath) + self._new_node_path = fullpath + self._setQTLabelElided(self._new_node_path_field, fullpath) self._create_button.setEnabled(True) break elif idx == 2: @@ -822,9 +826,9 @@ def generateNewNodeList(self, top_lib=None): self._new_node_path = new_node_path if self._new_node_path == '': - self._new_node_path_label.setText(NOPATH_MESSAGE) + self._new_node_path_field.setText(NOPATH_MESSAGE) else: - self._setQTLabelElided(self._new_node_path_label, new_node_path) + self._setQTLabelElided(self._new_node_path_field, new_node_path) self._new_node_list.clear() if top_lib is not None: @@ -876,11 +880,12 @@ def generateNewNodeListWindow(self): node_name_layout.addWidget(self._new_node_name_field) new_node_path_label = QtGui.QLabel("Path:", self._list_win) - self._new_node_path_label = QtGui.QLabel(NOPATH_MESSAGE, self._list_win) - self._new_node_path_label.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)) + self._new_node_path = '' + self._new_node_path_field = QtGui.QLabel(NOPATH_MESSAGE, self._list_win) + self._new_node_path_field.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)) path_layout = QtGui.QHBoxLayout() path_layout.addWidget(new_node_path_label) - path_layout.addWidget(self._new_node_path_label) + path_layout.addWidget(self._new_node_path_field) list_layout = QtGui.QVBoxLayout() list_layout.addWidget(self._list_label) From 07f6ff90b68c7a59926e863b9c58aae3af09e953 Mon Sep 17 00:00:00 2001 From: ash Date: Tue, 22 Sep 2015 15:25:23 -0700 Subject: [PATCH 24/60] Change error on shutil.copyfile for Python 3 --- lib/gpi/library.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/gpi/library.py b/lib/gpi/library.py index f7b57709..c3811c3d 100644 --- a/lib/gpi/library.py +++ b/lib/gpi/library.py @@ -414,10 +414,9 @@ def _createNewNode(self): try: shutil.copyfile(Config.GPI_NEW_NODE_TEMPLATE_FILE, fullpath) - except IOError: - # TODO: shutil errors change in Python3 - log.warn("Didn't create new node at path: " + fullpath + - " (check your permissions)") + except OSError as e: + print(e) + log.warn("Didn't create new node at path: " + fullpath) else: log.dialog("New node created at path: " + fullpath) new_node_created = True From d2c1cee50bf2304d19172445396f955c30bf3c96 Mon Sep 17 00:00:00 2001 From: nick Date: Tue, 22 Sep 2015 20:23:43 -0700 Subject: [PATCH 25/60] Fixed DisplayBox get_val to pass b/c qimage is not a serializable type. --- lib/gpi/widgets.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/gpi/widgets.py b/lib/gpi/widgets.py index 71937cd9..398dfd91 100644 --- a/lib/gpi/widgets.py +++ b/lib/gpi/widgets.py @@ -1865,7 +1865,9 @@ def get_noscroll(self): return self.scaleCheckBox.isChecked() def get_val(self): - return self._value + # QImage is not serializable + #return self._value + pass def get_line(self): return self.imageLabel.getLine() From 6e4e3674cda26d7ca9e51285a20bca122a59a095 Mon Sep 17 00:00:00 2001 From: nick Date: Tue, 22 Sep 2015 20:28:20 -0700 Subject: [PATCH 26/60] Added JSON network file I/O version-3, see #44. --- lib/gpi/network.py | 67 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 7 deletions(-) diff --git a/lib/gpi/network.py b/lib/gpi/network.py index f7e7de04..f9c56586 100644 --- a/lib/gpi/network.py +++ b/lib/gpi/network.py @@ -31,6 +31,7 @@ import os import re import sys +import json import time import pickle import traceback @@ -170,6 +171,48 @@ def convert_outgoing(self): # network['PLATFORM'] network['PLATFORM'] = Specs.table() +class Network_v3(Network_v2): + '''The third version of the network interface. This version supports + the json data file format for better multi-platform support. + ''' + def __init__(self, fname, contents=None): + super(Network_v3, self).__init__(fname, contents) + self._version = '3' + self._fname = fname # not actually needed since parent did the loading + self._contents = contents # a shortcut if the caller did the reading + # also used for storing network data to save + + def save(self): + # save network contents to a file + # file operations go here + + # convert to the right type + self.convert_outgoing() + + try: + fptr = open(self._fname, "w") + #pickle.dump(self._contents, fptr) + json.dump(self._contents, fptr, sort_keys=True, indent=1) + fptr.close() + log.dialog("Network saved.") + except: + log.error("Saving network failed.") + log.error(traceback.format_exc()) + + def load(self): + # load file contents into memory + # -since its a pickle file, this as already been done by the parent + # when checking the version. + # -file operations would go here + fptr = open(self._fname, "r") + self._contents = json.load(fptr) + + if self._contents is None: + # this shouldn't happen + log.error('parent didn\'t deserialize!') + return + + return self.convert_incoming() class Network_v1(Network_Base): '''The first version of the network interface compatible with the released @@ -309,16 +352,26 @@ def determine_version(self, fname): # the unpickled contents along with the version. In the future, if # pickle is no longer used, then the other format will be checked # first, then pickle as a backup. + + # TODO: there must be a better way to check then to keep adding + # nested try/except try: - fptr = open(fname, "rb") - contents = pickle.load(fptr, encoding="latin1") + # network v3 + fptr = open(fname, "r") + contents = json.load(fptr) fptr.close() except: - contents = None - log.error('Network file \''+str(fname)+'\' cannot be read. \n' + \ - '\tIt is either corrupted, permissions are not set, or it contains serialized objects that are version specific (OS or PyQt).') - log.error(traceback.format_exc()) - return 'UNREADABLE' + try: + # network v1 & 2 + fptr = open(fname, "rb") + contents = pickle.load(fptr, encoding="latin1") + fptr.close() + except: + contents = None + log.error('Network file \''+str(fname)+'\' cannot be read. \n' + \ + '\tIt is either corrupted, permissions are not set, or it contains serialized objects that are version specific (OS or PyQt).') + log.error(traceback.format_exc()) + return 'UNREADABLE' # check for dictionary structure if not isinstance(contents, dict): From e9caaaaefa9387f0c6227c66967bd56c5f3f4a70 Mon Sep 17 00:00:00 2001 From: nick Date: Wed, 23 Sep 2015 16:15:57 -0700 Subject: [PATCH 27/60] Added warning to bug caused by psutil API change. -this bug needs to be fixed, so this will nag --- lib/gpi/sysspecs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/gpi/sysspecs.py b/lib/gpi/sysspecs.py index de799ec2..f93ac7bd 100644 --- a/lib/gpi/sysspecs.py +++ b/lib/gpi/sysspecs.py @@ -61,6 +61,7 @@ def __init__(self): try: self._plat['TOTAL_PHYMEM'] = psutil.TOTAL_PHYMEM except: + log.warn("Couldn't get TOTAL_PHYMEM from psutil.") self._plat['TOTAL_PHYMEM'] = 0 self._plat['TOTAL_PHYMEM_STR'] = GetHumanReadable_bytes(self._plat['TOTAL_PHYMEM']) @@ -68,6 +69,7 @@ def __init__(self): try: self._plat['NUM_CPUS'] = psutil.NUM_CPUS except: + log.warn("Couldn't get NUM_CPUS from psutil.") self._plat['NUM_CPUS'] = 0 # process interface for THIS process From 12e8d58c5a475c6f9531738b8bc4ed8aa3d066d9 Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 24 Sep 2015 10:02:42 -0700 Subject: [PATCH 28/60] In the middle of adding a header to the Network_v3. --- lib/gpi/getversionstr.py | 24 ++++++++++++++++++++++++ lib/gpi/network.py | 31 ++++++++++++++++++++++--------- 2 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 lib/gpi/getversionstr.py diff --git a/lib/gpi/getversionstr.py b/lib/gpi/getversionstr.py new file mode 100644 index 00000000..a4bf3b01 --- /dev/null +++ b/lib/gpi/getversionstr.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +import re + +# "coding[:=]\s*([-\w.]+)" + +p = re.compile("(GPI|gpi)\s+v([\d.]+).*[Nn]et.*v([\d]+)") + +hdr = "# This comment was generated by GPI v0.6.0 for network v3" +hdr = "# This newtowrk was written by GPI v0.6.0-dev, network v3." +#hdr = "# This newtowrk was written by v0.6.0, network v3." +#hdr = "# This comment was generated by GPI v0.6.0 for Network v3" +#hdr = "# vim: set fileencoding=utf8 :" + + +m = p.search(hdr) + +if m: + try: + print('match: ', m.group(0)) + print('GPI version: ', m.group(2)) + print('Network version: ', m.group(3)) + except: + pass diff --git a/lib/gpi/network.py b/lib/gpi/network.py index f9c56586..58b7232a 100644 --- a/lib/gpi/network.py +++ b/lib/gpi/network.py @@ -175,6 +175,12 @@ class Network_v3(Network_v2): '''The third version of the network interface. This version supports the json data file format for better multi-platform support. ''' + + # TODO + # * UTF8 + # * header line "# This comment was generated by GPI v0.6.0 for network version 3" + # * find regex for this line. + def __init__(self, fname, contents=None): super(Network_v3, self).__init__(fname, contents) self._version = '3' @@ -182,6 +188,12 @@ def __init__(self, fname, contents=None): self._contents = contents # a shortcut if the caller did the reading # also used for storing network data to save + # write a simple header that can verify the network version directly + # without having to read the whole file. + self._header = "# This file was written with GPI v"+str(VERSION)+" using Network v"+str(self._version)+". Do not edit this line." + # validate the network version and potentially the GPI version + self._header_regex = "(GPI|gpi)\s+v([\d.]+).*[Nn]et.*v([\d]+)" + def save(self): # save network contents to a file # file operations go here @@ -190,10 +202,9 @@ def save(self): self.convert_outgoing() try: - fptr = open(self._fname, "w") - #pickle.dump(self._contents, fptr) - json.dump(self._contents, fptr, sort_keys=True, indent=1) - fptr.close() + with open(self._fname, "w", encoding='utf8') as fptr: + fptr.write(self._header) + json.dump(self._contents, fptr, sort_keys=True, indent=1) log.dialog("Network saved.") except: log.error("Saving network failed.") @@ -204,8 +215,10 @@ def load(self): # -since its a pickle file, this as already been done by the parent # when checking the version. # -file operations would go here - fptr = open(self._fname, "r") - self._contents = json.load(fptr) + with open(self._fname, "r", encoding='utf8') as fptr: + hdr = fptr.readline() + print("header: ", hdr) + self._contents = json.load(fptr) if self._contents is None: # this shouldn't happen @@ -357,9 +370,9 @@ def determine_version(self, fname): # nested try/except try: # network v3 - fptr = open(fname, "r") - contents = json.load(fptr) - fptr.close() + with open(fname, "r", encoding='utf8') as fptr: + hdr = fptr.readline() + contents = json.load(fptr) except: try: # network v1 & 2 From df1e65616769c0401b87c2e09faa34ecb5d75a77 Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 24 Sep 2015 18:23:10 -0700 Subject: [PATCH 29/60] Added a header to Netv3 and reorganized the net objects. -each object now has a separate test and are grabbed at --- lib/gpi/network.py | 321 +++++++++++++++++++++++---------------------- 1 file changed, 162 insertions(+), 159 deletions(-) diff --git a/lib/gpi/network.py b/lib/gpi/network.py index 58b7232a..9b5fd2c0 100644 --- a/lib/gpi/network.py +++ b/lib/gpi/network.py @@ -52,6 +52,8 @@ class Network_Base(object): '''All networks should implement this base class and be named with the 'Network_v' where 'version' is a str(integer). ''' + GPINET_VERSION='' + def __init__(self, fname, contents=None): # fname: filename for saving or loading # contents: an object containing network info compatible with the canvas' @@ -67,44 +69,57 @@ def save(self): # open a file for writing and write the network object contents to disk pass + def test(self): + # test to see if the network file is THIS version, return True if it is. + return False -class Network_v2(Network_Base): - '''The second version of the network interface. This version supports new - 2-level library scope which exists as an extra tag in the node settings dict. - The file format is pickled list() and dict() objects. The original + def version(self): + # return the version number as a str + return str(self.GPINET_VERSION) + +class Network_v1(Network_Base): + '''The first version of the network interface compatible with the released + GPI-beta. The file format is pickled list() and dict() objects. The original released header doesn't necessarily contain a version number so network files w/o are assumed to be this version. ''' + GPINET_VERSION='1' def __init__(self, fname, contents=None): - self._version = '2' self._fname = fname # not actually needed since parent did the loading self._contents = contents # a shortcut if the caller did the reading # also used for storing network data to save + def test(self): + # there is no way of verifying the first network version so it always + # returns false and is only invoked as a last resort. + # the best we can do is read in the whole file and check some fields + try: + with open(self._fname, "rb") as fptr: + contents = pickle.load(fptr, encoding="latin1") + except: + log.debug('Network_v1 test: '+str(traceback.format_exc())) + return False + + # check for dictionary structure + if not isinstance(contents, dict): + log.debug('Network_v1 test: Failure: contents are not of type dict.') + return False + return True + def load(self): # load file contents into memory - # -since its a pickle file, this as already been done by the parent - # when checking the version. - # -file operations would go here - if self._contents is None: - # this shouldn't happen - log.error('parent didnt unpickle!') - return - + with open(self._fname, "rb") as fptr: + self._contents = pickle.load(fptr, encoding="latin1") return self.convert_incoming() def save(self): # save network contents to a file - # file operations go here - # convert to the right type self.convert_outgoing() - try: - fptr = open(self._fname, "wb") - pickle.dump(self._contents, fptr) - fptr.close() + with open(self._fname, "wb") as fptr: + pickle.dump(self._contents, fptr) log.dialog("Network saved.") except: log.error("Saving network failed.") @@ -119,24 +134,22 @@ def convert_incoming(self): msg = 'Network file info:\n' if 'NETWORK_VERSION' in self._contents: msg += '\tnet-version: '+str(self._contents['NETWORK_VERSION']) + '\n' + else: + msg += '\tassumed net-version: '+str(self.version())+'\n' if 'GPI_VERSION' in self._contents: msg += '\tsaved with gpi-version: '+str(self._contents['GPI_VERSION']) + '\n' + log.warn(msg) - if 'DATETIME' in self._contents: - msg += '\tdate saved: '+str(self._contents['DATETIME']) + '\n' - - if 'WALLTIME' in self._contents: - msg += '\twall time: '+str(GetHumanReadable_time(float(self._contents['WALLTIME']))) + '\n' - - if 'TOTAL_PMEM' in self._contents: - msg += '\ttotal port mem: '+str(GetHumanReadable_bytes(int(self._contents['TOTAL_PMEM']))) + '\n' - - if 'PLATFORM' in self._contents: - for k,v in list(self._contents['PLATFORM'].items()): - msg += '\t'+k+': '+str(v)+'\n' - - log.dialog(msg) + # BACKWARD COMPATIBILITY + # add a dummy node key into all the node dicts + # -since the key is an empty string, the lookup will fall back to + # finding the best match + for node in self._contents['nodes']['nodes']: + node['key'] = '' + node['walltime'] = '0' + node['avgwalltime'] = '0' + node['stdwalltime'] = '0' return self._contents @@ -163,106 +176,57 @@ def convert_outgoing(self): # {'canvas': } # -size, shape (depends on display) #network['path'] = sys.path # no longer needed - network['NETWORK_VERSION'] = self._version + network['NETWORK_VERSION'] = self.version() network['GPI_VERSION'] = VERSION network['HEADER'] = 'This is a GPI Network File' - network['DATETIME'] = str(time.asctime(time.localtime())) - # network['PLATFORM'] - network['PLATFORM'] = Specs.table() - -class Network_v3(Network_v2): - '''The third version of the network interface. This version supports - the json data file format for better multi-platform support. +class Network_v2(Network_Base): + '''The second version of the network interface. This version supports new + 2-level library scope which exists as an extra tag in the node settings dict. + The file format is pickled list() and dict() objects. The original + released header doesn't necessarily contain a version number so network + files w/o are assumed to be this version. ''' - - # TODO - # * UTF8 - # * header line "# This comment was generated by GPI v0.6.0 for network version 3" - # * find regex for this line. + GPINET_VERSION='2' def __init__(self, fname, contents=None): - super(Network_v3, self).__init__(fname, contents) - self._version = '3' self._fname = fname # not actually needed since parent did the loading self._contents = contents # a shortcut if the caller did the reading # also used for storing network data to save - # write a simple header that can verify the network version directly - # without having to read the whole file. - self._header = "# This file was written with GPI v"+str(VERSION)+" using Network v"+str(self._version)+". Do not edit this line." - # validate the network version and potentially the GPI version - self._header_regex = "(GPI|gpi)\s+v([\d.]+).*[Nn]et.*v([\d]+)" - - def save(self): - # save network contents to a file - # file operations go here - - # convert to the right type - self.convert_outgoing() - + def test(self): + # the best we can do is read in the whole file and check some fields try: - with open(self._fname, "w", encoding='utf8') as fptr: - fptr.write(self._header) - json.dump(self._contents, fptr, sort_keys=True, indent=1) - log.dialog("Network saved.") + with open(self._fname, "rb") as fptr: + contents = pickle.load(fptr, encoding="latin1") except: - log.error("Saving network failed.") - log.error(traceback.format_exc()) + log.debug('Network_v2 test: '+str(traceback.format_exc())) + return False - def load(self): - # load file contents into memory - # -since its a pickle file, this as already been done by the parent - # when checking the version. - # -file operations would go here - with open(self._fname, "r", encoding='utf8') as fptr: - hdr = fptr.readline() - print("header: ", hdr) - self._contents = json.load(fptr) - - if self._contents is None: - # this shouldn't happen - log.error('parent didn\'t deserialize!') - return - - return self.convert_incoming() - -class Network_v1(Network_Base): - '''The first version of the network interface compatible with the released - GPI-beta. The file format is pickled list() and dict() objects. The original - released header doesn't necessarily contain a version number so network - files w/o are assumed to be this version. - ''' + # check for dictionary structure + if not isinstance(contents, dict): + log.debug('Network_v2 test: Failure: contents are not of type dict.') + return False - def __init__(self, fname, contents=None): - self._version = '1' - self._fname = fname # not actually needed since parent did the loading - self._contents = contents # a shortcut if the caller did the reading - # also used for storing network data to save + # check the version + if 'NETWORK_VERSION' in contents: + if str(contents['NETWORK_VERSION']) == self.version(): + return True + return False def load(self): # load file contents into memory - # -since its a pickle file, this as already been done by the parent - # when checking the version. - # -file operations would go here - if self._contents is None: - # this shouldn't happen - log.error('parent didnt unpickle!') - return - + with open(self._fname, "rb") as fptr: + self._contents = pickle.load(fptr, encoding="latin1") return self.convert_incoming() def save(self): # save network contents to a file - # file operations go here - # convert to the right type self.convert_outgoing() - try: - fptr = open(self._fname, "w") - pickle.dump(self._contents, fptr) - fptr.close() + with open(self._fname, "wb") as fptr: + pickle.dump(self._contents, fptr) log.dialog("Network saved.") except: log.error("Saving network failed.") @@ -277,22 +241,24 @@ def convert_incoming(self): msg = 'Network file info:\n' if 'NETWORK_VERSION' in self._contents: msg += '\tnet-version: '+str(self._contents['NETWORK_VERSION']) + '\n' - else: - msg += '\tassumed net-version: '+str(self._version)+'\n' if 'GPI_VERSION' in self._contents: msg += '\tsaved with gpi-version: '+str(self._contents['GPI_VERSION']) + '\n' - log.warn(msg) - # BACKWARD COMPATIBILITY - # add a dummy node key into all the node dicts - # -since the key is an empty string, the lookup will fall back to - # finding the best match - for node in self._contents['nodes']['nodes']: - node['key'] = '' - node['walltime'] = '0' - node['avgwalltime'] = '0' - node['stdwalltime'] = '0' + if 'DATETIME' in self._contents: + msg += '\tdate saved: '+str(self._contents['DATETIME']) + '\n' + + if 'WALLTIME' in self._contents: + msg += '\twall time: '+str(GetHumanReadable_time(float(self._contents['WALLTIME']))) + '\n' + + if 'TOTAL_PMEM' in self._contents: + msg += '\ttotal port mem: '+str(GetHumanReadable_bytes(int(self._contents['TOTAL_PMEM']))) + '\n' + + if 'PLATFORM' in self._contents: + for k,v in list(self._contents['PLATFORM'].items()): + msg += '\t'+k+': '+str(v)+'\n' + + log.dialog(msg) return self._contents @@ -319,9 +285,70 @@ def convert_outgoing(self): # {'canvas': } # -size, shape (depends on display) #network['path'] = sys.path # no longer needed - network['NETWORK_VERSION'] = self._version + network['NETWORK_VERSION'] = self.version() network['GPI_VERSION'] = VERSION network['HEADER'] = 'This is a GPI Network File' + network['DATETIME'] = str(time.asctime(time.localtime())) + + # network['PLATFORM'] + network['PLATFORM'] = Specs.table() + +class Network_v3(Network_v2): + '''The third version of the network interface. This version supports + the json data file format for better multi-platform support. + ''' + GPINET_VERSION='3' + + def __init__(self, fname, contents=None): + super(Network_v3, self).__init__(fname, contents) + self._fname = fname # not actually needed since parent did the loading + self._contents = contents # a shortcut if the caller did the reading + # also used for storing network data to save + + # write a simple header that can verify the network version directly + # without having to read the whole file. + self._header = "# This file was written with GPI v"+str(VERSION) + self._header += " using Network v"+str(self.version()) + self._header += ". Do not edit this line.\n" + + # validate the network version and potentially the GPI version + self._header_regex = "(GPI|gpi)\s+v([\d.]+).*[Nn]et.*v([\d]+)" + + def test(self): + try: + with open(self._fname, "r", encoding='utf8') as fptr: + # check the first line in the file + match = re.compile(self._header_regex).search(fptr.readline()) + if match: + if match.group(3) == self.version(): + # if the versions match, thats gold + return True + except: + log.debug('Network_v3 test: '+str(traceback.format_exc())) + return False + return False + + def save(self): + # save network contents to a file + # file operations go here + + # convert to the right type + self.convert_outgoing() + + try: + with open(self._fname, "w", encoding='utf8') as fptr: + fptr.write(self._header) + json.dump(self._contents, fptr, sort_keys=True, indent=1) + log.dialog("Network saved.") + except: + log.error("Saving network failed. "+str(traceback.format_exc())) + + def load(self): + # load network dict + with open(self._fname, "r", encoding='utf8') as fptr: + fptr.readline() # header + self._contents = json.load(fptr) + return self.convert_incoming() ######################################################################## @@ -359,6 +386,15 @@ def getLatestClass(self): self._latest_net_version = max(versions) self._latest_net_class = getattr(thismodule, 'Network_v'+self._latest_net_version) + def netDesc(self): + # all network objects that derive from Network_Base() + l = [] + for mnam in dir(sys.modules[__name__]): + mod = getattr(sys.modules[__name__], mnam) + if hasattr(mod, 'GPINET_VERSION'): + l.append(mod) + return l + def determine_version(self, fname): # check the file format for version and format info # -loading is the only way to check if its pickled, so just return @@ -366,43 +402,10 @@ def determine_version(self, fname): # pickle is no longer used, then the other format will be checked # first, then pickle as a backup. - # TODO: there must be a better way to check then to keep adding - # nested try/except - try: - # network v3 - with open(fname, "r", encoding='utf8') as fptr: - hdr = fptr.readline() - contents = json.load(fptr) - except: - try: - # network v1 & 2 - fptr = open(fname, "rb") - contents = pickle.load(fptr, encoding="latin1") - fptr.close() - except: - contents = None - log.error('Network file \''+str(fname)+'\' cannot be read. \n' + \ - '\tIt is either corrupted, permissions are not set, or it contains serialized objects that are version specific (OS or PyQt).') - log.error(traceback.format_exc()) - return 'UNREADABLE' - - # check for dictionary structure - if not isinstance(contents, dict): - log.error("No description dictionary found. This is probably not a GPI network file.") - return 'UNREADABLE' - - # check the version - if 'NETWORK_VERSION' in contents: - version = contents['NETWORK_VERSION'] - else: - # assume pre-version compat - version = '1' - - # This is a shortcut for the pickling format since the only test is to - # actually read all the data in. The footprint for this function should - # be to return the version only. Future formats will probably have a - # header section that is easier to read as a separate step. - self._unpickled_contents = contents + for obj in self.netDesc(): + n = obj(fname) + if n.test(): + version = n.version() if version != self._latest_net_version: log.warn('This network was saved in an older format, please re-save this network.') From a18ee2ce519d171d1728455e499271925568777e Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 24 Sep 2015 18:32:23 -0700 Subject: [PATCH 30/60] Added header to Netv3. -reorganized network objects and refined their abstract class a little more. --- lib/gpi/network.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/gpi/network.py b/lib/gpi/network.py index 9b5fd2c0..a81dc20c 100644 --- a/lib/gpi/network.py +++ b/lib/gpi/network.py @@ -77,6 +77,10 @@ def version(self): # return the version number as a str return str(self.GPINET_VERSION) + def __str__(self): + # for sorting + return self.version() + class Network_v1(Network_Base): '''The first version of the network interface compatible with the released GPI-beta. The file format is pickled list() and dict() objects. The original @@ -393,6 +397,7 @@ def netDesc(self): mod = getattr(sys.modules[__name__], mnam) if hasattr(mod, 'GPINET_VERSION'): l.append(mod) + l = sorted(l, key=str, reverse=True) # try highest version first return l def determine_version(self, fname): @@ -406,6 +411,7 @@ def determine_version(self, fname): n = obj(fname) if n.test(): version = n.version() + break if version != self._latest_net_version: log.warn('This network was saved in an older format, please re-save this network.') From 29b865dc7d3bc5df8873c143a29df92a34600947 Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 24 Sep 2015 19:02:08 -0700 Subject: [PATCH 31/60] Test file is no longer needed. --- lib/gpi/getversionstr.py | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 lib/gpi/getversionstr.py diff --git a/lib/gpi/getversionstr.py b/lib/gpi/getversionstr.py deleted file mode 100644 index a4bf3b01..00000000 --- a/lib/gpi/getversionstr.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python - -import re - -# "coding[:=]\s*([-\w.]+)" - -p = re.compile("(GPI|gpi)\s+v([\d.]+).*[Nn]et.*v([\d]+)") - -hdr = "# This comment was generated by GPI v0.6.0 for network v3" -hdr = "# This newtowrk was written by GPI v0.6.0-dev, network v3." -#hdr = "# This newtowrk was written by v0.6.0, network v3." -#hdr = "# This comment was generated by GPI v0.6.0 for Network v3" -#hdr = "# vim: set fileencoding=utf8 :" - - -m = p.search(hdr) - -if m: - try: - print('match: ', m.group(0)) - print('GPI version: ', m.group(2)) - print('Network version: ', m.group(3)) - except: - pass From 3cc7f363d0dd7fa1cd8ede32df2f7c9b141d0855 Mon Sep 17 00:00:00 2001 From: ash Date: Fri, 25 Sep 2015 15:06:36 -0700 Subject: [PATCH 32/60] Fix py3k division errors preventing me from getting my reduce on --- lib/gpi/widgets.py | 58 +++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/lib/gpi/widgets.py b/lib/gpi/widgets.py index 398dfd91..ac4b35cc 100644 --- a/lib/gpi/widgets.py +++ b/lib/gpi/widgets.py @@ -113,7 +113,7 @@ def __init__(self, parent=None): self._last_val = self.value() self._valChanged = False self._focusOutEvent = False - + def focusOutEvent(self, event): # keep track of this event as a focusOutEvent for downstream @@ -264,7 +264,7 @@ def __init__(self, parent=None): self._last_val = self.value() self._valChanged = False self._focusOutEvent = False - + def focusOutEvent(self, event): # keep track of this event as a focusOutEvent for downstream @@ -501,7 +501,7 @@ def __init__(self, parent=None): def checkCWbounds(self, c, w): ci = c wi = w - w2 = w/2 + w2 = w//2 mx = self.scenter.get_max() mn = self.scenter.get_min() ct = (c+w2-(not w % 2)) @@ -565,11 +565,11 @@ def ceilChanged(self, c): self.valueChanged.emit() def cwChanged(self, c, w): - self.sfloor.set_val(c-w/2) - self.sceil.set_val(c+w/2-(not w % 2)) + self.sfloor.set_val(c-w//2) + self.sceil.set_val(c+w//2-(not w % 2)) def fcChanged(self, f, c): - self.scenter.set_val((c-f)/2+f) + self.scenter.set_val((c-f)//2+f) self.swidth.set_val(c-f+1) def blockSliderSignals(self, val): @@ -709,12 +709,12 @@ def _listMediaDirs(self): return [] def applyFilterToPath(self, fname): - # Given a QFileDialog filter string, make sure the given path adheres + # Given a QFileDialog filter string, make sure the given path adheres # to the filter and return the filtered path string. flt = str(self.selectedFilter()) # Enforce the selected filter in the captured filename - # filters are strings with content of the type: + # filters are strings with content of the type: # 'Images (*.png *.xpm *.jpg);;Text files (*.txt);;XML files (*.xml)' par = ''.join(re.findall('\([^()]*\)', str(flt))) # just take whats in parens suf = ' '.join(re.split('[()]', par)) # split out parens @@ -729,7 +729,7 @@ def applyFilterToPath(self, fname): # take the first suffix if the filename doesn't match any in the list # append to fname (as opposed to basename) to allow the user to include # dots in the filename. - return fname+suf[0] + return fname+suf[0] def runSaveFileDialog(self): self.setAcceptMode(QtGui.QFileDialog.AcceptSave) @@ -1385,7 +1385,7 @@ def paintEvent(self, event): def drawPoint(self, event): if self._line_p1: - s = self._wg._scaleFact + s = self._wg._scaleFact x = ("%.0f" % (self._line_p1.x()/s)) y = ("%.0f" % (self._line_p1.y()/s)) @@ -1407,11 +1407,11 @@ def drawPoint(self, event): # buf p1 adj_x = 2 - if self._line_p1.x() > self._wg.imageLabel.size().width()/2: + if self._line_p1.x() > self._wg.imageLabel.size().width()//2: adj_x = -bw_p1 -2 adj_y = -2 - if self._line_p1.y() < self._wg.imageLabel.size().height()/2: + if self._line_p1.y() < self._wg.imageLabel.size().height()//2: adj_y = bh +2 p.drawText(self._line_p1 + QtCore.QPoint(adj_x, adj_y), buf_p1) @@ -1454,22 +1454,22 @@ def drawLine(self, event): # buf p1 adj_x = 2 - if self._line_p1.x() > self._wg.imageLabel.size().width()/2: + if self._line_p1.x() > self._wg.imageLabel.size().width()//2: adj_x = -bw_p1 -2 adj_y = -2 - if self._line_p1.y() < self._wg.imageLabel.size().height()/2: + if self._line_p1.y() < self._wg.imageLabel.size().height()//2: adj_y = bh +2 p.drawText(self._line_p1 + QtCore.QPoint(adj_x, adj_y), buf_p1) # buf p2 adj_x = 2 - if self._line_p2.x() > self._wg.imageLabel.size().width()/2: + if self._line_p2.x() > self._wg.imageLabel.size().width()//2: adj_x = -bw_p2 -2 adj_y = -2 - if self._line_p2.y() < self._wg.imageLabel.size().height()/2: + if self._line_p2.y() < self._wg.imageLabel.size().height()//2: adj_y = bh +2 p.drawText(self._line_p2 + QtCore.QPoint(adj_x, adj_y), buf_p2) @@ -1512,22 +1512,22 @@ def drawRectangle(self, event): # buf p1 adj_x = 2 - if self._line_p1.x() > self._wg.imageLabel.size().width()/2: + if self._line_p1.x() > self._wg.imageLabel.size().width()//2: adj_x = -bw_p1 -2 adj_y = -2 - if self._line_p1.y() < self._wg.imageLabel.size().height()/2: + if self._line_p1.y() < self._wg.imageLabel.size().height()//2: adj_y = bh +2 p.drawText(self._line_p1 + QtCore.QPoint(adj_x, adj_y), buf_p1) # buf p2 adj_x = 2 - if self._line_p2.x() > self._wg.imageLabel.size().width()/2: + if self._line_p2.x() > self._wg.imageLabel.size().width()//2: adj_x = -bw_p2 -2 adj_y = -2 - if self._line_p2.y() < self._wg.imageLabel.size().height()/2: + if self._line_p2.y() < self._wg.imageLabel.size().height()//2: adj_y = bh +2 p.drawText(self._line_p2 + QtCore.QPoint(adj_x, adj_y), buf_p2) @@ -1570,22 +1570,22 @@ def drawEllipse(self, event): # buf p1 adj_x = 2 - if self._line_p1.x() > self._wg.imageLabel.size().width()/2: + if self._line_p1.x() > self._wg.imageLabel.size().width()//2: adj_x = -bw_p1 -2 adj_y = -2 - if self._line_p1.y() < self._wg.imageLabel.size().height()/2: + if self._line_p1.y() < self._wg.imageLabel.size().height()//2: adj_y = bh +2 p.drawText(self._line_p1 + QtCore.QPoint(adj_x, adj_y), buf_p1) # buf p2 adj_x = 2 - if self._line_p2.x() > self._wg.imageLabel.size().width()/2: + if self._line_p2.x() > self._wg.imageLabel.size().width()//2: adj_x = -bw_p2 -2 adj_y = -2 - if self._line_p2.y() < self._wg.imageLabel.size().height()/2: + if self._line_p2.y() < self._wg.imageLabel.size().height()//2: adj_y = bh +2 p.drawText(self._line_p2 + QtCore.QPoint(adj_x, adj_y), buf_p2) @@ -1754,7 +1754,7 @@ def __init__(self, title, parent=None): def savetopng(self): - if self._pixmap is None: + if self._pixmap is None: log.warn('DisplayBox: There is no image to save, skipping.') return @@ -1886,7 +1886,7 @@ def get_points(self): def somethingChanged(self): '''send a downstream event.''' self.valueChanged.emit(0) - + def setImageScale(self, scale): self._scaleFact = scale if self._pixmap is not None: @@ -1908,7 +1908,7 @@ def applyImageScale(self): def adjustScrollBar(self, scrollBar, factor): scrollBar.setValue(int(factor * scrollBar.value() - + ((factor - 1) * scrollBar.pageStep()/2))) + + ((factor - 1) * scrollBar.pageStep()//2))) def fitMinWindowSize(self): labsize = self.imageLabel.size() @@ -2519,11 +2519,11 @@ def set_index(self, index): # getters def get_items(self): - items = [] + items = [] nr_items = self.wdg.count() for index in range(nr_items): items.append(str(self.wdg.itemText(index))) - return items + return items def get_val(self): return str(self.wdg.currentText()) From f96fee445f457221dea0b2fe82d9962c88f1ce45 Mon Sep 17 00:00:00 2001 From: nick Date: Sat, 26 Sep 2015 10:39:01 -0700 Subject: [PATCH 33/60] Fixed getting phy-mem and cpu-total from psutil (IF change). --- lib/gpi/sysspecs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gpi/sysspecs.py b/lib/gpi/sysspecs.py index f93ac7bd..5517217c 100644 --- a/lib/gpi/sysspecs.py +++ b/lib/gpi/sysspecs.py @@ -59,7 +59,7 @@ def __init__(self): # not sure what the default behavior is for psutil try: - self._plat['TOTAL_PHYMEM'] = psutil.TOTAL_PHYMEM + self._plat['TOTAL_PHYMEM'] = psutil.virtual_memory().total except: log.warn("Couldn't get TOTAL_PHYMEM from psutil.") self._plat['TOTAL_PHYMEM'] = 0 @@ -67,7 +67,7 @@ def __init__(self): self._plat['TOTAL_PHYMEM_STR'] = GetHumanReadable_bytes(self._plat['TOTAL_PHYMEM']) try: - self._plat['NUM_CPUS'] = psutil.NUM_CPUS + self._plat['NUM_CPUS'] = psutil.cpu_count() except: log.warn("Couldn't get NUM_CPUS from psutil.") self._plat['NUM_CPUS'] = 0 From 309aeddbb6faac3095ee7982d08b231035556cb6 Mon Sep 17 00:00:00 2001 From: ash Date: Wed, 30 Sep 2015 16:20:42 -0700 Subject: [PATCH 34/60] Add helper functions get_start and get_stop that provide chunking indices for threading --- include/multiproc/threads.c | 62 +++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/include/multiproc/threads.c b/include/multiproc/threads.c index 2bf615c1..2701221d 100644 --- a/include/multiproc/threads.c +++ b/include/multiproc/threads.c @@ -28,7 +28,7 @@ \file threads.c \author Ken Johnson \date created: 2007.06.07 - + \brief This library simplifies the implementation of threaded programing. This library simplifies threading by creating wrapper functions that complete the @@ -75,8 +75,8 @@ the threads are completed. \code void add_thread (int *num_threads, int *cur_thread, a, b, out) { - int start = *cur_thread * getsize(a)/ *num_threads; - int stop = (*cur_thread+1) * getsize(a)/ *num_threads; + int start = get_start(*num_threads, *cur_thread, getsize(a)); + int stop = get_stop(*num_threads, *cur_thread, getsize(a)); for (int i=start; i\= 0); assert (num_params <= THREADS_MAX_NUM_PARAMS); assert (sizeof(long) == sizeof(void *)); - + // this array is to hold the thread ids pthread_t *threads_id = NULL; threads_id = (pthread_t *) malloc (num_threads * sizeof(pthread_t) ); @@ -674,7 +674,7 @@ int create_threads (int num_threads, void (*func)(), long num_params, ...) { assert (rc == 0); params[t] = NULL; // don't worry thread_wrapper will delete the array } - + // wait for threads to finish (ie join) for(int t=0; t num_jobs) return -1; + + int elements_per_chunk = num_jobs / num_threads; + int extra_jobs = num_jobs % num_threads; + uint64_t start = elements_per_chunk * cur_thread; + if(cur_thread < extra_jobs) + { + start += cur_thread; + } else + { + start += extra_jobs; + } + return start; +} + +/** + Helper function to divide the number of jobs as evenly as possible among + worker threads. + + get_start() and get_stop() can be used to conveniently get the start and + stop indices for looping over jobs within a given thread. +**/ +uint64_t get_stop(int num_threads, int cur_thread, int num_jobs) { + if(cur_thread > num_jobs) return -1; + + int elements_per_chunk = num_jobs / num_threads; + int extra_jobs = num_jobs % num_threads; + uint64_t stop = get_start(num_threads, cur_thread, num_jobs); + stop += elements_per_chunk; + if(cur_thread < extra_jobs) + { + stop++; + } + return stop; +} + /** Print the current progress and ETA of the threaded function. From 7c0aaa3f0b4e3d623c69f2f0f93a050ef702762a Mon Sep 17 00:00:00 2001 From: ash Date: Fri, 2 Oct 2015 11:38:24 -0700 Subject: [PATCH 35/60] =?UTF-8?q?Fix=20unicode=20chars=20(=C2=B5=20and=20?= =?UTF-8?q?=CF=83)=20not=20showing=20properly=20in=20canvas=20tooltip.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/gpi/node.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/gpi/node.py b/lib/gpi/node.py index 876ad64c..03ad9507 100644 --- a/lib/gpi/node.py +++ b/lib/gpi/node.py @@ -1003,7 +1003,7 @@ def portMem(self): # numpy arrays have a direct byte count if hasattr(port.data, 'nbytes'): - bytes_held += port.data.nbytes + bytes_held += port.data.nbytes # try to get an estimate of the object w/ sys else: @@ -1030,8 +1030,8 @@ def updateToolTip(self): # NODE if len(self._computeDuration): avg = self.avgWallTime() std = self.stdWallTime() - tip += '\\u03BC = ' + GetHumanReadable_time(avg) - tip += ', \\u03C3 = ' + GetHumanReadable_time(std) + tip += '\u03BC = ' + GetHumanReadable_time(avg) + tip += ', \u03C3 = ' + GetHumanReadable_time(std) tip += ', n = ' + str(len(self._computeDuration)) + '\n' tip += 'Outport Mem: ' + GetHumanReadable_bytes( @@ -1367,7 +1367,7 @@ def getDetailLabelSize(self): self._nodeIF.getDetailLabelElideMode(), tw * 3) bw = fm.width(el_buf) + self._detailLabel_inset + self._right_margin - bh = fm.height() + bh = fm.height() return (bw, bh) def getMaxPortWidth(self): @@ -1390,7 +1390,7 @@ def getProgressWidth(self): w = 23 conf = self.getCurState() if (self._computeState is conf) and self.progressON(): - return w + return w elif self._progress_done.isActive() and self._progress_was_on: return w return 0 @@ -1552,11 +1552,11 @@ def drawRecalculating(self, painter): # arcs 1 rect = QtCore.QRectF(-8.0+self.getNodeWidth(), -10, 10.0, 10.0) - startAngle = (( 0 - self._progress_recalculate) % 360) * 16 + startAngle = (( 0 - self._progress_recalculate) % 360) * 16 spanAngle = 90 * 16 painter.setPen(QtGui.QPen(fade, 2.0, QtCore.Qt.SolidLine, QtCore.Qt.SquareCap, QtCore.Qt.RoundJoin)) painter.drawArc(rect, startAngle, spanAngle) - startAngle = ((180 - self._progress_recalculate) % 360) * 16 + startAngle = ((180 - self._progress_recalculate) % 360) * 16 spanAngle = 90 * 16 painter.drawArc(rect, startAngle, spanAngle) @@ -1567,11 +1567,11 @@ def drawRecalculating(self, painter): painter.setPen(QtGui.QPen(QtCore.Qt.red, 0.25)) fugde = 2 rect = QtCore.QRectF(-8.0+self.getNodeWidth()+fugde, -10+fugde, 20.0-fugde*2, 20.0-fugde*2) - startAngle = (( 0 - self._progress_recalculate2) % 360) * 16 + startAngle = (( 0 - self._progress_recalculate2) % 360) * 16 spanAngle = 90 * 16 painter.drawArc(rect, startAngle, spanAngle) - startAngle = ((180 - self._progress_recalculate2) % 360) * 16 + startAngle = ((180 - self._progress_recalculate2) % 360) * 16 spanAngle = 90 * 16 painter.drawArc(rect, startAngle, spanAngle) @@ -1582,11 +1582,11 @@ def drawRecalculating(self, painter): painter.setPen(QtGui.QPen(QtCore.Qt.darkGreen, 0.25)) fugde = 5 rect = QtCore.QRectF(-8.0+self.getNodeWidth()+fugde, -10+fugde, 20.0-fugde*2, 20.0-fugde*2) - startAngle = (( 0 - self._progress_recalculate3) % 360) * 16 + startAngle = (( 0 - self._progress_recalculate3) % 360) * 16 spanAngle = 90 * 16 painter.drawArc(rect, startAngle, spanAngle) - startAngle = ((180 - self._progress_recalculate3) % 360) * 16 + startAngle = ((180 - self._progress_recalculate3) % 360) * 16 spanAngle = 90 * 16 painter.drawArc(rect, startAngle, spanAngle) From e51f6ba1ce3acf3eed18ca331d381b33c5550a2e Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 2 Oct 2015 13:22:32 -0700 Subject: [PATCH 36/60] fixed shebang bug when launching from linux --- bin/gpi_launch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/gpi_launch b/bin/gpi_launch index cd1175e4..38425c16 100755 --- a/bin/gpi_launch +++ b/bin/gpi_launch @@ -1,4 +1,4 @@ -#!/usr/bin/env python # DEV +#!/usr/bin/env python # Copyright (C) 2014 Dignity Health # From 7ebf2234bba70693cd6760eb05bd4aad5a284d76 Mon Sep 17 00:00:00 2001 From: ash Date: Fri, 2 Oct 2015 17:00:44 -0700 Subject: [PATCH 37/60] Change pickle to json when checking if getVal gives you something szzzzzzzzble. --- lib/gpi/widgets.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/gpi/widgets.py b/lib/gpi/widgets.py index ac4b35cc..b15e731b 100644 --- a/lib/gpi/widgets.py +++ b/lib/gpi/widgets.py @@ -26,7 +26,7 @@ import os import re import math -import pickle +import json # gpi import gpi @@ -962,7 +962,8 @@ def getSettings(self): # WIDGET SETTINGS # do 'val' separately since it might fail try: # this fails if the object is not picklable - pickle.dumps(self.get_val()) + # pickle.dumps(self.get_val()) + json.dumps(self.get_val()) kwargs['val'] = self.get_val() except: kwargs['val'] = self.defaultValue() From 87ce0bdf784f0afefb864505ce63e615beb8151b Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 22 Oct 2015 10:57:25 -0700 Subject: [PATCH 38/60] Fixed error state to allow rerun. --- lib/gpi/node.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/gpi/node.py b/lib/gpi/node.py index 03ad9507..1088ba97 100644 --- a/lib/gpi/node.py +++ b/lib/gpi/node.py @@ -500,8 +500,8 @@ def initStateMachine(self): # NODE self._warningState.addTransition('check', self._chkInPortsState) self._warningState.addTransition('disable', self._disabledState) - # error - #self._errorState.addTransition('check', self._chkInPortsState) + # error - (unhandled or exceptions) + self._errorState.addTransition('check', self._chkInPortsState) self._errorState.addTransition('disable', self._disabledState) # disabled @@ -514,12 +514,9 @@ def start(self): # NODE elif self.inWarningState(): log.debug("NODE(" + self.name + "): FROM WARNING STATE: emit switchSig") self._switchSig.emit('check') # get out of warning - - # should we allow moving from error state??? - #elif self.inErrorState(): - # log.debug("NODE(" + self.name + "): FROM ERROR STATE: emit switchSig") - # self._switchSig.emit('check') # get out of error - + elif self.inErrorState(): + log.debug("NODE(" + self.name + "): FROM ERROR STATE: emit switchSig") + self._switchSig.emit('check') # get out of error else: log.error("NODE(" + self.name + "): Can't start node outside of idleState, skipping...") log.error(" ->current state: " + str(self.getCurStateName())) @@ -532,10 +529,12 @@ def idleRun(self, sig): self.debounceUISignals(sig) def debounceUISignals(self, sig): - if sig == 'finished' or sig == 'ignore' or sig == 'warning': # from post_compute or failed check - # before allowing new UI signals to be processed, require that the last signal - # was succesfully processed. -This significantly cuts down the amount of wdgEvents() emitted - # and prevents recursion limit errors. + if sig == 'finished' or sig == 'ignore' or sig == 'warning' or sig == 'error': + # from post_compute or failed check before allowing new UI signals + # to be processed, require that the last signal was succesfully + # processed. -This significantly cuts down the amount of + # wdgEvents() emitted and prevents recursion limit errors. + self._nodeIF.blockWdgSignals(False) # allow new UI signals to trigger # re-enter processing state don't go to processing if change is # from 'disabled' or 'init' states @@ -603,7 +602,9 @@ def post_computeRun(self, sig): # 0: SUCCESS # >0: WARNING -> Yellow # <0: FAILURE -> Red - if retcode < 0 or None: + if retcode is None: + self._switchSig.emit('error') + elif retcode < 0: self._switchSig.emit('error') elif retcode > 0: self._switchSig.emit('warning') @@ -630,6 +631,7 @@ def errorRun(self, sig): self.graph._switchSig.emit('pause') # move canvas to a paused state to let users fix the problem self._curState.emit('Error ('+str(sig)+')') self.forceUpdate_NodeUI() + self.debounceUISignals(sig) def warningRun(self, sig): self.printCurState() From 448596ae5650f327abe99a66bbfeb900e24407a3 Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 22 Oct 2015 13:13:37 -0700 Subject: [PATCH 39/60] Changed warning state to be used for validate-error state. --- lib/gpi/functor.py | 41 +++++++++++++++++++++-------------------- lib/gpi/node.py | 15 ++++++++++----- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/lib/gpi/functor.py b/lib/gpi/functor.py index 7f6e230f..c77987e1 100644 --- a/lib/gpi/functor.py +++ b/lib/gpi/functor.py @@ -54,7 +54,7 @@ def __init__(self, func): # A template API for each execution type. class GPIFunctor(QtCore.QObject): - finished = gpi.Signal() + finished = gpi.Signal(int) terminated = gpi.Signal() applyQueuedData_finished = gpi.Signal() _setData_finished = gpi.Signal() @@ -140,23 +140,24 @@ def curTime(self): def start(self): self._compute_start = time.time() + # VALIDATE # temporarily trick all widget calls to use GPI_APPLOOP for validate() tmp_exec = self._execType self._execType = GPI_APPLOOP - self._validate_retcode = self._validate() + try: + self._validate_retcode = self._validate() + except: + self._validate_retcode = 1 # validate error self._execType = tmp_exec # send validate() return code thru same channels - if self._validate_retcode is None: - self._validate_retcode = 0 - if self._validate_retcode < 0: + if self._validate_retcode != 0: log.error("start(): validate() failed.") self._node.appendWallTime(time.time() - self._compute_start) - self.finished.emit() - return - elif self._validate_retcode > 0: - log.warn("start(): validate() finished with a warning.") + self.finished.emit(1) # validate error + return + # COMPUTE if self._execType == GPI_PROCESS: log.debug("start(): buffer process parms") self._node._nodeIF.bufferParmSettings() @@ -194,18 +195,18 @@ def computeFinished(self): else: self._retcode = self._proc._retcode - if self._retcode == 0: - self._retcode = self._validate_retcode + #if self._retcode == 0: + # self._retcode = self._validate_retcode self.finalMatter() def applyQueuedData_Failed(self): log.critical("applyQueuedData_Failed():Node \'"+str(self._title)+"\'") - self.finished.emit() + self.finished.emit(-1) # compute error def finalMatter(self): - log.debug("computeFinished():Node \'"+str(self._title)+"\': compute time:"+str(time.time() - self._compute_start)+" sec.") + log.info("computeFinished():Node \'"+str(self._title)+"\': compute time:"+str(time.time() - self._compute_start)+" sec.") self._node.appendWallTime(time.time() - self._compute_start) - self.finished.emit() + self.finished.emit(self._retcode) # success def applyQueuedData_setData(self): @@ -237,7 +238,7 @@ def applyQueuedData_setData(self): except: log.error("applyQueuedData() failed. "+str(traceback.format_exc())) #raise - self._retcode = -1 + self._retcode = -1 # compute error self._setData_finished.emit() # Assemble Segmented Data @@ -287,8 +288,8 @@ def applyQueuedData(self): log.debug("applyQueuedData(): apply object "+str(o[0])+', '+str(o[1]) ) if o[0] == 'retcode': self._retcode = o[1] - if self._retcode == 0: - self._retcode = self._validate_retcode # validate is stored locally + #if self._retcode == 0: + # self._retcode = self._validate_retcode # validate is stored locally if o[0] == 'modifyWdg': self._node.modifyWdg(o[1], o[2]) if o[0] == 'setReQueue': @@ -304,7 +305,7 @@ def applyQueuedData(self): except: log.error("applyQueuedData() failed. "+str(traceback.format_exc())) #raise - self._retcode = -1 + self._retcode = -1 # compute error # transfer all setData() calls to a thread log.debug("applyQueuedData(): run _applyData_thread") @@ -420,7 +421,7 @@ def run(self): except: log.error('THREAD: \''+str(self._title)+'\':\''+str(self._label)+'\' compute() failed.\n'+str(traceback.format_exc())) #raise - self._retcode = -1 + self._retcode = -1 # compute error # The apploop-type blocks until finished, obviating the need for signals # or timer checks @@ -446,7 +447,7 @@ def run(self): except: log.error('APPLOOP: \''+str(self._title)+'\':\''+str(self._label)+'\' compute() failed.\n'+str(traceback.format_exc())) #raise - self._retcode = -1 + self._retcode = -1 # compute error def terminate(self): pass # can't happen b/c blocking mainloop diff --git a/lib/gpi/node.py b/lib/gpi/node.py index 1088ba97..95f80f0f 100644 --- a/lib/gpi/node.py +++ b/lib/gpi/node.py @@ -312,6 +312,7 @@ def __init__(self, CanvasBackend, nodeCatItem=None, nodeIF=None, nodeIFscroll=No self._node_disabled = False self._requeue = False self._markedForDeletion = False + self._returnCode = None # node name self.NodeLook = NodeAppearance() @@ -558,7 +559,9 @@ def chkInPortsRun(self, sig): self._switchSig.emit('error') # computeRun() support - def nextSigEmit(self): + def nextSigEmit(self, arg=None): + # get the retcode value from the runtime code + self._returnCode = arg self._switchSig.emit('next') def errorSigEmit(self): @@ -597,12 +600,13 @@ def post_computeRun(self, sig): self.updateToolTips() # for ports self.updateToolTip() # for node - retcode = self.nodeCompute_thread.returnCode() + #retcode = self.nodeCompute_thread.returnCode() + retcode = self._returnCode # retcode: None: Terminated # 0: SUCCESS - # >0: WARNING -> Yellow - # <0: FAILURE -> Red - if retcode is None: + # >0: VALIDATE ERROR -> Yellow + # <0: COMPUTE ERROR -> Red + if retcode is None: # assume compute error since validate will return self._switchSig.emit('error') elif retcode < 0: self._switchSig.emit('error') @@ -635,6 +639,7 @@ def errorRun(self, sig): def warningRun(self, sig): self.printCurState() + self.graph._switchSig.emit('pause') # move canvas to a paused state to let users fix the problem self._curState.emit('Warning ('+str(sig)+')') self.forceUpdate_NodeUI() self.debounceUISignals(sig) From aa4f320f92ce78613c8e294451bc97ea0e330916 Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 22 Oct 2015 15:37:44 -0700 Subject: [PATCH 40/60] Fixed retcode return from processes. See #35 -also changed a bunch of names from error to computeError and warning to validateError --- lib/gpi/functor.py | 19 +++++----- lib/gpi/macroNode.py | 24 ++++++------- lib/gpi/node.py | 83 ++++++++++++++++++++++---------------------- 3 files changed, 62 insertions(+), 64 deletions(-) diff --git a/lib/gpi/functor.py b/lib/gpi/functor.py index c77987e1..c83af43b 100644 --- a/lib/gpi/functor.py +++ b/lib/gpi/functor.py @@ -152,7 +152,6 @@ def start(self): # send validate() return code thru same channels if self._validate_retcode != 0: - log.error("start(): validate() failed.") self._node.appendWallTime(time.time() - self._compute_start) self.finished.emit(1) # validate error return @@ -194,15 +193,11 @@ def computeFinished(self): self.applyQueuedData() else: - self._retcode = self._proc._retcode - #if self._retcode == 0: - # self._retcode = self._validate_retcode + self._retcode = 0 # success + if self._proc._retcode != 0: + self._retcode = -1 # compute error self.finalMatter() - def applyQueuedData_Failed(self): - log.critical("applyQueuedData_Failed():Node \'"+str(self._title)+"\'") - self.finished.emit(-1) # compute error - def finalMatter(self): log.info("computeFinished():Node \'"+str(self._title)+"\': compute time:"+str(time.time() - self._compute_start)+" sec.") self._node.appendWallTime(time.time() - self._compute_start) @@ -268,6 +263,7 @@ def applyQueuedData_setData(self): self._node.setData(port, buf) + # run self.applyQueuedData_finalMatter() self._setData_finished.emit() def applyQueuedData(self): @@ -288,8 +284,8 @@ def applyQueuedData(self): log.debug("applyQueuedData(): apply object "+str(o[0])+', '+str(o[1]) ) if o[0] == 'retcode': self._retcode = o[1] - #if self._retcode == 0: - # self._retcode = self._validate_retcode # validate is stored locally + if self._retcode != 0: + self._retcode = -1 # compute error if o[0] == 'modifyWdg': self._node.modifyWdg(o[1], o[2]) if o[0] == 'setReQueue': @@ -314,7 +310,7 @@ def applyQueuedData(self): def applyQueuedData_finalMatter(self): if self._retcode == -1: - self.applyQueuedData_Failed() + self.finished.emit(-1) # compute error elapsed = (time.time() - self._ap_st_time) log.info("applyQueuedData(): time (total queue): "+str(elapsed)+" sec") @@ -322,6 +318,7 @@ def applyQueuedData_finalMatter(self): # shutdown the proxy manager self.cleanup() + # start self.finalMatter self.applyQueuedData_finished.emit() diff --git a/lib/gpi/macroNode.py b/lib/gpi/macroNode.py index 3a7f5668..dfe7ef91 100644 --- a/lib/gpi/macroNode.py +++ b/lib/gpi/macroNode.py @@ -264,10 +264,10 @@ def paint(self, painter, option, widget): # NODE if self._macroParent.isProcessing(): gradient.setColorAt(0, QtGui.QColor(QtCore.Qt.gray).lighter(70)) gradient.setColorAt(1, QtGui.QColor(QtCore.Qt.darkGray).lighter(70)) - elif (option.state & QtGui.QStyle.State_Sunken) or (self._macroParent.inErrorState()): + elif (option.state & QtGui.QStyle.State_Sunken) or (self._macroParent.inComputeErrorState()): gradient.setColorAt(0, QtGui.QColor(QtCore.Qt.red).lighter(150)) gradient.setColorAt(1, QtGui.QColor(QtCore.Qt.red).lighter(170)) - elif self._macroParent.inWarningState(): + elif self._macroParent.inValidateErrorState(): gradient.setColorAt(0, QtGui.QColor(QtCore.Qt.yellow).lighter(190)) gradient.setColorAt(1, QtGui.QColor(QtCore.Qt.yellow).lighter(170)) else: @@ -281,10 +281,10 @@ def paint(self, painter, option, widget): # NODE if self._computeState is conf: gradient.setColorAt(0, QtGui.QColor(QtCore.Qt.gray).lighter(70)) gradient.setColorAt(1, QtGui.QColor(QtCore.Qt.darkGray).lighter(70)) - elif (option.state & QtGui.QStyle.State_Sunken) or (self._errorState is conf): + elif (option.state & QtGui.QStyle.State_Sunken) or (self._computeErrorState is conf): gradient.setColorAt(0, QtGui.QColor(QtCore.Qt.red).lighter(150)) gradient.setColorAt(1, QtGui.QColor(QtCore.Qt.red).lighter(170)) - elif self._warningState is conf: + elif self._validateError is conf: gradient.setColorAt(0, QtGui.QColor(QtCore.Qt.yellow).lighter(190)) gradient.setColorAt(1, QtGui.QColor(QtCore.Qt.yellow).lighter(170)) else: @@ -819,29 +819,29 @@ def isProcessing(self): return True return False - def inErrorState(self): + def inComputeErrorState(self): '''Look at the run status of all nodes in the macro to determine if macro is in error. ''' for node in self._encap_nodes: - if node.inErrorState(): + if node.inComputeErrorState(): return True - if self._src.inErrorState(): + if self._src.inComputeErrorState(): return True - if self._src.inErrorState(): + if self._src.inComputeErrorState(): return True return False - def inWarningState(self): + def inValidateErrorState(self): '''Look at the run status of all nodes in the macro to determine if macro is in warning state. ''' for node in self._encap_nodes: - if node.inWarningState(): + if node.inValidateErrorState(): return True - if self._src.inWarningState(): + if self._src.inValidateErrorState(): return True - if self._src.inWarningState(): + if self._src.inValidateErrorState(): return True return False diff --git a/lib/gpi/node.py b/lib/gpi/node.py index 95f80f0f..0ad377c2 100644 --- a/lib/gpi/node.py +++ b/lib/gpi/node.py @@ -80,7 +80,7 @@ from .defaultTypes import GPIDefaultType from .defines import NodeTYPE, GPI_APPLOOP, REQUIRED, GPI_SHDM_PATH from .defines import GPI_WIDGET_EVENT, GPI_PORT_EVENT, GPI_INIT_EVENT, GPI_REQUEUE_EVENT -from .defines import printMouseEvent, getKeyboardModifiers, stw +from .defines import printMouseEvent, getKeyboardModifiers, stw, Cl from .defines import GetHumanReadable_bytes, GetHumanReadable_time from .logger import manager from .port import InPort, OutPort @@ -468,42 +468,42 @@ def initStateMachine(self): # NODE self._chkInPortsState = GPIState('chkInPorts', self.chkInPortsRun, self._machine) self._computeState = GPIState('compute', self.computeRun, self._machine) self._post_compState = GPIState('post_compute', self.post_computeRun, self._machine) - self._errorState = GPIState('error', self.errorRun, self._machine) - self._warningState = GPIState('warning', self.warningRun, self._machine) + self._computeErrorState = GPIState('c_error', self.computeErrorRun, self._machine) + self._validateError = GPIState('v_error', self.validateErrorRun, self._machine) self._disabledState = GPIState('disabled', self.disabledRun, self._machine) # make state graph # idle self._idleState.addTransition('check', self._chkInPortsState) self._idleState.addTransition('disable', self._disabledState) - self._idleState.addTransition('init_error', self._errorState) - self._idleState.addTransition('init_warn', self._warningState) # should this exist? + self._idleState.addTransition('init_error', self._computeErrorState) + self._idleState.addTransition('init_warn', self._validateError) # should this exist? # chkInPorts self._chkInPortsState.addTransition('compute', self._computeState) self._chkInPortsState.addTransition('ignore', self._idleState) - self._chkInPortsState.addTransition('warning', self._warningState) - self._chkInPortsState.addTransition('error', self._errorState) + self._chkInPortsState.addTransition('v_error', self._validateError) + self._chkInPortsState.addTransition('c_error', self._computeErrorState) self._chkInPortsState.addTransition('disable', self._disabledState) # compute - self._computeState.addTransition('error', self._errorState) + self._computeState.addTransition('c_error', self._computeErrorState) self._computeState.addTransition('next', self._post_compState) self._computeState.addTransition('disable', self._disabledState) # post_compute self._post_compState.addTransition('finished', self._idleState) - self._post_compState.addTransition('warning', self._warningState) - self._post_compState.addTransition('error', self._errorState) + self._post_compState.addTransition('v_error', self._validateError) + self._post_compState.addTransition('c_error', self._computeErrorState) self._post_compState.addTransition('disable', self._disabledState) # warning - self._warningState.addTransition('check', self._chkInPortsState) - self._warningState.addTransition('disable', self._disabledState) + self._validateError.addTransition('check', self._chkInPortsState) + self._validateError.addTransition('disable', self._disabledState) # error - (unhandled or exceptions) - self._errorState.addTransition('check', self._chkInPortsState) - self._errorState.addTransition('disable', self._disabledState) + self._computeErrorState.addTransition('check', self._chkInPortsState) + self._computeErrorState.addTransition('disable', self._disabledState) # disabled self._disabledState.addTransition('enable', self._idleState) @@ -512,10 +512,10 @@ def start(self): # NODE if self.inIdleState(): log.debug("NODE(" + self.name + "): emit switchSig") self._switchSig.emit('check') # get out of idle - elif self.inWarningState(): + elif self.inValidateErrorState(): log.debug("NODE(" + self.name + "): FROM WARNING STATE: emit switchSig") self._switchSig.emit('check') # get out of warning - elif self.inErrorState(): + elif self.inComputeErrorState(): log.debug("NODE(" + self.name + "): FROM ERROR STATE: emit switchSig") self._switchSig.emit('check') # get out of error else: @@ -530,7 +530,7 @@ def idleRun(self, sig): self.debounceUISignals(sig) def debounceUISignals(self, sig): - if sig == 'finished' or sig == 'ignore' or sig == 'warning' or sig == 'error': + if sig == 'finished' or sig == 'ignore' or sig == 'v_error' or sig == 'c_error': # from post_compute or failed check before allowing new UI signals # to be processed, require that the last signal was succesfully # processed. -This significantly cuts down the amount of @@ -556,7 +556,7 @@ def chkInPortsRun(self, sig): #log.error(sys.exc_info()) log.error(traceback.format_exc()) log.error(" ->error") - self._switchSig.emit('error') + self._switchSig.emit('c_error') # computeRun() support def nextSigEmit(self, arg=None): @@ -565,7 +565,7 @@ def nextSigEmit(self, arg=None): self._switchSig.emit('next') def errorSigEmit(self): - self._switchSig.emit('error') + self._switchSig.emit('c_error') def computeRun(self, sig): self.printCurState() @@ -589,7 +589,7 @@ def computeRun(self, sig): except: log.error("computeRun(): Failed") log.error(traceback.format_exc()) - self._switchSig.emit('error') + self._switchSig.emit('c_error') def post_computeRun(self, sig): self.printCurState() @@ -600,18 +600,19 @@ def post_computeRun(self, sig): self.updateToolTips() # for ports self.updateToolTip() # for node - #retcode = self.nodeCompute_thread.returnCode() - retcode = self._returnCode # retcode: None: Terminated # 0: SUCCESS # >0: VALIDATE ERROR -> Yellow # <0: COMPUTE ERROR -> Red - if retcode is None: # assume compute error since validate will return - self._switchSig.emit('error') - elif retcode < 0: - self._switchSig.emit('error') - elif retcode > 0: - self._switchSig.emit('warning') + if self._returnCode is None: # assume compute error since validate will return + log.error(Cl.FAIL+str(self.getName())+Cl.ESC+": compute() failed.") + self._switchSig.emit('c_error') + elif self._returnCode < 0: + log.error(Cl.FAIL+str(self.getName())+Cl.ESC+": compute() failed.") + self._switchSig.emit('c_error') + elif self._returnCode > 0: + log.error(Cl.FAIL+str(self.getName())+Cl.ESC+": validate() failed.") + self._switchSig.emit('v_error') else: log.info("post compute SUCCESS, nextSig") self._switchSig.emit('finished') # go to idle @@ -624,23 +625,23 @@ def post_computeRun(self, sig): except: log.error("post_computeRun(): Failed\n"+str(traceback.format_exc())) - self._switchSig.emit('error') + self._switchSig.emit('c_error') self._progress_timer.stop() if self._progress_was_on: self._progress_done.start() - def errorRun(self, sig): + def computeErrorRun(self, sig): self.printCurState() self.graph._switchSig.emit('pause') # move canvas to a paused state to let users fix the problem - self._curState.emit('Error ('+str(sig)+')') + self._curState.emit('Compute Error ('+str(sig)+')') self.forceUpdate_NodeUI() self.debounceUISignals(sig) - def warningRun(self, sig): + def validateErrorRun(self, sig): self.printCurState() self.graph._switchSig.emit('pause') # move canvas to a paused state to let users fix the problem - self._curState.emit('Warning ('+str(sig)+')') + self._curState.emit('Validate Error ('+str(sig)+')') self.forceUpdate_NodeUI() self.debounceUISignals(sig) @@ -670,20 +671,20 @@ def waitUntilIdle(self): # while not self.inIdleState(): QtGui.QApplication.processEvents() # allow gui to update - def inErrorState(self): - if self._errorState is self.getCurState(): + def inComputeErrorState(self): + if self._computeErrorState is self.getCurState(): return True return False - def inWarningState(self): - if self._warningState is self.getCurState(): + def inValidateErrorState(self): + if self._validateError is self.getCurState(): return True return False def isProcessingEvent(self): return (not self.inIdleState()) \ - and (not self.inWarningState()) \ - and (not self.inErrorState()) \ + and (not self.inValidateErrorState()) \ + and (not self.inComputeErrorState()) \ and (not self.inDisabledState()) def setDisabledState(self, val): @@ -1435,11 +1436,11 @@ def paint(self, painter, option, widget): # NODE gradient.setColorAt(0, QtGui.QColor(QtCore.Qt.gray).lighter(70)) gradient.setColorAt(1, QtGui.QColor(QtCore.Qt.darkGray).lighter(70)) - elif (option.state & QtGui.QStyle.State_Sunken) or (self._errorState is conf): + elif (option.state & QtGui.QStyle.State_Sunken) or (self._computeErrorState is conf): gradient.setColorAt(0, QtGui.QColor(QtCore.Qt.red).lighter(150)) gradient.setColorAt(1, QtGui.QColor(QtCore.Qt.red).lighter(170)) - elif self._warningState is conf: + elif self._validateError is conf: gradient.setColorAt(0, QtGui.QColor(QtCore.Qt.yellow).lighter(190)) gradient.setColorAt(1, QtGui.QColor(QtCore.Qt.yellow).lighter(170)) From 3b2d78acc54e6c1474ee3fb2920e9d5f145f7c67 Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 22 Oct 2015 19:55:15 -0700 Subject: [PATCH 41/60] Allow 0 and None == Success. Added initUIError state. See #35 --- lib/gpi/functor.py | 30 +++++++------------------ lib/gpi/node.py | 56 ++++++++++++++++++++++++++++++++-------------- 2 files changed, 47 insertions(+), 39 deletions(-) diff --git a/lib/gpi/functor.py b/lib/gpi/functor.py index c83af43b..f073d102 100644 --- a/lib/gpi/functor.py +++ b/lib/gpi/functor.py @@ -150,8 +150,10 @@ def start(self): self._validate_retcode = 1 # validate error self._execType = tmp_exec + # None as zero + # send validate() return code thru same channels - if self._validate_retcode != 0: + if self._validate_retcode != 0 and self._validate_retcode is not None: self._node.appendWallTime(time.time() - self._compute_start) self.finished.emit(1) # validate error return @@ -194,7 +196,7 @@ def computeFinished(self): else: self._retcode = 0 # success - if self._proc._retcode != 0: + if self._proc._retcode != 0 and self._proc._retcode is not None: self._retcode = -1 # compute error self.finalMatter() @@ -208,12 +210,6 @@ def applyQueuedData_setData(self): for o in self._proxy: try: log.debug("applyQueuedData_setData(): apply object "+str(o[0])+', '+str(o[1])) - #if o[0] == 'retcode': - # self._retcode = o[1] - #if o[0] == 'modifyWdg': - # self._node.modifyWdg(o[1], o[2]) - #if o[0] == 'setReQueue': - # self._node.setReQueue(o[1]) if o[0] == 'setData': # DataProxy is used for complex data types like numpy if type(o[2]) is DataProxy: @@ -232,7 +228,6 @@ def applyQueuedData_setData(self): self._node.setData(o[1], o[2]) except: log.error("applyQueuedData() failed. "+str(traceback.format_exc())) - #raise self._retcode = -1 # compute error self._setData_finished.emit() @@ -284,23 +279,16 @@ def applyQueuedData(self): log.debug("applyQueuedData(): apply object "+str(o[0])+', '+str(o[1]) ) if o[0] == 'retcode': self._retcode = o[1] - if self._retcode != 0: + if self._retcode != 0 and self._retcode is not None: self._retcode = -1 # compute error + else: + self._retcode = 0 # squash Nones if o[0] == 'modifyWdg': self._node.modifyWdg(o[1], o[2]) if o[0] == 'setReQueue': self._node.setReQueue(o[1]) - # move to thread - #if o[0] == 'setData': - # # flag any NPY array for threaded xfer - # if type(o[2]) is dict: - # if o[2].has_key('951413'): - # self._segmentedDataProxy = True - # continue - # self._node.setData(o[1], o[2]) except: log.error("applyQueuedData() failed. "+str(traceback.format_exc())) - #raise self._retcode = -1 # compute error # transfer all setData() calls to a thread @@ -356,7 +344,7 @@ def run(self): except: log.error('PROCESS: \''+str(self._title)+'\':\''+str(self._label)+'\' compute() failed.\n'+str(traceback.format_exc())) #raise - self._proxy.append(['retcode', -1]) + self._proxy.append(['retcode', -1]) # compute error def terminate(self): self._timer.stop() @@ -417,7 +405,6 @@ def run(self): log.info("TTask _func() finished") except: log.error('THREAD: \''+str(self._title)+'\':\''+str(self._label)+'\' compute() failed.\n'+str(traceback.format_exc())) - #raise self._retcode = -1 # compute error # The apploop-type blocks until finished, obviating the need for signals @@ -443,7 +430,6 @@ def run(self): self._retcode = self._func() except: log.error('APPLOOP: \''+str(self._title)+'\':\''+str(self._label)+'\' compute() failed.\n'+str(traceback.format_exc())) - #raise self._retcode = -1 # compute error def terminate(self): diff --git a/lib/gpi/node.py b/lib/gpi/node.py index 0ad377c2..016254cd 100644 --- a/lib/gpi/node.py +++ b/lib/gpi/node.py @@ -313,6 +313,7 @@ def __init__(self, CanvasBackend, nodeCatItem=None, nodeIF=None, nodeIFscroll=No self._requeue = False self._markedForDeletion = False self._returnCode = None + self._initUI_returnCode = None # node name self.NodeLook = NodeAppearance() @@ -421,15 +422,12 @@ def __init__(self, CanvasBackend, nodeCatItem=None, nodeIF=None, nodeIFscroll=No # process initUI return codes try: if hasattr(self._nodeIF, 'initUI_return'): - ret = self._nodeIF.initUI_return() - if (ret is None) or (ret == 0): - # success - pass - elif ret > 0: - self._machine.next('init_warn') - elif ret < 0: - self._machine.next('init_error') - + self._initUI_returnCode = self._nodeIF.initUI_return() + if (self._initUI_returnCode is None) or (self._initUI_returnCode == 0): + self._initUI_returnCode = 0 # success + else: + log.error(Cl.FAIL+str(self.getName())+Cl.ESC+": initUI() failed.") + self._machine.next('i_error') except: log.warn('initUI() retcode handling skipped. '+str(self.item.fullpath)) @@ -470,14 +468,16 @@ def initStateMachine(self): # NODE self._post_compState = GPIState('post_compute', self.post_computeRun, self._machine) self._computeErrorState = GPIState('c_error', self.computeErrorRun, self._machine) self._validateError = GPIState('v_error', self.validateErrorRun, self._machine) + self._initUIErrorState = GPIState('i_error', self.initUIErrorRun, self._machine) self._disabledState = GPIState('disabled', self.disabledRun, self._machine) # make state graph # idle self._idleState.addTransition('check', self._chkInPortsState) self._idleState.addTransition('disable', self._disabledState) - self._idleState.addTransition('init_error', self._computeErrorState) - self._idleState.addTransition('init_warn', self._validateError) # should this exist? + #self._idleState.addTransition('init_error', self._computeErrorState) + #self._idleState.addTransition('init_warn', self._validateError) # should this exist? + self._idleState.addTransition('i_error', self._initUIErrorState) # chkInPorts self._chkInPortsState.addTransition('compute', self._computeState) @@ -497,11 +497,11 @@ def initStateMachine(self): # NODE self._post_compState.addTransition('c_error', self._computeErrorState) self._post_compState.addTransition('disable', self._disabledState) - # warning + # error - validate() failed self._validateError.addTransition('check', self._chkInPortsState) self._validateError.addTransition('disable', self._disabledState) - # error - (unhandled or exceptions) + # error - compute() failed self._computeErrorState.addTransition('check', self._chkInPortsState) self._computeErrorState.addTransition('disable', self._disabledState) @@ -570,6 +570,12 @@ def errorSigEmit(self): def computeRun(self, sig): self.printCurState() self._curState.emit('Compute ('+str(sig)+')') + + #if self._initUI_returnCode != 0: + # log.error(Cl.FAIL+str(self.getName())+Cl.ESC+": initUI() failed 2.") + # self._machine.next('c_error') + # return + try: self.forceUpdate_NodeUI() self.resetOutportStatus() # changes color, allows 'change' to be determined @@ -604,10 +610,10 @@ def post_computeRun(self, sig): # 0: SUCCESS # >0: VALIDATE ERROR -> Yellow # <0: COMPUTE ERROR -> Red - if self._returnCode is None: # assume compute error since validate will return - log.error(Cl.FAIL+str(self.getName())+Cl.ESC+": compute() failed.") - self._switchSig.emit('c_error') - elif self._returnCode < 0: + #if self._returnCode is None: # assume compute error since validate will return + # log.error(Cl.FAIL+str(self.getName())+Cl.ESC+": compute() failed.") + # self._switchSig.emit('c_error') + if self._returnCode < 0: log.error(Cl.FAIL+str(self.getName())+Cl.ESC+": compute() failed.") self._switchSig.emit('c_error') elif self._returnCode > 0: @@ -645,6 +651,13 @@ def validateErrorRun(self, sig): self.forceUpdate_NodeUI() self.debounceUISignals(sig) + def initUIErrorRun(self, sig): + self.printCurState() + self.graph._switchSig.emit('pause') # move canvas to a paused state to let users fix the problem + self._curState.emit('InitUI Error ('+str(sig)+')') + self.forceUpdate_NodeUI() + self.debounceUISignals(sig) + def disabledRun(self, sig): self._curState.emit('Disabled ('+str(sig)+')') self.printCurState() @@ -676,6 +689,11 @@ def inComputeErrorState(self): return True return False + def inInitUIErrorState(self): + if self._initUIErrorState is self.getCurState(): + return True + return False + def inValidateErrorState(self): if self._validateError is self.getCurState(): return True @@ -1444,6 +1462,10 @@ def paint(self, painter, option, widget): # NODE gradient.setColorAt(0, QtGui.QColor(QtCore.Qt.yellow).lighter(190)) gradient.setColorAt(1, QtGui.QColor(QtCore.Qt.yellow).lighter(170)) + elif self._initUIErrorState is conf: + gradient.setColorAt(0, QtGui.QColor(QtCore.Qt.red).lighter(150)) + gradient.setColorAt(1, QtGui.QColor(QtCore.Qt.yellow).lighter(170)) + else: gradient.setColorAt(0, QtGui.QColor(QtCore.Qt.gray).lighter(150)) gradient.setColorAt(1, QtGui.QColor(QtCore.Qt.darkGray).lighter(150)) From 1414b77e5ed14ccc08124c162a63ee2751f4e5bc Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 22 Oct 2015 21:16:49 -0700 Subject: [PATCH 42/60] Fixed pause from canvas init and canvas locking due to initErrorState. --- lib/gpi/canvasGraph.py | 3 ++- lib/gpi/node.py | 3 ++- lib/gpi/nodeQueue.py | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/gpi/canvasGraph.py b/lib/gpi/canvasGraph.py index fdd4eb3b..8a2d73ed 100644 --- a/lib/gpi/canvasGraph.py +++ b/lib/gpi/canvasGraph.py @@ -224,6 +224,7 @@ def initStateMachine(self): # GRAPH # init self._initState.addTransition('init_check', self._checkEventsState) self._initState.addTransition('init_finished', self._idleState) + self._initState.addTransition('pause', self._pausedState) #self._initState.exited.connect(self.initWalltime) # idle @@ -416,7 +417,7 @@ def checkEventsRun(self, sig): # EVENTS # check for event status BEFORE triggering highest compute for node in self.getAllNodes(): - if node.hasEventPending() and not node.inDisabledState(): + if node.hasEventPending() and not node.inDisabledState() and not node.inInitUIErrorState(): # Re/-initialize queue and start processing. # This was called because 'a' node has an event status. self.nodeQueue.setQueue(self.getLinearNodeHierarchy()) diff --git a/lib/gpi/node.py b/lib/gpi/node.py index 016254cd..11d7dd4a 100644 --- a/lib/gpi/node.py +++ b/lib/gpi/node.py @@ -703,7 +703,8 @@ def isProcessingEvent(self): return (not self.inIdleState()) \ and (not self.inValidateErrorState()) \ and (not self.inComputeErrorState()) \ - and (not self.inDisabledState()) + and (not self.inDisabledState()) \ + and (not self.inInitUIErrorState()) def setDisabledState(self, val): if val is True: diff --git a/lib/gpi/nodeQueue.py b/lib/gpi/nodeQueue.py index a1398879..5f1d5b6a 100644 --- a/lib/gpi/nodeQueue.py +++ b/lib/gpi/nodeQueue.py @@ -96,7 +96,8 @@ def startNextNode(self): # find next node in queue if len(self._queue) > 0: while (not self._queue[0].hasEventPending()) \ - or (self._queue[0].inDisabledState()): + or (self._queue[0].inDisabledState()) \ + or (self._queue[0].inInitUIErrorState()): self._queue.pop(0) if len(self._queue) == 0: break From da825311e617a72df9481097ae884a4386107fbd Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 22 Oct 2015 21:21:10 -0700 Subject: [PATCH 43/60] Fixed macro to display new init error state. --- lib/gpi/macroNode.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/gpi/macroNode.py b/lib/gpi/macroNode.py index dfe7ef91..15aa4009 100644 --- a/lib/gpi/macroNode.py +++ b/lib/gpi/macroNode.py @@ -270,6 +270,9 @@ def paint(self, painter, option, widget): # NODE elif self._macroParent.inValidateErrorState(): gradient.setColorAt(0, QtGui.QColor(QtCore.Qt.yellow).lighter(190)) gradient.setColorAt(1, QtGui.QColor(QtCore.Qt.yellow).lighter(170)) + elif self._macroParent.inInitUIErrorState(): + gradient.setColorAt(0, QtGui.QColor(QtCore.Qt.red).lighter(150)) + gradient.setColorAt(1, QtGui.QColor(QtCore.Qt.yellow).lighter(170)) else: gradient.setColorAt(0, QtGui.QColor(QtCore.Qt.gray).lighter(150)) gradient.setColorAt( @@ -832,6 +835,19 @@ def inComputeErrorState(self): return True return False + def inInitUIErrorState(self): + '''Look at the run status of all nodes in the macro to determine if + macro is in error. + ''' + for node in self._encap_nodes: + if node.inInitUIErrorState(): + return True + if self._src.inInitUIErrorState(): + return True + if self._src.inInitUIErrorState(): + return True + return False + def inValidateErrorState(self): '''Look at the run status of all nodes in the macro to determine if macro is in warning state. From 2468837fae793ded3f03fc1d5a3583f6ad24299b Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 23 Oct 2015 09:57:25 -0700 Subject: [PATCH 44/60] Cleaned up hard coded return values into a class. --- lib/gpi/functor.py | 47 ++++++++++++++++++++++++++++++++++------------ lib/gpi/node.py | 30 +++++++---------------------- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/lib/gpi/functor.py b/lib/gpi/functor.py index f073d102..3609fc4b 100644 --- a/lib/gpi/functor.py +++ b/lib/gpi/functor.py @@ -39,6 +39,30 @@ # start logger for this module log = manager.getLogger(__name__) +class ReturnCodes(object): + + # Return codes from the functor have specific meaning to the node internals. + InitUIError = 2 + ValidateError = 1 + Success = 0 + ComputeError = -1 + + def isComputeError(self, ret): + return ret == self.ComputeError + def isValidateError(self, ret): + return ret == self.ValidateError + def isInitUIError(self, ret): + return ret == self.InitUIError + + # Return codes from the nodeAPI (i.e. compute(), validate() and initUI()) + # are either success or failure for each function. + def isSuccess(self, ret): + return (ret is None) or (ret == 0) + def isError(self, ret): + return (ret is not None) and (ret != 0) + +Return = ReturnCodes() # make a global copy + def ExecRunnable(runnable): tp = QtCore.QThreadPool.globalInstance() #print 'active threads: ', tp.activeThreadCount() @@ -196,8 +220,8 @@ def computeFinished(self): else: self._retcode = 0 # success - if self._proc._retcode != 0 and self._proc._retcode is not None: - self._retcode = -1 # compute error + if Return.isError(self._proc._retcode): + self._retcode = Return.ComputeError self.finalMatter() def finalMatter(self): @@ -228,7 +252,7 @@ def applyQueuedData_setData(self): self._node.setData(o[1], o[2]) except: log.error("applyQueuedData() failed. "+str(traceback.format_exc())) - self._retcode = -1 # compute error + self._retcode = Return.ComputeError self._setData_finished.emit() # Assemble Segmented Data @@ -279,8 +303,8 @@ def applyQueuedData(self): log.debug("applyQueuedData(): apply object "+str(o[0])+', '+str(o[1]) ) if o[0] == 'retcode': self._retcode = o[1] - if self._retcode != 0 and self._retcode is not None: - self._retcode = -1 # compute error + if Return.isError(self._retcode): + self._retcode = Return.ComputeError else: self._retcode = 0 # squash Nones if o[0] == 'modifyWdg': @@ -289,7 +313,7 @@ def applyQueuedData(self): self._node.setReQueue(o[1]) except: log.error("applyQueuedData() failed. "+str(traceback.format_exc())) - self._retcode = -1 # compute error + self._retcode = Return.ComputeError # transfer all setData() calls to a thread log.debug("applyQueuedData(): run _applyData_thread") @@ -297,8 +321,8 @@ def applyQueuedData(self): def applyQueuedData_finalMatter(self): - if self._retcode == -1: - self.finished.emit(-1) # compute error + if Return.isComputeError(self._retcode): + self.finished.emit(self._retcode) elapsed = (time.time() - self._ap_st_time) log.info("applyQueuedData(): time (total queue): "+str(elapsed)+" sec") @@ -343,8 +367,7 @@ def run(self): self._proxy.append(['retcode', self._func()]) except: log.error('PROCESS: \''+str(self._title)+'\':\''+str(self._label)+'\' compute() failed.\n'+str(traceback.format_exc())) - #raise - self._proxy.append(['retcode', -1]) # compute error + self._proxy.append(['retcode', Return.ComputeError]) def terminate(self): self._timer.stop() @@ -405,7 +428,7 @@ def run(self): log.info("TTask _func() finished") except: log.error('THREAD: \''+str(self._title)+'\':\''+str(self._label)+'\' compute() failed.\n'+str(traceback.format_exc())) - self._retcode = -1 # compute error + self._retcode = Return.ComputeError # The apploop-type blocks until finished, obviating the need for signals # or timer checks @@ -430,7 +453,7 @@ def run(self): self._retcode = self._func() except: log.error('APPLOOP: \''+str(self._title)+'\':\''+str(self._label)+'\' compute() failed.\n'+str(traceback.format_exc())) - self._retcode = -1 # compute error + self._retcode = Return.ComputeError def terminate(self): pass # can't happen b/c blocking mainloop diff --git a/lib/gpi/node.py b/lib/gpi/node.py index 11d7dd4a..d9d225d3 100644 --- a/lib/gpi/node.py +++ b/lib/gpi/node.py @@ -85,7 +85,7 @@ from .logger import manager from .port import InPort, OutPort from .stateMachine import GPI_FSM, GPIState -from .functor import GPIFunctor +from .functor import GPIFunctor, Return from .sysspecs import Specs # start logger for this module @@ -312,8 +312,7 @@ def __init__(self, CanvasBackend, nodeCatItem=None, nodeIF=None, nodeIFscroll=No self._node_disabled = False self._requeue = False self._markedForDeletion = False - self._returnCode = None - self._initUI_returnCode = None + self._returnCode = None # needed for passing between slots # node name self.NodeLook = NodeAppearance() @@ -422,10 +421,7 @@ def __init__(self, CanvasBackend, nodeCatItem=None, nodeIF=None, nodeIFscroll=No # process initUI return codes try: if hasattr(self._nodeIF, 'initUI_return'): - self._initUI_returnCode = self._nodeIF.initUI_return() - if (self._initUI_returnCode is None) or (self._initUI_returnCode == 0): - self._initUI_returnCode = 0 # success - else: + if Return.isError(self._nodeIF.initUI_return()): log.error(Cl.FAIL+str(self.getName())+Cl.ESC+": initUI() failed.") self._machine.next('i_error') except: @@ -559,8 +555,8 @@ def chkInPortsRun(self, sig): self._switchSig.emit('c_error') # computeRun() support - def nextSigEmit(self, arg=None): - # get the retcode value from the runtime code + def nextSigEmit(self, arg): + # save the retcode value from the runtime code for post-compute self._returnCode = arg self._switchSig.emit('next') @@ -571,11 +567,6 @@ def computeRun(self, sig): self.printCurState() self._curState.emit('Compute ('+str(sig)+')') - #if self._initUI_returnCode != 0: - # log.error(Cl.FAIL+str(self.getName())+Cl.ESC+": initUI() failed 2.") - # self._machine.next('c_error') - # return - try: self.forceUpdate_NodeUI() self.resetOutportStatus() # changes color, allows 'change' to be determined @@ -606,17 +597,10 @@ def post_computeRun(self, sig): self.updateToolTips() # for ports self.updateToolTip() # for node - # retcode: None: Terminated - # 0: SUCCESS - # >0: VALIDATE ERROR -> Yellow - # <0: COMPUTE ERROR -> Red - #if self._returnCode is None: # assume compute error since validate will return - # log.error(Cl.FAIL+str(self.getName())+Cl.ESC+": compute() failed.") - # self._switchSig.emit('c_error') - if self._returnCode < 0: + if Return.isComputeError(self._returnCode): log.error(Cl.FAIL+str(self.getName())+Cl.ESC+": compute() failed.") self._switchSig.emit('c_error') - elif self._returnCode > 0: + elif Return.isValidateError(self._returnCode): log.error(Cl.FAIL+str(self.getName())+Cl.ESC+": validate() failed.") self._switchSig.emit('v_error') else: From 09fe8d9ab1dd36e0005e8dc199a86f66a9a719fe Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 23 Oct 2015 10:09:44 -0700 Subject: [PATCH 45/60] Simplified node "ready" state (easier changes in the future). --- lib/gpi/canvasGraph.py | 2 +- lib/gpi/node.py | 4 ++++ lib/gpi/nodeQueue.py | 4 +--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/gpi/canvasGraph.py b/lib/gpi/canvasGraph.py index 8a2d73ed..d547ffd1 100644 --- a/lib/gpi/canvasGraph.py +++ b/lib/gpi/canvasGraph.py @@ -417,7 +417,7 @@ def checkEventsRun(self, sig): # EVENTS # check for event status BEFORE triggering highest compute for node in self.getAllNodes(): - if node.hasEventPending() and not node.inDisabledState() and not node.inInitUIErrorState(): + if node.isReady(): # Re/-initialize queue and start processing. # This was called because 'a' node has an event status. self.nodeQueue.setQueue(self.getLinearNodeHierarchy()) diff --git a/lib/gpi/node.py b/lib/gpi/node.py index d9d225d3..c41d28d9 100644 --- a/lib/gpi/node.py +++ b/lib/gpi/node.py @@ -690,6 +690,10 @@ def isProcessingEvent(self): and (not self.inDisabledState()) \ and (not self.inInitUIErrorState()) + def isReady(self): + return self.hasEventPending() and (not self.inDisabledState()) and + (not self.inInitUIErrorState()) + def setDisabledState(self, val): if val is True: self._machine.next('disable') diff --git a/lib/gpi/nodeQueue.py b/lib/gpi/nodeQueue.py index 5f1d5b6a..55aaff17 100644 --- a/lib/gpi/nodeQueue.py +++ b/lib/gpi/nodeQueue.py @@ -95,9 +95,7 @@ def startNextNode(self): # find next node in queue if len(self._queue) > 0: - while (not self._queue[0].hasEventPending()) \ - or (self._queue[0].inDisabledState()) \ - or (self._queue[0].inInitUIErrorState()): + while not self._queue[0].isReady(): self._queue.pop(0) if len(self._queue) == 0: break From e197ecb7cfb8cd899450ee2af03d89d580e78935 Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 26 Oct 2015 09:15:00 -0700 Subject: [PATCH 46/60] Fixed poorly wrapped line. --- lib/gpi/node.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/gpi/node.py b/lib/gpi/node.py index c41d28d9..b8ee4f5d 100644 --- a/lib/gpi/node.py +++ b/lib/gpi/node.py @@ -691,8 +691,7 @@ def isProcessingEvent(self): and (not self.inInitUIErrorState()) def isReady(self): - return self.hasEventPending() and (not self.inDisabledState()) and - (not self.inInitUIErrorState()) + return self.hasEventPending() and (not self.inDisabledState()) and (not self.inInitUIErrorState()) def setDisabledState(self, val): if val is True: From 118c7a19fb09e0aafe379ac753cbc1d5a86788a9 Mon Sep 17 00:00:00 2001 From: nick Date: Wed, 28 Oct 2015 19:34:13 -0700 Subject: [PATCH 47/60] A simple window. --- lib/gpi/update.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100755 lib/gpi/update.py diff --git a/lib/gpi/update.py b/lib/gpi/update.py new file mode 100755 index 00000000..088f574a --- /dev/null +++ b/lib/gpi/update.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +# Copyright (C) 2014 Dignity Health +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . +# +# NO CLINICAL USE. THE SOFTWARE IS NOT INTENDED FOR COMMERCIAL PURPOSES +# AND SHOULD BE USED ONLY FOR NON-COMMERCIAL RESEARCH PURPOSES. THE +# SOFTWARE MAY NOT IN ANY EVENT BE USED FOR ANY CLINICAL OR DIAGNOSTIC +# PURPOSES. YOU ACKNOWLEDGE AND AGREE THAT THE SOFTWARE IS NOT INTENDED FOR +# USE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITY, INCLUDING BUT NOT +# LIMITED TO LIFE SUPPORT OR EMERGENCY MEDICAL OPERATIONS OR USES. LICENSOR +# MAKES NO WARRANTY AND HAS NOR LIABILITY ARISING FROM ANY USE OF THE +# SOFTWARE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITIES. + +# Brief: Update utility. + +import sys +import os +from PyQt4 import QtGui, QtCore + +def update(): + + app = QtGui.QApplication(sys.argv) + + w = QtGui.QWidget() + w.resize(250, 150) + w.move(300, 300) + w.setWindowTitle('GPI Update') + w.show() + + sys.exit(app.exec_()) + +if __name__ == '__main__': + update() From 7a7593f03c307949b07ae1e304d483c211247f42 Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 29 Oct 2015 09:59:39 -0700 Subject: [PATCH 48/60] Troubleshooting functools error. --- lib/gpi/update.py | 78 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 67 insertions(+), 11 deletions(-) diff --git a/lib/gpi/update.py b/lib/gpi/update.py index 088f574a..dd5d8252 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -23,22 +23,78 @@ # MAKES NO WARRANTY AND HAS NOR LIABILITY ARISING FROM ANY USE OF THE # SOFTWARE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITIES. -# Brief: Update utility. +# Brief: Update utility, can be called directly from gpi or run as a separate +# program. -import sys import os -from PyQt4 import QtGui, QtCore +import sys +import subprocess -def update(): - - app = QtGui.QApplication(sys.argv) +# Check for Anaconda PREFIX, or assume that THIS file location is the CWD. +GPI_PREFIX = '/opt/anaconda1anaconda2anaconda3' # ANACONDA +if GPI_PREFIX == '/opt/'+''.join(['anaconda'+str(i) for i in range(1,4)]): + GPI_PREFIX, _ = os.path.split(os.path.dirname(os.path.realpath(__file__))) + +GPI_LIB_DIR = GPI_PREFIX +if not GPI_PREFIX.endswith('lib'): + GPI_LIB_DIR = os.path.join(GPI_PREFIX, 'lib') +if GPI_LIB_DIR not in sys.path: + sys.path.insert(0, GPI_LIB_DIR) - w = QtGui.QWidget() - w.resize(250, 150) - w.move(300, 300) - w.setWindowTitle('GPI Update') - w.show() +from gpi import QtGui, QtCore, Signal + + +class UpdateWindow(QtGui.QWidget): + def __init__(self): + super().__init__() + + if not condaIsAvailable(): + return + + okButton = QtGui.QPushButton("OK") + cancelButton = QtGui.QPushButton("Cancel") + + hbox = QtGui.QHBoxLayout() + hbox.addStretch(1) + hbox.addWidget(okButton) + hbox.addWidget(cancelButton) + + vbox = QtGui.QVBoxLayout() + vbox.addStretch(1) + vbox.addLayout(hbox) + + self.setLayout(vbox) + + self.setGeometry(300, 300, 250, 150) + self.setWindowTitle('GPI Update') + self.show() + self.raise_() + + def condaIsAvailable(self): + try: + subprocess.check_call('conda --version') + return True + except: + print('\'conda\' failed to execute, aborting...') + return False + + def getLatestPkgVersion(self, name, channel): + try: + output = subprocess.check_output('conda search -c '+channel+' -f '+name+' -o --json', shell=True) + except subprocess.CalledProcessError as e: + print(cmd, e.output) + sys.exit(e.returncode) + + conda = json.loads(output) + print(conda) + + def getLatestGPIVersion(self): + pass + +def update(): + app = QtGui.QApplication(sys.argv) + win = UpdateWindow() sys.exit(app.exec_()) if __name__ == '__main__': From 24222ef6d15e161264f836fa6d7366957baef4c4 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 30 Oct 2015 11:48:32 -0700 Subject: [PATCH 49/60] A working version of the update script cli. --- bin/gpi_update | 42 ++++++++++ lib/gpi/update.py | 195 ++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 206 insertions(+), 31 deletions(-) create mode 100755 bin/gpi_update diff --git a/bin/gpi_update b/bin/gpi_update new file mode 100755 index 00000000..d94fdcbf --- /dev/null +++ b/bin/gpi_update @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +# Copyright (C) 2014 Dignity Health +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . +# +# NO CLINICAL USE. THE SOFTWARE IS NOT INTENDED FOR COMMERCIAL PURPOSES +# AND SHOULD BE USED ONLY FOR NON-COMMERCIAL RESEARCH PURPOSES. THE +# SOFTWARE MAY NOT IN ANY EVENT BE USED FOR ANY CLINICAL OR DIAGNOSTIC +# PURPOSES. YOU ACKNOWLEDGE AND AGREE THAT THE SOFTWARE IS NOT INTENDED FOR +# USE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITY, INCLUDING BUT NOT +# LIMITED TO LIFE SUPPORT OR EMERGENCY MEDICAL OPERATIONS OR USES. LICENSOR +# MAKES NO WARRANTY AND HAS NOR LIABILITY ARISING FROM ANY USE OF THE +# SOFTWARE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITIES. + +import sys, os + +# Check for Anaconda PREFIX, or assume that THIS file location is the CWD. +GPI_PREFIX = '/opt/anaconda1anaconda2anaconda3' # ANACONDA +if GPI_PREFIX == '/opt/'+''.join(['anaconda'+str(i) for i in range(1,4)]): + GPI_PREFIX, _ = os.path.split(os.path.dirname(os.path.realpath(__file__))) + +GPI_LIB_DIR = os.path.join(GPI_PREFIX, 'lib') +if GPI_LIB_DIR not in sys.path: + sys.path.insert(0, GPI_LIB_DIR) + +# gpi +from gpi import update + +if __name__ == '__main__': + update.update() diff --git a/lib/gpi/update.py b/lib/gpi/update.py index dd5d8252..0120306a 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # Copyright (C) 2014 Dignity Health # # This program is free software: you can redistribute it and/or modify @@ -28,30 +27,65 @@ import os import sys +import json import subprocess -# Check for Anaconda PREFIX, or assume that THIS file location is the CWD. -GPI_PREFIX = '/opt/anaconda1anaconda2anaconda3' # ANACONDA -if GPI_PREFIX == '/opt/'+''.join(['anaconda'+str(i) for i in range(1,4)]): - GPI_PREFIX, _ = os.path.split(os.path.dirname(os.path.realpath(__file__))) - -GPI_LIB_DIR = GPI_PREFIX -if not GPI_PREFIX.endswith('lib'): - GPI_LIB_DIR = os.path.join(GPI_PREFIX, 'lib') -if GPI_LIB_DIR not in sys.path: - sys.path.insert(0, GPI_LIB_DIR) - from gpi import QtGui, QtCore, Signal +# get the anaconda path to ensure that THIS installation is being updated +ANACONDA_PREFIX = '/opt/anaconda1anaconda2anaconda3' # ANACONDA +if ANACONDA_PREFIX == '/opt/'+''.join(['anaconda'+str(i) for i in range(1,4)]): + # get the path from the user env + ANACONDA_PREFIX = os.path.dirname(subprocess.check_output('which conda', shell=True).decode('latin1').strip()) + +# Load multiple json objects from string. +# Returns loaded objects in a list. +class JSONStreamLoads(object): + + def __init__(self, in_str, linefeed=True): + + if type(in_str) == str: + self._buffer = in_str + else: + raise TypeError("JSONStreamLoads(): input must be of type \'str\'.") + + if linefeed: + self._load = self.loadsByLine() + else: + self._load = self.loadsByCharacter() + + def objects(self): + return self._load + + def loadsByLine(self): + out = [] + buf = '' + for l in self._buffer.splitlines(): + buf += l.strip().strip('\0') + try: + out.append(json.loads(buf)) + buf = '' + except: + pass + return out + + def loadsByCharacter(self): + out = [] + buf = '' + for l in self._buffer: + buf += l + try: + out.append(json.loads(buf.strip().strip('\0'))) + buf = '' + except: + pass + return out class UpdateWindow(QtGui.QWidget): def __init__(self): super().__init__() - if not condaIsAvailable(): - return - okButton = QtGui.QPushButton("OK") cancelButton = QtGui.QPushButton("Cancel") @@ -71,31 +105,130 @@ def __init__(self): self.show() self.raise_() - def condaIsAvailable(self): +class CondaUpdater(object): + # use conda to update to the latest package + + def __init__(self, conda_prefix=ANACONDA_PREFIX): + self._conda_prefix = conda_prefix + self._channel = 'nckz' + self._packages = ['gpi', 'gpi-core-nodes', 'gpi-docs'] + + self.checkConda() + + # Check for the current installed versions + self._current_versions = {} + for pkg in self._packages: + self._current_versions[pkg] = self.getInstalledPkgVersion(pkg) + + # Check for the latest versions online + self._latest_versions = {} + for pkg in self._packages: + if self._current_versions[pkg] is None: + self._latest_versions[pkg] = self.updatePkg(pkg, self._channel, dry_run=True, install=True) + else: + self._latest_versions[pkg] = self.updatePkg(pkg, self._channel, dry_run=True) + + def __str__(self): + msg = '' + + # updates + if len([ pkg for pkg in self._packages \ + if (self._latest_versions[pkg] is not None) and \ + (self._current_versions[pkg] is not None) ]): + msg += 'The following packages will be updated:\n' + for pkg in self._packages: + o = self._current_versions[pkg] + n = self._latest_versions[pkg] + msg += '\t'+str(o) + ' => ' + str(n) + '\n' + + # installs + if len([ pkg for pkg in self._packages \ + if (self._latest_versions[pkg] is not None) and \ + (self._current_versions[pkg] is None) ]): + msg += 'The following packages will be installed:\n' + for pkg in self._packages: + n = self._latest_versions[pkg] + msg += '\t'+str(n) + '\n' + + if msg == '': + msg = 'GPI is totes up to date.' + + return msg + + def checkConda(self): + cmd = self._conda_prefix+'/conda --version >/dev/null 2>&1' try: - subprocess.check_call('conda --version') - return True + subprocess.check_output(cmd, shell=True) + except subprocess.CalledProcessError as e: + print('Failed to execute conda, aborting...') + print(e.cmd, e.output) + raise except: print('\'conda\' failed to execute, aborting...') - return False + print(cmd) + raise + + def getInstalledPkgVersion(self, name): + cmd = self._conda_prefix+'/conda list -f '+name+' --json' + try: + output = subprocess.check_output(cmd, shell=True).decode('utf8') + conda = JSONStreamLoads(output).objects()[-1] + return conda[-1] + except: + print('Failed to retrieve installed package information on '+name+', skipping...') + print(cmd) + + def updateAllPkgs(self): + # Install or update all the packages that have been determined. + for pkg in self._packages: + # if there is no package (due to user changes) then install it + if self._current_versions[pkg] is None: + self.updatePkg(pkg, self._channel, install=True) + # if there is a latest version then update + if self._latest_versions[pkg] is not None: + self.updatePkg(pkg, self._channel) + + def updatePkg(self, name, channel, dry_run=False, install=False): + # Updates to the latest package and returns the package string. + # -dry_run will just return the latest package string. + # -install will install the package if its not currently installed. + + conda_sub = 'update' + if install: conda_sub = 'install' + dry_cmd = '' + if dry_run: dry_cmd = '--dry-run --no-deps' + cmd = self._conda_prefix+'/conda '+conda_sub+' -c '+channel+' '+name+' -y --json '+dry_cmd - def getLatestPkgVersion(self, name, channel): try: - output = subprocess.check_output('conda search -c '+channel+' -f '+name+' -o --json', shell=True) + output = subprocess.check_output(cmd, shell=True).decode('utf8') + conda = JSONStreamLoads(output).objects() + conda = conda[-1] + + if conda['success']: + if 'message' in conda: # if we're up to date + return + for pkg in conda['actions']['LINK']: + if pkg.startswith(name): + return pkg.split()[0] + else: + raise RuntimeError('conda returned a failed fetch.') except subprocess.CalledProcessError as e: - print(cmd, e.output) - sys.exit(e.returncode) + print('Failed to update to new package, aborting...') + print(e.cmd, e.output) + raise + except: + print('Failed to retrieve package update information, aborting...') + print(cmd) + raise + +def update(): - conda = json.loads(output) - print(conda) + updater = CondaUpdater() + print(updater) + updater.updateAllPkgs() - def getLatestGPIVersion(self): - pass + return -def update(): app = QtGui.QApplication(sys.argv) win = UpdateWindow() sys.exit(app.exec_()) - -if __name__ == '__main__': - update() From 1ce961b719d2d4f3a693fefcdda7517c97c28749 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 30 Oct 2015 14:25:03 -0700 Subject: [PATCH 50/60] Reworked for better error checking and added pdone. --- lib/gpi/update.py | 156 +++++++++++++++++++++++++++++++++------------- 1 file changed, 111 insertions(+), 45 deletions(-) diff --git a/lib/gpi/update.py b/lib/gpi/update.py index 0120306a..24336a2c 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -26,6 +26,7 @@ # program. import os +import re import sys import json import subprocess @@ -81,80 +82,97 @@ def loadsByCharacter(self): pass return out -class UpdateWindow(QtGui.QWidget): - - def __init__(self): - super().__init__() - - okButton = QtGui.QPushButton("OK") - cancelButton = QtGui.QPushButton("Cancel") - - hbox = QtGui.QHBoxLayout() - hbox.addStretch(1) - hbox.addWidget(okButton) - hbox.addWidget(cancelButton) - - vbox = QtGui.QVBoxLayout() - vbox.addStretch(1) - vbox.addLayout(hbox) - - self.setLayout(vbox) - self.setGeometry(300, 300, 250, 150) - self.setWindowTitle('GPI Update') - self.show() - self.raise_() - -class CondaUpdater(object): - # use conda to update to the latest package +# use conda to update to the latest package +class CondaUpdater(QtCore.QObject): + pdone = Signal(int) def __init__(self, conda_prefix=ANACONDA_PREFIX): + super().__init__() self._conda_prefix = conda_prefix self._channel = 'nckz' self._packages = ['gpi', 'gpi-core-nodes', 'gpi-docs'] + self._packages_for_installation = [] + self._packages_for_update = [] + + self._current_versions = {} + self._latest_versions = {} + self.checkConda() + def _status_pdone(self, pct, cr=False): + end = '' + if pct == 100: + end = '\n' + print('\tSearching for package updates: ', pct, '%\r', end=end) + self.pdone.emit(pct) + + def getStatus(self): + + # total divisions are len(self._packages)*3 + pdone = 0 + divs = len(self._packages)*3 + 1 + step = int(100/divs) + self._status_pdone(pdone) + # Check for the current installed versions - self._current_versions = {} for pkg in self._packages: self._current_versions[pkg] = self.getInstalledPkgVersion(pkg) + pdone += step + self._status_pdone(pdone) # Check for the latest versions online - self._latest_versions = {} for pkg in self._packages: if self._current_versions[pkg] is None: self._latest_versions[pkg] = self.updatePkg(pkg, self._channel, dry_run=True, install=True) else: self._latest_versions[pkg] = self.updatePkg(pkg, self._channel, dry_run=True) + pdone += step + self._status_pdone(pdone) + + # Sort targets into 'install' or 'update' + for pkg in self._packages: + # updates - if there is both an installed version and new version + if (self._latest_versions[pkg] is not None) and \ + (self._current_versions[pkg] is not None): + self._packages_for_update.append(pkg) + # installs - if there is no installed version, the latest will be + # whatever is available. + if (self._latest_versions[pkg] is not None) and \ + (self._current_versions[pkg] is None): + self._packages_for_installation.append(pkg) + pdone += step + self._status_pdone(pdone) + + self._status_pdone(100) def __str__(self): msg = '' # updates - if len([ pkg for pkg in self._packages \ - if (self._latest_versions[pkg] is not None) and \ - (self._current_versions[pkg] is not None) ]): + if len(self._packages_for_update): msg += 'The following packages will be updated:\n' - for pkg in self._packages: + for pkg in self._packages_for_update: o = self._current_versions[pkg] n = self._latest_versions[pkg] msg += '\t'+str(o) + ' => ' + str(n) + '\n' # installs - if len([ pkg for pkg in self._packages \ - if (self._latest_versions[pkg] is not None) and \ - (self._current_versions[pkg] is None) ]): + if len(self._packages_for_installation): msg += 'The following packages will be installed:\n' - for pkg in self._packages: + for pkg in self._packages_for_installation: n = self._latest_versions[pkg] msg += '\t'+str(n) + '\n' if msg == '': - msg = 'GPI is totes up to date.' + msg = 'GPI is up to date.' return msg + def statusMessage(self): + return str(self) + def checkConda(self): cmd = self._conda_prefix+'/conda --version >/dev/null 2>&1' try: @@ -169,24 +187,46 @@ def checkConda(self): raise def getInstalledPkgVersion(self, name): - cmd = self._conda_prefix+'/conda list -f '+name+' --json' + cmd = self._conda_prefix+'/conda list --json' try: output = subprocess.check_output(cmd, shell=True).decode('utf8') conda = JSONStreamLoads(output).objects()[-1] - return conda[-1] + for pkg in conda: + m = re.match('('+name+')-([0-9]+\.*[0-9]*\.*[0-9]*)-(.*)', pkg) + if m: + return pkg except: print('Failed to retrieve installed package information on '+name+', skipping...') print(cmd) + raise + + def _updateAllPkgs_pdone(self, pct, cr=False): + end = '' + if pct == 100: + end = '\n' + print('\tUpdating packages: ', pct, '%\r', end=end) + self.pdone.emit(pct) def updateAllPkgs(self): + # total divisions are the installation list plus the update list + pdone = 0 + divs = len(self._packages_for_installation) + len(self._packages_for_update) + 1 + step = int(100/divs) + self._updateAllPkgs_pdone(pdone) + # Install or update all the packages that have been determined. - for pkg in self._packages: + for pkg in self._packages_for_installation: # if there is no package (due to user changes) then install it - if self._current_versions[pkg] is None: - self.updatePkg(pkg, self._channel, install=True) + self.updatePkg(pkg, self._channel, install=True) + pdone += step + self._updateAllPkgs_pdone(pdone) + for pkg in self._packages_for_update: # if there is a latest version then update - if self._latest_versions[pkg] is not None: - self.updatePkg(pkg, self._channel) + self.updatePkg(pkg, self._channel) + pdone += step + self._updateAllPkgs_pdone(pdone) + + self._updateAllPkgs_pdone(100) def updatePkg(self, name, channel, dry_run=False, install=False): # Updates to the latest package and returns the package string. @@ -211,7 +251,7 @@ def updatePkg(self, name, channel, dry_run=False, install=False): if pkg.startswith(name): return pkg.split()[0] else: - raise RuntimeError('conda returned a failed fetch.') + raise RuntimeError('conda returned a failure status.') except subprocess.CalledProcessError as e: print('Failed to update to new package, aborting...') print(e.cmd, e.output) @@ -221,10 +261,36 @@ def updatePkg(self, name, channel, dry_run=False, install=False): print(cmd) raise +class UpdateWindow(QtGui.QWidget): + + def __init__(self): + super().__init__() + + okButton = QtGui.QPushButton("OK") + cancelButton = QtGui.QPushButton("Cancel") + + hbox = QtGui.QHBoxLayout() + hbox.addStretch(1) + hbox.addWidget(okButton) + hbox.addWidget(cancelButton) + + vbox = QtGui.QVBoxLayout() + vbox.addStretch(1) + vbox.addLayout(hbox) + + self.setLayout(vbox) + + self.setGeometry(300, 300, 250, 150) + self.setWindowTitle('GPI Update') + self.show() + self.raise_() + + def update(): updater = CondaUpdater() - print(updater) + updater.getStatus() + print(updater.statusMessage()) updater.updateAllPkgs() return From e597b8790e34c84a5e6c7da017f41785b61ad382 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 30 Oct 2015 16:42:09 -0700 Subject: [PATCH 51/60] Working update GUI. --- lib/gpi/runnable.py | 39 +++++++++++++++++++++ lib/gpi/update.py | 85 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 110 insertions(+), 14 deletions(-) create mode 100644 lib/gpi/runnable.py diff --git a/lib/gpi/runnable.py b/lib/gpi/runnable.py new file mode 100644 index 00000000..b841e758 --- /dev/null +++ b/lib/gpi/runnable.py @@ -0,0 +1,39 @@ +# Copyright (C) 2014 Dignity Health +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . +# +# NO CLINICAL USE. THE SOFTWARE IS NOT INTENDED FOR COMMERCIAL PURPOSES +# AND SHOULD BE USED ONLY FOR NON-COMMERCIAL RESEARCH PURPOSES. THE +# SOFTWARE MAY NOT IN ANY EVENT BE USED FOR ANY CLINICAL OR DIAGNOSTIC +# PURPOSES. YOU ACKNOWLEDGE AND AGREE THAT THE SOFTWARE IS NOT INTENDED FOR +# USE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITY, INCLUDING BUT NOT +# LIMITED TO LIFE SUPPORT OR EMERGENCY MEDICAL OPERATIONS OR USES. LICENSOR +# MAKES NO WARRANTY AND HAS NOR LIABILITY ARISING FROM ANY USE OF THE +# SOFTWARE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITIES. + +# A quick way to spawn a thread for function objects and bound methods +# Example: +# ExecRunnable(Runnable()) + +from gpi import QtCore + +def ExecRunnable(runnable): + tp = QtCore.QThreadPool.globalInstance() + tp.start(runnable) + +class Runnable(QtCore.QRunnable): + def __init__(self, func): + super().__init__() + self.run = func + self.setAutoDelete(True) diff --git a/lib/gpi/update.py b/lib/gpi/update.py index 24336a2c..20ea494b 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -32,6 +32,8 @@ import subprocess from gpi import QtGui, QtCore, Signal +from .widgets import TextBox +from .runnable import ExecRunnable, Runnable # get the anaconda path to ensure that THIS installation is being updated ANACONDA_PREFIX = '/opt/anaconda1anaconda2anaconda3' # ANACONDA @@ -86,6 +88,9 @@ def loadsByCharacter(self): # use conda to update to the latest package class CondaUpdater(QtCore.QObject): pdone = Signal(int) + message = Signal(str) + _getStatus_done = Signal() + _updateAllPkgs_done = Signal() def __init__(self, conda_prefix=ANACONDA_PREFIX): super().__init__() @@ -105,8 +110,10 @@ def _status_pdone(self, pct, cr=False): end = '' if pct == 100: end = '\n' - print('\tSearching for package updates: ', pct, '%\r', end=end) + msg = 'Searching for package updates: '+str(pct)+'%' + print('\t'+msg+'\r', end=end) self.pdone.emit(pct) + self.message.emit('Searching for package updates...') def getStatus(self): @@ -146,6 +153,7 @@ def getStatus(self): self._status_pdone(pdone) self._status_pdone(100) + self._getStatus_done.emit() def __str__(self): msg = '' @@ -204,29 +212,39 @@ def _updateAllPkgs_pdone(self, pct, cr=False): end = '' if pct == 100: end = '\n' - print('\tUpdating packages: ', pct, '%\r', end=end) + msg = 'Updating packages: '+str(pct)+'%' + print('\t'+msg+'\r', end=end) self.pdone.emit(pct) + def numberOfUpdates(self): + return len(self._packages_for_installation) + len(self._packages_for_update) + def updateAllPkgs(self): # total divisions are the installation list plus the update list pdone = 0 - divs = len(self._packages_for_installation) + len(self._packages_for_update) + 1 + divs = self.numberOfUpdates() + 1 step = int(100/divs) self._updateAllPkgs_pdone(pdone) + message_hdr = 'Updating packages...\n\t' + # Install or update all the packages that have been determined. for pkg in self._packages_for_installation: # if there is no package (due to user changes) then install it self.updatePkg(pkg, self._channel, install=True) pdone += step self._updateAllPkgs_pdone(pdone) + self.message.emit(message_hdr+pkg) for pkg in self._packages_for_update: # if there is a latest version then update self.updatePkg(pkg, self._channel) pdone += step self._updateAllPkgs_pdone(pdone) + self.message.emit(message_hdr+pkg) self._updateAllPkgs_pdone(100) + self.message.emit('Package updates complete.') + self._updateAllPkgs_done.emit() def updatePkg(self, name, channel, dry_run=False, install=False): # Updates to the latest package and returns the package string. @@ -262,19 +280,36 @@ def updatePkg(self, name, channel, dry_run=False, install=False): raise class UpdateWindow(QtGui.QWidget): - + _startGetStatus = Signal() + def __init__(self): super().__init__() - okButton = QtGui.QPushButton("OK") - cancelButton = QtGui.QPushButton("Cancel") + self._updater = CondaUpdater() + self._updater._getStatus_done.connect(self.showStatus) + self._updater._getStatus_done.connect(self._showOKorUpdateButton) + self._updater._updateAllPkgs_done.connect(self._showCloseButton) + + self._pbar = QtGui.QProgressBar(self) + self._updater.pdone.connect(self._pdone) + + self._txtbox = TextBox('') + self._updater.message.connect(self._txtbox.set_val) + self._txtbox.set_val('Checking for updates...') + + self._okButton = QtGui.QPushButton("OK") + self._okButton.setVisible(False) + self._cancelButton = QtGui.QPushButton("Cancel") + self._cancelButton.clicked.connect(self.close) hbox = QtGui.QHBoxLayout() hbox.addStretch(1) - hbox.addWidget(okButton) - hbox.addWidget(cancelButton) + hbox.addWidget(self._okButton) + hbox.addWidget(self._cancelButton) vbox = QtGui.QVBoxLayout() + vbox.addWidget(self._txtbox) + vbox.addWidget(self._pbar) vbox.addStretch(1) vbox.addLayout(hbox) @@ -284,17 +319,39 @@ def __init__(self): self.setWindowTitle('GPI Update') self.show() self.raise_() + + ExecRunnable(Runnable(self._updater.getStatus)) + def _installUpdates(self): + self._okButton.setVisible(False) + ExecRunnable(Runnable(self._updater.updateAllPkgs)) -def update(): + def _pdone(self, pct): + self._pbar.setValue(pct) + if pct < 100: + self._pbar.setVisible(True) + else: + self._pbar.setVisible(False) + self._pbar.setValue(0) - updater = CondaUpdater() - updater.getStatus() - print(updater.statusMessage()) - updater.updateAllPkgs() + def showStatus(self): + self._txtbox.set_val(self._updater.statusMessage()) - return + def _showOKorUpdateButton(self): + if self._updater.numberOfUpdates(): + self._okButton.setText('Update') + self._okButton.setVisible(True) + self._okButton.clicked.connect(self._installUpdates) + else: + self._okButton.setVisible(True) + self._okButton.clicked.connect(self.close) + def _showCloseButton(self): + self._okButton.setVisible(False) + self._cancelButton.setText('Close') + +# For running as a separate application. +def update(): app = QtGui.QApplication(sys.argv) win = UpdateWindow() sys.exit(app.exec_()) From af33491a3e73d623fb5bcecee7e1a24eec0d7f4c Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 30 Oct 2015 19:28:07 -0700 Subject: [PATCH 52/60] Changed 'Cancel' to 'Close' if there are no updates. --- lib/gpi/update.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gpi/update.py b/lib/gpi/update.py index 20ea494b..fffa0522 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -343,8 +343,8 @@ def _showOKorUpdateButton(self): self._okButton.setVisible(True) self._okButton.clicked.connect(self._installUpdates) else: - self._okButton.setVisible(True) - self._okButton.clicked.connect(self.close) + self._okButton.setVisible(False) + self._cancelButton.setText('Close') def _showCloseButton(self): self._okButton.setVisible(False) From 0f3f8ddb161153547b732c5be4ebd520106ac69b Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 30 Oct 2015 20:16:10 -0700 Subject: [PATCH 53/60] Allow textbox to stretch. --- lib/gpi/update.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/gpi/update.py b/lib/gpi/update.py index fffa0522..329bbea4 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -308,9 +308,8 @@ def __init__(self): hbox.addWidget(self._cancelButton) vbox = QtGui.QVBoxLayout() - vbox.addWidget(self._txtbox) + vbox.addWidget(self._txtbox, 1) vbox.addWidget(self._pbar) - vbox.addStretch(1) vbox.addLayout(hbox) self.setLayout(vbox) From 153eb0bb14d70f3cdf5c2fc98f29eb9d8da89ce6 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 30 Oct 2015 20:42:07 -0700 Subject: [PATCH 54/60] Made the progress bar look more like the textbox. --- lib/gpi/update.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/gpi/update.py b/lib/gpi/update.py index 329bbea4..59ec3393 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -121,7 +121,7 @@ def getStatus(self): pdone = 0 divs = len(self._packages)*3 + 1 step = int(100/divs) - self._status_pdone(pdone) + self._status_pdone(1) # Check for the current installed versions for pkg in self._packages: @@ -160,7 +160,7 @@ def __str__(self): # updates if len(self._packages_for_update): - msg += 'The following packages will be updated:\n' + msg += 'The following packages will be updated:\n\n' for pkg in self._packages_for_update: o = self._current_versions[pkg] n = self._latest_versions[pkg] @@ -168,7 +168,7 @@ def __str__(self): # installs if len(self._packages_for_installation): - msg += 'The following packages will be installed:\n' + msg += 'The following packages will be installed:\n\n' for pkg in self._packages_for_installation: n = self._latest_versions[pkg] msg += '\t'+str(n) + '\n' @@ -224,7 +224,7 @@ def updateAllPkgs(self): pdone = 0 divs = self.numberOfUpdates() + 1 step = int(100/divs) - self._updateAllPkgs_pdone(pdone) + self._updateAllPkgs_pdone(1) message_hdr = 'Updating packages...\n\t' @@ -290,7 +290,22 @@ def __init__(self): self._updater._getStatus_done.connect(self._showOKorUpdateButton) self._updater._updateAllPkgs_done.connect(self._showCloseButton) + style = ''' + QProgressBar { + background-color: rgb(226,226,226); + border: 1px solid rgb(222,222,222); + border-radius: 3px; + text-align: center; + } + + QProgressBar::chunk { + background-color: #0099FF; + height: 15px; + width: 1px; + } + ''' self._pbar = QtGui.QProgressBar(self) + self._pbar.setStyleSheet(style) self._updater.pdone.connect(self._pdone) self._txtbox = TextBox('') @@ -314,7 +329,7 @@ def __init__(self): self.setLayout(vbox) - self.setGeometry(300, 300, 250, 150) + self.setGeometry(300, 300, 400, 300) self.setWindowTitle('GPI Update') self.show() self.raise_() From c9d7fec0d4de530e7d253218f2dd4419fd4257f1 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 30 Oct 2015 21:45:06 -0700 Subject: [PATCH 55/60] Trying to work with the seemingly fixed chunk border radius. -2pix on the background border radius looks more like the chunk border radius --- lib/gpi/update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gpi/update.py b/lib/gpi/update.py index 59ec3393..7fec335a 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -294,7 +294,7 @@ def __init__(self): QProgressBar { background-color: rgb(226,226,226); border: 1px solid rgb(222,222,222); - border-radius: 3px; + border-radius: 2px; text-align: center; } From 4eba1924c599061d889168b8d527faffaace43bd Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 30 Oct 2015 22:14:53 -0700 Subject: [PATCH 56/60] Added updater option to main menu. --- lib/gpi/mainWindow.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/gpi/mainWindow.py b/lib/gpi/mainWindow.py index b748a9f4..9bcf5e67 100644 --- a/lib/gpi/mainWindow.py +++ b/lib/gpi/mainWindow.py @@ -40,6 +40,7 @@ from .logger import manager from .widgets import DisplayBox, TextBox, TextEdit from .sysspecs import Specs +from .update import UpdateWindow # start logger for this module log = manager.getLogger(__name__) @@ -452,12 +453,20 @@ def createMenus(self): self.helpMenu = QtGui.QMenu("&Help", self) aboutAction = self.helpMenu.addAction("&About") self.connect(aboutAction, QtCore.SIGNAL("triggered()"), self.about) + self.checkForUpdate = QtGui.QAction("Check For Updates...", self, triggered=self.openUpdater) + self.checkForUpdate.setMenuRole(QtGui.QAction.ApplicationSpecificRole) + self.helpMenu.addAction(self.checkForUpdate) self.helpMenu_openDocs = QtGui.QAction("Documentation", self, triggered=self.openWebsite) self.helpMenu.addAction(self.helpMenu_openDocs) self.helpMenu_openDocs = QtGui.QAction("Examples", self, triggered=self.openExamplesFolder) self.helpMenu.addAction(self.helpMenu_openDocs) self.menuBar().addMenu(self.helpMenu) + def openUpdater(self): + self._updateWin = UpdateWindow() + self._updateWin.show() + self._updateWin.raise_() + # TODO: move this and others like it to a common help-object that can errorcheck. def openWebsite(self): if not QtGui.QDesktopServices.openUrl(QtCore.QUrl('http://docs.gpilab.com')): From c5f23d380e118156ca2cb90b155b99a0187f7848 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 30 Oct 2015 22:59:10 -0700 Subject: [PATCH 57/60] Added relaunch to updater. --- lib/gpi/mainWindow.py | 2 +- lib/gpi/update.py | 29 +++++++++++++++++++++-------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/lib/gpi/mainWindow.py b/lib/gpi/mainWindow.py index 9bcf5e67..9df88e54 100644 --- a/lib/gpi/mainWindow.py +++ b/lib/gpi/mainWindow.py @@ -463,7 +463,7 @@ def createMenus(self): self.menuBar().addMenu(self.helpMenu) def openUpdater(self): - self._updateWin = UpdateWindow() + self._updateWin = UpdateWindow(dry_run=False) self._updateWin.show() self._updateWin.raise_() diff --git a/lib/gpi/update.py b/lib/gpi/update.py index 7fec335a..128eae28 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -92,8 +92,9 @@ class CondaUpdater(QtCore.QObject): _getStatus_done = Signal() _updateAllPkgs_done = Signal() - def __init__(self, conda_prefix=ANACONDA_PREFIX): + def __init__(self, conda_prefix=ANACONDA_PREFIX, dry_run=False): super().__init__() + self._dry_run = dry_run self._conda_prefix = conda_prefix self._channel = 'nckz' self._packages = ['gpi', 'gpi-core-nodes', 'gpi-docs'] @@ -173,6 +174,10 @@ def __str__(self): n = self._latest_versions[pkg] msg += '\t'+str(n) + '\n' + if self.numberOfUpdates(): + msg += '\nGPI will be automatically restarted after updating.' \ + + ' Make sure your networks are saved before proceeding.' + if msg == '': msg = 'GPI is up to date.' @@ -220,6 +225,11 @@ def numberOfUpdates(self): return len(self._packages_for_installation) + len(self._packages_for_update) def updateAllPkgs(self): + if self._dry_run: + self.message.emit('Package updates complete.') + self._updateAllPkgs_done.emit() + return + # total divisions are the installation list plus the update list pdone = 0 divs = self.numberOfUpdates() + 1 @@ -282,13 +292,13 @@ def updatePkg(self, name, channel, dry_run=False, install=False): class UpdateWindow(QtGui.QWidget): _startGetStatus = Signal() - def __init__(self): + def __init__(self, dry_run=False): super().__init__() - self._updater = CondaUpdater() + self._updater = CondaUpdater(dry_run=dry_run) self._updater._getStatus_done.connect(self.showStatus) self._updater._getStatus_done.connect(self._showOKorUpdateButton) - self._updater._updateAllPkgs_done.connect(self._showCloseButton) + self._updater._updateAllPkgs_done.connect(self._relaunchGPI) style = ''' QProgressBar { @@ -309,6 +319,8 @@ def __init__(self): self._updater.pdone.connect(self._pdone) self._txtbox = TextBox('') + self._txtbox.set_wordwrap(True) + self._txtbox.set_openExternalLinks(True) self._updater.message.connect(self._txtbox.set_val) self._txtbox.set_val('Checking for updates...') @@ -353,16 +365,17 @@ def showStatus(self): def _showOKorUpdateButton(self): if self._updater.numberOfUpdates(): - self._okButton.setText('Update') + self._okButton.setText('Update && Relaunch') self._okButton.setVisible(True) self._okButton.clicked.connect(self._installUpdates) else: self._okButton.setVisible(False) self._cancelButton.setText('Close') - def _showCloseButton(self): - self._okButton.setVisible(False) - self._cancelButton.setText('Close') + def _relaunchGPI(self): + args = sys.argv[:] + args.insert(0, sys.executable) + os.execv(sys.executable, args) # For running as a separate application. def update(): From ca37967ca1d1c64f0a281006a0a13f3f670566f1 Mon Sep 17 00:00:00 2001 From: nick Date: Fri, 30 Oct 2015 23:54:14 -0700 Subject: [PATCH 58/60] Cleaned up display text. -added rich text to the update display --- lib/gpi/update.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/gpi/update.py b/lib/gpi/update.py index 128eae28..df1b5d4f 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -158,24 +158,27 @@ def getStatus(self): def __str__(self): msg = '' + tab = '    ' # updates if len(self._packages_for_update): - msg += 'The following packages will be updated:\n\n' + msg += 'The following packages will be updated:

' for pkg in self._packages_for_update: o = self._current_versions[pkg] n = self._latest_versions[pkg] - msg += '\t'+str(o) + ' => ' + str(n) + '\n' + msg += tab+str(o) + '  ➪  ' + str(n) + '
' # installs if len(self._packages_for_installation): - msg += 'The following packages will be installed:\n\n' + if msg != '': + msg += '

' + msg += 'The following packages will be installed:

' for pkg in self._packages_for_installation: n = self._latest_versions[pkg] - msg += '\t'+str(n) + '\n' + msg += tab+str(n) + '
' if self.numberOfUpdates(): - msg += '\nGPI will be automatically restarted after updating.' \ + msg += '

GPI will be automatically restarted after updating.' \ + ' Make sure your networks are saved before proceeding.' if msg == '': @@ -236,7 +239,8 @@ def updateAllPkgs(self): step = int(100/divs) self._updateAllPkgs_pdone(1) - message_hdr = 'Updating packages...\n\t' + tab = '    ' + message_hdr = 'Updating packages...
'+tab # Install or update all the packages that have been determined. for pkg in self._packages_for_installation: @@ -319,6 +323,7 @@ def __init__(self, dry_run=False): self._updater.pdone.connect(self._pdone) self._txtbox = TextBox('') + self._txtbox.wdg.setTextFormat(QtCore.Qt.RichText) self._txtbox.set_wordwrap(True) self._txtbox.set_openExternalLinks(True) self._updater.message.connect(self._txtbox.set_val) From bc7b8e9f709deedb8185858075098cabbd53c37b Mon Sep 17 00:00:00 2001 From: nick Date: Sat, 31 Oct 2015 00:16:59 -0700 Subject: [PATCH 59/60] Added final success message before relaunching. --- lib/gpi/update.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/gpi/update.py b/lib/gpi/update.py index df1b5d4f..36420c7a 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -29,6 +29,7 @@ import re import sys import json +import time import subprocess from gpi import QtGui, QtCore, Signal @@ -229,7 +230,7 @@ def numberOfUpdates(self): def updateAllPkgs(self): if self._dry_run: - self.message.emit('Package updates complete.') + self.message.emit('Package updates complete. Relaunching...') self._updateAllPkgs_done.emit() return @@ -257,7 +258,7 @@ def updateAllPkgs(self): self.message.emit(message_hdr+pkg) self._updateAllPkgs_pdone(100) - self.message.emit('Package updates complete.') + self.message.emit('Package updates complete. Relaunching...') self._updateAllPkgs_done.emit() def updatePkg(self, name, channel, dry_run=False, install=False): @@ -346,7 +347,7 @@ def __init__(self, dry_run=False): self.setLayout(vbox) - self.setGeometry(300, 300, 400, 300) + self.setGeometry(300, 300, 400, 350) self.setWindowTitle('GPI Update') self.show() self.raise_() @@ -378,6 +379,9 @@ def _showOKorUpdateButton(self): self._cancelButton.setText('Close') def _relaunchGPI(self): + self.update() + QtGui.QApplication.processEvents() # allow gui to update + time.sleep(5) args = sys.argv[:] args.insert(0, sys.executable) os.execv(sys.executable, args) From 7ddd1f27da975bcc52360ff16bd902f33b0bcf07 Mon Sep 17 00:00:00 2001 From: nick Date: Sat, 31 Oct 2015 12:48:15 -0700 Subject: [PATCH 60/60] Added error handling to UpdateWindow() and updated public methods. --- lib/gpi/update.py | 65 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/lib/gpi/update.py b/lib/gpi/update.py index 36420c7a..570bb707 100755 --- a/lib/gpi/update.py +++ b/lib/gpi/update.py @@ -90,8 +90,9 @@ def loadsByCharacter(self): class CondaUpdater(QtCore.QObject): pdone = Signal(int) message = Signal(str) - _getStatus_done = Signal() - _updateAllPkgs_done = Signal() + failed = Signal(str) + getStatus_done = Signal() + updateAllPkgs_done = Signal() def __init__(self, conda_prefix=ANACONDA_PREFIX, dry_run=False): super().__init__() @@ -106,7 +107,6 @@ def __init__(self, conda_prefix=ANACONDA_PREFIX, dry_run=False): self._current_versions = {} self._latest_versions = {} - self.checkConda() def _status_pdone(self, pct, cr=False): end = '' @@ -118,6 +118,15 @@ def _status_pdone(self, pct, cr=False): self.message.emit('Searching for package updates...') def getStatus(self): + try: + self._getStatus() + self.getStatus_done.emit() + except: + self.failed.emit('Failed to fetch updates.') + + def _getStatus(self): + + self.checkConda() # total divisions are len(self._packages)*3 pdone = 0 @@ -155,7 +164,6 @@ def getStatus(self): self._status_pdone(pdone) self._status_pdone(100) - self._getStatus_done.emit() def __str__(self): msg = '' @@ -229,9 +237,15 @@ def numberOfUpdates(self): return len(self._packages_for_installation) + len(self._packages_for_update) def updateAllPkgs(self): + try: + self._updateAllPkgs() + self.updateAllPkgs_done.emit() + except: + self.failed.emit('Failed to install updates.') + + def _updateAllPkgs(self): if self._dry_run: self.message.emit('Package updates complete. Relaunching...') - self._updateAllPkgs_done.emit() return # total divisions are the installation list plus the update list @@ -259,7 +273,6 @@ def updateAllPkgs(self): self._updateAllPkgs_pdone(100) self.message.emit('Package updates complete. Relaunching...') - self._updateAllPkgs_done.emit() def updatePkg(self, name, channel, dry_run=False, install=False): # Updates to the latest package and returns the package string. @@ -301,9 +314,10 @@ def __init__(self, dry_run=False): super().__init__() self._updater = CondaUpdater(dry_run=dry_run) - self._updater._getStatus_done.connect(self.showStatus) - self._updater._getStatus_done.connect(self._showOKorUpdateButton) - self._updater._updateAllPkgs_done.connect(self._relaunchGPI) + self._updater.getStatus_done.connect(self.showStatus) + self._updater.getStatus_done.connect(self.showOKorUpdateButton) + self._updater.failed.connect(self.initFailureMode) + self._updater.updateAllPkgs_done.connect(self.relaunchProc) style = ''' QProgressBar { @@ -321,7 +335,7 @@ def __init__(self, dry_run=False): ''' self._pbar = QtGui.QProgressBar(self) self._pbar.setStyleSheet(style) - self._updater.pdone.connect(self._pdone) + self._updater.pdone.connect(self.pdone) self._txtbox = TextBox('') self._txtbox.wdg.setTextFormat(QtCore.Qt.RichText) @@ -354,11 +368,11 @@ def __init__(self, dry_run=False): ExecRunnable(Runnable(self._updater.getStatus)) - def _installUpdates(self): + def installUpdates(self): self._okButton.setVisible(False) ExecRunnable(Runnable(self._updater.updateAllPkgs)) - def _pdone(self, pct): + def pdone(self, pct): self._pbar.setValue(pct) if pct < 100: self._pbar.setVisible(True) @@ -369,22 +383,31 @@ def _pdone(self, pct): def showStatus(self): self._txtbox.set_val(self._updater.statusMessage()) - def _showOKorUpdateButton(self): + def initFailureMode(self, msg): + self._pbar.setVisible(False) + self._txtbox.set_val(msg) + self._okButton.setVisible(False) + self._cancelButton.setText('Close') + + def showOKorUpdateButton(self): if self._updater.numberOfUpdates(): self._okButton.setText('Update && Relaunch') self._okButton.setVisible(True) - self._okButton.clicked.connect(self._installUpdates) + self._okButton.clicked.connect(self.installUpdates) else: self._okButton.setVisible(False) self._cancelButton.setText('Close') - def _relaunchGPI(self): - self.update() - QtGui.QApplication.processEvents() # allow gui to update - time.sleep(5) - args = sys.argv[:] - args.insert(0, sys.executable) - os.execv(sys.executable, args) + def relaunchProc(self): + try: + self.update() + QtGui.QApplication.processEvents() # allow gui to update + time.sleep(5) + args = sys.argv[:] + args.insert(0, sys.executable) + os.execv(sys.executable, args) + except: + self.initFailureMode('Failed to relaunch GPI.') # For running as a separate application. def update():