Repository: intel/dffml

Issue:
df: types: Autoconvert Definitions with spec into their spec
Conversion should happen upon instantiation of an `Input`, if the `value` is a `dict` and the definition has a `spec`, convert the `dict` into and instance of class provided by the `spec` and use that as the `value`.

https://github.com/intel/dffml/blob/01a3deb4bfa01bd1ce8ff690358b46d8b1e037bc/dffml/df/types.py#L253-L278

For testing that this works, modify the git feature operations which use `git_repository` and `git_repository_checked_out`.

https://github.com/intel/dffml/blob/01a3deb4bfa01bd1ce8ff690358b46d8b1e037bc/feature/git/dffml_feature_git/feature/operations.py#L273-L348

`repo["directory"]` will become `repo.directory`, etc. There are more operations towards the top of the file that will need modification. Hold off on changing `clone_git_repo` until we have a working pull request.

Definitions and specs for reference:

https://github.com/intel/dffml/blob/01a3deb4bfa01bd1ce8ff690358b46d8b1e037bc/feature/git/dffml_feature_git/feature/definitions.py#L7-L35


Discussion:
@pdxjohnny I'd like to work on this issue.

Resolving patch:
diff --git a/dffml/df/types.py b/dffml/df/types.py
--- a/dffml/df/types.py
+++ b/dffml/df/types.py
@@ -271,6 +271,8 @@ def __init__(
         # instance name this Input is intended for.
         if parents is None:
             parents = []
+        if isinstance(value, dict) and definition.spec is not None:
+            value = definition.spec(**value)
         self.value = value
         self.definition = definition
         self.parents = parents
diff --git a/dffml/operation/output.py b/dffml/operation/output.py
--- a/dffml/operation/output.py
+++ b/dffml/operation/output.py
@@ -35,9 +35,7 @@ async def resolve(
         return cls(**exported)
 
 
-group_by_spec = Definition(
-    name="group_by_spec", primitive="Dict[str, Any]", spec=GroupBySpec
-)
+group_by_spec = Definition(name="group_by_spec", primitive="Dict[str, Any]")
 
 group_by_output = Definition(
     name="group_by_output", primitive="Dict[str, List[Any]]"
diff --git a/feature/git/dffml_feature_git/feature/operations.py b/feature/git/dffml_feature_git/feature/operations.py
--- a/feature/git/dffml_feature_git/feature/operations.py
+++ b/feature/git/dffml_feature_git/feature/operations.py
@@ -104,7 +104,7 @@ async def clone_git_repo(URL: str):
 )
 async def git_repo_default_branch(repo: Dict[str, str]):
     branches = (
-        await check_output("git", "branch", "-r", cwd=repo["directory"])
+        await check_output("git", "branch", "-r", cwd=repo.directory)
     ).split("\n")
     main = [branch for branch in branches if "->" in branch][0].split()[-1]
     main = main.split("/")[-1]
@@ -116,12 +116,12 @@ async def git_repo_default_branch(repo: Dict[str, str]):
     outputs={"repo": git_repository_checked_out},
 )
 async def git_repo_checkout(repo: Dict[str, str], commit: str):
-    await check_output("git", "checkout", commit, cwd=repo["directory"])
-    # NOTE Don't modify variables which are mearly references! This will create
-    # more permutations than intended.
-    checked_out = repo.copy()
-    checked_out["commit"] = commit
-    return {"repo": checked_out}
+    await check_output("git", "checkout", commit, cwd=repo.directory)
+    return {
+        "repo": GitRepoCheckedOutSpec(
+            URL=repo.URL, directory=repo.directory, commit=commit,
+        )
+    }
 
 
 @op(
@@ -139,7 +139,7 @@ async def git_repo_commit_from_date(
             "1",
             '--before="%s"' % (date,),
             branch,
-            cwd=repo["directory"],
+            cwd=repo.directory,
         )
     ).strip()
     if not sha:
@@ -151,7 +151,7 @@ async def git_repo_commit_from_date(
                     "--reverse",
                     '--after="%s"' % (date,),
                     branch,
-                    cwd=repo["directory"],
+                    cwd=repo.directory,
                 )
             )
             .strip()
@@ -184,7 +184,7 @@ async def git_repo_author_lines_for_dates(
         "--after",
         "%s" % (end),
         branch,
-        cwd=repo["directory"],
+        cwd=repo.directory,
     )
     while not proc.stdout.at_eof():
         line = await proc.stdout.readline()
@@ -272,7 +272,7 @@ async def git_repo_release(
         "--after",
         "%s" % (end),
         branch,
-        cwd=repo["directory"],
+        cwd=repo.directory,
     )
     while not proc.stdout.at_eof() and not present:
         line = await proc.stdout.readline()
@@ -290,7 +290,7 @@ async def git_repo_release(
 )
 async def lines_of_code_by_language(repo: Dict[str, str]):
     # cloc creates temporary files >:(
-    proc = await create("tokei", repo["directory"], cwd=repo["directory"])
+    proc = await create("tokei", repo.directory, cwd=repo.directory)
     cols = []
     lines_by_language = {}
     while not proc.stdout.at_eof():
@@ -358,7 +358,7 @@ async def git_commits(repo: Dict[str, str], branch: str, start_end: List[str]):
         "--after",
         end,
         branch,
-        cwd=repo["directory"],
+        cwd=repo.directory,
     )
     while not proc.stdout.at_eof():
         line = await proc.stdout.readline()
@@ -378,5 +378,5 @@ async def count_authors(author_lines: dict):
 
 @op(inputs={"repo": git_repository}, outputs={}, stage=Stage.CLEANUP)
 async def cleanup_git_repo(repo: Dict[str, str]):
-    shutil.rmtree(repo["directory"])
+    shutil.rmtree(repo.directory)
     return {}


Test patch: