Repository: intel/dffml

Issue:
model: scikit: scikit model parameters not accepting negative values
While testing CLI usage of scikit this came up:
```console
$ dffml train     -model scikitlr     -features def:Years:int:1     -model-predict Salary  -model-n_jobs -1  -sources f=csv     -source-filename dataset.csv     -source-readonly     -log debug
DEBUG:dffml.cli.Train:Setting features = [Years(DefFeature.<locals>.DefinedFeature)[<class 'int'>, 1]]
DEBUG:dffml.cli.Train:Setting log = 20
DEBUG:dffml.cli.Train:Setting model = <class 'abc.LinearRegressionModel'>
DEBUG:dffml.cli.Train:Setting sources = [<class 'abc.CSVSource'>]
DEBUG:dffml.cli.Train:Setting timeout = 120
DEBUG:dffml.CSVSource:CSVSourceConfig(filename='dataset.csv', readonly=True, key='src_url', label='unlabeled', label_column='label')
Traceback (most recent call last):
  File "/home/nucleon/anaconda3/bin/dffml", line 11, in <module>
    load_entry_point('dffml', 'console_scripts', 'dffml')()
  File "/home/nucleon/Desktop/GSoC/dffml/dffml/util/cli/cmd.py", line 169, in main
    result = loop.run_until_complete(cls.cli(*argv[1:]))
  File "/home/nucleon/anaconda3/lib/python3.7/asyncio/base_events.py", line 584, in run_until_complete
    return future.result()
  File "/home/nucleon/Desktop/GSoC/dffml/dffml/util/cli/cmd.py", line 145, in cli
    cmd = args.cmd(**cls.sanitize_args(vars(args)))
  File "/home/nucleon/Desktop/GSoC/dffml/dffml/util/cli/cmds.py", line 142, in __init__
    self.model = self.model.withconfig(self.extra_config)
  File "/home/nucleon/Desktop/GSoC/dffml/dffml/base.py", line 207, in withconfig
    return cls(cls.config(config, *above))
  File "/home/nucleon/Desktop/GSoC/dffml/model/scikit/dffml_model_scikit/scikit_models.py", line 162, in config
    params[name] = cls.config_get(config, above, name)
  File "/home/nucleon/Desktop/GSoC/dffml/dffml/base.py", line 177, in config_get
    value = arg["type"](value)
  File "/home/nucleon/Desktop/GSoC/dffml/dffml/base.py", line 104, in parser_helper
    if value.lower() in ["null", "nil", "none"]:
AttributeError: 'bool' object has no attribute 'lower'
```
Must be bacause it isn't supporting -ve value of n_jobs as it seems to work fine with +ve values.

Discussion:
sakshamarora1: https://github.com/intel/dffml/blob/6dd23ba005772ca3292f1a67010f55af77508a72/dffml/base.py#L273

ast was being used but wasn't imported.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4ab77c481..29fd61599 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Fixed
 - Correctly identify when functions decorated with `op` use `self` to reference
   the `OperationImplementationContext`.
+- Negative values are correctly parsed when input via the command line.
 
 ## [0.3.1] - 2019-12-12
 ### Added
diff --git a/dffml/base.py b/dffml/base.py
index 17bdc6647..6725697d4 100644
--- a/dffml/base.py
+++ b/dffml/base.py
@@ -3,6 +3,7 @@ Base classes for DFFML. All classes in DFFML should inherit from these so that
 they follow a similar API for instantiation and usage.
 """
 import abc
+import ast
 import copy
 import inspect
 import argparse
diff --git a/dffml/util/cli/arg.py b/dffml/util/cli/arg.py
index 008cdd1ad..90204d2d8 100644
--- a/dffml/util/cli/arg.py
+++ b/dffml/util/cli/arg.py
@@ -11,7 +11,10 @@ def parse_unknown(*unknown):
     name = []
     add_to_parsed = []
     for arg in unknown:
-        if arg.startswith("-"):
+        if (
+            arg.startswith("-")
+            and not arg.lstrip("-").replace(".", "").isnumeric()
+        ):
             if not name:
                 name = arg.lstrip("-").split("-")
             if not add_to_parsed:


Test patch:
diff --git a/tests/test_base.py b/tests/test_base.py
index e14dcdfff..af0b042fa 100644
--- a/tests/test_base.py
+++ b/tests/test_base.py
@@ -18,6 +18,7 @@ from dffml.util.cli.cmd import parse_unknown
 
 @config
 class FakeTestingConfig:
+    num: float
     files: List[str]
     features: Features
     name: str = field("Name of FakeTesting")
@@ -49,6 +50,7 @@ class TestAutoArgsConfig(unittest.TestCase):
                         "fake": {
                             "arg": None,
                             "config": {
+                                "num": {"arg": Arg(type=float), "config": {},},
                                 "files": {
                                     "arg": Arg(type=str, nargs="+"),
                                     "config": {},
@@ -98,6 +100,8 @@ class TestAutoArgsConfig(unittest.TestCase):
             parse_unknown(
                 "--test-fake-name",
                 "feedface",
+                "--test-num",
+                "-4.2",
                 "--test-files",
                 "a",
                 "b",
@@ -109,6 +113,7 @@ class TestAutoArgsConfig(unittest.TestCase):
                 "def:Commits:int:10",
             )
         )
+        self.assertEqual(config.num, -4.2)
         self.assertEqual(config.files, ["a", "b", "c"])
         self.assertEqual(config.name, "feedface")
         self.assertEqual(config.label, "unlabeled")
@@ -127,6 +132,8 @@ class TestAutoArgsConfig(unittest.TestCase):
             parse_unknown(
                 "--test-fake-name",
                 "feedface",
+                "--test-num",
+                "-4.2",
                 "--test-fake-label",
                 "default-label",
                 "--test-fake-readonly",
@@ -143,6 +150,7 @@ class TestAutoArgsConfig(unittest.TestCase):
                 "def:Commits:int:10",
             )
         )
+        self.assertEqual(config.num, -4.2)
         self.assertEqual(config.files, ["a", "b", "c"])
         self.assertEqual(config.name, "feedface")
         self.assertEqual(config.label, "default-label")