''' accept input from command line or through the web and return the result. ''' import urllib, os.path, traceback from indigo import Indigo, IndigoException import common, options, molecule class HelpError(common.MCFError): pass class Processor(object): ''' parses input and invokes backend, returns result ''' def __init__(self, rawargs, data, formfields, progname, webform, rpc): self.rawargs = rawargs self.data = data self.formfields = formfields # if the user renames the script file or the # web client, use their new names self.progname = os.path.split(progname)[-1] # flags that indicate origin of input self.webform = webform self.rpc = rpc self.optionparser = options.getParser() self.options = dict(common.settings) # data obtained from the proper source go here self.data_string = None def version_text(self): ''' print the program version ''' return common.version_text(progname=self.progname) def help_text(self): ''' error messages for the command line interface. ''' return common.help_text(progname=self.progname) def parseInputCli(self): ''' parse input that came through the command line (locally or rpc) return success flag and either error message or data ''' # catch empty input if not self.rawargs and not self.data: ht = self.help_text() raise HelpError, ht # parse options and arguments try: parsed_options, datalist = self.optionparser.process_cli(self.rawargs) except Exception, msg: if str(msg).endswith('not recognized'): # getopt error msg = str(msg) + \ ". Try %s --help to see a list of available options." % self.progname raise HelpError, msg # if we get here, we have parsed options and a possibly empty datalist self.options.update(parsed_options) # before we go on to check on the data, we will satisfy help requests, # which we treat like an error if self.options['help']: raise HelpError, self.help_text() elif self.options['version']: raise HelpError, self.version_text() if self.data is not None: datalist.append(self.data) # at this point, we should have reached the same state # by rpc and local invocation if len(datalist) != 1: if not datalist: raise common.MCFError, "No input data supplied" raise common.MCFError, "Please give only one file or data string as input" data = datalist[0] if not self.rpc and self.options['input'] == 'file': try: data = open(data).read() except IOError: raise common.MCFError, "Can't read file %s" % data self.data_string = data def parseInputWeb(self): ''' parse options and provide data provided through the web form ''' parsed_options, warnings = self.optionparser.process_form_fields(self.formfields) if warnings: raise common.MCFError, '
\n'.join(warnings) # no warnings ... self.options.update(parsed_options) self.data_string = self.data def process(self): ''' process input from both web form and CLI ''' if not self.webform: self.parseInputCli() else: self.parseInputWeb() # let toolkit parse the molecule, and process it tkmol = self.parseMolecule() # we now know how to deal with orphan atoms #atoms, bonds = tkmol.countAtoms(), tkmol.countBonds() #if atoms <= 1 or bonds == 0: #raise common.MCFError, "Input contains no bonds---can't render structure" mol = molecule.Molecule(self.options, tkmol) return mol def parseMolecule(self): ''' turn the input into a toolkit molecule according to user settings indigo is supposed to read transparently, so we can do away with the format setting, basically. If it's numeric, we ask pubchem, if it isn't, we consider it a molecule. ''' rawinput = self.data_string try: pubchemId = int(rawinput) except ValueError: pubchemId = None if pubchemId is not None: try: url = common.pubchem_url % pubchemId pubchemContent = urllib.urlopen(url).read() except IOError: raise common.MCFError, 'No connection to PubChem' self.data_string = pubchemContent #common.debug('rpc: %s' % self.rpc) #common.debug('data ---\n%s\n---' % self.data_string) try: tkmol = Indigo().loadMolecule(self.data_string) except IndigoException: raise common.MCFError, "Invalid input data" hydrogens = self.options['hydrogens'] if hydrogens == 'add': tkmol.unfoldHydrogens() tkmol.layout() # needed to give coordinates to added Hs elif hydrogens == 'delete': tkmol.foldHydrogens() if not tkmol.hasCoord() or self.options['recalculate_coordinates']: tkmol.layout() return tkmol def process(rawargs=None, data=None, formfields=None, progname="mol2chemfig", webform=False, rpc=False): ''' process is a convenience wrapper for external callers ''' p = Processor(rawargs, data, formfields, progname, webform, rpc) try: mol = p.process() except HelpError, msg: return False, msg except common.MCFError, msg: # anticipated error - brief message enough msg = traceback.format_exc().splitlines()[-1] msg = msg[len('MCFError: '):] return False, msg except Exception, msg: # unexpected error - get full traceback tb = traceback.format_exc() return False, tb return True, mol