Source code for hyperstream.workflow.plate_manager

# The MIT License (MIT)
# Copyright (c) 2014-2017 University of Bristol
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
# OR OTHER DEALINGS IN THE SOFTWARE.

from ..utils import Printable, MetaDataTree, PlateEmptyError, PlateDefinitionError
from ..models import PlateDefinitionModel
from plate import Plate

import logging
from mongoengine.context_managers import switch_db
from mongoengine import DoesNotExist, MultipleObjectsReturned


[docs]class PlateManager(Printable): """ Plate manager. Manages the mapping between plates defined in the database with the global meta data definition. """ def __init__(self, global_meta_data): """ Initialise the manager Want to get meta-data dictionaries for all plate combinations e.g. H plate, want {'H': [ {'house': '1'}, {'house': '2'} ]} H1 plate, want {'H1': [{'house': '1'}]} H.R plate, want {'H.R': [ {'house': '1', 'resident': '1'}, {'house': '1': 'resident': '2'}, {'house': '2': 'resident': '1'} } :param global_meta_data: The global meta data, which will be stored in a tree structure """ self.plates = {} self.global_plate_definitions = MetaDataTree() # Populate the global plate definitions from dict given in the config file for item in global_meta_data: self.global_plate_definitions.create_node(**item) logging.info("Global plate definitions: ") logging.info(self.global_plate_definitions) # Plate definitions (arrays of streams) with switch_db(PlateDefinitionModel, db_alias="hyperstream"): for p in PlateDefinitionModel.objects: self.add_plate(p)
[docs] def create_plate(self, plate_id, description, meta_data_id, values, complement, parent_plate): """ Create a new plate, and commit it to the database :param plate_id: The plate id - required to be unique :param description: A human readable description :param meta_data_id: The meta data id, which should correspond to the tag in the global meta data :param values: Either a list of string values, or the empty list (for use with complement) :param complement: If complement is true, then the complement of the values list will be used when getting values from the global meta data :param parent_plate: The parent plate identifier :return: The newly created plate :type plate_id: str | unicode :type complement: bool :type values: list | tuple """ # Make sure the plate id doesn't already exist with switch_db(PlateDefinitionModel, db_alias='hyperstream'): try: p = PlateDefinitionModel.objects.get(plate_id=plate_id) if p: logging.info("Plate with id {} already exists".format(plate_id)) return self.plates[plate_id] except DoesNotExist: pass except MultipleObjectsReturned: raise plate_definition = PlateDefinitionModel( plate_id=plate_id, description=description, meta_data_id=meta_data_id, values=values, complement=complement, parent_plate=parent_plate ) self.add_plate(plate_definition) plate_definition.save() return self.plates[plate_id]
[docs] def add_plate(self, plate_definition): """ Add a plate using the plate definition :param plate_definition: The plate definition :return: None :type plate_definition: PlateDefinitionModel """ values = self.get_plate_values(plate_definition) # TODO: We should also be checking that the plate is defined over all of the values of the parent plate self.plates[plate_definition.plate_id] = Plate( plate_id=plate_definition.plate_id, meta_data_id=plate_definition.meta_data_id, values=values, parent_plate=self.plates[plate_definition.parent_plate] if plate_definition.parent_plate else None) logging.debug("Added plate: {}".format(self.plates[plate_definition.plate_id]))
[docs] def get_plate_values(self, plate_definition): """ Gets the plate values from the global meta data according to the given plate definition :param plate_definition: The plate definition :return: The plate values :type plate_definition: PlateDefinitionModel """ if not plate_definition.values and not plate_definition.complement: raise PlateDefinitionError() values = [] for n in self.global_plate_definitions.all_nodes(): if n.tag == plate_definition.meta_data_id: if not plate_definition.values or n.data in plate_definition.values: if plate_definition.parent_plate: # This plate has parent plates, so we need to get parent data for the node parent_plate_value = self.get_parent_plate_value(self.global_plate_definitions, n) if tuple(parent_plate_value) not in self.plates[plate_definition.parent_plate].values: continue values.insert(0, self.get_parent_data(self.global_plate_definitions, n, {n.tag: n.data})) else: values.insert(0, {n.tag: n.data}) if not values: raise PlateEmptyError(plate_definition.plate_id) return values
[docs] def get_parent_plate_value(self, tree, node, value=None): """ Recurse up the tree getting parent plate values :param tree: The tree :param node: The current node :param value: The initial plate value :return: The plate value as a list of tuples """ if value is None: value = [] parent = tree.parent(node.identifier) if parent.is_root(): # value.append((parent.tag, parent.identifier)) return value value = self.get_parent_plate_value(tree, parent, value) if "." in parent.identifier: pass value.append((parent.tag, parent.data)) return value
@staticmethod
[docs] def get_parent_data(tree, node, d): """ Recurse up the tree getting parent data :param tree: The tree :param node: The current node :param d: The initial dictionary :return: The hierarchical dictionary """ parent = tree.parent(node.identifier) if parent.is_root(): return d d[parent.tag] = parent.data return PlateManager.get_parent_data(tree, parent, d)