Skip to content

Commit

Permalink
Merge pull request #16 from EdinburghGenomics/parsepools
Browse files Browse the repository at this point in the history
Parse pools in a Step as dict
  • Loading branch information
Timothee Cezard committed Dec 18, 2017
2 parents 8e44d24 + 4d80ae8 commit b358a92
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 9 deletions.
62 changes: 60 additions & 2 deletions pyclarity_lims/descriptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,8 @@ def _setitem(self, key, value):
self._elems[0].attrib[key] = value

def _delitem(self, key):
if key in ['artifact', 'step', 'rework-step']:
key = key + '-uri'
del self._elems[0].attrib[key]


Expand All @@ -312,13 +314,20 @@ def _parse_element(self, element, **kwargs):
def _setitem(self, key, value):
if not isinstance(key, str):
raise ValueError()
elem1 = None
for node in self._elems:
if node.find('value').text == key:
elem1 = node
break
if elem1:
self.rootnode(self.instance).remove(elem1)
elem1 = ElementTree.SubElement(self.rootnode(self.instance), 'placement', uri=value.uri, limsid=value.id)
elem2 = ElementTree.SubElement(elem1, 'value')
elem2.text = key

def _delitem(self, key):
for node in self._elems:
if node.text == key:
if node.find('value').text == key:
self.rootnode(self.instance).remove(node)
break

Expand Down Expand Up @@ -347,7 +356,10 @@ def _setitem(self, key, value):
tag_node = self.rootnode(self.instance).find(self.tag)
if tag_node is None:
tag_node = ElementTree.SubElement(self.rootnode(self.instance), self.tag)
elem = ElementTree.SubElement(tag_node, key)

elem = tag_node.find(key)
if elem is None:
elem = ElementTree.SubElement(tag_node, key)
elem.text = value

def _delitem(self, key):
Expand All @@ -358,6 +370,52 @@ def _delitem(self, key):
break


class XmlPooledInputDict(XmlDictionary, Nestable):
"""An dictionary where the key is the pool name and the value a tuples (pool, inputs)
the first item of the tuple is an output Artifact representing the pool.
the second item is a tuple containing the input artifacts for that pool.
"""

def __init__(self, instance, *args, **kwargs):
Nestable.__init__(self, nesting=['pooled-inputs'])
XmlDictionary.__init__(self, instance, *args, **kwargs)

def _update_elems(self):
self._elems = self.rootnode(self.instance).findall('pool')

def _setitem(self, key, value):
if not isinstance(key, str):
raise ValueError()
if not isinstance(value, tuple) or not len(value) == 2:
raise TypeError('You need to provide a tuple of 2 elements not ' + type(value))
pool, list_input = value
self._delitem(key)
node = ElementTree.SubElement(self.rootnode(self.instance), 'pool')
node.attrib['name'] = key
node.attrib['uri'] = pool.uri
for inart in list_input:
sub = ElementTree.Element('input')
sub.attrib['uri'] = inart.uri
node.append(sub)

def _delitem(self, key):
for node in self._elems:
if node.attrib['name'] == key:
self.rootnode(self.instance).remove(node)
break

def _parse_element(self, element, **kwargs):
from pyclarity_lims.entities import Artifact
dict.__setitem__(
self,
element.attrib.get('name'),
(
Artifact(self.instance.lims, uri=element.attrib.get('output-uri')),
tuple(Artifact(self.instance.lims, uri=sub.attrib.get('uri')) for sub in element.findall('input'))
)
)


# List types
class XmlList(XmlMutable, list):
"""Class that behave like a list and modify the provided instance as the list gets updated"""
Expand Down
17 changes: 16 additions & 1 deletion pyclarity_lims/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
InputOutputMapList, LocationDescriptor, IntegerAttributeDescriptor, \
StringAttributeDescriptor, EntityListDescriptor, StringListDescriptor, PlacementDictionaryDescriptor, \
ReagentLabelList, AttributeListDescriptor, StringDictionaryDescriptor, OutputPlacementListDescriptor, \
XmlActionList, MutableDescriptor
XmlActionList, MutableDescriptor, XmlPooledInputDict

try:
from urllib.parse import urlsplit, urlparse, parse_qs, urlunparse
except ImportError:
Expand Down Expand Up @@ -774,6 +775,18 @@ class StepProgramStatus(Entity):
message = StringDescriptor('message')
"""Message return by the program"""


class StepPools(Entity):
pooled_inputs = MutableDescriptor(XmlPooledInputDict)
"""Dictionary where the key are the pool names and the values are tuples (pool, inputs) representing a pool.
Each tuple has two elements:
* an output Artifact containing the pool.
* a tuple containing the input artifacts for that pool.
"""

class Step(Entity):
"Step, as defined by the genologics API."

Expand All @@ -792,6 +805,8 @@ class Step(Entity):
"""link to the :py:class:`StepDetails <pyclarity_lims.entities.StepDetails>` entity"""
program_status = EntityDescriptor('program-status', StepProgramStatus)
"""link to the :py:class:`StepProgramStatus <pyclarity_lims.entities.StepProgramStatus>` entity"""
pools = EntityDescriptor('pools', StepPools)
"""link to the :py:class:`StepPools <pyclarity_lims.entities.StepPools>` entity"""
date_started = StringDescriptor('date-started')
"""The date at which the step started in format Year-Month-DayTHour:Min:Sec i.e. 2016-11-22T10:43:32.857+00:00"""
date_completed = StringDescriptor('date-completed')
Expand Down
71 changes: 65 additions & 6 deletions tests/test_descriptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pyclarity_lims.descriptors import StringDescriptor, StringAttributeDescriptor, StringListDescriptor, \
StringDictionaryDescriptor, IntegerDescriptor, BooleanDescriptor, UdfDictionary, EntityDescriptor, \
InputOutputMapList, EntityListDescriptor, PlacementDictionary, EntityList, SubTagDictionary, ExternalidList,\
XmlElementAttributeDict, XmlAttributeList, XmlReagentLabelList
XmlElementAttributeDict, XmlAttributeList, XmlReagentLabelList, XmlPooledInputDict
from pyclarity_lims.entities import Artifact
from pyclarity_lims.lims import Lims
from tests import elements_equal
Expand All @@ -20,14 +20,16 @@
from unittest.mock import Mock


def _tostring(e):
outfile = BytesIO()
ElementTree.ElementTree(e).write(outfile, encoding='utf-8', xml_declaration=True)
return outfile.getvalue().decode("utf-8")


class TestDescriptor(TestCase):
def _make_desc(self, klass, *args, **kwargs):
return klass(*args, **kwargs)

def _tostring(self, e):
outfile = BytesIO()
ElementTree.ElementTree(e).write(outfile, encoding='utf-8', xml_declaration=True)
return outfile.getvalue()

class TestStringDescriptor(TestDescriptor):
def setUp(self):
Expand Down Expand Up @@ -385,10 +387,28 @@ def test___getitem__(self):
def test___setitem__(self):
assert len(self.dict1.rootnode(self.dict1.instance).findall('placement')) == 1
art2 = Artifact(lims=self.lims, id='a2')
self.dict1['A:1'] = art2
assert len(self.dict1.rootnode(self.dict1.instance).findall('placement')) == 1

self.dict1['A:2'] = art2
assert len(self.dict1.rootnode(self.dict1.instance).findall('placement')) == 2
assert self.dict1['A:2'] == art2

def test___setitem__2(self):
et = ElementTree.fromstring("""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<test-entry xmlns:udf="http://genologics.com/ri/userdefined">
</test-entry>""")
instance = Mock(root=et, lims=self.lims)
dict = PlacementDictionary(instance)
assert len(dict.rootnode(dict.instance).findall('placement')) == 0
dict['A:1'] = self.art1
assert len(dict.rootnode(dict.instance).findall('placement')) == 1

def test___delitem__(self):
assert len(self.dict1.rootnode(self.dict1.instance).findall('placement')) == 1
del self.dict1['A:1']
assert len(self.dict1.rootnode(self.dict1.instance).findall('placement')) == 0


class TestSubTagDictionary(TestCase):

Expand All @@ -408,9 +428,13 @@ def test___getitem__(self):

def test___setitem__(self):
assert len(self.dict1.rootnode(self.dict1.instance).find('test-tag')) == 1
art2 = Artifact(lims=self.lims, id='a2')
assert self.dict1.rootnode(self.dict1.instance).find('test-tag').find('key1').text == 'value1'
self.dict1['key1'] = 'value11'
assert len(self.dict1.rootnode(self.dict1.instance).find('test-tag')) == 1
assert self.dict1.rootnode(self.dict1.instance).find('test-tag').find('key1').text == 'value11'
self.dict1['key2'] = 'value2'
assert len(self.dict1.rootnode(self.dict1.instance).find('test-tag')) == 2
assert self.dict1.rootnode(self.dict1.instance).find('test-tag').find('key2').text == 'value2'
assert self.dict1['key2'] == 'value2'


Expand Down Expand Up @@ -441,6 +465,41 @@ def test___setitem__(self):
assert self.dict1.rootnode(self.dict1.instance).findall('test-tag')[0].attrib['attrib1'] == 'value2'


class TestXmlPooledInputDict(TestCase):

def setUp(self):
et = ElementTree.fromstring('''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<test-entry>
<pooled-inputs>
<pool output-uri="{uri}/out1" name="pool1">
<input uri="{uri}/in1"/>
<input uri="{uri}/in2"/>
</pool>
<pool output-uri="{uri}/out2" name="pool2">
<input uri="{uri}/in3"/>
<input uri="{uri}/in4"/>
</pool>
</pooled-inputs>
</test-entry>'''.format(uri='http://testgenologics.com:4040'))

self.lims = Lims('http://testgenologics.com:4040', username='test', password='password')
self.instance1 = Mock(root=et, lims=self.lims)
self.dict1 = XmlPooledInputDict(self.instance1)

self.out1 = Artifact(self.lims, uri='http://testgenologics.com:4040/out1')
self.in1 = Artifact(self.lims, uri='http://testgenologics.com:4040/in1')
self.in2 = Artifact(self.lims, uri='http://testgenologics.com:4040/in2')

def test___getitem__(self):
assert self.dict1['pool1'] == (self.out1, (self.in1, self.in2))

def test___setitem__(self):
assert len(self.dict1) == 2
assert len(self.dict1.rootnode(self.dict1.instance)) == 2
self.dict1['pool3'] = (self.out1, (self.in1, self.in2))
assert len(self.dict1) == 3
assert len(self.dict1.rootnode(self.dict1.instance)) == 3

class TestEntityList(TestCase):

def setUp(self):
Expand Down

0 comments on commit b358a92

Please sign in to comment.