class MockWrappedXml(WrappedXml): """ Class to host test properties """ # Attribute Properties root_attribute = attribute_property("root-attr") root_attribute_as_number = attribute_property("root-attr", types.NUMERIC) missing_attribute = attribute_property("missing-attribute") # Subnode Properties subnode = subnode_property("sub_node") missing_subnode = subnode_property("missing-subnode") # Subnode Link Properties subnode_link_read_only = subnode_link(WrappedXml, "link_node", readonly=True) subnode_link = subnode_link(WrappedXml, "link_node") subnode_links = subnode_links(WrappedXml, "link_node") # Subnode Proptery Literal Dictionary # # prop_name, subprop_name, name_attribute='name', value_attribute='value', readonly=False): subnode_property_literal_dict_instance = subnode_property_literal_dict( "dict", "dict_entry") subnode_property_literal_dict__alt_names_instance = subnode_property_literal_dict( "dict", "dict_entry", name_attribute="alt_name", value_attribute="alt_val") subnode_property_dict_instance = subnode_property_dict( "dict", as_attributes=("name", "value"))
class ContainerDimension(WrappedXml): is_alpha = subnode_property("is-alpha", types.BOOLEAN) offset = subnode_property("offset", types.NUMERIC) # type: float size = subnode_property("size", types.NUMERIC) # type: float @lazy_property def dimension_range(self): """ List of the labels for the given dimension (row or column) :return: list[int]|list[str] """ # Cast these to integers from floats to avoid deprecation warnings from range. start = int(self.offset) end = int(self.offset + self.size) if not self.is_alpha: return list(range(start, end)) else: return list(map(chr, range(65 + start, 65 + end))) def as_index(self, label): if label.isdigit(): return int(label) - int(self.offset) else: return ord(label[0]) - 65 - int(self.offset) def as_label(self, index): if self.is_alpha: return chr(65 + index + int(self.offset)) else: return str(index + int(self.offset))
class Automation(s4.clarity._internal.ClarityElement): UNIVERSAL_TAG = "{http://genologics.com/ri/automation}automation" name = attribute_property('name') context = subnode_property('context') command_string = subnode_property('string') channel = subnode_property('channel') process_types = subnode_links(ProcessType, "process-type", "process-types")
class Project(FieldsMixin, ClarityElement): UNIVERSAL_TAG = "{http://genologics.com/ri/project}project" ATTACH_TO_NAME = "Project" open_date = subnode_property("open-date", types.DATE) close_date = subnode_property("close-date", types.DATE) invoice_date = subnode_property("invoice-date", types.DATE) researcher = subnode_link(Researcher, "researcher", attributes=('uri', )) # type: Researcher
class Container(FieldsMixin, ClarityElement): UNIVERSAL_TAG = "{http://genologics.com/ri/container}container" ATTACH_TO_NAME = "Container" container_type = subnode_link(ContainerType, "type", attributes=('name', 'uri')) occupied_wells = subnode_property("occupied-wells", typename=types.NUMERIC, readonly=True) state = subnode_property("state", typename=types.STRING, readonly=True) @property def type_name(self): """ Read-only shortcut to containertype name, which we know without doing another GET. :type: str """ typenode = self.xml_find('./type') return typenode.get('name') @property def placements(self): """ Dict of string "Y:X" -> Artifacts. :type: dict[str, Artifact] """ return self.xml_all_as_dict( "placement", lambda n: n.find("value").text, # key lambda n: self.lims.artifacts.from_link_node(n) # value ) def artifact_at(self, well): """ :param well: String matching "Y:X" where Y is a column index and X is a row index. The string may use letters or numbers depending on the container type. :type well: str :rtype: Artifact or None """ try: return self.placements[well] except KeyError: raise KeyError("Container '%s' has no artifact at '%s'." % (self.name, well))
class ProcessTemplate(s4.clarity._internal.FieldsMixin, s4.clarity._internal.ClarityElement): UNIVERSAL_TAG = "{http://genologics.com/ri/processtemplate}process-template" type = subnode_link(ProcessType, 'type', attributes=('uri', )) is_default = subnode_property('is-default', types.BOOLEAN)
class Udf(ClarityElement): UNIVERSAL_TAG = "{http://genologics.com/ri/configuration}udfconfig" CREATION_TAG = "{http://genologics.com/ri/configuration}field" # Alternate name to avoid collision with built-in 'type' field_type = attribute_property("type") # type: str attach_to_name = subnode_property("attach-to-name") # type: str show_in_lablink = subnode_property("show-in-lablink", types.BOOLEAN) # type: bool allow_non_preset_values = subnode_property("allow-non-preset-values", types.BOOLEAN) # type: bool first_preset_is_default_value = subnode_property( "first-preset-is-default-value", types.BOOLEAN) # type: bool show_in_tables = subnode_property("show-in-tables", types.BOOLEAN) # type: bool is_editable = subnode_property("is-editable", types.BOOLEAN) # type: bool is_deviation = subnode_property("is-deviation", types.BOOLEAN) # type: bool is_controlled_vocabulary = subnode_property("is-controlled-vocabulary", types.BOOLEAN) # type: bool is_required = subnode_property("is-required", types.BOOLEAN) # type: bool attach_to_category = subnode_property("attach-to-category") # type: str # Only valid for Numeric types min_value = subnode_property("min-value", types.NUMERIC) # type: float max_value = subnode_property("max-value", types.NUMERIC) # type: float precision = subnode_property("precision", types.NUMERIC) # type: float @property def presets(self): """ :type: list """ preset_nodes = self.xml_root.findall('preset') return [ types.clarity_string_to_obj(self.field_type, preset_node.text) for preset_node in preset_nodes ] def add_preset(self, new_preset_value): """ Add a new preset value to the end of the list. Ignores values that are already present. :type new_preset_value: str|unicode|int|float|datetime.date|bool :param new_preset_value: the preset value to add, with a type appropriate to the UDF. The value is not validated to be the correct type. """ preset = self._find_preset_by_value(new_preset_value) if preset is not None: return self._add_preset_internal(new_preset_value) def remove_preset(self, preset_value): """ Remove a preset value from the list. :type preset_value: str|unicode|int|float|datetime.date|bool :param preset_value: the preset value to remove, with a type appropriate to the UDF. The value is not validated to be the correct type. """ preset = self._find_preset_by_value(preset_value) if preset is not None: self.xml_root.remove(preset) def set_default_preset(self, default_preset_value): """ Sets a preset value as the default (puts first in the list). Adds value if it isn't already preset. :type default_preset_value: str|unicode|int|float|datetime.date|bool :param default_preset_value: the new default preset value, with a type appropriate to the UDF. The value is not validated to be the correct type. :raises Exception: if the udf's first-preset-is-default property is currently false """ if not self.first_preset_is_default_value: raise Exception( "Setting the default value will have no effect, as first-preset-is-default-value is false." ) current_preset_nodes = self.xml_findall('preset') # Initialize the new list of presets with the default new_preset_values = [types.obj_to_clarity_string(default_preset_value)] for preset in current_preset_nodes: # Only grab values other than the new default, in case it was already in there if types.clarity_string_to_obj( self.field_type, preset.text) != default_preset_value: new_preset_values.append(preset.text) self.xml_root.remove(preset) for preset_value in new_preset_values: self._add_preset_internal(preset_value) def _find_preset_by_value(self, preset_value): all_presets = self.xml_root.findall("preset") for preset in all_presets: if types.clarity_string_to_obj(self.field_type, preset.text) == preset_value: return preset def _add_preset_internal(self, preset_value): preset_node = ETree.SubElement(self.xml_root, 'preset') preset_node.text = types.obj_to_clarity_string(preset_value)
class ContainerType(ClarityElement): UNIVERSAL_TAG = "{http://genologics.com/ri/containertype}container-type" is_tube = subnode_property("is-tube", types.BOOLEAN) # type: bool x_dimension = subnode_element(ContainerDimension, "x-dimension") # type: ContainerDimension y_dimension = subnode_element(ContainerDimension, "y-dimension") # type: ContainerDimension def well_to_rc(self, well): """ Converts a Clarity well position to the zero based index of the row and column. Example:: 'B:4' -> (1, 3) :param well: A Clarity formatted well position :type well: str :return: The zero based index of the row and the column. :rtype: tuple[int] """ location_pieces = well.split(":") return self.y_dimension.as_index( location_pieces[0]), self.x_dimension.as_index(location_pieces[1]) def rc_to_well(self, rc): """ Converts a zero based index of the row and column to a Clarity well position. Example:: (1, 3) -> 'B:4' :param rc: The zero based index of the row and the column. :type rc: tuple[int] :return: A Clarity formatted well position :rtype: str """ return "%s:%s" % (self.y_dimension.as_label( rc[0]), self.x_dimension.as_label(rc[1])) def row_order_wells(self): """ Wells in row order, e.g., A:1, B:1, ... Unavailable wells are omitted :rtype: list[str] """ l = [] for y in self.y_dimension.dimension_range: for x in self.x_dimension.dimension_range: well_name = "%s:%s" % (str(y), str(x)) if well_name not in self.unavailable_wells: l.append(well_name) return l def column_order_wells(self): """ Wells in column order, e.g., A:1, A:2, ... Unavailable wells are omitted :rtype: list[str] """ l = [] for x in self.x_dimension.dimension_range: for y in self.y_dimension.dimension_range: well_name = "%s:%s" % (str(y), str(x)) if well_name not in self.unavailable_wells: l.append(well_name) return l @lazy_property def unavailable_wells(self): """ :type: set[str] """ unavailable_well_nodes = self.xml_findall("unavailable-well") return set(node.text for node in unavailable_well_nodes) @lazy_property def total_capacity(self): """ :type: int """ return len(self.x_dimension.dimension_range) * len( self.y_dimension.dimension_range) - len(self.unavailable_wells) def x_dimension_range(self): """ :deprecated: use :class:`ContainerType.x_dimension.dimension_range` instead """ return self.x_dimension.dimension_range def y_dimension_range(self): """ :deprecated: use :class:`ContainerType.y_dimension.dimension_range` instead """ return self.y_dimension.dimension_range
class StepConfiguration(ClarityElement): UNIVERSAL_TAG = "{http://genologics.com/ri/protocolconfiguration}step" def __init__(self, protocol, node): """ :type protocol: Protocol """ super(StepConfiguration, self).__init__(protocol.lims, uri=None, xml_root=node) self.protocol = protocol properties = subnode_property_literal_dict('step-properties', 'step-property') protocol_step_index = subnode_property("protocol-step-index", types.NUMERIC) queue_fields = subnode_element_list(ProtocolStepField, "queue-fields", "queue-field") step_fields = subnode_element_list(ProtocolStepField, "step-fields", "step-field") sample_fields = subnode_element_list(ProtocolStepField, "sample-fields", "sample-field") triggers = subnode_property_list_of_dicts('epp-triggers/epp-trigger', as_attributes=[ 'status', 'point', 'type', 'name' ]) transitions = subnode_property_list_of_dicts('transitions/transition', as_attributes=[ 'name', 'sequence', 'next-step-uri' ], order_by=lambda x: int(x.get('sequence'))) def refresh(self): """ :raise Exception: Unable to refresh step directly, use protocol """ # FIXME? raise Exception("Unable to refresh step directly, use protocol") def put_and_parse(self, alternate_uri=None): self.protocol.put_and_parse(alternate_uri) def post_and_parse(self, alternate_uri=None): self.protocol.post_and_parse(alternate_uri) @lazy_property def process_type(self): """ :type: ProcessType """ pt_display_name = self.get_node_text('process-type') results = self.lims.process_types.query(displayname=pt_display_name) if results: return results[0] else: raise Exception("Process type '%s' not found in Clarity", pt_display_name) @lazy_property def required_reagent_kits(self): """ :type: ReagentKit """ reagent_kits = self.xml_findall("./required-reagent-kits/reagent-kit") return [ReagentKit(self.lims, p.get("uri")) for p in reagent_kits] @lazy_property def permitted_control_types(self): """ :type: ControlType """ control_types = self.xml_findall("./permitted-control-types/control-type") return [ControlType(self.lims, p.get("uri")) for p in control_types] @lazy_property def queue(self): """ :type: Queue """ return self.lims.queues.from_limsid(self.limsid)
class SubNode(WrappedXml): node_one = subnode_property("node_one") node_two = subnode_property("node_two")