This repository has been archived by the owner on Sep 17, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 53
/
Copy pathlims.py
327 lines (298 loc) · 15.2 KB
/
lims.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
"""Python interface to GenoLogics LIMS via its REST API.
LIMS interface.
Per Kraulis, Science for Life Laboratory, Stockholm, Sweden.
Copyright (C) 2012 Per Kraulis
"""
__all__ = ['Lab', 'Researcher', 'Project', 'Sample',
'Containertype', 'Container', 'Processtype', 'Process',
'Artifact', 'Lims']
import urllib
from cStringIO import StringIO
# http://docs.python-requests.org/
import requests
from .entities import *
class Lims(object):
"LIMS interface through which all entity instances are retrieved."
VERSION = 'v1'
def __init__(self, baseuri, username, password):
"""baseuri: Base URI for the GenoLogics server, excluding
the 'api' or version parts!
For example: https://genologics.scilifelab.se:8443/
username: The account name of the user to login as.
password: The password for the user account to login as.
"""
self.baseuri = baseuri.rstrip('/') + '/'
self.username = username
self.password = password
self.cache = dict()
def get_uri(self, *segments, **query):
"Return the full URI given the path segments and optional query."
segments = ['api', self.VERSION] + list(segments)
url = urlparse.urljoin(self.baseuri, '/'.join(segments))
if query:
url += '?' + urllib.urlencode(query)
return url
def get(self, uri, params=dict()):
"GET data from the URI. Return the response XML as an ElementTree."
r = requests.get(uri, params=params,
auth=(self.username, self.password),
headers=dict(accept='application/xml'))
return self.parse_response(r)
def put(self, uri, data, params=dict()):
"""PUT the serialized XML to the given URI.
Return the response XML as an ElementTree.
"""
r = requests.put(uri, data=data, params=params,
auth=(self.username, self.password),
headers={'content-type':'application/xml',
'accept': 'application/xml'})
return self.parse_response(r)
def post(self, uri, data, params=dict()):
"""POST the serialized XML to the given URI.
Return the response XML as an ElementTree.
"""
r = requests.post(uri, data=data, params=params,
auth=(self.username, self.password),
headers={'content-type': 'application/xml',
'accept': 'application/xml'})
return self.parse_response(r)
def check_version(self):
"""Raise ValueError if the version for this interface
does not match any of the versions given for the API.
"""
uri = urlparse.urljoin(self.baseuri, 'api')
r = requests.get(uri, auth=(self.username, self.password))
root = self.parse_response(r)
tag = nsmap('ver:versions')
assert tag == root.tag
for node in root.findall('version'):
if node.attrib['major'] == self.VERSION: return
raise ValueError('version mismatch')
def parse_response(self, response):
"""Parse the XML returned in the response.
Raise an HTTP error if the response status is not 200.
"""
root = ElementTree.fromstring(response.content)
if response.status_code != 200:
node = root.find('message')
if node is None:
response.raise_for_status()
message = "%s: %s" % (response.status_code, node.text)
node = root.find('suggested-actions')
if node is not None:
message += ' ' + node.text
raise requests.exceptions.HTTPError(message)
return root
def get_labs(self, name=None, last_modified=None,
udf=dict(), udtname=None, udt=dict(), start_index=None):
"""Get a list of labs, filtered by keyword arguments.
name: Lab name, or list of names.
last_modified: Since the given ISO format datetime.
udf: dictionary of UDFs with 'UDFNAME[OPERATOR]' as keys.
udtname: UDT name, or list of names.
udt: dictionary of UDT UDFs with 'UDTNAME.UDFNAME[OPERATOR]' as keys
and a string or list of strings as value.
start_index: Page to retrieve; all if None.
"""
params = self._get_params(name=name,
last_modified=last_modified,
start_index=start_index)
params.update(self._get_params_udf(udf=udf, udtname=udtname, udt=udt))
return self._get_instances(Lab, params=params)
def get_researchers(self, firstname=None, lastname=None, username=None,
last_modified=None,
udf=dict(), udtname=None, udt=dict(),start_index=None):
"""Get a list of researchers, filtered by keyword arguments.
firstname: Researcher first name, or list of names.
lastname: Researcher last name, or list of names.
username: Researcher account name, or list of names.
last_modified: Since the given ISO format datetime.
udf: dictionary of UDFs with 'UDFNAME[OPERATOR]' as keys.
udtname: UDT name, or list of names.
udt: dictionary of UDT UDFs with 'UDTNAME.UDFNAME[OPERATOR]' as keys
and a string or list of strings as value.
start_index: Page to retrieve; all if None.
"""
params = self._get_params(firstname=firstname,
lastname=lastname,
username=username,
last_modified=last_modified,
start_index=start_index)
params.update(self._get_params_udf(udf=udf, udtname=udtname, udt=udt))
return self._get_instances(Researcher, params=params)
def get_projects(self, name=None, open_date=None, last_modified=None,
udf=dict(), udtname=None, udt=dict(), start_index=None):
"""Get a list of projects, filtered by keyword arguments.
name: Project name, or list of names.
open_date: Since the given ISO format date.
last_modified: Since the given ISO format datetime.
udf: dictionary of UDFs with 'UDFNAME[OPERATOR]' as keys.
udtname: UDT name, or list of names.
udt: dictionary of UDT UDFs with 'UDTNAME.UDFNAME[OPERATOR]' as keys
and a string or list of strings as value.
start_index: Page to retrieve; all if None.
"""
params = self._get_params(name=name,
open_date=open_date,
last_modified=last_modified,
start_index=start_index)
params.update(self._get_params_udf(udf=udf, udtname=udtname, udt=udt))
return self._get_instances(Project, params=params)
def get_samples(self, name=None, projectname=None, projectlimsid=None,
udf=dict(), udtname=None, udt=dict(), start_index=None):
"""Get a list of samples, filtered by keyword arguments.
name: Sample name, or list of names.
projectlimsid: Samples for the project of the given LIMS id.
projectname: Samples for the project of the name.
udf: dictionary of UDFs with 'UDFNAME[OPERATOR]' as keys.
udtname: UDT name, or list of names.
udt: dictionary of UDT UDFs with 'UDTNAME.UDFNAME[OPERATOR]' as keys
and a string or list of strings as value.
start_index: Page to retrieve; all if None.
"""
params = self._get_params(name=name,
projectname=projectname,
projectlimsid=projectlimsid,
start_index=start_index)
params.update(self._get_params_udf(udf=udf, udtname=udtname, udt=udt))
return self._get_instances(Sample, params=params)
def get_artifacts(self, name=None, type=None, process_type=None,
artifact_flag_name=None, working_flag=None, qc_flag=None,
sample_name=None, artifactgroup=None, containername=None,
containerlimsid=None, reagent_label=None,
udf=dict(), udtname=None, udt=dict(), start_index=None):
"""Get a list of artifacts, filtered by keyword arguments.
name: Artifact name, or list of names.
type: Artifact type, or list of types.
process_type: Produced by the process type, or list of types.
artifact_flag_name: Tagged with the genealogy flag, or list of flags.
working_flag: Having the given working flag; boolean.
qc_flag: Having the given QC flag: UNKNOWN, PASSED, FAILED.
sample_name: Related to the given sample name.
artifactgroup: Belonging to the artifact group (experiment in client).
containername: Residing in given container, by name, or list.
containerlimsid: Residing in given container, by LIMS id, or list.
reagent_label: having attached reagent labels.
udf: dictionary of UDFs with 'UDFNAME[OPERATOR]' as keys.
udtname: UDT name, or list of names.
udt: dictionary of UDT UDFs with 'UDTNAME.UDFNAME[OPERATOR]' as keys
and a string or list of strings as value.
start_index: Page to retrieve; all if None.
"""
params = self._get_params(name=name,
type=type,
process_type=process_type,
artifact_flag_name=artifact_flag_name,
working_flag=working_flag,
qc_flag=qc_flag,
sample_name=sample_name,
artifactgroup=artifactgroup,
containername=containername,
containerlimsid=containerlimsid,
reagent_label=reagent_label,
start_index=start_index)
params.update(self._get_params_udf(udf=udf, udtname=udtname, udt=udt))
return self._get_instances(Artifact, params=params)
def get_containers(self, name=None, type=None,
state=None, last_modified=None,
udf=dict(), udtname=None, udt=dict(), start_index=None):
"""Get a list of containers, filtered by keyword arguments.
name: Containers name, or list of names.
type: Container type, or list of types.
state: Container state: Empty, Populated, Discarded, Reagent-Only.
last_modified: Since the given ISO format datetime.
udf: dictionary of UDFs with 'UDFNAME[OPERATOR]' as keys.
udtname: UDT name, or list of names.
udt: dictionary of UDT UDFs with 'UDTNAME.UDFNAME[OPERATOR]' as keys
and a string or list of strings as value.
start_index: Page to retrieve; all if None.
"""
params = self._get_params(name=name,
type=type,
state=state,
last_modified=last_modified,
start_index=start_index)
params.update(self._get_params_udf(udf=udf, udtname=udtname, udt=udt))
return self._get_instances(Container, params=params)
def get_processes(self, last_modified=None, type=None,
inputartifactslimsid=None,
techfirstname=None, techlastname=None, projectname=None,
udf=dict(), udtname=None, udt=dict(), start_index=None):
"""Get a list of processes, filtered by keyword arguments.
last_modified: Since the given ISO format datetime.
type: Process type, or list of types.
inputartifactslimsid: Input artifact LIMS id, or list of.
udf: dictionary of UDFs with 'UDFNAME[OPERATOR]' as keys.
udtname: UDT name, or list of names.
udt: dictionary of UDT UDFs with 'UDTNAME.UDFNAME[OPERATOR]' as keys
and a string or list of strings as value.
techfirstname: First name of researcher, or list of.
techlastname: Last name of researcher, or list of.
projectname: Name of project, or list of.
start_index: Page to retrieve; all if None.
"""
params = self._get_params(last_modified=last_modified,
type=type,
inputartifactslimsid=inputartifactslimsid,
techfirstname=techfirstname,
techlastname=techlastname,
projectname=projectname,
start_index=start_index)
params.update(self._get_params_udf(udf=udf, udtname=udtname, udt=udt))
return self._get_instances(Process, params=params)
def _get_params(self, **kwargs):
"Convert keyword arguments to a kwargs dictionary."
result = dict()
for key, value in kwargs.iteritems():
if value is None: continue
result[key.replace('_', '-')] = value
return result
def _get_params_udf(self, udf=dict(), udtname=None, udt=dict()):
"Convert UDF-ish arguments to a params dictionary."
result = dict()
for key, value in udf.iteritems():
result["udf.%s" % key] = value
if udtname is not None:
result['udt.name'] = udtname
for key, value in udt.iteritems():
result["udt.%s" % key] = value
return result
def _get_instances(self, klass, params=dict()):
result = []
tag = klass._TAG
if tag is None:
tag = klass.__name__.lower()
root = self.get(self.get_uri(klass._URI), params=params)
while params.get('start-index') is None: # Loop over all pages.
for node in root.findall(tag):
result.append(klass(self, uri=node.attrib['uri']))
node = root.find('next-page')
if node is None: break
root = self.get(node.attrib['uri'], params=params)
return result
def get_batch(self, instances):
"Get the content of a set of instances using the efficient batch call."
if not instances:
return []
klass = instances[0].__class__
root = ElementTree.Element(nsmap('ri:links'))
for instance in instances:
ElementTree.SubElement(root, 'link', dict(uri=instance.uri,
rel=klass._URI))
uri = self.get_uri(klass._URI, 'batch/retrieve')
data = self.tostring(ElementTree.ElementTree(root))
root = self.post(uri, data)
result = []
for node in root.getchildren():
instance = klass(self, uri=node.attrib['uri'])
instance.root = node
result.append(instance)
return result
def tostring(self, etree):
"Return the ElementTree contents as a UTF-8 encoded XML string."
outfile = StringIO()
self.write(outfile, etree)
return outfile.getvalue()
def write(self, outfile, etree):
"Write the ElementTree contents as UTF-8 encoded XML to the open file."
etree.write(outfile, encoding='UTF-8')