Skip to content

Commit 2d77661

Browse files
Merge pull request #24 from MatildaAslin/reagent_kit_barcode_in_runfolder_info
Metadata in runfolder info
2 parents 24466a1 + 6688898 commit 2d77661

File tree

4 files changed

+153
-4
lines changed

4 files changed

+153
-4
lines changed

requirements/prod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ git+https://github.com/arteria-project/[email protected]#egg=arteria-core
22
jsonpickle==0.9.2
33
tornado==4.2.1
44
PyYAML==3.11
5+
xmltodict==0.11.0

runfolder/services.py

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import socket
33
import logging
44
import time
5+
import xmltodict
56
from runfolder import __version__ as version
67

78
from arteria.web.state import State
@@ -12,7 +13,7 @@ class RunfolderInfo:
1213
Information about a runfolder. Status must be defined in RunfolderState:
1314
"""
1415

15-
def __init__(self, host, path, state):
16+
def __init__(self, host, path, state, metadata):
1617
"""
1718
Initializes the object
1819
@@ -25,6 +26,7 @@ def __init__(self, host, path, state):
2526
self.path = path
2627
self.state = state
2728
self.service_version = version
29+
self.metadata = metadata
2830

2931
def __repr__(self):
3032
return "{0}: {1}@{2}".format(self.state, self.path, self.host)
@@ -100,6 +102,27 @@ def create_runfolder(self, path):
100102
os.makedirs(path)
101103
self._logger.info(
102104
"Created a runfolder at {0} - intended for tests only".format(path))
105+
runparameters_path = os.path.join(path, "runParameters.xml")
106+
if os.path.isfile(runparameters_path):
107+
raise CannotOverrideFile("runParameters.xml already exists at {0}".format(runparameters_path))
108+
109+
runparameters_dict = {
110+
'RunParameters': {
111+
'ReagentKitBarcode': 'AB1234567-123V1',
112+
'RfidsInfo': {
113+
'LibraryTubeSerialBarcode': 'NV0012345-LIB'
114+
}
115+
}
116+
}
117+
118+
output_xml = xmltodict.unparse(runparameters_dict, pretty=True)
119+
120+
with open(runparameters_path, 'a') as f:
121+
f.write(output_xml)
122+
123+
self._logger.info(
124+
"Added 'runParameters.xml' to '{0}' - intended for tests only".format(runparameters_path))
125+
103126

104127
def add_sequencing_finished_marker(self, path):
105128
"""
@@ -137,7 +160,8 @@ def get_runfolder_by_path(self, path):
137160

138161
if not self._dir_exists(path):
139162
raise DirectoryDoesNotExist("Directory does not exist: '{0}'".format(path))
140-
info = RunfolderInfo(self._host(), path, self.get_runfolder_state(path))
163+
info = RunfolderInfo(self._host(), path, self.get_runfolder_state(path),
164+
self.get_metadata(path))
141165
return info
142166

143167
def _get_runfolder_state_from_state_file(self, runfolder):
@@ -249,14 +273,63 @@ def _enumerate_runfolders(self):
249273
directory = os.path.join(monitored_root, subdir)
250274
self._logger.debug("Found potential runfolder {0}".format(directory))
251275
state = self.get_runfolder_state(directory)
252-
info = RunfolderInfo(self._host(), directory, state)
276+
info = RunfolderInfo(self._host(), directory, state,
277+
self.get_metadata(directory))
253278
yield info
254279

255280
def _requires_enabled(self, config_key):
256281
"""Raises an ActionNotEnabled exception if the specified config value is false"""
257282
if not self._configuration_svc[config_key]:
258283
raise ActionNotEnabled("The action {0} is not enabled".format(config_key))
259284

285+
def get_metadata(self, path):
286+
run_parameters = self.read_run_parameters(path)
287+
reagent_kit_barcode = self.get_reagent_kit_barcode(path, run_parameters)
288+
library_tube_barcode = self.get_library_tube_barcode(path, run_parameters)
289+
metadata = {}
290+
if reagent_kit_barcode:
291+
metadata['reagent_kit_barcode'] = reagent_kit_barcode
292+
if library_tube_barcode:
293+
metadata['library_tube_barcode'] = library_tube_barcode
294+
return metadata
295+
296+
def get_reagent_kit_barcode(self, path, run_parameters):
297+
try:
298+
barcode = run_parameters['RunParameters']['ReagentKitBarcode']
299+
except KeyError:
300+
# Reagent kit barcode is not available for all run types,
301+
# it is therefore expected to not be found in all cases
302+
self._logger.debug("Reagent kit barcode not found")
303+
return None
304+
except TypeError:
305+
self._logger.debug("[Rr]unParameters.xml not found")
306+
return None
307+
return barcode
308+
309+
def get_library_tube_barcode(self, path, run_parameters):
310+
try:
311+
barcode = run_parameters['RunParameters']['RfidsInfo']['LibraryTubeSerialBarcode']
312+
except KeyError:
313+
# Library tube barcode is not available for all run types,
314+
# it is therefore expected to not be found in all cases
315+
self._logger.debug("Library tube barcode not found")
316+
return None
317+
except TypeError:
318+
self._logger.debug("[Rr]unParameters.xml not found")
319+
return None
320+
return barcode
321+
322+
def read_run_parameters(self, path):
323+
alt_1 = os.path.join(path, "runParameters.xml")
324+
alt_2 = os.path.join(path, "RunParameters.xml")
325+
if os.path.exists(alt_1):
326+
with open(alt_1) as f:
327+
return xmltodict.parse(f.read())
328+
elif os.path.exists(alt_2):
329+
with open(alt_2) as f:
330+
return xmltodict.parse(f.read())
331+
else:
332+
return None
260333

261334
class CannotOverrideFile(Exception):
262335
pass
@@ -284,4 +357,3 @@ class InvalidRunfolderState(Exception):
284357

285358
class ConfigurationError(Exception):
286359
pass
287-

runfolder_tests/integration/rest_tests.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,16 @@ def test_next_runfolder(self):
139139
# Remove the path created, so it does not interfere with other tests
140140
shutil.rmtree(path)
141141

142+
def test_metadata_in_runfolder_info(self):
143+
path = self._create_ready_runfolder()
144+
self.assertTrue(self._exists(path))
145+
response = self.get("./runfolders/path{}".format(path), expect=200)
146+
response_json = jsonpickle.loads(response.text)
147+
self.assertEqual(response_json["metadata"]["reagent_kit_barcode"], 'AB1234567-123V1')
148+
self.assertEqual(response_json["metadata"]["library_tube_barcode"], 'NV0012345-LIB')
149+
# Remove the path created, so it does not interfere with other tests
150+
shutil.rmtree(path)
151+
142152
def test_call_next_without_ready_runfolder(self):
143153
# Status code 204 is no content.
144154
response = self.get("./runfolders/next", expect=204)

runfolder_tests/unit/runfolder_tests.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import unittest
22
import logging
3+
import mock
34

45
from arteria.web.state import State
56

@@ -81,6 +82,71 @@ def test_monitored_directory_validates(self):
8182
configuration_svc["monitored_directories"] = ["/data/testarteria1/runfolders/"]
8283
runfolder_svc._validate_is_being_monitored(runfolder)
8384

85+
def test_get_reagent_kit_barcode_found(self):
86+
# Setup
87+
configuration_svc = dict()
88+
configuration_svc["monitored_directories"] = ["/data/testarteria1/runfolders"]
89+
runfolder_svc = RunfolderService(configuration_svc, logger)
90+
runparameters_dict = {'RunParameters': {'ReagentKitBarcode': 'ABC-123'}}
91+
92+
# Test
93+
self.assertEqual(runfolder_svc.get_reagent_kit_barcode('/path/to/runfolder/', runparameters_dict), 'ABC-123')
94+
95+
def test_get_reagent_kit_barcode_not_found(self):
96+
# Setup
97+
configuration_svc = dict()
98+
configuration_svc["monitored_directories"] = ["/data/testarteria1/runfolders"]
99+
runfolder_svc = RunfolderService(configuration_svc, logger)
100+
runparameters_dict = {'RunParameters': {'OtherLabel': 'ABC-123'}}
101+
102+
# Test
103+
self.assertEqual(runfolder_svc.get_reagent_kit_barcode('/path/to/runfolder/', runparameters_dict), None)
104+
105+
def test_get_library_tube_barcode_found(self):
106+
# Setup
107+
configuration_svc = dict()
108+
configuration_svc["monitored_directories"] = ["/data/testarteria1/runfolders"]
109+
runfolder_svc = RunfolderService(configuration_svc, logger)
110+
runparameters_dict = {'RunParameters': {'RfidsInfo': {'LibraryTubeSerialBarcode': 'NV0012345-LIB'}}}
111+
112+
# Test
113+
self.assertEqual(runfolder_svc.get_library_tube_barcode('/path/to/runfolder/', runparameters_dict), 'NV0012345-LIB')
114+
115+
def test_get_library_tube_barcode_not_found(self):
116+
# Setup
117+
configuration_svc = dict()
118+
configuration_svc["monitored_directories"] = ["/data/testarteria1/runfolders"]
119+
runfolder_svc = RunfolderService(configuration_svc, logger)
120+
runparameters_dict = {'RunParameters': {'OtherLabel': 'ABC-123'}}
121+
122+
# Test
123+
self.assertEqual(runfolder_svc.get_library_tube_barcode('/path/to/runfolder/', runparameters_dict), None)
124+
125+
def test_get_metadata_values_found(self):
126+
# Setup
127+
configuration_svc = dict()
128+
configuration_svc["monitored_directories"] = ["/data/testarteria1/runfolders"]
129+
runfolder_svc = RunfolderService(configuration_svc, logger)
130+
runparameters_dict = {'RunParameters': { 'ReagentKitBarcode': 'ABC-123',
131+
'RfidsInfo': {'LibraryTubeSerialBarcode': 'NV0012345-LIB'}}}
132+
133+
# Test
134+
with mock.patch.object(RunfolderService, 'read_run_parameters', return_value = runparameters_dict):
135+
metadata_dict = runfolder_svc.get_metadata('/path/to/runfolder/')
136+
self.assertEqual(metadata_dict['reagent_kit_barcode'], 'ABC-123')
137+
self.assertEqual(metadata_dict['library_tube_barcode'], 'NV0012345-LIB')
138+
139+
def test_get_metadata_values_not_found(self):
140+
# Setup
141+
configuration_svc = dict()
142+
configuration_svc["monitored_directories"] = ["/data/testarteria1/runfolders"]
143+
runfolder_svc = RunfolderService(configuration_svc, logger)
144+
runparameters_dict = {'RunParameters': {'OtherLabel': 'ABC-123'}}
145+
146+
# Test
147+
with mock.patch.object(RunfolderService, 'read_run_parameters', return_value = runparameters_dict):
148+
metadata_dict = runfolder_svc.get_metadata('/path/to/runfolder/')
149+
self.assertEqual(metadata_dict, {})
84150

85151
if __name__ == '__main__':
86152
unittest.main()

0 commit comments

Comments
 (0)