Dear Xin,
  Sorry, I was wrong in the last reply, the command line argument
problem was just bad result of cut and paste from the email.

However, I have found a couple of issues which are hopefully addressed
in the two attached files.

1) A change to ProvideAsuContents.py to work without a database. This
should replace the file:
/home/programs/ccp4-9/lib/python3.9/site-packages/ccp4i2/wrappers/ProvideAsuContents/script/ProvideAsuContents.py

2) i2run was simply not exiting properly after finishing. The attached
CCP4I2Runner.py should fix this. This belongs in:
/home/programs/ccp4-9/lib/python3.9/site-packages/ccp4i2/core/CCP4I2Runner.py

Please let me know if these do or not fix the problem.

Best wishes,
Stuart

On Fri, 30 Aug 2024 at 08:26, Stuart McNicholas
<stuart.mcnicho...@york.ac.uk> wrote:
>
> Dear Xin,
>   It appears that the command line arguments are not being parsed
> properly. I am looking into this right now. I hope to have a fix for
> you very shortly.
>
> Best wishes,
> Stuart
>
> On Fri, 30 Aug 2024 at 06:30, zx2...@connect.hku.hk
> <zx2...@connect.hku.hk> wrote:
> >
> > Hi all,
> >
> > I am experiencing some issues with i2run command after updating CCP4 to 
> > version 9.0.
> >
> > The command I used is as follows:
> >
> > $CCP4/lib/python3.9/site-packages/ccp4i2/bin/i2run ProvideAsuContents \
> > --ASU_CONTENT \
> > sequence=${sequence} \
> >  nCopies=1 \
> >  polymerType=PROTEIN \
> > --noDb > ASU.log
> >
> > However, I am receiving the following error message:
> >
> > Traceback (most recent call last):
> > File 
> > "/home/programs/ccp4-9/lib/python3.9/site-packages/ccp4i2/core/CCP4I2Runner.py",
> >  line 740, in <module>
> > threads = app.findChildren(QtCore.QThread)
> > AttributeError: 'NoneType' object has no attribute 'findChildren'
> >
> > I was wondering if you could provide some suggestions on how to solve this 
> > problem.
> >
> > Thank you very much for your time and assistance.
> >
> > Best regards,
> > Xin
> >
> > ________________________________
> >
> > To unsubscribe from the CCP4BB list, click the following link:
> > https://www.jiscmail.ac.uk/cgi-bin/WA-JISC.exe?SUBED1=CCP4BB&A=1

########################################################################

To unsubscribe from the CCP4BB list, click the following link:
https://www.jiscmail.ac.uk/cgi-bin/WA-JISC.exe?SUBED1=CCP4BB&A=1

This message was issued to members of www.jiscmail.ac.uk/CCP4BB, a mailing list 
hosted by www.jiscmail.ac.uk, terms & conditions are available at 
https://www.jiscmail.ac.uk/policyandsecurity/
from core.CCP4PluginScript import CPluginScript
from core import CCP4ModelData
import os,sys
import shutil
from lxml import etree

class ProvideAsuContents(CPluginScript):

    TASKNAME = 'ProvideAsuContents'                        # Task name - should be same as class name
    RUNEXTERNALPROCESS=False
   
    def startProcess(self, command, **kw):
      asuFileObject = self.container.outputData.ASUCONTENTFILE
      asuFileObject.fileContent.seqList.set(self.container.inputData.ASU_CONTENT)
      if self._dbHandler:
          asuFileObject.saveFile(  { 'projectName': self._dbHandler.projectName,
                                                                  'projectId' : self._dbHandler.projectId,
                                                                  'jobId' : None,
                                                                  'jobNumber' : None } )

      xmlroot = etree.Element('ASUCONTENTMATTHEWS')
      totWeight = 0.0
      if len(self.container.inputData.ASU_CONTENT) > 0:
          entries = etree.SubElement(xmlroot,"entries")
          polymerMode = ""
          for seqObj in self.container.inputData.ASU_CONTENT:
              if seqObj.nCopies > 0:
                  if seqObj.polymerType == "PROTEIN":
                      if polymerMode == "D":
                          polymerMode = "C"
                      elif polymerMode == "":
                          polymerMode = "P"
                  if seqObj.polymerType in ["DNA","RNA"]:
                      if polymerMode == "P":
                          polymerMode = "C"
                      elif polymerMode == "":
                          polymerMode = "D"
              totWeight = totWeight + seqObj.molecularWeight(seqObj.polymerType)
              entry = etree.SubElement(entries,"entry")
              nCopies = etree.SubElement(entry,"copies")
              name = etree.SubElement(entry,"name")
              weight = etree.SubElement(entry,"weight")
              sequence = etree.SubElement(entry,"sequence")
              nCopies.text = str(seqObj.nCopies)
              name.text = str(seqObj.name)
              weight.text = "{0:.1f}".format(float(seqObj.molecularWeight(seqObj.polymerType)))
              sequence.text = str(seqObj.sequence)
          totalWeightTag = etree.SubElement(xmlroot,"totalWeight")
          totalWeightTag.text = str(totWeight)

      if self.container.inputData.HKLIN.isSet() and len(self.container.inputData.ASU_CONTENT) > 0:
          if totWeight > 1e-6:
              rv = self.container.inputData.HKLIN.fileContent.matthewsCoeff(molWt=totWeight,polymerMode=polymerMode)
              vol = rv.get('cell_volume','Unkown')
              volumeTag = etree.SubElement(xmlroot,"cellVolume")
              volumeTag.text = str(vol)
              matthewsComposition = etree.SubElement(xmlroot,"matthewsCompositions")
              for result in rv.get('results',[]):
                  comp = etree.SubElement(matthewsComposition,"composition")
                  nMolecules = etree.SubElement(comp,"nMolecules")
                  solventPercentage = etree.SubElement(comp,"solventPercentage")
                  matthewsCoeff = etree.SubElement(comp,"matthewsCoeff")
                  matthewsProbability = etree.SubElement(comp,"matthewsProbability")
                  nMolecules.text = str(result.get('nmol_in_asu'))
                  solventPercentage.text = "{0:.2f}".format(float(result.get('percent_solvent')))
                  matthewsCoeff.text = "{0:.2f}".format(float(result.get('matth_coef')))
                  matthewsProbability.text = "{0:.2f}".format(float(result.get('prob_matth')))

      newXml = etree.tostring(xmlroot,pretty_print=True)
      with open (self.makeFileName('PROGRAMXML')+'_tmp','w') as programXmlFile:
          if sys.version_info > (3,0):
              programXmlFile.write(newXml.decode("utf-8"))
          else:
              programXmlFile.write(newXml)
      shutil.move(self.makeFileName('PROGRAMXML')+'_tmp', self.makeFileName('PROGRAMXML'))
          
              
      return CPluginScript.SUCCEEDED
from __future__ import print_function

import argparse
import sys
import os
import traceback
import time
from xml.etree import ElementTree as ET
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from core import CCP4TaskManager
from core import CCP4Config
from core import CCP4File
from core import CCP4Data
from core import CCP4ModelData
from core import CCP4XtalData
if sys.platform == "win32":
    import ccp4mg
    import hklfile
else:
    from ccp4mg import hklfile
from core import CCP4Modules
#from lxml import etree
from xml.etree import ElementTree as ET
#import clipper
import gemmi
import numpy
import re
from core.CCP4ErrorHandling import CException

class CI2Runner(object):
    def __init__(self, cmdLineArgs, theParser=None):
        super(CI2Runner, self).__init__()
        self.defXml = None
        if not theParser:
            theParser = argparse.ArgumentParser(description='C2Runner')
        self.add_arguments(theParser, cmdLineArgs)
        self.namespace = theParser.parse_args(cmdLineArgs)
        self.asuFiles = {}

    def availableNameBasedOn(self, filePath):
        if not os.path.exists(filePath): return filePath
        if "." in filePath:
            #Clumsy thing to deal with double dotted extensions like .scene.xml
            basePath = filePath.split(".")[0]
            extension = "."+".".join(filePath.split(".")[1:])
        else:
            basePath = filePath
            extension = ""
        counter = 1
        while os.path.exists(basePath+"_"+str(counter)+extension):
            counter += 1
        return basePath+"_"+str(counter)+extension

    def smartSplitMTZ(self, inputFilePath=None, inputColumnPath=None, objectPath=None, intoDirectory=None):
        if inputFilePath is None: raise Exception("smartSplitMTZ Exception:", "Must provide an input file")
        if not os.path.isfile(inputFilePath): raise Exception("smartSplitMTZ Exception:", "inputFile must exist"+str(inputFilePath))
        if inputColumnPath is None: raise Exception("smartSplitMTZ Exception:", "Must provide an input columnPath e.g. '/*/*/[F,SIGFP]'")
        if objectPath is not None and intoDirectory is not None: raise Exception("smartSplitMTZ Exception:", "Provide either full output path for file, or name of directory where file should be placed")
        if objectPath is  None and intoDirectory is  None: raise Exception("smartSplitMTZ Exception:", "Provide either full output path for file, or name of directory where file should be placed")
        

        mtz_file = clipper.CCP4MTZfile()
        hkl_info = clipper.HKL_info()
        mtz_file.open_read (inputFilePath)
        mtz_file.import_hkl_info ( hkl_info )
        xtal = clipper.MTZcrystal()
        mtz_file.import_crystal( xtal, inputColumnPath )
        dataset=clipper.MTZdataset()
        mtz_file.import_dataset( dataset, inputColumnPath )
        providedColumnPaths = mtz_file.column_paths()
        
        selectedColumnLabelsExp=re.compile(r"^/(?P<XtalName>[A-Za-z0-9_. -+\*,]+)/(?P<DatasetName>[A-Za-z0-9_. -+\*,]+)/\[(?P<Columns>[A-Za-z0-9_. -+\*,]+)\]")
        columnsMatch=selectedColumnLabelsExp.search(inputColumnPath)
        selectedColumnLabelExp=re.compile(r"^/(?P<XtalName>[A-Za-z0-9_. -+\*,]+)/(?P<DatasetName>[A-Za-z0-9_. -+\*,]+)/(?P<Column>[A-Za-z0-9_. -+\*,]+)")
        columnMatch=selectedColumnLabelExp.search(inputColumnPath)
        if columnsMatch is not None:
            selectedColumnPaths  =["/{}/{}/{}".format(columnsMatch.group("XtalName"),columnsMatch.group("DatasetName"),column) for column in columnsMatch.group("Columns").split(",") ]
        elif columnMatch is not None:
            selectedColumnPaths  =["/{}/{}/{}".format(columnMatch.group("XtalName"),columnMatch.group("DatasetName"),columnMatch.group("Column"))]

        typeSignature = ""
        for selectedColumnPath in selectedColumnPaths:
            selectedColumnMatch = selectedColumnLabelExp.search(selectedColumnPath)
            for providedColumnPath in providedColumnPaths:
                #Generating clipper String and then calling str to deal with
                #Known unpredictable bug in clipper-python
                try:
                  columnName, columnType = str(clipper.String(providedColumnPath)).split(" ")
                except NotImplementedError as err:
                  columnName, columnType = str(providedColumnPath).split(" ")
                parsedColumnMatch = selectedColumnLabelExp.search(columnName)
                if ((selectedColumnMatch.group("XtalName") == "*" or selectedColumnMatch.group("XtalName") == parsedColumnMatch.group("XtalName")) and
                    (selectedColumnMatch.group("DatasetName") == "*" or selectedColumnMatch.group("DatasetName") == parsedColumnMatch.group("DatasetName")) and
                    selectedColumnMatch.group("Column") == parsedColumnMatch.group("Column")):
                    typeSignature += columnType
                    break
        #print("Type signature", typeSignature)
        if typeSignature == "FQ":
            extractedData = clipper.HKL_data_F_sigF_float(hkl_info)
            cls = CCP4XtalData.CObsDataFile
            contentType = 4
        if typeSignature == "JQ":
            extractedData = clipper.HKL_data_I_sigI_float(hkl_info)
            cls = CCP4XtalData.CObsDataFile
            contentType = 3
        if typeSignature == "GLGL" or typeSignature == "FQFQ":
            extractedData = clipper.HKL_data_F_sigF_ano_float(hkl_info)
            cls = CCP4XtalData.CObsDataFile
            contentType = 2
        if typeSignature == "KMKM"  or typeSignature == "JQJQ":
            extractedData = clipper.HKL_data_I_sigI_ano_float(hkl_info)
            cls = CCP4XtalData.CObsDataFile
            contentType = 1
        elif typeSignature == "AAAA":
            extractedData = clipper.HKL_data_ABCD_float(hkl_info)
            cls = CCP4XtalData.CPhsDataFile
            contentType = 1
        elif typeSignature == "PW":
            extractedData = clipper.HKL_data_Phi_fom_float(hkl_info)
            cls = CCP4XtalData.CPhsDataFile
            contentType = 2
        elif typeSignature == "I":
            extractedData = clipper.HKL_data_Flag(hkl_info)
            cls = CCP4XtalData.CFreeRDataFile
            contentType = 1
        outputColumnPath = "[{}]".format(','.join(getattr(cls, "CONTENT_SIGNATURE_LIST")[contentType-1]))

        mtz_file.import_hkl_data( extractedData, inputColumnPath )
        mtz_file.close_read()

        if intoDirectory is not None:
            firstGuess = os.path.join(intoDirectory,typeSignature+'_ColumnsFrom_'+os.path.split(inputFilePath)[1])
            objectPath = availableNameBasedOn(firstGuess)

        mtzout = clipper.CCP4MTZfile()
        mtzout.open_write( objectPath )
        mtzout.export_hkl_info( hkl_info )
        crystalName = clipper.String(xtal.crystal_name())
        datasetName = clipper.String(dataset.dataset_name())
        outputColumnPath = "/{}/{}/{}".format(str(crystalName), str(datasetName), outputColumnPath )
        mtzout.export_crystal( xtal, outputColumnPath )
        mtzout.export_dataset( dataset, outputColumnPath )
        mtzout.export_hkl_data( extractedData, outputColumnPath )
        mtzout.close_write()

        return objectPath

    def gemmiSplitMTZ(self, inputFilePath=None, inputColumnPath=None, objectPath=None, intoDirectory=None):
        if inputFilePath is None: raise Exception("smartSplitMTZ Exception:", "Must provide an input file")
        if not os.path.isfile(inputFilePath): raise Exception("smartSplitMTZ Exception:", "inputFile must exist"+str(inputFilePath))
        if inputColumnPath is None: raise Exception("smartSplitMTZ Exception:", "Must provide an input columnPath e.g. '/*/*/[F,SIGFP]'")
        if objectPath is not None and intoDirectory is not None: raise Exception("smartSplitMTZ Exception:", "Provide either full output path for file, or name of directory where file should be placed")
        if objectPath is  None and intoDirectory is  None: raise Exception("smartSplitMTZ Exception:", "Provide either full output path for file, or name of directory where file should be placed")

        mtzin = gemmi.read_mtz_file(inputFilePath)
        providedColumnNames = mtzin.column_labels()
        if inputColumnPath.startswith('/'): inputColumnPath = inputColumnPath[1:]
        if len(inputColumnPath.split('/')) not in [1,3]: raise Exception("smartSplitMTZ Exception:", "Invalid input columnPath")
        selectedColumns = re.sub('[\[\] ]','',inputColumnPath.split('/')[-1]).split(',')
        outputColumns = [mtzin.column_with_label(label) for label in ['H', 'K', 'L']]
        typeSignature = ''
        for columnLabel in selectedColumns:
            if providedColumnNames.count(columnLabel) == 1:
                column = mtzin.column_with_label(columnLabel)
                outputColumns.append(column)
                typeSignature += column.type
            else:
                if len(inputColumnPath.split('/')) != 3: raise Exception("smartSplitMTZ Exception:", "Input file requires full input columnPath e.g. '/crystal/dataset/[F,SIGFP]'")
                for dataset in mtzin.datasets:
                    if dataset.crystal_name == inputColumnPath.split('/')[-3] and dataset.dataset_name == inputColumnPath.split('/')[-2]:
                        column = mtzin.column_with_label(columnLabel, mtzin.dataset(dataset.id))
                        outputColumns.append(column)
                        typeSignature += column.type
        if len(outputColumns[3:]) != len(selectedColumns): raise Exception("smartSplitMTZ Exception:", "Unable to select columns from input file'")

        if intoDirectory is not None:
            firstGuess = os.path.join(intoDirectory,typeSignature+'_ColumnsFrom_'+os.path.split(inputFilePath)[1])
            objectPath = availableNameBasedOn(firstGuess)
        mtzout = gemmi.Mtz()
        mtzout.spacegroup = mtzin.spacegroup
        mtzout.cell = mtzin.cell
        mtzout.add_dataset('HKL_base')
        if len (mtzin.datasets) > 1:
            dataset = outputColumns[-1].dataset
            ds = mtzout.add_dataset(dataset.project_name)
            ds.crystal_name = dataset.crystal_name
            ds.dataset_name = dataset.dataset_name
            ds.wavelength = dataset.wavelength
        outputColumnLabels = ['H', 'K', 'L']
        labelsDict = {'FQ':{'cls':CCP4XtalData.CObsDataFile, 'contentType':4},
                    'JQ':{'cls':CCP4XtalData.CObsDataFile, 'contentType':3},
                    'GLGL':{'cls':CCP4XtalData.CObsDataFile, 'contentType':2},
                    'FQFQ':{'cls':CCP4XtalData.CObsDataFile, 'contentType':2}, # surely not
                    'KMKM':{'cls':CCP4XtalData.CObsDataFile, 'contentType':1},
                    'JQJQ':{'cls':CCP4XtalData.CObsDataFile, 'contentType':1}, # surely not
                    'AAAA':{'cls':CCP4XtalData.CPhsDataFile, 'contentType':1},
                    'PW':{'cls':CCP4XtalData.CPhsDataFile, 'contentType':2},
                    'I':{'cls':CCP4XtalData.CFreeRDataFile, 'contentType':1}
                    }
        outputColumnLabels.extend(getattr(labelsDict[typeSignature]['cls'], "CONTENT_SIGNATURE_LIST")[labelsDict[typeSignature]['contentType']-1])
        for i, column in enumerate(outputColumns):
            mtzout.add_column(outputColumnLabels[i], column.type, dataset_id=0) if i < 3 or len(mtzin.datasets) <= 1 else mtzout.add_column(outputColumnLabels[i], column.type, dataset_id=1)
        data = numpy.stack(outputColumns, axis=1)
        mtzout.set_data(data)
        mtzout.history = ['MTZ file created from {} using gemmi.'.format(os.path.basename(inputFilePath))]
        mtzout.write_to_file(objectPath)

        return objectPath
    
    def recursivelyBuildXML(self, fileName):
        #print ("In build of ", fileName)
        taskDefXML = ET.parse(fileName)
        #print (ET.tostring(taskDefXML.getroot()))
        #print fileName
        #print CCP4i2Utils.prettifyXML(ET.tostring(taskDefXML.getroot()))
        taskBodyNode = taskDefXML.getroot().findall('ccp4i2_body')[0]
        superclassDefXMLNodes = taskBodyNode.findall('file')
        
        for superclassDefXMLNode in superclassDefXMLNodes:
            #assert(superclassDefXMLNode.findall('project')[0].text == 'CCP4I2_TOP')
            #Windows fix needed here
            fullPath = os.path.join(os.path.dirname(__file__),"..",
                                    superclassDefXMLNode.findall('CI2XmlDataFile/relPath')[0].text,
                                    superclassDefXMLNode.findall('CI2XmlDataFile/baseName')[0].text)
            superclassXML = self.recursivelyBuildXML(fullPath)
            superclassBodyNode = superclassXML.getroot().findall('ccp4i2_body')[0]

            for inputFolderName in ["inputData", "controlParameters", "keywords"]:
            
                try:
                    xpath = 'ccp4i2_body/container[@id="{}"]'.format(inputFolderName)
                    classDataNode = taskDefXML.getroot().findall(xpath)[0]
                except IndexError as err:
                    classDataNode = ET.SubElement(taskBodyNode,"container",id=inputFolderName)
                
                try:
                    xpath = 'ccp4i2_body/container[@id="{}"]'.format(inputFolderName)
                    superclassXMLDataNode = superclassXML.getroot().findall(xpath)[0]
                except IndexError as err:
                    superclassXMLDataNode = ET.SubElement(superclassBodyNode,"container",id=inputFolderName)
                
                for contentNode in superclassXMLDataNode.findall('content'):
                    xpathOfContent ='content[@id="{}"]'.format(contentNode.attrib['id'])
                    if len(classDataNode.findall(xpathOfContent)) == 0:
                        classDataNode.append(contentNode)
                    else:
                        print("Not replacing", contentNode)
                        
                for containerNode in superclassXMLDataNode.findall('container'):
                    xpathOfContent ='container[@id="{}"]'.format(containerNode.attrib['id'])
                    if len(classDataNode.findall(xpathOfContent)) == 0:
                        classDataNode.append(containerNode)
                    else:
                        print("Not replacing", containerNode)
        
            taskBodyNode.remove(superclassDefXMLNode)
        #print ET.tostring(taskDefXML.getroot())
        #print "Out build of ", fileName

        return taskDefXML

    def defXMLForTaskName(self, taskName):
        cachedData = {}
        with open(os.path.join(os.path.split(__file__)[0], "DefXMLCache.json"),"r") as cacheFile:
            cachedData = json.loads(cacheFile.read())
        relPath = cachedData[taskName]
        fullPath = os.path.join(os.environ['CCP4'], relPath[1:])
        return self.recursivelyBuildXML(fullPath)
    
    def setEntityValue(self, entityToModify, valueItem):
        #print("EtoM [{}] [{}]".format(entityToModify, valueItem))
        if isinstance(entityToModify,(CCP4File.CDataFile,)) and isinstance(valueItem, (str,)):
            #Here if setting a CDataFile with a string look to see if using fileUse
            searchGroup = re.match('(?P<propertyName>[^=]*)\=(?P<propertyValue>.*)', valueItem)
            if searchGroup is not None:
                print(searchGroup.group('propertyName'), searchGroup.group('propertyValue'))
            print('setting CDataFile with string...setting fullpath')
            entityToModify.setFullPath(os.path.normpath(os.path.expandvars(valueItem)))
            print('New full path', entityToModify.getFullPath())
            return
        try:
            entityToModify.set(valueItem)
            #print("Set {} type {} to value {}".format(entityToModify, type(entityToModify), valueItem))
            #print(entityToModify.isSet())
        except CException as err:
            print("Failed to set {} type {} to value {}".format(entityToModify, type(entityToModify), valueItem))
            print(err)
            raise err
        except ValueError as err:
            print("Failed setting attribute {} to value {}".format(entityToModify, valueItem))
            print(err)
            raise err
    
    def fileUse(self, projectName, propertyValue):
        jobNumber, jobParamName = propertyValue.split(".")
        #See if jobParamName includes an array-like index
        arrayGroup = re.match('(?P<jobParamName>.*)\[(?P<arrayIndexStr>[0-9]+)\]', jobParamName)
        paramToFind = jobParamName
        oneToTake = 0
        if arrayGroup is not None:
            paramToFind = arrayGroup.group('jobParamName')
            oneToTake = int(arrayGroup.group('arrayIndexStr'))
        #print(jobParamName, paramToFind, jobNumber)
        try:
            intJobNumber = int(jobNumber)
            #print("intJobNumber", intJobNumber)
            if intJobNumber < 0:
                allJobs = self.pm.db().getProjectJobListInfo(mode=['jobnumber','taskname'], projectName=projectName, topLevelOnly=True)
                print("Corresponding job", allJobs[intJobNumber])
                #NB intJobNumber + 1 because the current job is last in this list (i.e. python index -1),
                #the previous job is -2, etc.
                jobNumber = allJobs[intJobNumber-1]['jobnumber']
                print("intJobNumber", intJobNumber)
        except:
            print("jobNumber is not int-able", jobNumber)
            
        jobId = self.pm.db().getJobInfo(projectName=projectName, jobNumber=jobNumber)['jobid']
        try:
            filesInfo = self.pm.db().getJobFilesInfo(jobId=jobId, jobParamName=paramToFind)
            return filesInfo[oneToTake]['fileId']
        except IndexError as err:
            filesInfo = self.pm.db().getJobFilesInfo(jobId=jobId, jobParamName=paramToFind, input=True)
            try:
                return filesInfo[oneToTake]['fileId']
            except IndexError as err:
                raise Exception('Failed to find a fileUse with param name {} on job with Id  {}'.format(paramToFind, jobId))
                
    def addSequenceToASU(self, cAsuDataFile, sequenceToExtract):
        #print('ASUfile [{}] [{}] [{}]'.format( cAsuDataFile.fullPath, cAsuDataFile.fullPath is None, len(cAsuDataFile.fullPath)==0,  sequenceToExtract))
        firstSequence = False
        #print("cAsuDataFile full Path is ", cAsuDataFile.fullPath)
        if cAsuDataFile.fullPath is None or len(cAsuDataFile.fullPath) == 0:
            #print("fullPath is None")
            import tempfile
            tempASUFile = tempfile.NamedTemporaryFile(delete=True, suffix=".asucontent.xml")
            tempASUFile.close()
            
            xmlFileObject = CCP4File.CI2XmlDataFile(tempASUFile.name)
            xmlFileObject.header.setCurrent()
            xmlFileObject.header.function.set('ASUCONTENT')
            #xmlFileObject.header.projectName.set(projectName)
            baseRoot=ET.Element("root")
            sequenceListRoot=ET.SubElement(baseRoot, 'seqList')
            xmlFileObject.saveFile(baseRoot)
            cAsuDataFile.setFullPath(tempASUFile.name)
            firstSequence = True
        #print('ASUfile [{}] [{}]'.format( cAsuDataFile.fullPath, sequenceToExtract))
        cAsuDataFile.loadFile()

        sequenceFile = CCP4ModelData.CSeqDataFile()
        sequenceFile.setFullPath(sequenceToExtract)
        sequenceFile.loadFile()
        
        if not firstSequence:
            cAsuDataFile.fileContent.seqList.append(cAsuDataFile.fileContent.seqList.makeItem())
        try:
            entry = cAsuDataFile.fileContent.seqList[-1]
        except IndexError as err:
            #print ("Adding a sequence element to the ASU Data file")
            cAsuDataFile.fileContent.seqList.append(cAsuDataFile.fileContent.seqList.makeItem())
            entry = cAsuDataFile.fileContent.seqList[-1]
            
        entry.nCopies = 1
        entry.sequence = sequenceFile.fileContent.sequence
        entry.name = sequenceFile.fileContent.identifier.replace(" ","_").replace("|","_").replace("/","_").replace(":","_")
        entry.description = sequenceFile.fileContent.description
        entry.autoSetPolymerType()
        cAsuDataFile.buildSelection()
        cAsuDataFile.saveFile()
        return cAsuDataFile.fullPath
        
    def extractColumns(self, inputFile, columnsToExtract, jobDirectory):
        targetPath = os.path.join(jobDirectory,os.path.basename(inputFile.getFullPath().__str__()))
        targetPath = self.availableNameBasedOn(targetPath)
        #print("targetPath", targetPath)
        self.gemmiSplitMTZ(inputFilePath=inputFile.getFullPath().__str__(),
                           inputColumnPath=columnsToExtract,
                           objectPath=targetPath)
        inputFile.setFullPath(targetPath)
        inputFile.setContentFlag(reset=True)

    def add_arguments(self, parser, cmdLineArgs):
        taskName = cmdLineArgs[1]
        #print "taskName",taskName
        #Add this to swallow the taskname which is mostly used as the first positional argument
        parser.add_argument(taskName, type=str, nargs='+',)
        #print "CCP4TaskManager.__file__",CCP4TaskManager.__file__
        self.taskManager = CCP4TaskManager.CTaskManager()
        defXmlPath = self.taskManager.searchDefFile(taskName)
        if defXmlPath is None:
            raise Exception('No defXML discovered for task with name {}'.format(taskName))
        from core import CCP4File
        defXml = self.recursivelyBuildXML(defXmlPath)
        parent_map = dict((c, p) for p in defXml.iter() for c in p)
        
        keywords = defXml.findall(".//content")
        outputKeywords = defXml.findall('.//container[@id="outputData"]/content')
        parser.add_argument('--projectName', type=str,)
        parser.add_argument('--projectPath', type=str, default=None)
        parser.add_argument('--dbFile', default=None)
        parser.add_argument('--noDb', action='store_true')
        parser.add_argument('--taskName', type=str, default=taskName)
        parser.add_argument('--jobDirectory', type=str, default=os.getcwd())
        
        for keyword in keywords:
            if keyword not in outputKeywords:
                
                argumentText = keyword.attrib['id']

                #Here handle case that the same "ultimate" content name occurs more than once,
                #presumably due to having distinct "container" nesting in the .def.xml
                
                if len(defXml.findall('.//content[@id="{}"]'.format(keyword.attrib['id']))) > 1:
                    currentNode = keyword
                    #print(parent_map[currentNode].attrib, parent_map[currentNode].attrib['id'])
                    while parent_map[currentNode].tag != "ccp4i2_body":
                        argumentText = parent_map[currentNode].attrib['id']+'.'+argumentText
                        currentNode = parent_map[currentNode]
                        #print(currentNode)
                        
                commandFlag = '--'+argumentText
                
                className = "".join([classNameNode.text for  classNameNode in  keyword.findall("className")])
                helpText = "".join([toolTipNode.text for toolTipNode in  keyword.findall("qualifiers/toolTip") if toolTipNode.text is not None])
                try:
                    if className in ["CList", "CImportUnmergedList", "CAsuContentSeqList", "CEnsembleList"]:
                        parser.add_argument(commandFlag, type=str, nargs='+', help="{}:{}".format(className, helpText), action="append")
                    else:
                        try:
                            enumerators = keyword.findall('qualifiers/enumerators')
                            parser.add_argument(commandFlag, type=str, help="{}:{}".format(className, helpText), choices=enumerators[0].text.split(","), nargs='+')
                        except:
                            parser.add_argument(commandFlag, type=str, help="{}:{}".format(className, helpText), nargs='+')
                except argparse.ArgumentError as err:
                    print("Problem handling argument ", err)
        #print ET.tostring(defXml.getroot())
        self.defXml = defXml

    def configure(self):
        kwargs = vars(self.namespace)
        #print("kwargs", kwargs)
        def etree_iter_path(node, tag=None, path='.'):
            if tag == "*":
                tag = None
            if tag is None or node.tag == tag:
                yield node, path
            for child in node:
                _child_path = '%s/%s' % (path,  child.attrib.get('id',None))
                for child, child_path in etree_iter_path(child, tag, path=_child_path):
                    yield child, child_path
    
        pathMap = {}
        for elem, path in etree_iter_path(self.defXml.getroot()):
            pathMap[elem] = path
        
        sys.path.append(os.path.dirname(os.path.dirname(__file__)))
        from core.CCP4TaskManager import CTaskManager
        theClass = CTaskManager().getPluginScriptClass(kwargs['taskName'])
        if not os.path.isdir(kwargs['jobDirectory']):
            print("Job directory {} does not exist".format(kwargs['jobDirectory']))
        
        if kwargs["noDb"]:
            theWrapper = theClass(workDirectory=kwargs['jobDirectory'])
            jobDirectory = kwargs['jobDirectory']
        else:
            from core import CCP4ProjectsManager
            from utils import startup
            CCP4ProjectsManager.CProjectsManager.insts = None
            
            self.pm = startup.startProjectsManager(dbFileName=kwargs.get('dbFile',None))
            if kwargs.get('projectPath', None) is not None:
                try:
                    projectId =  self.pm.createProject(projectName=kwargs.get('projectName',None),
                                                       projectPath=kwargs.get('projectPath',None))
                    print('Created project [{}] with name [{}] in directory [{}]'.format(projectId, kwargs.get('projectName',None), kwargs.get('projectPath',None)))
                except CException as err:
                    #print(len(err._reports))
                    if err._reports[0]['code'] == 117:
                        print("Project with this path already exists")
            projectData = self.pm.db().getProjectInfo(projectName=kwargs.get('projectName',None), projectId=kwargs.get('projectId',None))
            self.pm.db().resetLastJobNumber(projectId=projectData['projectid'])
            print('projectData', projectData)
            projectName = projectData['projectname']
            projectId = projectData['projectid']
            from dbapi import CCP4DbUtils
            theWrapper = CCP4DbUtils.COpenJob(projectId=projectData['projectid'])
            theWrapper.createJob(taskName = kwargs['taskName'])
            jobDirectory = theWrapper.jobDir
            
        #print ("kwargs.items", kwargs.items())
        
        #Here a fix, because it may happen that a keyword will match a content id in the "outputData" folder,
        #since outputData elements may take the same name as an input.  Here I collect content nodes taht are
        #descendents of outputData, to exclude from being set downstream
        
        outputDataContainers = self.defXml.findall('.//container[@id="outputData"]')
        outputDataNodes = []
        for outputDataContainer in outputDataContainers:
            outputDataNodes += outputDataContainer.findall('.//content')
            
        ccp4i2BodyNodes = self.defXml.findall('ccp4i2_body')
        assert len(ccp4i2BodyNodes) == 1
        ccp4i2BodyNode = ccp4i2BodyNodes[0]
        
        def expandValue(value):
            if isinstance(value, list):
                patchedSubkeywordList = []
                subKeywordIterator = iter(value)
                appending = False
                for nextElement in subKeywordIterator:
                    try:
                        #nextElement = subKeywordIterator.__next__()
                        if isinstance(nextElement, list):
                            nextElement = expandValue(nextElement)
                            patchedSubkeywordList.append(nextElement)
                        elif "=" in nextElement:
                            patchedSubkeywordList.append(nextElement)
                            appending = True
                        elif appending:
                            patchedSubkeywordList[-1] += " {}".format(nextElement)
                        else:
                            patchedSubkeywordList.append(nextElement)
                    except StopIteration:
                        break
                value = patchedSubkeywordList
            return value
        
        for key, value in kwargs.items():
            #Here a truly nasty thing to deal with possibility that value is a list of
            #subKeyword=subValue entries *one or more of which might have spaces in the subValuefield*
            if isinstance(value, list):
                value = expandValue(value)
                
            if value is not None:
                print("Processing command ", key, value)
                currentNode = ccp4i2BodyNode
                keyPath = key.split(".")
                #print('keyPath', keyPath)
                for i in range(len(keyPath)-1):
                    currentNode = currentNode.findall('container[@id="{}"]'.format(keyPath[i]))[0]
                valueNodes = currentNode.findall('.//content[@id="'+keyPath[-1]+'"]')
                #print("nodes", valueNodes)
                nonOutputValueNodes = [node for node in valueNodes if node not in outputDataNodes]
                #print("nonOutputValueNodes", nonOutputValueNodes)
                assert len(nonOutputValueNodes) <= 1
                for defXmlNode in nonOutputValueNodes:
                    #defXmlNode = nodes[0]
                    dataPathElements = pathMap[defXmlNode].split('/')
                    theEntity = getattr(theWrapper,"container")
                    for pathElement in  dataPathElements[2:]:
                        #print("Moving on to pathElement", pathElement)
                        theEntity = getattr(theEntity, pathElement)
                        
                    parameterName = dataPathElements[-1]
                    #print('Parameter name: [{}] [{}]'.format( parameterName, type(theEntity)))
                    
                    if isinstance(theEntity,(CCP4Data.CList,)) and not isinstance(theEntity,(CCP4Data.CString,)):
                        entityList = theEntity
                        valueLists = value
                    else:
                        entityList = [theEntity]
                        valueLists = [value]
                    #print(parameterName, valueLists)

                    lastDictKey="NullKey"
                    for iValue, valueAsList in enumerate(valueLists):
                        #print("1:", iValue, valueAsList, type(valueAsList), isinstance(valueAsList, CCP4Data.CString))
                        if isinstance(entityList,(CCP4Data.CList,)):
                            while len(entityList) < iValue+1:
                                entityList.append(entityList.makeItem())
                        entityToModify = entityList[iValue]
                        
                        for valueItem in valueAsList:
                            #print("\t",valueItem)
                            #First deal with double quoted value.
                            #Assume that the entityToModify will accept a "set" for the quoted value
                            if valueItem.startswith('"') and valueItem.endswith('"'):
                                try:
                                    entityToModify.set(valueItem[1:-2])
                                except CException as err:
                                    print("Failed setting attribute {} on {} to value {}".format(parameterName, entityToModify, valueItem[1:-2]))
                                    print(err)
                                    raise err
                                except ValueError as err:
                                    print("Failed setting attribute {} on {} to value {}".format(parameterName, entityToModify, valueItem[1:-2]))
                                    print(err)
                                    raise err
                            #Now deal with subElement=subValue examples
                            elif "=" in valueItem:
                                valueItemGroup = re.match('(?P<propertyName>[^=]*)\=(?P<propertyValue>.*)', valueItem)
                                propertyName = valueItemGroup.group('propertyName')
                                propertyValue = valueItemGroup.group('propertyValue')
                                
                                propertyPathElements = propertyName.split("/")
                                propertyToModify = entityToModify
                                parentProperty = None
                                for iPathElement, propertyPathElement in enumerate(propertyPathElements):
                                    #Look to see if there is an index in the propertyPathELement
                                    pathElementGroup = re.match('(?P<arrayName>[^=]*)\[(?P<arrayIndex>.*)\]', propertyPathElement)
                                    if pathElementGroup is None:
                                        deconvolutedPathElement = propertyPathElement
                                        deconvolutedIndex = 0
                                    else:
                                        deconvolutedPathElement = pathElementGroup.group('arrayName')
                                        deconvolutedIndex = int(pathElementGroup.group('arrayIndex'))
                                        
                                    #print('Reached {} {} {}'.format(type(propertyToModify), deconvolutedPathElement, propertyValue) )
                                    #Here handle specialisations for various CDataFile types
                                    if isinstance(propertyToModify,(CCP4Data.CDict,)):
                                        #print("CCP4Data.CDict", deconvolutedPathElement, propertyValue)
                                        if deconvolutedPathElement == 'key':
                                            lastDictKey = propertyValue
                                            continue
                                        elif deconvolutedPathElement == 'value':
                                            #print("CDict/value", deconvolutedPathElement, deconvolutedIndex, propertyToModify, lastDictKey)
                                            propertyToModify[lastDictKey] = propertyValue
                                            continue
                                    elif isinstance(propertyToModify,(CCP4ModelData.CAtomSelection,)):
                                        print(entityToModify)
                                        if deconvolutedPathElement == 'text':
                                            propertyToModify.text.set(propertyValue)
                                            #print("[{}] [{}] [{}]".format(entityToModify, entityToModify.selection, entityToModify.selection.text))
                                            #print(etree.tostring(entityToModify.getEtree()))
                                            continue
                                    elif isinstance(propertyToModify,(CCP4ModelData.CAsuDataFile,)):
                                        if deconvolutedPathElement == 'seqFile':
                                            fullPath = self.addSequenceToASU(propertyToModify, os.path.normpath(os.path.expandvars(propertyValue)))
                                            propertyToModify.setFullPath(fullPath)
                                            #print("CCP4ModelData.CAsuDataFile", deconvolutedPathElement, propertyToModify.fullPath, propertyToModify.baseName)
                                            continue
                                    elif isinstance(propertyToModify,(CCP4XtalData.CMiniMtzDataFile,)):
                                        if deconvolutedPathElement == 'columnLabels':
                                            self.extractColumns(propertyToModify, propertyValue, jobDirectory)
                                            #print("CCP4XtalData.CMiniMtzDataFile", deconvolutedPathElement)
                                            continue
                                    #Following applies to all CDataFiledescendents
                                    if isinstance(propertyToModify,(CCP4File.CDataFile,)):
                                        #print("Evaluating CDataFile ", projectName, deconvolutedPathElement, iPathElement, len(propertyPathElements))
                                        if deconvolutedPathElement == 'fileUse':
                                            dbFileId = self.fileUse(projectName, propertyValue)
                                            propertyToModify.setDbFileId(dbFileId)
                                            continue
                                        elif deconvolutedPathElement == 'fullPath':
                                            propertyToModify.setFullPath(os.path.normpath(os.path.expandvars(propertyValue)))
                                            continue
                                        elif deconvolutedPathElement == 'dbFileId':
                                            propertyToModify.setDbFileId(propertyValue)
                                            continue

                                    try:
                                        propertyOrArryToModify = getattr(propertyToModify, deconvolutedPathElement)
                                        if isinstance(propertyOrArryToModify,(CCP4Data.CList,)):
                                            #print("property {} is a CList".format(deconvolutedPathElement))
                                            #Here if the pathElement is a CList...see if index is specified
                                            while len(propertyOrArryToModify) < deconvolutedIndex+1:
                                                propertyOrArryToModify.append(propertyOrArryToModify.makeItem())
                                            propertyToModify = propertyOrArryToModify[deconvolutedIndex]
                                        else:
                                            propertyToModify = propertyOrArryToModify
                                        #print(type(propertyToModify))
                                    except CException as err:
                                        print("Failed to get property {} on {}".format(deconvolutedPathElement, type(propertyToModify)))
                                        raise err
                                        
                                    if iPathElement == len(propertyPathElements)-1:
                                        self.setEntityValue(propertyToModify, propertyValue)
                                        continue
                            else:
                                self.setEntityValue(entityToModify, valueItem)
                    #print('after setting, the Entity:', theEntity)
        #print (getattr(theWrapper,"container"))
        return theWrapper
    
    def run(self):
        kwargs = vars(self.namespace)
        theWrapper = self.configure()
        if kwargs['noDb']:
            self.runNoDb(theWrapper)
        else:
            self.runWithDb(theWrapper)

    def runNoDb(self, theWrapper):
        theWrapper.doAsync=False
        theWrapper.process()
        rv = theWrapper.getErrorReport()
        print(rv.report(ifStack=True))

    def runWithDb(self, cOpenJob):
        from PySide2 import QtCore
        rv = cOpenJob.saveParams()
    
        cOpenJob.openJob()
        ifImportFile, errors = self.pm.importFiles(jobId=cOpenJob.jobId, container=cOpenJob.container)
        #print(ifImportFile, errors)
        #Record input files in database
        from dbapi import CCP4DbApi
        self.pm.db().gleanJobFiles(jobId=cOpenJob.jobId,container=cOpenJob.container,
                              roleList=[CCP4DbApi.FILE_ROLE_IN])
        rv = cOpenJob.saveParams()

        jc=CCP4Modules.JOBCONTROLLER()
        jc.setDiagnostic(True)
        jc.setDbFile(self.pm.db()._fileName)
        lastJobFinishCheckTime = time.time()
        jc.runTask(cOpenJob.jobId)
        
        doContinue = True
        while doContinue:
            t = time.time()
            finishedJobs = self.pm.db().getRecentlyFinishedJobs(after=lastJobFinishCheckTime)
            print("Any recently finished jobs ...?")
            print(finishedJobs)
            lastJobFinishCheckTime = t
            if len(finishedJobs) > 0:
                print("... yes ...")
                for j in finishedJobs:
                    if len(j)>5 and not j[5]:
                         print("... attempting to stop ...")
                         doContinue = False
            time.sleep(4)
        
        print("Attempting to close DB...")
        self.pm.db().close()
        print("Returning...")
        return

if __name__ == "__main__":
    print("##################################################")
    print("##################################################")
    print("RUNNING NEW CCP4I2Runner")
    print("##################################################")
    print("##################################################")

    try:
        theRunner = CI2Runner(sys.argv)
        theRunner.run()
#Quit any web server threads
        from PySide2 import QtCore
        app = QtCore.QCoreApplication.instance()
        if app:
            threads = app.findChildren(QtCore.QThread)
            print("##################################################")
            print("Quitting threads ...")
            print("##################################################")
            for t in threads:
                if hasattr(t,"quitServer"):
                    t.quitServer()
                print("Waiting for thread",t)
                timer = QtCore.QDeadlineTimer(1000)
                t.wait(timer)
                t.exit()
            print("##################################################")
            print("##################################################")
            print("EXITING FROM NEW CCP4I2Runner")
            print("##################################################")
            print("##################################################")

        sys.exit(0)
    except Exception as err:
        print("Failed with exception ", err)
        traceback.print_exc()

    sys.exit(1)

Reply via email to