class SimpleConnector(object): """ Provide a simple in-memory connection between a Database and a single Backend. @param dbParams: A C{DatabaseParameters} instance. @param backend: A C{Backend} instance, or C{None} if a backend should be created. @param filePrefix: Either a C{str} file name prefix to use as a default when saving or C{None} if no default save file is needed. """ SAVE_SUFFIX = '.lmco' def __init__(self, dbParams, backend=None, filePrefix=None): self.dbParams = dbParams if backend: self._backend = backend else: self._backend = Backend(filePrefix=filePrefix) self._backend.configure(dbParams) self._filePrefix = filePrefix # Most of our implementation comes directly from our backend. for method in ('addSubject', 'getIndexBySubject', 'getSubjectByIndex', 'getSubjects', 'subjectCount', 'hashCount', 'totalResidues', 'totalCoveredResidues', 'checksum'): setattr(self, method, getattr(self._backend, method)) def find(self, read, findParams=None, storeFullAnalysis=False, subjectIndices=None): """ Check which database sequences a read matches. @param read: A C{dark.read.AARead} instance. @param findParams: An instance of C{light.parameters.FindParameters} or C{None} to use default find parameters. @param storeFullAnalysis: A C{bool}. If C{True} the intermediate significance analysis computed in the Result will be stored. @param subjectIndices: A C{set} of subject indices, or C{None}. If a set is passed, only subject indices in the set will be returned in the results. If C{None}, all matching subject indices are returned. @return: A C{light.result.Result} instance. """ matches, hashCount, nonMatchingHashes = self._backend.find( read, storeFullAnalysis=storeFullAnalysis, subjectIndices=subjectIndices) return Result(read, self, matches, hashCount, findParams, nonMatchingHashes=nonMatchingHashes, storeFullAnalysis=storeFullAnalysis) def shutdown(self, save, filePrefix): """ Shut down the connector. @param save: If C{True}, save the connector state. @param filePrefix: When saving, use this C{str} as a file name prefix. """ self._backend.shutdown(save, filePrefix) if save: self.save(filePrefix) def save(self, fpOrFilePrefix=None): """ Save state to a file. @param fpOrFilePrefix: A file pointer, or the C{str} prefix of a file name, or C{None}. If a C{str}, self.SAVE_SUFFIX is appended to get the full file name. If C{None}, self._filePrefix will be used as a file prefix unless it is also C{None}. @raises ValueError: If C{fpOrFilePrefix} and C{self._filePrefix} are both C{None} """ if isinstance(fpOrFilePrefix, str): saveFile = fpOrFilePrefix + self.SAVE_SUFFIX elif fpOrFilePrefix is None: if self._filePrefix is None: raise ValueError('save must be given an argument (or the ' 'database must have been restored from a ' 'file).') else: saveFile = self._filePrefix + self.SAVE_SUFFIX else: saveFile = fpOrFilePrefix with as_handle(saveFile, 'w') as fp: self.dbParams.save(fp) self._backend.save(fpOrFilePrefix) @classmethod def restore(cls, fpOrFilePrefix): """ Restore state from a file. @param fpOrFilePrefix: A file pointer or the C{str} prefix of a file name. If a C{str}, self.SAVE_SUFFIX is appended to get the full file name. @return: An instance of L{SimpleConnector}. @raises ValueError: If valid JSON cannot be loaded from C{fp}. """ if isinstance(fpOrFilePrefix, str): saveFile = fpOrFilePrefix + cls.SAVE_SUFFIX filePrefix = fpOrFilePrefix else: saveFile = fpOrFilePrefix filePrefix = None with as_handle(saveFile) as fp: dbParams = DatabaseParameters.restore(fp) return cls(dbParams, backend=Backend.restore(fpOrFilePrefix), filePrefix=filePrefix) def print_(self, printHashes=False, margin='', result=None): """ Print information about this connector. @param printHashes: If C{True}, print all hashes and associated subjects from the backend. @param margin: A C{str} that should be inserted at the start of each line of output. @param result: A C{MultilineString} instance, or C{None} if a new C{MultilineString} should be created. @return: If C{result} was C{None}, return a C{str} representation of the connector, else C{None}. """ if result is None: result = MultilineString(margin=margin) returnNone = False else: returnNone = True if printHashes: result.append('Backends:') result.indent() self._backend.print_(margin=margin, result=result) result.outdent() if not returnNone: return str(result)
class BackendComponent(Component): NAME = 'backend' @asyncio.coroutine def onJoin(self, details): self._sessionId = details.session logging.info('Joined with WAMP server, session id %s', self._sessionId) self._ApiConfigured = False args = self.config.extra['args'] if args.filePrefix: saveFile = args.filePrefix + Backend.SAVE_SUFFIX if os.path.exists(saveFile): self._backend = Backend.restore(saveFile) else: # Note that the backend will be configured by the WAMP connector # via a call to our configure method (below). self._backend = Backend() @asyncio.coroutine def configure(paramsStr, suggestedName, suggestedChecksum): """ Configure our backend and register its API methods. @param paramsStr: The C{str} 'save' of a C{DatabaseParameters} instance. @param suggestedName: The C{str} suggested name for this backend. If the backend has already been configured (from a file restore) with a different name, the suggested name is ignored. @param suggestedChecksum: The C{int} suggested checksum for the backend, or C{None} if there is no initial value. @return: A 2-tuple consisting of the C{str} name of the backend and its checksum. These will either be the suggested values or those that were already in use (if the backend was already configured). """ fp = StringIO(paramsStr) dbParams = DatabaseParameters.restore(fp) name, checksum, subjectCount = self._backend.configure( dbParams, suggestedName, suggestedChecksum) if not self._ApiConfigured: self._ApiConfigured = True yield from self.registerAPIMethods() return name, checksum, subjectCount # Register our configure command. yield from self.register(configure, 'configure-%s' % self._sessionId) logging.info('Registered configure method.') # Register our shutdown command. def shutdown(save, filePrefix): """ Shut down the backend. @param save: If C{True}, save the backend state. @param filePrefix: When saving, use this C{str} as a file name prefix. """ logging.info('Shutdown called.') self._backend.shutdown(save, filePrefix) self.leave('goodbye!') yield from self.register(shutdown, 'shutdown-%s' % self._sessionId) logging.info('Registered shutdown method.') @asyncio.coroutine def registerAPIMethods(self): """ Register our API methods with the router. """ yield from self.register(self.find, 'find-%s' % self._sessionId) logging.info('Registered find method.') # Most of our implementation comes directly from our backend. for method in ('addSubject', 'getIndexBySubject', 'getSubjectByIndex', 'getSubjects', 'subjectCount', 'hashCount', 'totalResidues', 'totalCoveredResidues', 'checksum'): yield from self.register(getattr(self._backend, method), '%s-%s' % (method, self._sessionId)) logging.info('Registered %s method.', method) def find(self, read, storeFullAnalysis=False, subjectIndices=None): """ Check which database sequences a read matches. @param read: A C{dark.read.AARead} instance. @param storeFullAnalysis: A C{bool}. If C{True} the intermediate significance analysis computed in the Result will be stored. @param subjectIndices: A C{set} of subject indices, or C{None}. If a set is passed, only subject indices in the set will be returned in the results. If C{None}, all matching subject indices are returned. @return: The result of calling 'find' on our backend. """ return self._backend.find(read, storeFullAnalysis, subjectIndices)