Skip to content
Snippets Groups Projects
MP-plasmid_parents.py 12.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • Managed Property Script for handling PLASMID parents of YEAST samples.
    
    """space that all parents come from (fixed)"""
    
    """input pattern matching one plasmid, e.g.: 
    - 'FRP1 (DEL:URA3)', 
    - 'FRP2 (INT)', 
    - 'FRP3(MOD:URA3)', 
    - 'FRP4'
    """
    
    INPUT_PATTERN = """
                     # no '^': allow whitespace at the beginning
        ([^ (]*)     # 1st group: match code of a sample, everything before a space or '(' (e.g. 'FRP')
        (\ *\(       # start of 2nd group (matches an optional relationship type with annotation) 
                     # any spaces followed by a '('
        ([^:]*)      # 3rd group: match relationship type, any character but ':' (e.g. 'DEL', 'INT', 'MOD')
        :?           # optional ':' separator
        (.*)         # 4th group: match annotation, any text (e.g. 'URA3')
        \))?         # end of 2nd (optional) group: closing bracket of relationship details
                     # no '$': allow whitespace at the end
    """
    
    
    """relationship types shortcuts"""
    
    DEL_REL_TYPE = 'DEL'
    INT_REL_TYPE = 'INT'
    MOD_REL_TYPE = 'MOD'
    
    
    """tuple of supported relationship types as shortcuts"""
    
    REL_TYPES = (DEL_REL_TYPE, INT_REL_TYPE, MOD_REL_TYPE)
    
    """dictionary from relationship type shortcut to its 'character' representation"""
    
        DEL_REL_TYPE: '^', # TODO 2011-14-03, Piotr Buczek: use u'\u0394' for '∆', don't encode HTML
        INT_REL_TYPE: '::', 
        MOD_REL_TYPE: '_' 
    
    """dictionary from relationship type shortcut to its full name/label"""
    
        DEL_REL_TYPE: 'deletion', 
        INT_REL_TYPE: 'integration', 
        MOD_REL_TYPE: 'modification' 
    
    REL_TYPE_LABEL_OTHER = '(other)'
    REL_TYPE_LABELS_WITH_NONE = tuple([REL_TYPE_LABEL_OTHER] + REL_TYPE_LABELS.values())
    
    
    """names of additional sample XML element attributes"""
    
    
    ATR_CODE = "code"
    ATR_RELATIONSHIP = "rel"
    ATR_ANNOTATION = "annotation"
    
    
    CONNECTION_LABEL = "connection"
    LINK_LABEL = "link"
    CODE_LABEL = "code"
    RELATIONSHIP_LABEL = "relationship"
    ANNOTATION_LABEL = "annotation"
    
    
    """action labels"""
    
    ADD_ACTION_LABEL = "Add"
    EDIT_ACTION_LABEL = "Edit"
    DELETE_ACTION_LABEL = "Delete"
    
    def _group(pattern, input):
    
        """@return: groups returned by performing pattern search with given @pattern on given @input"""
    
        return pattern.search(input).groups()
    
    
    def _translateToChar(relationship):
    
        """
           @param relationship: relationship type as a shortcut (@see REL_TYPES), may be null
           @return: character representation of given @relationship, 
                    empty string for null
                    '[<relationship>]' for unknown relationship
        """
        if relationship:
            if relationship in REL_TYPE_CHARS:
                return REL_TYPE_CHARS[relationship]
            else:
                return "[" + relationship + "]"
    
        
    def _translateToLabel(relationship):
    
        """
           @param relationship: relationship type as a shortcut (@see REL_TYPES), may be null
           @return: full name of given @relationship, 
                    empty string for null, 
                    '[<relationship>]' for unknown relationship
        """
        if relationship:
            if relationship in REL_TYPE_LABELS:
                return REL_TYPE_LABELS[relationship]
            else:
                return "[" + relationship + "]"
    
            return REL_TYPE_LABEL_OTHER    
    
    def _translateFromLabel(relationshipLabel):
        """
           @param relationshipLabel: relationship type as label (@see REL_TYPE_LABELS_WITH_NONE)
           @return: type of given @relationshipLabel, None for REL_TYPE_LABEL_OTHER, 
        """
        if relationshipLabel == REL_TYPE_LABEL_OTHER:
            return None
        elif relationshipLabel == 'deletion':
            return DEL_REL_TYPE
        elif relationshipLabel == 'integration':
            return INT_REL_TYPE
        elif relationshipLabel == 'modification':
            return MOD_REL_TYPE    
    
    def _createConnectionString(code, relationship, annotation):
    
        """
           @param code: code of a sample
           @param relationship: relationship type as a shortcut (@see REL_TYPES), may be null
           @param annotation: annotation of the relationship, may be null
           @return: string representation of a connection with @relationship translated to a 'character'
        """
    
            result += _translateToChar(relationship)
    
        if annotation:
            result += annotation
        return result
    
    
    def _createSampleLink(code, relationship, annotation):
    
        """
           Creates sample link XML element for sample with specified @code. The element will contain
           given @code as 'code' attribute apart from standard 'permId' attribute. If specified 
           @relationship or @annotation are not null they will also be contained as attributes.
           
           If the sample doesn't exist in DB a fake link will be created with @code as permId.
           
           @param code: code of a sample
           @param relationship: relationship type as a shortcut (@see REL_TYPES), may be null
           @param annotation: annotation of the relationship, may be null
           @return: sample link XML element as string, e.g.:
           - '<Sample code="FRP1" permId="20110309154532868-4219"/>'
           - '<Sample code="FRP2" permId="20110309154532868-4219" relationship="DEL" annotation="URA3"/>'
           - '<Sample code="FAKE_SAMPLE_CODE" permId="FAKE_SAMPLE_CODE"/>
           - '<Sample code="FRP4" permId="20110309154532868-4219" relationship="INT"/>'
           @raise ValidationException: if the specified relationship type is unknown
        """
    
        permId = entityInformationProvider().getSamplePermId(SPACE, code)
        if not permId:
            permId = code
        sampleLink = elementFactory().createSampleLink(permId)
        sampleLink.addAttribute(ATR_CODE, code)
        if relationship:
            sampleLink.addAttribute(ATR_RELATIONSHIP, relationship)
            if relationship in REL_TYPES:
    
                connectionString = _createConnectionString(code, relationship, annotation)
    
            else:
                raise ValidationException("Unknown relationship: '" + relationship + 
    
                                          "'. Expected one of: " + REL_TYPES)
    
        if annotation:
            sampleLink.addAttribute(ATR_ANNOTATION, annotation)
        return sampleLink    
    
    
    FRP1 (DEL:URA3), FRP2 (INT), FRP3 (MOD:URA3), FRP4
    
    Relationship types:
    - DEL: deletion
    - INT: integration
    - MOD: modification
    """
    
    def updateFromBatchInput(bindings):
    
        inputPattern = re.compile(INPUT_PATTERN, re.VERBOSE)
        input = bindings.get('')
        plasmids = input.split(',')
        elements = []
        for p in plasmids:
    
            (code, g, relationship, annotation) = _group(inputPattern, p.strip())
            sampleLink = _createSampleLink(code, relationship, annotation)
    
            elements.append(sampleLink)
        property.value = propertyConverter().convertToString(elements)
    
    
        
        """Create table builder and add columns."""
    
        tableBuilder = createTableBuilder()
        tableBuilder.addHeader(LINK_LABEL)
    
        tableBuilder.addHeader(CONNECTION_LABEL)
    
        tableBuilder.addHeader(CODE_LABEL)
        tableBuilder.addHeader(RELATIONSHIP_LABEL)
        tableBuilder.addHeader(ANNOTATION_LABEL)
    
    
        """The property value should contain XML with list of samples. Add a new row for every sample."""
    
        elements = list(propertyConverter().convertToElements(property))
        for plasmid in elements:
            code = plasmid.getAttribute(ATR_CODE, "")
            relationship = plasmid.getAttribute(ATR_RELATIONSHIP, "")
            annotation = plasmid.getAttribute(ATR_ANNOTATION, "")
       
            row = tableBuilder.addRow()
    
            row.setCell(CONNECTION_LABEL, _createConnectionString(code, relationship, annotation))
    
            row.setCell(LINK_LABEL, plasmid, code)
    
            row.setCell(CODE_LABEL, code)
    
            row.setCell(RELATIONSHIP_LABEL, _translateToLabel(relationship))
    
            row.setCell(ANNOTATION_LABEL, annotation)
            
    
        """Specify that the property should be shown in a tab and set the table output."""
    
        uiDescription = property.getUiDescription()
        uiDescription.useTableOutput(tableBuilder.getTableModel())
        
        """
           Define and add actions with input fields used to:
    
           1. specify attributes of new plasmid relationship,
    
        """
        addAction = uiDescription.addTableAction(ADD_ACTION_LABEL)\
                                 .setDescription('Add new plasmid relationship:')
        widgets = [
            inputWidgetFactory().createTextInputField(CODE_LABEL)\
                                .setMandatory(True)\
                                .setValue('FRP')\
                                .setDescription('Code of plasmid sample, e.g. "FRP1"'),
            inputWidgetFactory().createComboBoxInputField(RELATIONSHIP_LABEL, REL_TYPE_LABELS_WITH_NONE)\
                                .setMandatory(False)\
                                .setValue(REL_TYPE_LABEL_OTHER),
            inputWidgetFactory().createTextInputField(ANNOTATION_LABEL)\
                                .setMandatory(False)\
                                .setDescription('Relationship annotation, e.g. "URA3"'),
        ]
        addAction.addInputWidgets(widgets)
          
        """
    
           2. modify attributes of a selected plasmid relationship,
    
        """
        editAction = uiDescription.addTableAction(EDIT_ACTION_LABEL)\
                                  .setDescription('Edit selected plasmid relationship:')
        # Exactly 1 row needs to be selected to enable action.
        editAction.setRowSelectionRequiredSingle()            
        widgets = [
            inputWidgetFactory().createTextInputField(CODE_LABEL)\
                                .setMandatory(True)\
                                .setDescription('Code of plasmid sample, e.g. "FRP1"'),
            inputWidgetFactory().createComboBoxInputField(RELATIONSHIP_LABEL, REL_TYPE_LABELS_WITH_NONE)\
                                .setMandatory(False),
            inputWidgetFactory().createTextInputField(ANNOTATION_LABEL)\
                                .setMandatory(False)\
                                .setDescription('Relationship annotation, e.g. "URA3"'),
        ]
        editAction.addInputWidgets(widgets)
        # Bind field name with column name.
        editAction.addBinding(CODE_LABEL, CODE_LABEL)
        editAction.addBinding(RELATIONSHIP_LABEL, RELATIONSHIP_LABEL)
        editAction.addBinding(ANNOTATION_LABEL, ANNOTATION_LABEL)
      
        """
    
           3. delete selected plasmid relationships.
    
        """
        deleteAction = uiDescription.addTableAction(DELETE_ACTION_LABEL)\
                                    .setDescription('Are you sure you want to delete selected plasmid relationships?')
        # Delete is enabled when at least 1 row is selected.
        deleteAction.setRowSelectionRequired()
        
        
    def updateFromUI(action):
        """Extract list of elements from old value of the property."""
        converter = propertyConverter()
        elements = list(converter.convertToElements(property))
      
        """Implement behaviour of user actions."""
        if action.name == ADD_ACTION_LABEL:
            """
    
               For 'add' action create new plasmid relationship element with values from input fields
    
               and add it to existing elements.
            """
    
            code = action.getInputValue(CODE_LABEL)
            relationshipLabel = action.getInputValue(RELATIONSHIP_LABEL)
    
            relationship = _translateFromLabel(relationshipLabel)
    
            annotation = action.getInputValue(ANNOTATION_LABEL)
    
            sampleLink = _createSampleLink(code, relationship, annotation)
    
            elements.append(sampleLink)
        elif action.name == EDIT_ACTION_LABEL:
            """
    
               For 'edit' action find the plasmid relationship element corresponding to selected row
    
               and replace it with an element with values from input fields.
            """
    
            code = action.getInputValue(CODE_LABEL)
            relationshipLabel = action.getInputValue(RELATIONSHIP_LABEL)
    
            relationship = _translateFromLabel(relationshipLabel)
    
            annotation = action.getInputValue(ANNOTATION_LABEL)
    
            sampleLink = _createSampleLink(code, relationship, annotation)
            
            selectedRowId = action.getSelectedRows()[0]
            elements[selectedRowId] = sampleLink
        elif action.name == DELETE_ACTION_LABEL:
            """
    
               For 'delete' action delete the relationships that correspond to selected rows.
    
               NOTE: As many rows can be deleted at once it is easier to delete them in reversed order.
            """
            rowIds = list(action.getSelectedRows())
            rowIds.reverse()       
            for rowId in rowIds:
                elements.pop(rowId)      
        else:
            raise ValidationException('action not supported')
          
        """Update value of the managed property to XML string created from modified list of elements."""
        property.value = converter.convertToString(elements)