Skip to content

Commit b71bb7e

Browse files
authored
Merge pull request #207 from Galithil/master
Extended Researcher to handle Roles and Permissions.
2 parents e7eb7f0 + 7b09f5f commit b71bb7e

File tree

4 files changed

+108
-9
lines changed

4 files changed

+108
-9
lines changed

genologics/descriptors.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ def __init__(self, instance, *args, **kwargs):
164164

165165
@property
166166
def rootnode(self):
167-
if not self._rootnode:
167+
if self._rootnode is None:
168168
self._rootnode = self.instance.root
169169
for rootkey in self.rootkeys:
170170
self._rootnode = self._rootnode.find(rootkey)
@@ -431,6 +431,45 @@ def __get__(self, instance, cls):
431431

432432
return result
433433

434+
class NestedBooleanDescriptor(TagDescriptor):
435+
def __init__(self, tag, *args):
436+
super(NestedBooleanDescriptor, self).__init__(tag)
437+
self.rootkeys = args
438+
439+
def __get__(self, instance, cls):
440+
instance.get()
441+
result = None
442+
rootnode = instance.root
443+
for rootkey in self.rootkeys:
444+
rootnode = rootnode.find(rootkey)
445+
result = rootnode.find(self.tag).text.lower() == 'true'
446+
return result
447+
448+
def __set__(self, instance, value):
449+
rootnode = instance.root
450+
for rootkey in self.rootkeys:
451+
rootnode = rootnode.find(rootkey)
452+
rootnode.find(self.tag).text = str(value).lower()
453+
454+
class NestedStringDescriptor(TagDescriptor):
455+
def __init__(self, tag, *args):
456+
super(NestedStringDescriptor, self).__init__(tag)
457+
self.rootkeys = args
458+
459+
def __get__(self, instance, cls):
460+
instance.get()
461+
result = None
462+
rootnode = instance.root
463+
for rootkey in self.rootkeys:
464+
rootnode = rootnode.find(rootkey)
465+
result = rootnode.find(self.tag).text
466+
return result
467+
468+
def __set__(self, instance, value):
469+
rootnode = instance.root
470+
for rootkey in self.rootkeys:
471+
rootnode = rootnode.find(rootkey)
472+
rootnode.find(self.tag).text = value
434473

435474
class NestedAttributeListDescriptor(StringAttributeDescriptor):
436475
"""An instance yielding a list of dictionnaries of attributes
@@ -514,6 +553,8 @@ def __get__(self, instance, cls):
514553
from genologics.entities import Container
515554
instance.get()
516555
node = instance.root.find(self.tag)
556+
if node is None:
557+
return (None,None)
517558
uri = node.find('container').attrib['uri']
518559
return Container(instance.lims, uri=uri), node.find('value').text
519560

@@ -572,3 +613,4 @@ def get_dict(self, lims, node):
572613
if node is not None:
573614
result['parent-process'] = Process(lims, node.attrib['uri'])
574615
return result
616+

genologics/entities.py

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
UdtDictionaryDescriptor, ExternalidListDescriptor, EntityDescriptor, BooleanDescriptor, EntityListDescriptor, \
1212
StringAttributeDescriptor, StringListDescriptor, DimensionDescriptor, IntegerDescriptor, \
1313
PlacementDictionaryDescriptor, InputOutputMapList, LocationDescriptor, ReagentLabelList, NestedEntityListDescriptor, \
14-
NestedStringListDescriptor, NestedAttributeListDescriptor, IntegerAttributeDescriptor
14+
NestedStringListDescriptor, NestedAttributeListDescriptor, IntegerAttributeDescriptor, NestedStringDescriptor, \
15+
NestedBooleanDescriptor
1516

1617
try:
1718
from urllib.parse import urlsplit, urlparse, parse_qs, urlunparse
@@ -317,13 +318,26 @@ def _create(cls, lims, creation_tag=None, **kwargs):
317318
@classmethod
318319
def create(cls, lims, creation_tag=None, **kwargs):
319320
"""Create an instance from attributes then post it to the LIMS"""
320-
instance = cls._create(lims, creation_tag=None, **kwargs)
321+
instance = cls._create(lims, creation_tag=creation_tag, **kwargs)
321322
data = lims.tostring(ElementTree.ElementTree(instance.root))
322323
instance.root = lims.post(uri=lims.get_uri(cls._URI), data=data)
323324
instance._uri = instance.root.attrib['uri']
324325
return instance
325326

326327

328+
class Instrument(Entity):
329+
"""Lab Instrument
330+
"""
331+
_URI = "instruments"
332+
_tag = "instrument"
333+
_PREFIX = "inst"
334+
335+
name = StringDescriptor('name')
336+
type = StringDescriptor('type')
337+
serial_number = StringDescriptor('serial-number')
338+
expiry_date = StringDescriptor('expiry-date')
339+
archived = BooleanDescriptor('archived')
340+
327341
class Lab(Entity):
328342
"Lab; container of researchers."
329343

@@ -338,7 +352,6 @@ class Lab(Entity):
338352
externalids = ExternalidListDescriptor()
339353
website = StringDescriptor('website')
340354

341-
342355
class Researcher(Entity):
343356
"Person; client scientist or lab personnel. Associated with a lab."
344357

@@ -357,11 +370,26 @@ class Researcher(Entity):
357370
externalids = ExternalidListDescriptor()
358371

359372
# credentials XXX
373+
username = NestedStringDescriptor('username', 'credentials')
374+
account_locked = NestedBooleanDescriptor('account-locked', 'credentials')
360375

361376
@property
362377
def name(self):
363378
return "%s %s" % (self.first_name, self.last_name)
364379

380+
class Permission(Entity):
381+
"""A Clarity permission. Only supports GET"""
382+
name = StringDescriptor('name')
383+
action = StringDescriptor('action')
384+
description = StringDescriptor('description')
385+
386+
387+
class Role(Entity):
388+
"""Clarity Role, hosting permissions"""
389+
name = StringDescriptor('name')
390+
researchers = NestedEntityListDescriptor('researcher', Researcher, 'researchers')
391+
permissions = NestedEntityListDescriptor('permission', Permission, 'permissions')
392+
365393

366394
class Reagent_label(Entity):
367395
"""Reagent label element"""
@@ -473,6 +501,9 @@ def get_placements(self):
473501
self.lims.get_batch(list(result.values()))
474502
return result
475503

504+
def delete(self):
505+
self.lims.delete(self.uri)
506+
476507

477508
class Processtype(Entity):
478509
_TAG = 'process-type'
@@ -517,8 +548,8 @@ class Process(Entity):
517548
udt = UdtDictionaryDescriptor()
518549
files = EntityListDescriptor(nsmap('file:file'), File)
519550
process_parameter = StringDescriptor('process-parameter')
551+
instrument = EntityDescriptor('instrument', Instrument)
520552

521-
# instrument XXX
522553
# process_parameters XXX
523554

524555
def outputs_per_input(self, inart, ResultFile=False, SharedResultFile=False, Analyte=False):
@@ -704,13 +735,14 @@ class StepPools(Entity):
704735
def _remove_available_inputs(self, input_art):
705736
""" removes an input from the available inputs, one replicate at a time
706737
"""
738+
self.get_available_inputs()
707739
rep = self._available_inputs.get(input_art, {'replicates': 0}).get('replicates', 1)
708740
if rep > 1:
709741
self._available_inputs[input_art]['replicates'] = rep - 1
710742
elif rep == 1:
711743
del(self._available_inputs[input_art])
712744
else:
713-
raise Exception("No more replicates left for artifact {0}".format(input_art))
745+
logger.info("using more inputs than replicates for input {0}".format(input_art.uri))
714746
self.available_inputs = self._available_inputs
715747

716748
def set_available_inputs(self, available_inputs):
@@ -719,7 +751,7 @@ def set_available_inputs(self, available_inputs):
719751
for input_art in available_inputs:
720752
current_elem = ElementTree.SubElement(available_inputs_root, "input")
721753
current_elem.attrib['uri'] = input_art.uri
722-
current_elem.attrib['replicates'] = available_inputs[input_art]['replicates']
754+
current_elem.attrib['replicates'] = str(available_inputs[input_art]['replicates'])
723755
self._available_inputs = available_inputs
724756

725757
def get_available_inputs(self):
@@ -784,7 +816,7 @@ def get_placement_list(self):
784816
for node in self.root.find('output-placements').findall('output-placement'):
785817
input = Artifact(self.lims, uri=node.attrib['uri'])
786818
location = (None, None)
787-
if node.find('location'):
819+
if node.find('location') is not None:
788820
location = (
789821
Container(self.lims, uri=node.find('location').find('container').attrib['uri']),
790822
node.find('location').find('value').text
@@ -963,6 +995,7 @@ class Step(Entity):
963995
program_status = EntityDescriptor('program-status', StepProgramStatus)
964996

965997
def advance(self):
998+
self.get()
966999
self.root = self.lims.post(
9671000
uri="{}/advance".format(self.uri),
9681001
data=self.lims.tostring(ElementTree.ElementTree(self.root))
@@ -1035,6 +1068,7 @@ def __init__(self, lims, uri=None, id=None):
10351068
if child.attrib.get("name") == "Sequence":
10361069
self.sequence = child.attrib.get("value")
10371070

1071+
10381072
class Queue(Entity):
10391073
"""Queue of a given step"""
10401074
_URI = "queues"
@@ -1049,4 +1083,5 @@ class Queue(Entity):
10491083
Artifact.workflow_stages = NestedEntityListDescriptor('workflow-stage', Stage, 'workflow-stages')
10501084
Step.configuration = EntityDescriptor('configuration', ProtocolStep)
10511085
StepProgramStatus.configuration = EntityDescriptor('configuration', ProtocolStep)
1086+
Researcher.roles = NestedEntityListDescriptor('role', Role, 'credentials')
10521087

genologics/lims.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,16 @@ def post(self, uri, data, params=dict()):
157157
'accept': 'application/xml'})
158158
return self.parse_response(r, accept_status_codes=[200, 201, 202])
159159

160+
def delete(self, uri, params=dict()):
161+
"""sends a DELETE to the given URI.
162+
Return the response XML as an ElementTree.
163+
"""
164+
r = requests.delete(uri, params=params,
165+
auth=(self.username, self.password),
166+
headers={'content-type': 'application/xml',
167+
'accept': 'application/xml'})
168+
return self.validate_response(r, accept_status_codes=[204])
169+
160170
def check_version(self):
161171
"""Raise ValueError if the version for this interface
162172
does not match any of the versions given for the API.
@@ -365,6 +375,13 @@ def get_artifacts(self, name=None, type=None, process_type=None,
365375
else:
366376
return self._get_instances(Artifact, params=params)
367377

378+
def get_container_types(self, name=None, start_index=None):
379+
"""Get a list of container types, filtered by keyword arguments.
380+
name: Container Type name.
381+
start-index: Page to retrieve, all if None."""
382+
params = self._get_params(name=name, start_index=start_index)
383+
return self._get_instances(Containertype, params=params)
384+
368385
def get_containers(self, name=None, type=None,
369386
state=None, last_modified=None,
370387
udf=dict(), udtname=None, udt=dict(), start_index=None,
@@ -455,6 +472,11 @@ def get_reagent_lots(self, name=None, kitname=None, number=None,
455472
start_index=start_index)
456473
return self._get_instances(ReagentLot, params=params)
457474

475+
def get_instruments(self, name=None):
476+
"""Returns a list of Instruments, can be filtered by name"""
477+
params = self._get_params(name=name)
478+
return self._get_instances(Instrument, params=params)
479+
458480
def _get_params(self, **kwargs):
459481
"Convert keyword arguments to a kwargs dictionary."
460482
result = dict()

genologics/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__="0.3.16"
1+
__version__="0.3.17"

0 commit comments

Comments
 (0)