Repository: pyiron/pyiron_base

Issue:
ScriptJob broken with data container 
Same issue as https://github.com/pyiron/pyiron_base/issues/44

```
from pyiron import Project
pr = Project('JOB')
job = pr.create_job('ScriptJob', 'job')
job.input['value'] = 300
job.script_path = 'test.ipynb'
job.run()
```
in `test.ipynb`: 

```
from pyiron import Notebook as nb
coeff_tot = nb.get_custom_dict()
print(coeff_tot['value'])
```

The error message I get: 

```
ValueError                                Traceback (most recent call last)
<ipython-input-1-34e7bd0a0faf> in <module>
      1 from pyiron import Notebook as nb
----> 2 coeff_tot = nb.get_custom_dict()
      3 print(coeff_tot['value'])

~/PycharmProjects/pyiron_base/pyiron_base/job/external.py in get_custom_dict()
     38         if Path(hdf_file).exists():
     39             hdf = FileHDFio(hdf_file)
---> 40             custom_dict = hdf[folder + '/input/custom_dict/data']
     41             custom_dict["project_dir"] = str(project_folder)
     42             return custom_dict

~/PycharmProjects/pyiron_base/pyiron_base/generic/hdfio.py in __getitem__(self, item)
    833                     hdf_object = self.copy()
    834                     hdf_object.h5_path = "/".join(item_abs_lst[:-1])
--> 835                     return hdf_object[item_abs_lst[-1]]
    836 
    837     def _read(self, item):

~/PycharmProjects/pyiron_base/pyiron_base/generic/hdfio.py in __getitem__(self, item)
    805                         obj = hdf_item.copy()
    806                         return obj
--> 807                 raise ValueError("Unknown item: {}".format(item))
    808             else:
    809                 if item_lst[0] == "":  # item starting with '/', thus we have an absoute HDF5 path

ValueError: Unknown item: data
```


Discussion:

Patch:
diff --git a/pyiron_base/generic/hdfio.py b/pyiron_base/generic/hdfio.py
index b2845ae3..2cb8545c 100644
--- a/pyiron_base/generic/hdfio.py
+++ b/pyiron_base/generic/hdfio.py
@@ -804,7 +804,7 @@ class FileHDFio(object):
                     with self.open(item) as hdf_item:
                         obj = hdf_item.copy()
                         return obj
-                raise ValueError("Unknown item: {}".format(item))
+                raise ValueError("Unknown item: {} {} {}".format(item, self.file_name, self.h5_path))
             else:
                 if item_lst[0] == "":  # item starting with '/', thus we have an absoute HDF5 path
                     item_abs_lst = os.path.normpath(item).replace("\\", "/").split("/")
diff --git a/pyiron_base/job/external.py b/pyiron_base/job/external.py
index cf4e4eaf..d2ff33ba 100644
--- a/pyiron_base/job/external.py
+++ b/pyiron_base/job/external.py
@@ -9,6 +9,7 @@ import json
 from pathlib2 import Path
 import warnings
 from pyiron_base.generic.hdfio import FileHDFio
+from pyiron_base.generic.datacontainer import DataContainer
 
 __author__ = "Osamu Waseda"
 __copyright__ = (
@@ -34,12 +35,15 @@ class Notebook(object):
         folder = Path(".").cwd().parts[-1]
         project_folder = Path(".").cwd().parents[1]
         hdf_file = project_folder / folder
-        hdf_file = str(hdf_file) + ".h5"
+        hdf_file = str(hdf_file).replace("\\", "/") + ".h5"
         if Path(hdf_file).exists():
-            hdf = FileHDFio(hdf_file)
-            custom_dict = hdf[folder + '/input/custom_dict/data']
-            custom_dict["project_dir"] = str(project_folder)
-            return custom_dict
+            obj = DataContainer()
+            obj.from_hdf(
+                hdf = FileHDFio(hdf_file),
+                group_name = folder + '/input/custom_dict'
+            )
+            obj["project_dir"] = str(project_folder)
+            return obj
         elif Path("input.json").exists():
             with open("input.json") as f:
                 return json.load(f)
diff --git a/pyiron_base/job/generic.py b/pyiron_base/job/generic.py
index c40f2049..2d07e0bc 100644
--- a/pyiron_base/job/generic.py
+++ b/pyiron_base/job/generic.py
@@ -1483,7 +1483,7 @@ class GenericJob(JobCore):
             if len(self.__module__.split(".")) > 1:
                 self._executable = Executable(
                     codename=self.__name__,
-                    module=self.__module__.split(".")[-2],
+                    module=self.__module__.split(".")[1],
                     path_binary_codes=s.resource_paths,
                 )
             else:


Test patch:
diff --git a/pyiron_base/_tests.py b/pyiron_base/_tests.py
index 92243286..30f71aa6 100644
--- a/pyiron_base/_tests.py
+++ b/pyiron_base/_tests.py
@@ -31,7 +31,7 @@ class TestWithProject(unittest.TestCase, ABC):
     def setUpClass(cls):
         cls.file_location = dirname(abspath(__file__)).replace("\\", "/")
         cls.project_name = "test_project"
-        cls.project_path = join(cls.file_location, cls.project_name)
+        cls.project_path = join(cls.file_location, cls.project_name).replace("\\", "/")
         cls.project = Project(cls.project_path)
 
     @classmethod
diff --git a/tests/job/test_external.py b/tests/job/test_external.py
new file mode 100644
index 00000000..211b8a37
--- /dev/null
+++ b/tests/job/test_external.py
@@ -0,0 +1,38 @@
+import os
+import unittest
+
+from pyiron_base import Project
+
+job_py_source = """
+from pyiron_base import Notebook as nb
+coeff_tot = nb.get_custom_dict()
+print(coeff_tot['value'])
+"""
+
+class TestScriptJob(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        cls.file_location = os.path.dirname(os.path.abspath(__file__)).replace("\\", "/")  # replace to satisfy windows
+        cls.job_location = os.path.join(cls.file_location, "job.py")
+        cls.project = Project(os.path.join(cls.file_location, "test_notebook").replace("\\", "/"))
+        cls.job = cls.project.create_job(cls.project.job_type.ScriptJob,
+                                               "test")
+        with open(cls.job_location, 'w') as f:
+            f.write(job_py_source)
+
+    @classmethod
+    def tearDownClass(cls):
+        os.remove(cls.job_location)
+        cls.project.remove(enable=True)
+
+
+    def test_notebook_input(self):
+        """
+        Test that input is readable from external scripts.
+        """
+        self.job.input['value'] = 300
+        self.job.script_path = self.job_location
+        try:
+            self.job.run()
+        except Exception as e:
+            self.fail("Running script job failed with {}".format(e))
diff --git a/tests/job/test_script.py b/tests/job/test_script.py
index 2e3f608a..bad674cb 100644
--- a/tests/job/test_script.py
+++ b/tests/job/test_script.py
@@ -6,20 +6,19 @@ import unittest
 from os.path import dirname, abspath, join
 from os import remove
 from pyiron_base.project.generic import Project
+from pyiron_base._tests import TestWithProject
 
 
-class TestScriptJob(unittest.TestCase):
+class TestScriptJob(TestWithProject):
     @classmethod
     def setUpClass(cls):
-        cls.file_location = dirname(abspath(__file__)).replace("\\", "/")
-        cls.project_abspath = join(cls.file_location, "test_sriptjob").replace("\\", "/")  # replace to satisfy windows
-        cls.project = Project(cls.project_abspath)
+        super().setUpClass()
         cls.simple_script = join(cls.file_location, 'simple.py')
         cls.complex_script = join(cls.file_location, 'complex.py')
 
     @classmethod
     def tearDownClass(cls):
-        cls.project.remove(enable=True)
+        super().tearDownClass()
         for file in [cls.simple_script, cls.complex_script]:
             try:
                 remove(file)
@@ -27,9 +26,11 @@ class TestScriptJob(unittest.TestCase):
                 continue
 
     def setUp(self):
+        super().setUp()
         self.job = self.project.create.job.ScriptJob('script')
 
     def tearDown(self):
+        super().tearDown()
         self.project.remove_job('script')
 
     def test_script_path(self):
@@ -46,7 +47,7 @@ class TestScriptJob(unittest.TestCase):
         self.project.data.write()
         with open(self.complex_script, 'w') as f:
             f.write("from pyiron_base import Project\n")
-            f.write(f"pr = Project('{self.project_abspath}')\n")
+            f.write(f"pr = Project('{self.project_path}')\n")
             f.write("pr.data.out = pr.data.in_ * 7\n")
             f.write("pr.data.write()\n")
         # WARNING: If a user parallelizes this with multiple ScriptJobs, it would be possible to get a log jam with
diff --git a/tests/job/test_scriptjob.py b/tests/job/test_scriptjob.py
new file mode 100644
index 00000000..94b6b5e2
--- /dev/null
+++ b/tests/job/test_scriptjob.py
@@ -0,0 +1,30 @@
+import os
+import unittest
+
+from pyiron_base import Project
+
+class TestScriptJob(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        cls.file_location = os.path.dirname(os.path.abspath(__file__)).replace(
+            "\\", "/"
+        )
+        cls.project = Project(os.path.join(cls.file_location, "test_scriptjob"))
+        cls.job = cls.project.create_job(cls.project.job_type.ScriptJob,
+                                               "test")
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.project.remove(enable=True)
+
+
+    def test_notebook_input(self):
+        """
+        Makes sure that the ScriptJob saves its input class in
+        hdf["input/custom_group"] as this is needed when running external
+        Notebook jobs c.f. `Notebook.get_custom_dict()`.
+        """
+        self.job.input['value'] = 300
+        self.job.save()
+        self.assertTrue("custom_dict" in self.job["input"].list_groups(),
+                        "Input not saved in the 'custom_dict' group in HDF")