Repository: phac-nml/staramr

Issue:
Change strict restriction on PointFinder organisms to be a warning
Currently, running with `--pointfinder-organism ORGANISM` with anything other than `salmonella` or `campylobacter` will result in an error due to a check I perform against a list of acceptable organisms here:

https://github.com/phac-nml/staramr/blob/107e2cd8a60cc9a3acce35ff6ede9ff4e75659c2/staramr/blast/pointfinder/PointfinderBlastDatabase.py#L78-L83

This was done to ensure that I only allow results to be generated from PointFinder databases/organisms that I have validated work with staramr. But, there are additional mutations from the PointFinder databases belonging to other organisms that could still be useful to run with staramar, even if not every mutation (or indel) will work properly.

Instead of restricting which organisms can be used by staramr for PointFinder I wish to switch this to a warning. That is, if `--pointfinder-organism ORGANISM` is set to anything outside of the list of validated organisms, results will still be generated by staramr, but a warning message will be printed.

Comments:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c9bd308..6e6bce0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 * Fixed up some Python warnings related to pandas (0.8.0.dev0).
 * Adjusted `mlst` tests to account for differences in results for newer versions (0.8.0.dev0).
 * Drop support for Python 3.5 as it leads to issues with managing dependency versions (0.8.0.dev0).
+* Switched from disallowing to generating a warning when the PointFinder organism is not one of the validated organisms (0.8.0.dev1).
 
 # Version 0.7.2.zenodo0
 
diff --git a/staramr/__init__.py b/staramr/__init__.py
index 7556fbd..45fe705 100644
--- a/staramr/__init__.py
+++ b/staramr/__init__.py
@@ -1,1 +1,1 @@
-__version__ = '0.8.0.dev0'
+__version__ = '0.8.0.dev1'
diff --git a/staramr/blast/pointfinder/PointfinderBlastDatabase.py b/staramr/blast/pointfinder/PointfinderBlastDatabase.py
index ef4b26b..58219a6 100644
--- a/staramr/blast/pointfinder/PointfinderBlastDatabase.py
+++ b/staramr/blast/pointfinder/PointfinderBlastDatabase.py
@@ -37,6 +37,13 @@ class PointfinderBlastDatabase(AbstractBlastDatabase):
     def get_path(self, database_name):
         return path.join(self.pointfinder_database_dir, database_name + self.fasta_suffix)
 
+    def is_validated(self):
+        """
+        Whether or not this particular PointFinder organism is part of the validated set.
+        :return: True if this PointFinder organism/database is validated, False otherwise.
+        """
+        return self.organism in self.get_available_organisms()
+
     def get_resistance_codons(self, gene, codon_mutations):
         """
         Gets a list of resistance codons from the given gene and codon mutations.
diff --git a/staramr/databases/BlastDatabaseRepositories.py b/staramr/databases/BlastDatabaseRepositories.py
index 95dd591..81c8cf4 100644
--- a/staramr/databases/BlastDatabaseRepositories.py
+++ b/staramr/databases/BlastDatabaseRepositories.py
@@ -1,7 +1,7 @@
 import logging
 import shutil
 from collections import OrderedDict
-from typing import Dict
+from typing import Dict, List
 
 from staramr.blast.AbstractBlastDatabase import AbstractBlastDatabase
 from staramr.blast.plasmidfinder.PlasmidfinderBlastDatabase import PlasmidfinderBlastDatabase
@@ -87,6 +87,9 @@ class BlastDatabaseRepositories:
 
         for name, repo in self._database_repositories.items():
             info.update(repo.info())
+            if name == 'pointfinder':
+                info['pointfinder_organisms_all'] = ', '.join(sorted(self.get_pointfinder_organisms()))
+                info['pointfinder_organisms_valid'] = ', '.join(sorted(self.get_valid_pointfinder_organisms()))
 
         return info
 
@@ -142,6 +145,25 @@ class BlastDatabaseRepositories:
 
         return repos
 
+    def get_pointfinder_organisms(self) -> List[str]:
+        """
+        Gets a list of all pointfinder organisms from this database.
+        :return: A list of PointFinder organisms from this database.
+        """
+        try:
+            return PointfinderBlastDatabase.get_organisms(self.get_repo_dir('pointfinder'))
+        except FileNotFoundError as e:
+            logger.debug(e)
+            return []
+
+
+    def get_valid_pointfinder_organisms(self) -> List[str]:
+        """
+        Gets a list of all valid pointfinder organisms.
+        :return: A list of all valid pointfinder organisms.
+        """
+        return PointfinderBlastDatabase.get_available_organisms()
+
     def build_blast_database(self, database_name: str, options: Dict[str, str] = {}) -> AbstractBlastDatabase:
         """
         Builds a staramr.blast.AbstractBlastDatabase from the given parameters.
diff --git a/staramr/subcommand/Database.py b/staramr/subcommand/Database.py
index 18da93f..131bbbd 100644
--- a/staramr/subcommand/Database.py
+++ b/staramr/subcommand/Database.py
@@ -286,6 +286,13 @@ class Info(Database):
 
         arg_drug_table = ARGDrugTable()
 
+        def write_database_info(database_repos):
+                database_info = database_repos.info()
+                database_info['mlst_version'] = JobHandler.get_mlst_version(JobHandler)
+
+                database_info.update(arg_drug_table.get_resistance_table_info())
+                sys.stdout.write(get_string_with_spacing(database_info))
+
         if len(args.directories) == 0:
             database_repos = AMRDatabasesManager.create_default_manager().get_database_repos()
             if not AMRDatabasesManager.is_database_repos_default_commits(database_repos):
@@ -294,12 +301,7 @@ class Info(Database):
                     "AMR genes depending on how the database files are structured.")
 
             try:
-                database_info = database_repos.info()
-                database_info['mlst_version'] = JobHandler.get_mlst_version(JobHandler)
-
-                database_info.update(arg_drug_table.get_resistance_table_info())
-                sys.stdout.write(get_string_with_spacing(database_info))
-
+                write_database_info(database_repos)
             except DatabaseNotFoundException as e:
                 logger.error("No database found. Perhaps try restoring the default with 'staramr db restore-default'")
         else:
@@ -312,9 +314,7 @@ class Info(Database):
                             "differences in the detected AMR genes depending on how the database files are structured.",
                             directory)
 
-                    database_info = database_repos.info()
-                    database_info.update(arg_drug_table.get_resistance_table_info())
-                    sys.stdout.write(get_string_with_spacing(database_info))
+                    write_database_info(database_repos)
                 except DatabaseNotFoundException as e:
                     logger.error("Database not found in [%s]. Perhaps try building with 'staramr db build --dir %s'",
                                  directory, directory)
diff --git a/staramr/subcommand/Search.py b/staramr/subcommand/Search.py
index 43e8f43..468fe13 100644
--- a/staramr/subcommand/Search.py
+++ b/staramr/subcommand/Search.py
@@ -55,11 +55,15 @@ class Search(SubCommand):
                                                 help='Search for AMR genes')
 
         self._default_database_dir = AMRDatabasesManager.get_default_database_directory()
+        default_database_repos = AMRDatabasesManager.create_default_manager().get_database_repos()
+
         cpu_count = multiprocessing.cpu_count()
 
         arg_parser.add_argument('--pointfinder-organism', action='store', dest='pointfinder_organism', type=str,
-                                help='The organism to use for pointfinder {' + ', '.join(
-                                    PointfinderBlastDatabase.get_available_organisms()) + '}. Defaults to disabling search for point mutations. [None].',
+                                help=(f'The organism to use for pointfinder. '
+                                      f"Validated: {set(default_database_repos.get_valid_pointfinder_organisms())}. "
+                                      f"All: {set(default_database_repos.get_pointfinder_organisms())}. "
+                                      f"Defaults to disabling search for point mutations. [None]."),
                                 default=None, required=False)
         arg_parser.add_argument('--plasmidfinder-database-type', action='store', dest='plasmidfinder_database_type',
                                 type=str,
@@ -293,10 +297,20 @@ class Search(SubCommand):
 
             logger.info("Finished. Took %s minutes.", time_difference_minutes)
 
+            included_pointfinder = pointfinder_database is not None
+
             settings = database_repos.info()
 
             settings['mlst_version'] = JobHandler.get_mlst_version(JobHandler)
             settings['command_line'] = ' '.join(sys.argv)
+            settings['pointfinder_organism'] = pointfinder_database.organism if included_pointfinder else 'None'
+
+            if included_pointfinder and not pointfinder_database.is_validated():
+                settings['messages'] = (f'Warning: Selected organism [{pointfinder_database.organism}] is '
+                                        f'not part of the validated set of organisms for PointFinder:'
+                                        f' {set(pointfinder_database.get_available_organisms())}. Cannot guarantee that all '
+                                        f'point mutations were detected properly.')
+
             settings['version'] = self._version
             settings['start_time'] = start_time.strftime(self.TIME_FORMAT)
             settings['end_time'] = end_time.strftime(self.TIME_FORMAT)
@@ -352,9 +366,15 @@ class Search(SubCommand):
 
         resfinder_database = database_repos.build_blast_database('resfinder')
         if (args.pointfinder_organism):
-            if args.pointfinder_organism not in PointfinderBlastDatabase.get_available_organisms():
-                raise CommandParseException("The only Pointfinder organism(s) currently supported are " + str(
-                    PointfinderBlastDatabase.get_available_organisms()), self._root_arg_parser)
+            if args.pointfinder_organism not in database_repos.get_pointfinder_organisms():
+                raise CommandParseException(f"The organism \"{args.pointfinder_organism}\" is not found in the selected PointFinder database. The "
+                                            f"only organisms available are: {set(database_repos.get_pointfinder_organisms())}. "
+                                            f"Of these, only {set(PointfinderBlastDatabase.get_available_organisms())} have been validated.",
+                                            self._root_arg_parser)
+            elif args.pointfinder_organism not in PointfinderBlastDatabase.get_available_organisms():
+                logger.warning("The only validated Pointfinder organism(s) are " + str(
+                    set(PointfinderBlastDatabase.get_available_organisms())) + f'. By selecting "{args.pointfinder_organism}" you are not guaranteed ' \
+                    + 'that all point mutations in this PointFinder database will be properly detected.')
             pointfinder_database = database_repos.build_blast_database('pointfinder',
                                                                        {'organism': args.pointfinder_organism})
         else:


Test patch:
diff --git a/staramr/tests/integration/databases/test_BlastDatabaseRepositories.py b/staramr/tests/integration/databases/test_BlastDatabaseRepositories.py
index f9f3924..1e09e19 100644
--- a/staramr/tests/integration/databases/test_BlastDatabaseRepositories.py
+++ b/staramr/tests/integration/databases/test_BlastDatabaseRepositories.py
@@ -90,5 +90,9 @@ class BlastDatabaseRepositoriesIT(unittest.TestCase):
                          'Resfinder commits invalid')
         self.assertEqual(database_info['pointfinder_db_commit'], self.POINTFINDER_VALID_COMMIT,
                          'Pointfinder commits invalid')
+        self.assertEqual(database_info['pointfinder_organisms_all'], 'campylobacter, e.coli, gonorrhoeae, salmonella, tuberculosis',
+                         'Pointfinder organisms are invalid')
+        self.assertEqual(database_info['pointfinder_organisms_valid'], 'campylobacter, salmonella',
+                         'Pointfinder organisms are invalid')
         self.assertEqual(database_info['plasmidfinder_db_commit'], self.PLASMIDFINDER_VALID_COMMIT,
                          'Plasmidfinder commits invalid')