def batch_get(self, uris, prefetch=True): # type: (Iterable[str], bool) -> List[ClarityElement] """ Queries Clarity for a list of uris described by their REST API endpoint. If this query can be made as a single request it will be done that way. :param uris: A List of uris :param prefetch: Force load full content for each element. :return: A list of the elements returned by the query. """ if not uris: return [] # just return an empty list if there were no uris if self.can_batch_get(): links_root = ETree.Element("{http://genologics.com/ri}links") n_queries = 0 querying_now = set() for uri in uris: uri = self._strip_params(uri) if uri in querying_now: # already covered continue obj = self._cache.get(uri) if prefetch and (obj is None or not obj.is_fully_retrieved()): link = ETree.SubElement(links_root, "link") link.set("uri", uri) link.set("rel", self._plural_name) querying_now.add(uri) n_queries += 1 if n_queries > 0: result_root = self.lims.request('post', self.uri + "/batch/retrieve", links_root) result_nodes = result_root.findall( './' + self.element_class.UNIVERSAL_TAG) for node in result_nodes: uri = node.get("uri") uri = self._strip_params(uri) old_obj = self._cache.get(uri) if old_obj is not None: old_obj.xml_root = node else: new_obj = self.element_class(self.lims, uri=uri, xml_root=node) self._cache[uri] = new_obj return [self._cache[uri] for uri in uris] else: return [self.get(uri, force_full_get=prefetch) for uri in uris]
def _add_action_subnode(self, routing_node, action, workflow_or_stage_uri): """ Generates a ElementTree.SubElement according to the action (assign / unassign) and the workflow/stage uri """ if 'stage' in workflow_or_stage_uri: assign_node = ETree.SubElement( routing_node, action, {'stage-uri': workflow_or_stage_uri}) else: assign_node = ETree.SubElement( routing_node, action, {'workflow-uri': workflow_or_stage_uri}) return assign_node
def set_location_well(self, container, well): """" Sets this artifact's location (usually for sample creation) with the given well location, in the given container. :param container: The Sample's container :type container: s4.clarity.Container :param well: The well position in the form "<row>:<col>" :type well: str """ location_node = self.make_subelement_with_parents("./location") ETree.SubElement(location_node, 'value').text = well # attach container node, which must have the uri ETree.SubElement(location_node, 'container', {'uri': container.uri})
def __set__(self, instance, value): """ :type instance: s4.clarity._internal.element.WrappedXml :type value: s4.clarity._internal.element.ClarityElement """ self._ensure_settable(instance) # a link is of the form: # <project limsid="SWI1" uri="https://qalocal/api/v2/projects/SWI1"/> node = instance.get_or_create_subnode(self.property_name) attribs = {} for attrname in self.link_attributes: if hasattr(value, attrname): attrvalue = getattr(value, attrname) if attrvalue is not None: attribs[attrname] = attrvalue if node is None: ETree.SubElement(instance.xml_root, self.property_name, attribs) else: for k, v in attribs.items(): node.set(k, v)
def raise_on_exception(cls, response, data=None): # Make sure we are an exception, if not carry on if not cls.is_response_exception(response): return root = ETree.XML(response.content) try: msg = root.find('message').text except AttributeError: msg = "No message provided by Clarity." try: msg += "\nSuggested actions: " + root.find( 'suggested-actions').text except AttributeError: # no suggested-actions pass extra = root.get('category') if extra is not None: msg += "\nException category: " + extra extra = root.get('code') if extra is not None: msg += "\nException code: " + extra if "File does not exist" in msg: raise FileNotFoundException(msg) else: instance = cls(msg) instance.request_body = data.decode("UTF-8") if isinstance( data, bytes) else data raise instance
def add_role(self, new_role): credentials_node = self.xml_find('credentials') for child in credentials_node: if child.tag == "role" and child.get("name") == new_role.name: return ETree.SubElement(credentials_node, 'role', {"uri": new_role.uri})
def _create_routing_node(self): """ Generates the XML for workflow/stage assignment/unassignment """ routing_node = ETree.Element( "{http://genologics.com/ri/routing}routing") for action, routes in self.routing_dict.items(): for workflow_or_stage_uri, artifact_set in routes.items(): if artifact_set: assign_node = self._add_action_subnode( routing_node, action, workflow_or_stage_uri) # create an artifact assign node for each samples for artifact in artifact_set: ETree.SubElement(assign_node, "artifact", {"uri": artifact.uri}) return routing_node
def get_or_create_subnode(self, path): node = self.xml_find(path) if node is None: parent = self.xml_root for node_name in path.split('/'): node = parent.find(node_name) if node is None: node = ETree.SubElement(parent, node_name) parent = node return node
def _dict_into_node(self, instance, value, parent, name, node=None): if node is None: node = parent.find('./' + name) or ETree.SubElement(parent, name) if type(value) == dict: for k, v in value.items(): if k in self.as_attributes: node.set(k, self._value_to_string(v)) else: self._dict_into_node(instance, v, node, k) elif type(value) == list: for subvalue in value: # call self again, but value = subvalue, prechosen node self._dict_into_node(instance, subvalue, parent, name, node) # get a new node node = ETree.SubElement(parent, name) # release the last node in the list parent.remove(node) else: node.text = self._value_to_string(value)
def test_decimal_commas_bug(self): # some Clarity installs return numeric values with commas in place of decimal points due to locale setting test_parsed_xml = ETree.fromstring(""" <art:artifact xmlns:art="http://genologics.com/ri/artifact"> <udf:field xmlns:udf="http://genologics.com/ri/userdefined" type="Numeric" name="udfname">0,005</udf:field> </art:artifact> """) element = Artifact(lims=self.lims, xml_root=test_parsed_xml) self.assertEqual(element['udfname'], 0.005)
def make_subelement_with_parents(self, xpath): node = self.xml_root if not xpath.startswith("."): raise Exception("xpath must start with . to make all subelements.") for subelement_name in xpath.split('/')[1:]: subnode = node.find("./" + subelement_name) if subnode is None: subnode = ETree.SubElement(node, subelement_name) node = subnode return node
def _get_or_make_dict(self, instance): node = instance.xml_find('./' + self.property_name) # we check node in case we've gotten a new xml tree and still have the old dict. if self._dict is not None and self._dict.top_node == node: return self._dict if node is None: node = ETree.SubElement(instance.xml_root, self.property_name) return _ClarityLiteralDict(node, self.subprop_name, self.name_attribute, self.value_attribute)
def element_from_xml(element_class, xml, **extra_kwargs): try: return element_class(lims=FakeLims(), xml_root=ETree.fromstring(xml), **extra_kwargs) except TypeError as e: str(e) if "__init__() takes at least" in str(e): raise TypeError( "Unable to instantiate %s, provide extra args in extra_kwargs. %s" % (element_class.__name__, e)) else: raise e
def _get_or_create_node(self, key): """ Get a field XML node, or create and append it to the XML fields node if it doesn't exist. :type key: str :rtype: ETree.Element """ field_node = self._real_dict.get(key) if field_node is None: field_node = ETree.SubElement(self._root_node, FIELD_TAG) field_node.set('name', key) self._real_dict[key] = field_node return field_node
def batch_update(self, elements): # type: (Iterable[ClarityElement]) -> None """ Persists the ClarityElements back to Clarity. Will preform this action as a single query if possible. :param elements: All ClarityElements to save the state of. :raises ClarityException: if Clarity returns an exception as XML """ if not elements: return if self.can_batch_update(): details_root = ETree.Element(self.batch_tag) for el in elements: details_root.append(el.xml_root) self.lims.request('post', self.uri + "/batch/update", details_root) else: for el in elements: self.lims.request('post', el.uri, el.xml_root)
def batch_create(self, elements): # type: (Iterable[ClarityElement]) -> List[ClarityElement] """ Creates new records in Clarity for each element and returns these new records as ClarityElements. If this operation can be performed in a single network operation it will be. :param elements: A list of new ClarityElements that have not been persisted to Clarity yet. :return: New ClarityElement records from Clarity, created with the data supplied to the method. :raises ClarityException: if Clarity returns an exception as XML """ if not elements: return [] if self.can_batch_create(): details_root = ETree.Element(self.batch_tag) for el in elements: details_root.append(el.xml_root) links = self.lims.request('post', self.uri + "/batch/create", details_root) return self.from_link_nodes(links) else: objects = [] for el in elements: new_obj = self.element_class(self.lims, xml_root=self.lims.request( 'post', el.uri, el.xml_root)) self._cache[new_obj.uri] = new_obj objects.append(new_obj) return objects
def new(self, **kwargs): # type: (**str) -> ClarityElement """ Create a new ClarityElement pre-populated with the provided values. This object has yet to be persisted to Clarity. :param kwargs: Key/Value list of attribute name/value pairs to initialize the element with. :return: A new ClarityElement, pre-populated with provided values. """ # creating some types requires using special tag, ie samples # are created by posting a 'samplecreation' element, not a 'sample' el_tag = getattr(self.element_class, 'CREATION_TAG', self.element_class.UNIVERSAL_TAG) # create xml_root, call class constructor new_xml_root = ETree.Element(el_tag) new_obj = self.element_class(self.lims, xml_root=new_xml_root) # set attributes from kwargs to new_object for k, v in kwargs.items(): setattr(new_obj, k, v) return new_obj
def _normalize_xml(xml_string): xml_obj_one = ETree.fromstring(xml_string) return ETree.tostring(xml_obj_one)
def xml(self): """:rtype: str|bytes""" return ETree.tostring(self.xml_root)
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)
def _new_step_from_configuration(self, input_uri_list, controls, containertype, reagenttype): """ Creates a new step in clarity. :param input_uri_list: A list of artifacts that will be use to run the step :param controls: The list of controls that will be included in this step :param containertype: The container that output artifacts will be placed in. :param reagenttype: The name of the reagent category to use for the step. """ log.info("Creating %s (user: %s)", self.__class__.__name__, self.lims.username) if not input_uri_list and not controls: raise StepRunnerException( "Unable to create new step with no input artifacts or contols." ) root = ETree.Element("{http://genologics.com/ri/step}step-creation") ETree.SubElement(root, "configuration", {'uri': self.step_config.uri}) inputsnode = ETree.SubElement(root, "inputs") if input_uri_list: for input_uri in input_uri_list: attrib = { 'uri': input_uri, 'replicates': str(self.replicates_for_inputuri(input_uri)) } ETree.SubElement(inputsnode, "input", attrib) if controls: for control in controls: attrib = { 'control-type-uri': control.uri, 'replicates': str(self.replicates_for_control(control)) } ETree.SubElement(inputsnode, "input", attrib) if containertype is None: permitted_containers = self.step_config.xml_find( "./permitted-containers") if permitted_containers is not None and len( permitted_containers) > 0: containertype = permitted_containers[0].text if containertype is not None: node = ETree.SubElement(root, "container-type") node.text = containertype else: log.warning("No container type specified for step.") if reagenttype is None: permitted_reagents = self.step_config.xml_find( "./permitted-reagent-categories") if permitted_reagents is not None and len(permitted_reagents) > 0: reagenttype = permitted_reagents[0].text if reagenttype is not None: node = ETree.SubElement(root, "reagent-category") node.text = reagenttype step_xml_root = self.lims.request("post", self.lims.root_uri + "/steps", root) step = Step(self.lims, uri=step_xml_root.get("uri"), xml_root=step_xml_root, limsid=step_xml_root.get("limsid")) log.info("%s started step %s" % (self.__class__.__name__, step.uri)) step.wait_for_epp() return step
def special_type(self, value): special_type = self.xml_find("./special-type") if special_type is None: special_type = ETree.SubElement(self.xml_root, "special-type") special_type.set("name", value)
def __setitem__(self, key, value): node = self._node_for(key) if node is None: node = ETree.SubElement(self.top_node, self.subnode_name, {self.name_attribute: key}) node.set(self.value_attribute, value)