Source code for ccpn.core.NmrChain

"""
"""
#=========================================================================================
# Licence, Reference and Credits
#=========================================================================================
__copyright__ = "Copyright (C) CCPN project (www.ccpn.ac.uk) 2014 - $Date: 2016-08-05 11:02:08 +0100 (Fri, 05 Aug 2016) $"
__credits__ = "Wayne Boucher, Rasmus H Fogh, Simon P Skinner, Geerten W Vuister"
__license__ = ("CCPN license. See www.ccpn.ac.uk/license"
              "or ccpnmodel.ccpncore.memops.Credits.CcpnLicense for license text")
__reference__ = ("For publications, please use reference from www.ccpn.ac.uk/license"
                " or ccpnmodel.ccpncore.memops.Credits.CcpNmrReference")

#=========================================================================================
# Last code modification:
#=========================================================================================
__author__ = "$Author: rhfogh $"
__date__ = "$Date: 2016-08-05 11:02:08 +0100 (Fri, 05 Aug 2016) $"
__version__ = "$Revision: 9763 $"

#=========================================================================================
# Start of code
#=========================================================================================

import collections
import typing

from ccpn.core.Chain import Chain
from ccpn.core.Project import Project
from ccpn.core.Residue import Residue
from ccpn.core._implementation.AbstractWrapperObject import AbstractWrapperObject
from ccpn.core.lib import Pid
from ccpnmodel.ccpncore.api.ccp.nmr.Nmr import NmrChain as ApiNmrChain
from ccpnmodel.ccpncore.lib import Util as modelUtil
from ccpnmodel.ccpncore.lib import Constants


[docs]class NmrChain(AbstractWrapperObject): """NmrChains are used for NMR assignment. An NmrChain is by definition assigned to the Chain with the same shortName (if any). An NmrChain created without a name will be given the name '@ij', where ij is the serial number of the NmrChain. Names of this form are reserved. Setting the NmrChain shortName to None will revert to this default name. The order of NmrResidues within an NmrChain is not significant (they are given in sorted order). NmrChains with isConnected==True are used to describe connected but as yet unassigned stretches of NmrResidues, and here the NmrResidues are given in sequential order (N-terminal to C-terminal for proteins). Connected NmrChains have names of the form '#ij' where ij is the serial number of the NmrChain, and cannot be renamed. Names of this form are reserved. """ #: Short class name, for PID. shortClassName = 'NC' # Attribute it necessary as subclasses must use superclass className className = 'NmrChain' _parentClass = Project #: Name of plural link to instances of class _pluralLinkName = 'nmrChains' #: List of child classes. _childClasses = [] # Qualified name of matching API class _apiClassQualifiedName = ApiNmrChain._metaclass.qualifiedName() # CCPN properties @property def _apiNmrChain(self) -> ApiNmrChain: """ CCPN NmrChain matching NmrChain""" return self._wrappedData @property def _key(self) -> str: """short form of name, as used for id with illegal characters replaced by Pid.altCharacter""" return self._wrappedData.code.translate(Pid.remapSeparators) @property def shortName(self) -> str: """short form of name, used in Pid and to identify the NmrChain Names of the form '\@ijk' and '#ijk' (where ijk is an integers) are reserved and cannot be set. They can be obtained by the deassign command. Connected NmrChains (isConnected == True) always have canonical names of the form '#ijk'""" return self._wrappedData.code @property def label(self) -> str: """Identifying label of NmrChain. Defaults to '?'""" return self._wrappedData.label @label.setter def label(self, value:str): self._wrappedData.label = value @property def _parent(self) -> Project: """Parent (containing) object.""" return self._project @property def serial(self) -> int: """NmrChain serial number - set at creation and unchangeable""" return self._wrappedData.serial @property def isConnected(self) -> bool: """True if this this NmrChain is a connected stretch (in which case the mainNmrResidues are sequentially connected).""" return self._wrappedData.isConnected @property def comment(self) -> str: """Free-form text comment""" return self._wrappedData.details @comment.setter def comment(self, value:str): self._wrappedData.details = value @property def chain(self) -> Chain: """Chain to which NmrChain is assigned""" chain = self._wrappedData.chain return None if chain is None else self._project._data2Obj.get(chain) @chain.setter def chain(self, value:Chain): if value is None: if self.chain is None: return else: self.deassign() else: # NB The API code will throw ValueError if there is already an NmrChain with that code self.rename(value._wrappedData.code)
[docs] def rename(self, value:str): """Rename NmrChain, changing its shortName and Pid. Use the 'deassign' function if you want to revert to the canonical name""" # NBNB TODO Allow renaming to names of teh form '@123' (?) wrappedData = self._apiNmrChain if self._wrappedData.isConnected: raise ValueError("Connected NmrChain cannot be renamed") elif not value: raise ValueError("NmrChain name must be set") elif value == wrappedData.code: return elif wrappedData.code == Constants.defaultNmrChainCode: raise ValueError("NmrChain:%s cannot be renamed" % Constants.defaultNmrChainCode) elif Pid.altCharacter in value: raise ValueError("Character %s not allowed in ccpn.NmrChain.shortName" % Pid.altCharacter) else: # NB names that clash with existing NmrChains cause ValueError at the API level. self._startFunctionCommandBlock('rename', value) try: wrappedData.code = value finally: self._project._appBase._endCommandBlock()
[docs] def deassign(self): """Reset NmrChain back to its originalName, cutting all assignment links""" self._startFunctionCommandBlock('deassign') try: self._wrappedData.code = None finally: self._project._appBase._endCommandBlock()
[docs] def assignConnectedResidues(self, firstResidue:typing.Union[Residue, str]): """Assign all NmrResidues in connected NmrChain sequentially, with the first NmrResidue assigned to firstResidue. Returns ValueError if NmrChain is not connected, or if any of the Residues are missing or already assigned""" apiNmrChain = self._wrappedData project = self._project if not self.isConnected: raise ValueError("assignConnectedResidues only allowed for connected NmrChains") if isinstance(firstResidue, str): xx = project.getByPid(firstResidue) if xx is None: raise ValueError("No object found matching Pid %s" % firstResidue) else: firstResidue = xx apiStretch = apiNmrChain.mainResonanceGroups if firstResidue.nmrResidue is not None: raise ValueError("Cannot assign %s NmrResidue stretch: First Residue %s is already assigned" % (len(apiStretch), firstResidue.id)) residues = [firstResidue] for ii in range(len(apiStretch) - 1): res = residues[ii] next = res.nextResidue if next is None: raise ValueError("Cannot assign %s NmrResidues to %s Residues from Chain" % (len(apiStretch), len(residues))) elif next.nmrResidue is not None: raise ValueError("Cannot assign %s NmrResidue stretch: Residue %s is already assigned" % (len(apiStretch), next.id)) else: residues.append(next) # If we get here we are OK - assign residues and delete NmrChain self._startFunctionCommandBlock('assignConnectedResidues', firstResidue) try: for ii,res in enumerate(residues): apiStretch[ii].assignedResidue = res._wrappedData apiNmrChain.delete() finally: self._project._appBase._endCommandBlock()
@classmethod def _getAllWrappedData(cls, parent: Project)-> list: """get wrappedData (Nmr.DataSources) for all Spectrum children of parent Project""" return parent._wrappedData.sortedNmrChains()
def getter(self:Chain) -> NmrChain: obj = self._project._wrappedData.findFirstNmrChain(code=self._wrappedData.code) return None if obj is None else self._project._data2Obj.get(obj) def setter(self:Chain, value:NmrChain): if value is None: raise ValueError("nmrChain cannot be set to None") else: value.chain = self Chain.nmrChain = property(getter, setter, None, "NmrChain to which Chain is assigned") del getter del setter def _newNmrChain(self:Project, shortName:str=None, isConnected:bool=False, label:str='?', comment:str=None) -> NmrChain: """Create new NmrChain. Setting isConnected=True produces a connected NmrChain. :param str shortName: shortName for new nmrChain (optional, defaults to '@ijk' or '#ijk', ijk positive integer :param bool isConnected: (default to False) If true the NmrChain is a connected stretch. This can NOT be changed later :param str label: Modifiable NmrChain identifier that does not change with reassignment. Defaults to '@ijk'/'#ijk' :param str comment: comment for new nmrChain (optional)""" defaults = collections.OrderedDict((('shortName', None), ('isConnected', False), ('label', '?'), ('comment', None))) nmrProject = self._apiNmrProject serial = None if shortName: previous = self.getNmrChain(shortName.translate(Pid.remapSeparators)) if previous is not None: raise ValueError("%s already exists" % previous.longPid) if shortName[0] in '#@': try: serial = int(shortName[1:]) except ValueError: # the rest of the name is not an int. We are OK pass if serial is not None and serial > 0: # this is a reserved name - try to set it with serial if nmrProject.findFirstNmrChain(serial=serial) is None: # We are setting a shortName that matches the passed-in serial. OK. # Set isConnected to match - this overrides the isConnected parameter. isConnected = (shortName[0] == '#') shortName = None else: raise ValueError("Cannot create NmrChain with reserved name %s" % shortName) else: shortName = None dd = {'code':shortName, 'isConnected':isConnected, 'label':label, 'details':comment} self._startFunctionCommandBlock('newNmrChain', values=locals(), defaults=defaults, parName='newNmrChain') result = None try: newApiNmrChain = nmrProject.newNmrChain(**dd) result = self._data2Obj.get(newApiNmrChain) if serial is not None: try: modelUtil.resetSerial(newApiNmrChain, serial, 'nmrChains') except ValueError: self.project._logger.warning("Could not set shortName of %s to %s - keeping default value" %(result, shortName)) result._finaliseAction('rename') finally: self._project._appBase._endCommandBlock() return result def _fetchNmrChain(self:Project, shortName:str=None) -> NmrChain: """Fetch chain with given shortName; If none exists call newNmrChain to make one first If shortName is None returns a new NmrChain with name starting with '@' """ self._startFunctionCommandBlock('fetchNmrChain', shortName, parName='newNmrChain') try: if not shortName: result = self.newNmrChain() else: apiNmrChain = self._apiNmrProject.findFirstNmrChain(code=shortName) if apiNmrChain is None: result = self.newNmrChain(shortName=shortName) else: result = self._data2Obj.get(apiNmrChain) finally: self._project._appBase._endCommandBlock() return result # Clean-up # Connections to parents: Project.newNmrChain = _newNmrChain del _newNmrChain Project.fetchNmrChain = _fetchNmrChain del _fetchNmrChain # Notifiers: className = ApiNmrChain._metaclass.qualifiedName() Project._apiNotifiers.extend( ( ('_finaliseApiRename', {}, className, 'setImplCode'), ) ) Chain._setupCoreNotifier('rename', AbstractWrapperObject._finaliseRelatedObjectFromRename, {'pathToObject':'nmrChain', 'action':'rename'}) # NB Chain<->NmrChain link depends solely on the NmrChain name. # So no notifiers on the link - notify on the NmrChain rename instead.