From bd5d02631554f9923207b743c6390e3419243ab1 Mon Sep 17 00:00:00 2001
From: Stefan Bienert <stefan.bienert@unibas.ch>
Date: Wed, 26 Aug 2015 09:52:49 +0200
Subject: [PATCH] Alignment option is now required

---
 core/pymod/core/pm3argparse.py | 10 +++++--
 core/tests/test_pm3argparse.py | 53 +++++++++++++++++++++++++---------
 2 files changed, 48 insertions(+), 15 deletions(-)

diff --git a/core/pymod/core/pm3argparse.py b/core/pymod/core/pm3argparse.py
index 59cc9485..e7d1da32 100644
--- a/core/pymod/core/pm3argparse.py
+++ b/core/pymod/core/pm3argparse.py
@@ -121,9 +121,13 @@ def _AssembleTrgTplAln(target, template):
     new_aln.SetSequenceOffset(1, start)
     return new_aln
 
+
 class PM3StoreOnceAction(argparse.Action):
     """Action for argument parsing to prevent multiple calls to an option.
     """
+    #pylint: disable=too-few-public-methods
+    def __init__(self, *args, **kwargs):
+        super(PM3StoreOnceAction, self).__init__(*args, **kwargs)
     def __call__(self, parser, namespace, values, option_string=None):
         if getattr(namespace, self.dest, None) is not None:
             raise argparse.ArgumentError(self, 'may only be used once.')
@@ -281,7 +285,7 @@ class PM3ArgumentParser(argparse.ArgumentParser):
         """
         Actually add alignment arguments/ options
         """
-        aln_grp = self.add_mutually_exclusive_group()
+        aln_grp = self.add_mutually_exclusive_group(required=True)
         # FastA input: - always pairwise alignments
         #              - callable multiple times
         #              - goes by 'trg:<SEQNAME> <FILE>'
@@ -301,7 +305,6 @@ class PM3ArgumentParser(argparse.ArgumentParser):
         aln_grp.add_argument('-j', '--json', metavar='<OBJECT>|<FILE>',
                              help='Alignments provided as JSON file/ object.',
                              action=PM3StoreOnceAction)
-        # possibility to add JSON: mention limitation!
 
 class PM3OptionsNamespace(object):
     """
@@ -332,6 +335,9 @@ class PM3OptionsNamespace(object):
                 seqfile, new_aln = _FetchAlnFromFastaOpt(src)
                 self.alignments.append(new_aln)
                 self.aln_sources.append(seqfile)
+            return
+        # Now for JSON input. Since one of the options needs to be given and
+        # we already checked for FastA, no need to open a new branch, here.
 
 #  LocalWords:  param attr prog argparse ArgumentParser bool sys os init str
 #  LocalWords:  progattr descattr argpinit argv formatter meth args namespace
diff --git a/core/tests/test_pm3argparse.py b/core/tests/test_pm3argparse.py
index 271dfc26..7a5943ea 100644
--- a/core/tests/test_pm3argparse.py
+++ b/core/tests/test_pm3argparse.py
@@ -38,6 +38,7 @@ class PM3ArgParseTests(unittest.TestCase):
         with self.assertRaises(SystemExit) as ecd:
             parser.Parse(['-x'])
         self.assertEqual(ecd.exception.code, 2)
+        self.assertEqual(len(self.log.messages['ERROR']), 2)
         self.assertEqual(self.log.messages['ERROR'],
                          ['usage: test_pm3argparse.py [-h]',
                           'test_pm3argparse.py: error: unrecognized '+
@@ -85,9 +86,10 @@ class PM3ArgParseTests(unittest.TestCase):
         with self.assertRaises(SystemExit) as ecd:
             parser.Parse(['--fasta', 'data/fasta/alignment.fas'])
         self.assertEqual(ecd.exception.code, 2)
+        self.assertEqual(len(self.log.messages['ERROR']), 2)
         self.assertEqual(self.log.messages['ERROR'],
-                         ['usage: test_pm3argparse.py [-h] [-f trg:<NAME> '+
-                          '<FILE> | -j <OBJECT>|<FILE>]',
+                         ['usage: test_pm3argparse.py [-h] (-f trg:<NAME> '+
+                          '<FILE> | -j <OBJECT>|<FILE>)',
                           'test_pm3argparse.py: error: argument -f/--fasta: '+
                           'expected 2 argument(s)'])
 
@@ -99,9 +101,10 @@ class PM3ArgParseTests(unittest.TestCase):
         with self.assertRaises(SystemExit) as ecd:
             parser.Parse(['--fasta', 'trg:target'])
         self.assertEqual(ecd.exception.code, 2)
+        self.assertEqual(len(self.log.messages['ERROR']), 2)
         self.assertEqual(self.log.messages['ERROR'],
-                         ['usage: test_pm3argparse.py [-h] [-f trg:<NAME> '+
-                          '<FILE> | -j <OBJECT>|<FILE>]',
+                         ['usage: test_pm3argparse.py [-h] (-f trg:<NAME> '+
+                          '<FILE> | -j <OBJECT>|<FILE>)',
                           'test_pm3argparse.py: error: argument -f/--fasta: '+
                           'expected 2 argument(s)'])
 
@@ -114,6 +117,7 @@ class PM3ArgParseTests(unittest.TestCase):
         with self.assertRaises(SystemExit) as ecd:
             parser.Parse(['--fasta', 'foo', 'bar'])
         self.assertEqual(ecd.exception.code, 11)
+        self.assertEqual(len(self.log.messages['ERROR']), 1)
         self.assertEqual(self.log.messages['ERROR'][0],
                          "'--fasta foo bar' requires one "+
                          "argument prefixed with 'trg:' marking the target "+
@@ -128,6 +132,7 @@ class PM3ArgParseTests(unittest.TestCase):
         with self.assertRaises(SystemExit) as ecd:
             parser.Parse(['--fasta', 'trg:', 'bar'])
         self.assertEqual(ecd.exception.code, 14)
+        self.assertEqual(len(self.log.messages['ERROR']), 1)
         self.assertEqual(self.log.messages['ERROR'][0], "'--fasta trg: bar' "+
                          "requires argument 'trg:' defining the target "+
                          "sequence name, empty one found: 'trg: bar'")
@@ -141,6 +146,7 @@ class PM3ArgParseTests(unittest.TestCase):
         with self.assertRaises(SystemExit) as ecd:
             parser.Parse(['--fasta', 'bar', 'trg:'])
         self.assertEqual(ecd.exception.code, 14)
+        self.assertEqual(len(self.log.messages['ERROR']), 1)
         self.assertEqual(self.log.messages['ERROR'][0], "'--fasta bar trg:' "+
                          "requires argument 'trg:' defining the target "+
                          "sequence name, empty one found: 'bar trg:'")
@@ -153,6 +159,7 @@ class PM3ArgParseTests(unittest.TestCase):
         with self.assertRaises(SystemExit) as ecd:
             parser.Parse(['--fasta', 'trg:foo', 'notexistingfile.fas'])
         self.assertEqual(ecd.exception.code, 12)
+        self.assertEqual(len(self.log.messages['ERROR']), 1)
         self.assertEqual(self.log.messages['ERROR'][0],
                          "Alignment file does not exist: notexistingfile.fas")
 
@@ -164,6 +171,7 @@ class PM3ArgParseTests(unittest.TestCase):
         with self.assertRaises(SystemExit) as ecd:
             parser.Parse(['--fasta', 'trg:foo', 'data/fasta/alignment.fas'])
         self.assertEqual(ecd.exception.code, 15)
+        self.assertEqual(len(self.log.messages['ERROR']), 1)
         self.assertEqual(self.log.messages['ERROR'][0],
                          "'--fasta trg:foo data/fasta/alignment.fas' refers "+
                          "to an empty file or its in the wrong format.")
@@ -175,6 +183,7 @@ class PM3ArgParseTests(unittest.TestCase):
         with self.assertRaises(SystemExit) as ecd:
             parser.Parse(['--fasta', 'trg:target', 'data/fasta/1ake_3.fas'])
         self.assertEqual(ecd.exception.code, 16)
+        self.assertEqual(len(self.log.messages['ERROR']), 1)
         self.assertEqual(self.log.messages['ERROR'][0], "'--fasta trg:target "+
                          "data/fasta/1ake_3.fas' points to an alignment with "+
                          "more than 2 sequences.")
@@ -186,6 +195,7 @@ class PM3ArgParseTests(unittest.TestCase):
         with self.assertRaises(SystemExit) as ecd:
             parser.Parse(['--fasta', 'trg:trg', 'data/fasta/1ake.fas'])
         self.assertEqual(ecd.exception.code, 17)
+        self.assertEqual(len(self.log.messages['ERROR']), 1)
         self.assertEqual(self.log.messages['ERROR'][0], "'--fasta trg:trg "+
                          "data/fasta/1ake.fas' does not define a target name "+
                          "found in the alignment.")
@@ -197,6 +207,7 @@ class PM3ArgParseTests(unittest.TestCase):
         with self.assertRaises(SystemExit) as ecd:
             parser.Parse(['--fasta', 'trg:target', 'data/fasta/1ake_nel.fas'])
         self.assertEqual(ecd.exception.code, 18)
+        self.assertEqual(len(self.log.messages['ERROR']), 1)
         self.assertEqual(self.log.messages['ERROR'][0], "'--fasta trg:target "+
                          "data/fasta/1ake_nel.fas': sequences in the "+
                          "alignment have different length.")
@@ -274,18 +285,35 @@ class PM3ArgParseTests(unittest.TestCase):
         self.assertEqual(opts.alignments[0].GetSequence(1).name, '1AKE.B')
         self.assertEqual(opts.aln_sources[0], 'data/fasta/1ake.fas')
 
+    def testAddAlignmentNoneGiven(self):
+        # if we have none of '--fasta', '--json', fail.
+        parser = pm3argparse.PM3ArgumentParser(__doc__, action=False)
+        parser.AddAlignment()
+        parser.AssembleParser()
+        with self.assertRaises(SystemExit) as ecd:
+            parser.Parse([])
+        self.assertEqual(ecd.exception.code, 2)
+        self.assertEqual(len(self.log.messages['ERROR']), 2)
+        self.assertEqual(len(self.log.messages['ERROR']), 2)
+        self.assertEqual(self.log.messages['ERROR'][0],
+                         'usage: test_pm3argparse.py [-h] (-f trg:<NAME> '+
+                         '<FILE> | -j <OBJECT>|<FILE>)',
+                         'test_pm3argparse.py: error: one of the arguments '+
+                         '-f/--fasta -j/--json is required')
+
     def testAddAlignmentFastaAndJson(self):
         # testing that --fasta and --json DO NOT work together
         parser = pm3argparse.PM3ArgumentParser(__doc__, action=False)
         parser.AddAlignment()
         parser.AssembleParser()
         with self.assertRaises(SystemExit) as ecd:
-            opts = parser.Parse(['--fasta', 'trg:target',
-                                 'data/fasta/1ake.fas', '--json', 'foo'])
+            parser.Parse(['--fasta', 'trg:target', 'data/fasta/1ake.fas',
+                          '--json', 'foo'])
         self.assertEqual(ecd.exception.code, 2)
+        self.assertEqual(len(self.log.messages['ERROR']), 2)
         self.assertEqual(self.log.messages['ERROR'][0],
-                         'usage: test_pm3argparse.py [-h] [-f trg:<NAME> '+
-                         '<FILE> | -j <OBJECT>|<FILE>]',
+                         'usage: test_pm3argparse.py [-h] (-f trg:<NAME> '+
+                         '<FILE> | -j <OBJECT>|<FILE>)',
                          'test_pm3argparse.py: error: argument -j/--json: '+
                          'not allowed with argument -f/--fasta')
 
@@ -296,16 +324,15 @@ class PM3ArgParseTests(unittest.TestCase):
         parser.AddAlignment()
         parser.AssembleParser()
         with self.assertRaises(SystemExit) as ecd:
-            opts = parser.Parse(['--json', 'foo', '--json', 'bar'])
+            parser.Parse(['--json', 'foo', '--json', 'bar'])
         self.assertEqual(ecd.exception.code, 2)
+        self.assertEqual(len(self.log.messages['ERROR']), 2)
         self.assertEqual(self.log.messages['ERROR'][0],
-                         'usage: test_pm3argparse.py [-h] [-f trg:<NAME> '+
-                         '<FILE> | -j <OBJECT>|<FILE>]',
+                         'usage: test_pm3argparse.py [-h] (-f trg:<NAME> '+
+                         '<FILE> | -j <OBJECT>|<FILE>)',
                          'test_pm3argparse.py: error: argument -j/--json: '+
                          'may only be used once.')
 
-# test no multiple --json
-
 if __name__ == "__main__":
     from ost import testutils
     testutils.RunTests()
-- 
GitLab