Repository: PaulSchweizer/flowpipe

Issue:
Add sub output plugs through Node decorator
This should be made possible:

```python
@Node(outputs=["out.a", "out.b"])
MyNode():
    pass
```

Resulting in a node like this:

```
+-----------+
|  MyNode   |
+-----------+
|       out %
|    out.a  o
|    out.b  o
+-----------+
```

Discussion:

Patch:
diff --git a/flowpipe/node.py b/flowpipe/node.py
index 7fc825d..a9671e9 100644
--- a/flowpipe/node.py
+++ b/flowpipe/node.py
@@ -436,8 +436,13 @@ class FunctionNode(INode):
         metadata = copy.deepcopy(self.metadata)
         metadata.update(kwargs.pop("metadata", {}))
         graph = kwargs.pop('graph', 'default')
+        outputs = []
+        for o in self.outputs.values():
+            outputs.append(o.name)
+            for key in o._sub_plugs.keys():
+                outputs.append("{0}.{1}".format(o.name, key))
         return self.__class__(func=self.func,
-                              outputs=[o for o in self.outputs],
+                              outputs=outputs,
                               metadata=metadata,
                               graph=graph,
                               **kwargs)
@@ -511,7 +516,15 @@ class FunctionNode(INode):
 
         if outputs is not None:
             for output in outputs:
-                OutputPlug(output, self)
+                if "." in output:
+                    parent, subplug = output.split(".")
+                    parent_plug = self.outputs.get(parent)
+                    if parent_plug is None:
+                        parent_plug = OutputPlug(parent, self)
+                    SubOutputPlug(subplug, self, parent_plug)
+                else:
+                    if self.outputs.get(output) is None:
+                        OutputPlug(output, self)
 
     def to_pickle(self):  # pragma: no cover
         """Pickle the node. -- DOES NOT WORK FOR FunctionNode."""
diff --git a/flowpipe/plug.py b/flowpipe/plug.py
index b58e4c6..85e6f65 100644
--- a/flowpipe/plug.py
+++ b/flowpipe/plug.py
@@ -303,6 +303,7 @@ class SubInputPlug(IPlug):
             (OutputPlug, SubOutputPlug))
         self.key = key
         self.parent_plug = parent_plug
+        self.parent_plug._sub_plugs[key] = self
         self.value = value
         self.is_dirty = True
 
@@ -362,6 +363,7 @@ class SubOutputPlug(IPlug):
             (InputPlug, SubInputPlug))
         self.key = key
         self.parent_plug = parent_plug
+        self.parent_plug._sub_plugs[key] = self
         self.value = value
         self.is_dirty = True
 


Test patch:
diff --git a/tests/test_convert_function_to_node.py b/tests/test_convert_function_to_node.py
index 2d84466..e99a8dc 100644
--- a/tests/test_convert_function_to_node.py
+++ b/tests/test_convert_function_to_node.py
@@ -182,3 +182,20 @@ def test_node_reserved_names():
         def function(func, name, identifier, inputs,
                      outputs, metadata, omit, graph):
             pass
+
+
+def test_create_node_with_sub_output_plugs():
+
+    @Node(outputs=["out.a", "out", "out.b"])
+    def function1(in_):
+        pass
+
+    node = function1(name="contains_all_plugs")
+    assert len(node.outputs["out"]._sub_plugs) == 2
+
+    @Node(outputs=["out.a", "out.b"])
+    def function2(in_):
+        pass
+
+    node = function2(name="contains_only_subplugs")
+    assert len(node.outputs["out"]._sub_plugs) == 2