Repository: phac-nml/irida-uploader

Issue:
Allow Directory Parser to use full file paths, in addition to file names.
Suggested by @happykhan 

Use case: Reads can sometimes be scattered around, so it would be nice to be able to just give the path directly in the SampleList.csv file.

Related code: `/parsers/directory/sample_parser.py` in `_parse_sample_list` search for `path.join`

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4443c81..8b5d251 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ Added functionality:
 * Added config option overrides for the command line
     * Can define in command line `--option parameter` or without a parameter `--option` for a user prompt
     * Use the `--help` option for more details on these options
+* The `directory` parser can now handle full (absolute) file paths in the sample list file
 
 Bug Fixes:
 * Fixed hard crash that sometimes occurred when valid url's were given as the base_url
diff --git a/parsers/directory/sample_parser.py b/parsers/directory/sample_parser.py
index 315c0fa..0af6cfc 100644
--- a/parsers/directory/sample_parser.py
+++ b/parsers/directory/sample_parser.py
@@ -79,6 +79,9 @@ def _parse_sample_list(sample_sheet_file):
     data_dir = path.dirname(sample_sheet_file)
     data_dir_file_list = next(walk(data_dir))[2]  # Create a file list of the data directory, only hit the os once
 
+    data_dir_file_list_full_path = []
+    for file_name in data_dir_file_list:
+        data_dir_file_list_full_path.append(path.join(path.abspath(data_dir), file_name))
     has_paired_end_read = False
     has_single_end_read = False
 
@@ -94,13 +97,15 @@ def _parse_sample_list(sample_sheet_file):
             has_single_end_read = True
 
         # Check if file names are in the files we found in the directory
-        if sample_dict['File_Forward'] not in data_dir_file_list:
+        if (sample_dict['File_Forward'] not in data_dir_file_list and
+           sample_dict['File_Forward'] not in data_dir_file_list_full_path):
             raise exceptions.SampleSheetError(
                 ("Your sample sheet is malformed. {} Does not match any file in the directory {}"
                  "".format(sample_dict['File_Forward'], data_dir)),
                 sample_sheet_file
             )
-        if paired_end_read and sample_dict['File_Reverse'] not in data_dir_file_list:
+        if ((paired_end_read and sample_dict['File_Reverse'] not in data_dir_file_list) and
+           (paired_end_read and sample_dict['File_Reverse'] not in data_dir_file_list_full_path)):
             raise exceptions.SampleSheetError(
                 ("Your sample sheet is malformed. {} Does not match any file in the directory {}"
                  "".format(sample_dict['File_Reverse'], data_dir)),
@@ -108,8 +113,9 @@ def _parse_sample_list(sample_sheet_file):
             )
 
         # Add the dir to each file to create the full path
-        sample_dict['File_Forward'] = path.join(data_dir, sample_dict['File_Forward'])
-        if paired_end_read:
+        if sample_dict['File_Forward'] not in data_dir_file_list_full_path:
+            sample_dict['File_Forward'] = path.join(data_dir, sample_dict['File_Forward'])
+        if paired_end_read and sample_dict['File_Reverse'] not in data_dir_file_list_full_path:
             sample_dict['File_Reverse'] = path.join(data_dir, sample_dict['File_Reverse'])
 
     # Verify we don't have both single end and paired end reads


Test patch:
diff --git a/tests/parsers/directory/test_sample_parser.py b/tests/parsers/directory/test_sample_parser.py
index 38735b1..671bb67 100644
--- a/tests/parsers/directory/test_sample_parser.py
+++ b/tests/parsers/directory/test_sample_parser.py
@@ -1,6 +1,9 @@
 import unittest
+from collections import OrderedDict
 from os import path
+from unittest.mock import patch
 
+import parsers.directory
 import parsers.directory.sample_parser as sample_parser
 from parsers.exceptions import SampleSheetError
 import model
@@ -196,6 +199,41 @@ class TestParseSampleList(unittest.TestCase):
         self.assertEqual(res[0]["File_Forward"], file_path_1)
         self.assertEqual(res[0]["File_Reverse"], file_path_2)
 
+    @patch("parsers.directory.sample_parser._parse_samples")
+    def test_valid_full_file_path(self, mock_parse_samples):
+        """
+        Given a valid sample sheet with full file paths, parse correctly
+        :return:
+        """
+        sheet_file = path.join(path_to_module, "fake_dir_data",
+                               "SampleList_simple.csv")
+
+        file_path_1 = path.join(path_to_module,
+                                "fake_dir_data", "file_1.fastq.gz")
+        file_path_2 = path.join(path_to_module,
+                                "fake_dir_data", "file_2.fastq.gz")
+
+
+        sample_dict_list = [OrderedDict([
+            ('Sample_Name', 'my-sample-1'),
+            ('Project_ID', '75'),
+            ('File_Forward', path.abspath(file_path_1)),
+            ('File_Reverse', path.abspath(file_path_2))
+        ])]
+
+        mock_parse_samples.return_value = sample_dict_list
+
+        res = parsers.directory.sample_parser._parse_sample_list(sheet_file)
+
+        mock_parse_samples.assert_called_with(sheet_file)
+        # Check we have 1 sample
+        self.assertEqual(len(res), 1)
+        # Check if data is correct
+        self.assertEqual(res[0]["Sample_Name"], "my-sample-1")
+        self.assertEqual(res[0]["Project_ID"], "75")
+        self.assertEqual(res[0]["File_Forward"], path.abspath(file_path_1))
+        self.assertEqual(res[0]["File_Reverse"], path.abspath(file_path_2))
+
     def test_no_forward_read(self):
         """
         No Valid files were found with names given in sample sheet