class ReactionConditions(metaclass=LazyEntityMeta, database='CGRdb'): id = PrimaryKey(int, auto=True) date = Required(datetime, default=datetime.utcnow) user = DoubleLink(Required('User', reverse='reaction_conditions'), Set('ReactionConditions')) structure = DoubleLink(Required('Reaction', reverse='metadata'), Set('ReactionConditions')) data = Required(Json)
class MoleculeProperties(metaclass=LazyEntityMeta, database='CGRdb'): id = PrimaryKey(int, auto=True) date = Required(datetime, default=datetime.utcnow) user = DoubleLink(Required('User', reverse='molecule_properties'), Set('MoleculeProperties')) structure = DoubleLink(Required('Molecule', reverse='metadata'), Set('MoleculeProperties')) data = Required(Json)
class ReactionClass(metaclass=LazyEntityMeta, database='CGRdb'): id = PrimaryKey(int, auto=True) name = Required(str) type = Required(int, default=0) structures = DoubleLink( Set('Reaction', table='Reaction_ReactionClass', reverse='classes'), Set('ReactionClass'))
class MoleculeClass(metaclass=LazyEntityMeta, database='CGRdb'): id = PrimaryKey(int, auto=True) name = Required(str) _type = Required(int, default=0, column='type') structures = DoubleLink( Set('Molecule', table='Molecule_MoleculeClass', reverse='classes'), Set('MoleculeClass'))
class MoleculeStructure(FingerprintMolecule, metaclass=LazyEntityMeta, database='CGRdb'): id = PrimaryKey(int, auto=True) user = DoubleLink(Required('User', reverse='molecule_structures'), Set('MoleculeStructure')) molecule = Required('Molecule') date = Required(datetime, default=datetime.utcnow) last = Required(bool, default=True) data = Required(bytes, optimistic=False) signature = Required(bytes, unique=True) bit_array = Required(IntArray, optimistic=False, index=False, lazy=True) def __init__(self, molecule, structure, user): super().__init__(molecule=molecule, data=dumps(structure), user=user, signature=bytes(structure), bit_array=self.get_fingerprint(structure)) @property def structure(self): if self.__cached_structure is None: self.__cached_structure = loads(self.data) return self.__cached_structure __cached_structure = None
class Molecule(SearchMolecule, metaclass=LazyEntityMeta, database='CGRdb'): id = PrimaryKey(int, auto=True) date = Required(datetime, default=datetime.utcnow) user = DoubleLink(Required('User', reverse='molecules'), Set('Molecule')) _structures = Set('MoleculeStructure') reactions = Set('MoleculeReaction') special = Optional(Json) def __init__(self, structure, user, special=None): super().__init__(user=user) self._cached_structure = self._database_.MoleculeStructure(self, structure, user) self._cached_structures_all = (self._cached_structure,) if special: self.special = special def __str__(self): """ signature of last edition of molecule """ return str(self.structure) def __bytes__(self): """ hashed signature of last edition of molecule """ return bytes(self.structure) @property def structure(self): return self.last_edition.structure @property def structure_raw(self): return self.raw_edition.structure @property def structures_all(self): return tuple(x.structure for x in self.all_editions) @property def last_edition(self): if self._cached_structure is None: self._cached_structure = self._structures.filter(lambda x: x.last).first() return self._cached_structure @property def raw_edition(self): if self._cached_structure_raw is not None: return self._cached_structure_raw raise AttributeError('available in entities from queries results only') @property def all_editions(self): if self._cached_structures_all is None: s = tuple(self._structures.select()) self._cached_structures_all = s if self._cached_structure is None: self._cached_structure = next(x for x in s if x.last) return self._cached_structures_all _cached_structure = _cached_structure_raw = _cached_structures_all = None
class Reaction(SearchReaction, metaclass=LazyEntityMeta, database='CGRdb'): id = PrimaryKey(int, auto=True) date = Required(datetime, default=datetime.utcnow) user = DoubleLink(Required('User', reverse='reactions'), Set('Reaction')) molecules = Set('MoleculeReaction') reaction_indexes = Set('ReactionIndex') special = Optional(Json) def __init__(self, structure, user, special=None): """ storing reaction in DB. :param structure: CGRtools ReactionContainer :param user: user entity :param special: Json serializable Data (expected dict) """ super().__init__(user=user) # preload all molecules and structures signatures = {bytes(m) for m in structure.reagents } | {bytes(m) for m in structure.products} ms, s2ms = defaultdict(list), {} for x in select(x for x in self._database_.MoleculeStructure if x.molecule in select( y.molecule for y in self._database_.MoleculeStructure if y.signature in signatures)).prefetch( self._database_.Molecule): # NEED PR # select(y for x in db.MoleculeStructure if x.signature in signatures_set # for y in db.MoleculeStructure if y.molecule == x.molecule) if x.signature in signatures: s2ms[x.signature] = x if x.last: x.molecule._cached_structure = x ms[x.molecule].append(x) for m, s in ms.items(): m._cached_structures_all = tuple(s) combinations, duplicates = [], {} for sl, is_p in ((structure.reagents, False), (structure.products, True)): for s in sl: sig = bytes(s) ms = s2ms.get(sig) if ms: mapping = ms.structure.get_mapping(s) self._database_.MoleculeReaction(reaction=self, molecule=ms.molecule, is_product=is_p, mapping=mapping) # first MoleculeStructure always last if ms.last: # last structure equal to reaction structure c = [s] c.extend( x.structure.remap(mapping, copy=True) for x in ms.molecule.all_editions if not x.last) else: # last structure remapping c = [ms.molecule.structure.remap(mapping, copy=True)] c.extend( x.structure.remap(mapping, copy=True ) if x != ms else s for x in ms.molecule.all_editions if not x.last) combinations.append(c) else: # New molecule if sig not in duplicates: m = duplicates[sig] = self._database_.Molecule(s, user) mapping = None else: m = duplicates[sig] mapping = m.structure.get_mapping(s) self._database_.MoleculeReaction(reaction=self, molecule=m, is_product=is_p, mapping=mapping) combinations.append([s]) reagents_len = len(structure.reagents) combinations = tuple(product(*combinations)) if len(combinations) == 1: # optimize self._cached_structures_all = (structure, ) self._cached_structure = structure self._database_.ReactionIndex(self, structure, True) else: x = combinations[0] self._cached_structure = ReactionContainer( reagents=x[:reagents_len], products=x[reagents_len:]) self._database_.ReactionIndex(self, self._cached_structure, True) cgr = {} for x in combinations[1:]: x = ReactionContainer(reagents=x[:reagents_len], products=x[reagents_len:]) cgr[~x] = x self._cached_structures_all = (self._cached_structure, *cgr.values()) for x in cgr: self._database_.ReactionIndex(self, x, False) if special: self.special = special def __str__(self): """ canonical signature of reaction """ return str(self.structure) def __bytes__(self): """ hashed CGR signature of reaction """ return bytes(self.cgr) @property def structure(self): """ ReactionContainer object """ if self._cached_structure is None: # mapping and molecules preload mrs = self.molecules.order_by(lambda x: x.id).prefetch( self._database_.Molecule)[:] # last molecule structures preload ms = {x.molecule for x in mrs} for x in self._database_.MoleculeStructure.select( lambda x: x.last and x.molecule in ms): x.molecule._cached_structure = x self._cached_structure = r = ReactionContainer() for m in mrs: s = m.molecule.structure r['products' if m.is_product else 'reagents'].append( s.remap(m.mapping, copy=True) if m.mapping else s) return self._cached_structure @property def structures_all(self): if self._cached_structures_all is None: # mapping and molecules preload mrs = self.molecules.order_by(lambda x: x.id).prefetch( self._database_.Molecule)[:] # structures preload ms = {x.molecule: [] for x in mrs} for x in self._database_.MoleculeStructure.select( lambda x: x.molecule in ms.keys()): if x.last: x.molecule._cached_structure = x ms[x.molecule].append(x) for m, s in ms.items(): m._cached_structures_all = tuple(s) # all possible reaction structure combinations combinations = tuple( product(*(x.molecule.structures_all for x in mrs))) structures = [] for x in combinations: r = ReactionContainer() structures.append(r) for s, m in zip(x, mrs): r['products' if m.is_product else 'reagents'].append( s.remap(m.mapping, copy=True) if m.mapping else s) self._cached_structures_all = tuple(structures) if self._cached_structure is None: if len(structures) == 1: # optimize self._cached_structure = structures[0] else: self._cached_structure = r = ReactionContainer() for m in mrs: s = m.molecule.structure r['products' if m.is_product else 'reagents'].append( s.remap(m.mapping, copy=True) if m.mapping else s) return self._cached_structures_all @property def structure_raw(self): if self._cached_structure_raw is not None: return self._cached_structure_raw raise AttributeError('available in entities from queries results only') @property def cgr(self): """ CRG of reaction """ if self.__cached_cgr is None: self.__cached_cgr = ~self.structure return self.__cached_cgr @property def cgrs_all(self): if self.__cached_cgrs_all is None: self.__cached_cgrs_all = tuple(~x for x in self.structures_all) return self.__cached_cgrs_all @property def cgr_raw(self): if self.__cached_cgr_raw is None: self.__cached_cgr_raw = ~self.structure_raw return self.__cached_cgr_raw _cached_structure = _cached_structures_all = _cached_structure_raw = None __cached_cgr = __cached_cgrs_all = __cached_cgr_raw = None