Tags: patch

Hello everyone,

attached is a debdiff with an upgrade to the latest upstream version 1.3.3, 
which supports python3, and a patch removing any python2 dependencies (which is 
basically only removing the python2 tests). The binary package is renamed to 
python3-versuchung and now depends on python3.

Unfortunately, the upstream maintainer does not tag the releases anymore in 
github, but the new releases are on pypi: https://pypi.org/project/versuchung/

Since this is my first patch I submit, I'm unsure how to proceed. Is it usually 
the package maintainer (in this case Christoph Egger), who takes care of 
signing and uploading to sid? Thanks for helping me out here.

lintian by the way complains about the standards version and the compat 
version... not sure if this is a big problem. Also, building seems to leave 
some artifacts (folder versuchung.egg-info and the file 
tests/reinit_types/DownstreamReinitTypesTest-3e32260cc0788418d241e6134893e7f2/metadata),
 not sure if this is intentional. When building again, these need to be removed 
in any case.

Best Regards,
Jann

On Fri, 30 Aug 2019 07:48:18 +0000 Matthias Klose <d...@debian.org> wrote:
> Package: src:python-versuchung
> Version: 1.1-3
> Severity: normal
> Tags: sid bullseye
> User: debian-pyt...@lists.debian.org
> Usertags: py2removal
> 
> Python2 becomes end-of-live upstream, and Debian aims to remove
> Python2 from the distribution, as discussed in
> https://lists.debian.org/debian-python/2019/07/msg00080.html
> 
> Your package either build-depends, depends on Python2, or uses Python2
> in the autopkg tests.  Please stop using Python2, and fix this issue
> by one of the following actions.
> 
> - Convert your Package to Python3. This is the preferred option.  In
>   case you are providing a Python module foo, please consider dropping
>   the python-foo package, and only build a python3-foo package.  Please
>   don't drop Python2 modules, which still have reverse dependencies,
>   just document them.
>   
>   This is the preferred option.
> 
> - If the package is dead upstream, cannot be converted or maintained
>   in Debian, it should be removed from the distribution.  If the
>   package still has reverse dependencies, raise the severity to
>   "serious" and document the reverse dependencies with the BTS affects
>   command.  If the package has no reverse dependencies, confirm that
>   the package can be removed, reassign this issue to ftp.debian.org,
>   make sure that the bug priority is set to normal and retitle the
>   issue to "RM: PKG -- removal triggered by the Python2 removal".
> 
> - If the package has still many users (popcon >= 300), or is needed to
>   build another package which cannot be removed, document that by
>   adding the "py2keep" user tag (not replacing the py2remove tag),
>   using the debian-pyt...@lists.debian.org user.  Also any
>   dependencies on an unversioned python package (python, python-dev)
>   must not be used, same with the python shebang.  These have to be
>   replaced by python2/python2.7 dependencies and shebang.
> 
>   This is the least preferred option.
> 
> If the conversion or removal needs action on another package first,
> please document the blocking by using the BTS affects command, like
> 
>   affects <bug number of blocking py2removal bug> + src:python-versuchung
> 
> If there is no py2removal bug for that reverse-dependency, please file
> a bug on this package (similar to this bug report).
> 
> If there are questions, please refer to the wiki page for the removal:
> https://wiki.debian.org/Python/2Removal, or ask for help on IRC
> #debian-python, or the debian-pyt...@lists.debian.org mailing list.
> 
> 
diff -Nru python-versuchung-1.1/debian/changelog python-versuchung-1.3.3/debian/changelog
--- python-versuchung-1.1/debian/changelog	2014-11-02 23:29:07.000000000 +0100
+++ python-versuchung-1.3.3/debian/changelog	2020-05-03 10:27:25.000000000 +0200
@@ -1,3 +1,11 @@
+python-versuchung (1.3.3-1) UNRELEASED; urgency=medium
+
+  * Non-Maintainer Upload
+  * Upgrade to version 1.3.3
+  * Ported to python3 (Closes: #938248)
+
+ -- Jann Haber <ja...@selfnet.de>  Sun, 03 May 2020 10:27:25 +0200
+
 python-versuchung (1.1-3) unstable; urgency=medium
 
   * Add dependency on git, time for the tests (Closes: #767600)
diff -Nru python-versuchung-1.1/debian/control python-versuchung-1.3.3/debian/control
--- python-versuchung-1.1/debian/control	2014-11-02 23:28:24.000000000 +0100
+++ python-versuchung-1.3.3/debian/control	2020-05-03 10:27:25.000000000 +0200
@@ -4,21 +4,21 @@
 Maintainer: Christoph Egger <christ...@debian.org>
 Build-Depends:
  debhelper (>= 9~),
- python,
+ python3,
  dh-python,
- python-setuptools,
- python-sphinx,
+ python3-setuptools,
+ python3-sphinx,
  git,
  time
 Standards-Version: 3.9.6
 Homepage: https://github.com/stettberger/versuchung
 
-Package: python-versuchung
+Package: python3-versuchung
 Architecture: all
 Depends:
  ${shlibs:Depends},
  ${misc:Depends},
- ${python:Depends},
+ ${python3:Depends},
  ${sphinxdoc:Depends}
 Description: toolbox for reproducible research
  Versuchung is a toolbox for reproducible research. It automates the
diff -Nru python-versuchung-1.1/debian/patches/no-tests-python2.patch python-versuchung-1.3.3/debian/patches/no-tests-python2.patch
--- python-versuchung-1.1/debian/patches/no-tests-python2.patch	1970-01-01 01:00:00.000000000 +0100
+++ python-versuchung-1.3.3/debian/patches/no-tests-python2.patch	2020-05-03 10:27:25.000000000 +0200
@@ -0,0 +1,16 @@
+Skip running tests for python2
+--- a/tests/Makefile
++++ b/tests/Makefile
+@@ -6,11 +6,7 @@
+ check: $(TESTS)
+ 
+ define test_cmd
+-$(1): py2-$(1) py3-$(1)
+-
+-py2-$(1): FORCE
+-	@echo -n "Running test python2: $(1)..."
+-	@cd $(1); PYTHONPATH=$(PWD)/../src python2 test.py
++$(1): py3-$(1)
+ 
+ py3-$(1):
+ 	@echo -n "Running test python3: $(1)..."
diff -Nru python-versuchung-1.1/debian/patches/series python-versuchung-1.3.3/debian/patches/series
--- python-versuchung-1.1/debian/patches/series	1970-01-01 01:00:00.000000000 +0100
+++ python-versuchung-1.3.3/debian/patches/series	2020-05-03 10:27:25.000000000 +0200
@@ -0,0 +1 @@
+no-tests-python2.patch
diff -Nru python-versuchung-1.1/debian/rules python-versuchung-1.3.3/debian/rules
--- python-versuchung-1.1/debian/rules	2014-10-25 17:08:16.000000000 +0200
+++ python-versuchung-1.3.3/debian/rules	2020-05-03 10:21:47.000000000 +0200
@@ -1,11 +1,11 @@
 #!/usr/bin/make -f
 
 %:
-	dh $@ --with=python2,sphinxdoc --buildsystem=pybuild
+	dh $@ --with=python3,sphinxdoc --buildsystem=pybuild
 
 override_dh_auto_build:
 	PYTHONPATH=. http_proxy='127.0.0.1:9' sphinx-build -N -bhtml doc/ build/html
 	dh_auto_build
 
 override_dh_auto_test:
-	python setup.py test
+	python3 setup.py test
diff -Nru python-versuchung-1.1/doc/conf.py python-versuchung-1.3.3/doc/conf.py
--- python-versuchung-1.1/doc/conf.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/doc/conf.py	2018-08-22 17:23:14.000000000 +0200
@@ -57,7 +57,7 @@
 p = Popen("git describe --always", stdout=PIPE, stderr=STDOUT, shell=True)
 (stdout, _) = p.communicate()
 if p.returncode == 0 and len(stdout) > 0:
-    version = stdout.rstrip()
+    version = stdout.rstrip().decode()
 else:
     version = "0.1"
 
diff -Nru python-versuchung-1.1/doc/types/filesystem.rst python-versuchung-1.3.3/doc/types/filesystem.rst
--- python-versuchung-1.1/doc/types/filesystem.rst	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/doc/types/filesystem.rst	2018-08-22 17:23:14.000000000 +0200
@@ -2,7 +2,11 @@
 *********************
 
 .. autoclass:: versuchung.files.File
-	:members: path,flush,copy_contents,value,write
+	:members: path,flush,copy_contents,value,write,make_executable
+
+.. autoclass:: versuchung.files.Executable
+	:members: path, execute
 
 .. autoclass:: versuchung.files.Directory
-	:members: path,value,new_file, mirror_directory
+	:members: path,value,new_file, new_directory, mirror_directory
+
diff -Nru python-versuchung-1.1/README.md python-versuchung-1.3.3/README.md
--- python-versuchung-1.1/README.md	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/README.md	2018-08-22 17:23:14.000000000 +0200
@@ -24,7 +24,7 @@
 Documentation
 =============
 
-For the documentation, please refer to https://vamos.informatik.uni-erlangen.de/versuchung-doc/
+For the documentation, please refer to https://versuchung.readthedocs.io
 
 Getting the latest version
 ==========================
diff -Nru python-versuchung-1.1/setup.py python-versuchung-1.3.3/setup.py
--- python-versuchung-1.1/setup.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/setup.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,21 +1,23 @@
 #!/usr/bin/env python
 
-from distutils.core import setup
-from distutils.cmd import Command
-from distutils.spawn import spawn
+from __future__ import print_function
+
+from setuptools import setup, Command
+
+import sys
 
 try:
     from sphinx.setup_command import BuildDoc
     cmdclass = {'doc': BuildDoc}
 except:
-    print "No Sphinx installed (python-sphinx) so no documentation can be build"
+    print("No Sphinx installed (python-sphinx) so no documentation can be build")
     cmdclass = {}
 
 
 class TestCommand(Command):
     user_options = []
     def run(self):
-        spawn(["make", "-C", "tests"], verbose = 1)
+        self.spawn(["make", "-C", "tests", "PYTHON=%s" % (sys.executable,)])
 
     def initialize_options(self):
         pass
@@ -25,13 +27,18 @@
 
 cmdclass["test"] = TestCommand
 
+with open("README.md", "r") as fh:
+    long_description = fh.read()
+
 version_info = {
     'name': 'versuchung',
-    'version': '1.1',
+    'version': '1.3.3',
     'description': 'A toolbox for experiments',
     'author': 'Christian Dietrich',
     'author_email': 'stettber...@dokucode.de',
     'url': 'http://github.de/stettberger/versuchung',
+    'long_description': long_description,
+    'long_description_content_type': "text/markdown",
     'license': 'GPLv3',
     'classifiers': [
         'Development Status :: 4 - Beta',
@@ -39,7 +46,10 @@
         'Intended Audience :: Science/Research',
         'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
         'Operating System :: OS Independent',
-        'Programming Language :: Python :: 2.4' ],
+        'Programming Language :: Python :: 2.6',
+        'Programming Language :: Python :: 3.5'
+    ],
+    'include_package_data': True,
 }
 
 
diff -Nru python-versuchung-1.1/src/versuchung/archives.py python-versuchung-1.3.3/src/versuchung/archives.py
--- python-versuchung-1.1/src/versuchung/archives.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/src/versuchung/archives.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,17 +1,18 @@
 # This file is part of versuchung.
-# 
+#
 # versuchung is free software: you can redistribute it and/or modify it under the
 # terms of the GNU General Public License as published by the Free Software
 # Foundation, either version 3 of the License, or (at your option) any later
 # version.
-# 
+#
 # versuchung is distributed in the hope that it will be useful, but WITHOUT ANY
 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 # PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU General Public License along with
 # versuchung.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import print_function
 
 from versuchung.types import Type, InputParameter
 from versuchung.files import Directory, Directory_op_with, File
@@ -19,6 +20,11 @@
 import logging
 import os
 import sys
+import gzip
+try:
+    from StringIO import StringIO as BytesIO
+except Exception:
+    from io import BytesIO
 
 class TarArchive(Type, InputParameter, Directory_op_with):
     """Can be used as: **input parameter**
@@ -78,9 +84,11 @@
 
         extract_mode = ""
         if "tar.gz" in fn or "tgz" in fn:
-            extract_mode = "x"
+            extract_mode = "z"
         if "tar.bz2" in fn or "bzip2" in fn:
             extract_mode = "j"
+        if "tar.xz" in fn or "txz" in fn:
+            extract_mode = "J"
 
         with self.tmp_directory as d:
             try:
@@ -90,7 +98,7 @@
                 pass
             with Directory(self.name) as d2:
                 dirname = os.path.abspath(".")
-                (out, ret) = shell("tar %szvf %s", extract_mode, fn)
+                (out, ret) = shell("tar %sxvf %s", extract_mode, fn)
                 if ret != 0:
                     raise RuntimeError("Extracting of %s failed" % fn)
 
@@ -183,7 +191,7 @@
 
             (lines, ret) = shell(cmd)
             if ret != 0 or lines == 0:
-                print "\n".join(lines)
+                print("\n".join(lines))
                 sys.exit(-1)
 
             self.__hash = lines[0].split("\t")[0]
@@ -225,7 +233,7 @@
             (lines, ret) = shell(cmd, *args)
 
             if ret != 0:
-                print "\n".join(lines)
+                print("\n".join(lines))
                 sys.exit(-1)
 
             if not self.__shallow:
@@ -234,7 +242,7 @@
                 (lines, ret) = shell(cmd, *args)
 
                 if ret != 0:
-                    print "\n".join(lines)
+                    print("\n".join(lines))
                     sys.exit(-1)
 
 
@@ -254,42 +262,33 @@
 
 
 class GzipFile(File):
-    def original_filename(self):
-        return self.__original_filename
+    def __init__(self, default_filename=""):
+        File.__init__(self, default_filename, binary=True)
 
     @property
     def path(self):
-        path = File.path.fget(self)
-        if self.parameter_type == "input" and not os.path.exists(path):
-            shell("gunzip < %s > %s", self.__original_filename,
-                  path)
-        return path
+        """Decompress file into the temporary directory and return path to this location"""
+        assert self.tmp_directory is not None, \
+            "Can gunzip file only as part of an active experiment"
 
-    @property
-    def value(self):
         path = File.path.fget(self)
-        if self.parameter_type == "input" and not os.path.exists(path):
-            shell("gunzip < %s > %s", self.__original_filename,
-                  path)
-        return File.value.fget(self)
-
-    @value.setter
-    def value(self, value):
-        File.value.fset(self, value)
-
-    def before_experiment_run(self, parameter_type):
-        self.parameter_type = parameter_type
-        if parameter_type == "input":
-            self.__original_filename = File.path.fget(self)
-            self.subobjects["filename"] = File(self.__original_filename)
-            filename = self.name + "_" + os.path.basename(self.path.rstrip(".gz"))
-            self.set_path(self.tmp_directory.path, filename)
-
-        File.before_experiment_run(self, parameter_type)
-
-    def after_experiment_run(self, parameter_type):
-        File.after_experiment_run(self, parameter_type)
-        if parameter_type == "output":
-            shell("gzip -c %s > %s.1", self.path, self.path)
-            shell("mv %s.1 %s", self.path, self.path)
-
+        base = os.path.basename(path.rstrip(".gz"))
+        filename = os.path.join(self.tmp_directory.path,
+                                self.name + "_" + base)
+
+        if not os.path.exists(filename):
+            shell("gunzip < %s > %s", path, filename)
+
+        return filename
+
+    def after_read(self, value):
+        x = BytesIO(value)
+        fd = gzip.GzipFile(fileobj=x)
+        return fd.read().decode()
+
+    def before_write(self, value):
+        x = BytesIO()
+        fd = gzip.GzipFile(fileobj=x, mode="w")
+        fd.write(value.encode())
+        fd.close()
+        return x.getvalue()
diff -Nru python-versuchung-1.1/src/versuchung/database.py python-versuchung-1.3.3/src/versuchung/database.py
--- python-versuchung-1.1/src/versuchung/database.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/src/versuchung/database.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,17 +1,19 @@
 # This file is part of versuchung.
-# 
+#
 # versuchung is free software: you can redistribute it and/or modify it under the
 # terms of the GNU General Public License as published by the Free Software
 # Foundation, either version 3 of the License, or (at your option) any later
 # version.
-# 
+#
 # versuchung is distributed in the hope that it will be useful, but WITHOUT ANY
 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 # PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU General Public License along with
 # versuchung.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import print_function
+
 from versuchung.types import Type, InputParameter, OutputParameter
 import logging
 import sqlite3
@@ -91,7 +93,7 @@
         directory = self.tmp_directory.new_directory(self.name)
         path = os.path.join(directory.path, "my.cnf")
         logging.debug("MYSQL_HOME=%s", path)
-        with os.fdopen(os.open(path, os.O_WRONLY | os.O_CREAT, 0600), 'w') as handle:
+        with os.fdopen(os.open(path, os.O_WRONLY | os.O_CREAT, 0o600), 'w') as handle:
             handle.write("""[client]
 host=%s
 user=%s
@@ -135,7 +137,7 @@
                          self.dynamic_experiment.experiment_identifier,
                          str(self.dynamic_experiment.metadata))
 
-    @property 
+    @property
     def handle(self):
         """:return: handle -- MySQLdb database handle"""
         assert self.__database_connection
@@ -153,7 +155,7 @@
         self.__database_connection.commit()
         return c
 
-    def create_table(self, name, fields = [("key", "text"), ("value", "text")], 
+    def create_table(self, name, fields = [("key", "text"), ("value", "text")],
                      keys = None, conflict_strategy = None):
         """Creates a new table in the database. ``name`` is the name
         of newly created table. The ``fields`` are a list of
@@ -261,7 +263,7 @@
         """:return: string -- path to the sqlite database file"""
         return os.path.join(self.base_directory, self.__database_path)
 
-    @property 
+    @property
     def handle(self):
         """:return: handle -- sqlite3 database handle"""
         assert self.__database_connection
@@ -278,7 +280,7 @@
 
 
 
-    def create_table(self, name, fields = [("key", "text"), ("value", "text")], 
+    def create_table(self, name, fields = [("key", "text"), ("value", "text")],
                      keys = None, conflict_strategy = "REPLACE"):
         """Creates a new table in the database. ``name`` is the name
         of newly created table. The ``fields`` are a list of
@@ -326,7 +328,7 @@
     is text. If it's a tuple the first entry is the name and the second its type::
 
     >>> [("foo", "integer"), "barfoo"]
-   
+
     This will result in two columns, one with type integer and one
     with type text. If a db is given this one is used instead of a
     default sqlite database named ``sqlite3.db``
@@ -366,7 +368,7 @@
         return real_fields
 
     def before_experiment_run(self, parameter_type):
-        # Add database object as an 
+        # Add database object as an
         self.subobjects["database"] = self.__db
         Type.before_experiment_run(self, parameter_type)
 
@@ -483,7 +485,7 @@
 class Database_SQlite_Merger:
     def log(self, msg, *args):
         if self.logging:
-            print "merger: " + (msg % args)
+            print("merger: " + (msg % args))
 
     def __init__(self, target_path, source_paths = [], logging = True):
         self.target_path = target_path
@@ -535,7 +537,7 @@
         cur = self.target.cursor()
 
         TableDictrows = set()
-        
+
         for name in self.tables:
             rows = set()
             headers = None
@@ -555,7 +557,7 @@
 
             if headers == ["experiment", "key", "value"]:
                 TableDictrows.update(rows)
-        
+
         cur.execute("CREATE TABLE IF NOT EXISTS TableDict (experiment text, key text, value text,"
                     "UNIQUE (key) ON CONFLICT REPLACE)")
         cur.executemany("INSERT INTO TableDict (experiment, key, value) values(?,?,?)",
@@ -569,15 +571,14 @@
         self.collect_and_create_tables(drop = not update)
         self.collect_data()
         self.target.close()
-        
+
 
 if __name__ == '__main__':
     import sys
     if len(sys.argv) < 2:
-        print sys.argv[0] + " <target-database-file> [<source-db1> <source-db2> ...]"
-        print " -- merges different versuchung sqlite databases into a single one"
+        print(sys.argv[0] + " <target-database-file> [<source-db1> <source-db2> ...]")
+        print(" -- merges different versuchung sqlite databases into a single one")
         sys.exit(-1)
 
     merger = Database_SQlite_Merger(sys.argv[1], sys.argv[2:])
     merger.merge()
-
diff -Nru python-versuchung-1.1/src/versuchung/execute.py python-versuchung-1.3.3/src/versuchung/execute.py
--- python-versuchung-1.1/src/versuchung/execute.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/src/versuchung/execute.py	2018-08-22 17:23:14.000000000 +0200
@@ -17,7 +17,10 @@
 import logging
 import os
 import resource
-import thread
+try:
+    import thread
+except ImportError:
+    import _thread as thread
 import time
 import pipes
 from versuchung.tools import AdviceManager, Advice
@@ -68,7 +71,7 @@
     command = command % args
 
     logging.debug("executing: " + command)
-    p = Popen(command, stdout=PIPE, stderr=STDOUT, shell=True)
+    p = Popen(command, stdout=PIPE, stderr=STDOUT, shell=True, universal_newlines=True)
     stdout = ""
     while True:
         x = p.stdout.readline()
diff -Nru python-versuchung-1.1/src/versuchung/experiment.py python-versuchung-1.3.3/src/versuchung/experiment.py
--- python-versuchung-1.1/src/versuchung/experiment.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/src/versuchung/experiment.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,17 +1,18 @@
 # This file is part of versuchung.
-# 
+#
 # versuchung is free software: you can redistribute it and/or modify it under the
 # terms of the GNU General Public License as published by the Free Software
 # Foundation, either version 3 of the License, or (at your option) any later
 # version.
-# 
+#
 # versuchung is distributed in the hope that it will be useful, but WITHOUT ANY
 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 # PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU General Public License along with
 # versuchung.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import print_function
 
 from optparse import OptionParser
 import datetime
@@ -124,12 +125,29 @@
         """
         Type.__init__(self)
         InputParameter.__init__(self)
-
         self.title = self.__class__.__name__
         self.static_experiment = self
+        if default_experiment_instance and not os.path.exists(default_experiment_instance):
+            default_experiment_instance = None
+        self.__reinit__(default_experiment_instance)
+
+    def __reinit__(self, experiment_path):
+        self.__experiment_instance = experiment_path
+
+        if experiment_path:
+            if "/" in experiment_path:
+                self.__experiment_instance = os.path.basename(experiment_path)
+
+            self.base_directory = os.path.join(os.curdir, experiment_path)
+            self.base_directory = os.path.realpath(self.base_directory)
+            assert os.path.exists(self.base_directory)
 
-        self.__experiment_instance = default_experiment_instance
-        self.__metadata = None
+            with open(os.path.join(experiment_path, "metadata")) as fd:
+                metadata = eval(fd.read())
+                self.__metadata = metadata
+        else:
+            self.base_directory = None
+            self.__metadata = None
 
         # Copy input and output objects
         self.inputs = JavascriptStyleDictAccess(copy.deepcopy(self.__class__.inputs))
@@ -137,11 +155,7 @@
         self.outputs = JavascriptStyleDictAccess(copy.deepcopy(self.__class__.outputs))
         self.o = self.outputs
 
-        if default_experiment_instance != None:
-            self.base_directory = os.path.join(os.curdir, self.__experiment_instance)
-            self.base_directory = os.path.realpath(self.base_directory)
-        else:
-            self.base_directory = os.path.realpath(os.curdir)
+        self.subobjects.clear()
 
         # Sanity checking for input parameters.
         for (name, inp) in self.inputs.items():
@@ -149,26 +163,45 @@
             if type(inp) == LambdaType:
                 continue
             if not isinstance(inp, InputParameter):
-                print "%s cannot be used as an input parameter" % name
+                print("%s cannot be used as an input parameter" % name)
                 sys.exit(-1)
             self.subobjects[name] = inp
 
+
         for (name, outp) in self.outputs.items():
             if not isinstance(outp, OutputParameter):
-                print "%s cannot be used as an output parameter" % name
+                print("%s cannot be used as an output parameter" % name)
                 sys.exit(-1)
             self.subobjects[name] = outp
 
+
+        # Reinit Children Attributes
+        if experiment_path:
+            for (name, inp) in self.inputs.items():
+                if hasattr(inp, "__reinit__"):
+                    try:
+                        inp.__reinit__(metadata[name])
+                    except:
+                        logging.debug('Cannot reinit field %s. Setting it to None', name)
+                        self.inputs[name] = None
+                else:
+                    # We cannot reinit this input from metadata. Therefore it is better to clear it.
+                    logging.debug('Cannot reinit field %s. Setting it to None', name)
+                    self.inputs[name] = None
+
+
+
     def __setup_parser(self):
         self.__parser = OptionParser("%prog <options>")
         self.__parser.add_option('-d', '--base-dir', dest='base_dir', action='store',
                                  help="Directory which is used for storing the experiment data",
                                  default = ".")
-        self.__parser.add_option('-l', '--list', dest='do_list', action='store_true',
-                                 help="list all experiment results")
+        self.__parser.add_option('--dummy', dest='dummy_result', action='store_true',
+                                 help="Use dummy result directory",
+                                 default=False)
         self.__parser.add_option('-s', '--symlink', dest='do_symlink', action='store_true',
                                  help="symlink the result dir (as newest)")
-        self.__parser.add_option('-v', '--verbose', dest='verbose', action='count',
+        self.__parser.add_option('-v', '--verbose', dest='verbose', action='count', default=0,
                                  help="increase verbosity (specify multiple times for more)")
 
         for (name, inp) in self.inputs.items():
@@ -176,12 +209,6 @@
                 continue
             inp.inp_setup_cmdline_parser(self.__parser)
 
-    def __setup_tmp_directory(self):
-        """Creat temporary directory and assign it to every input and
-        output directories tmp_directory slots"""
-        # Create temp directory
-        self.tmp_directory = Directory(tempfile.mkdtemp())
-        self.subobjects["tmp_directory"] = self.tmp_directory
 
     def execute(self, args = [], **kwargs):
         """Calling this method will execute the experiment
@@ -205,24 +232,18 @@
         >>> experiment.execute(input_parameter="foo")
         """
         self.dynamic_experiment = self
+        self.startup_directory = os.path.abspath(os.curdir)
+
         self.subobjects.update()
 
         # Set up the argument parsing
         self.__setup_parser()
         (opts, args) = self.__parser.parse_args(args)
-        os.chdir(opts.base_dir)
         setup_logging(opts.verbose)
 
         self.__opts = opts
         self.__args = args
 
-        if self.__opts.do_list:
-            for experiment in os.listdir(self.base_directory):
-                if experiment.startswith(self.title):
-                    print "EXP", experiment
-                    self.__do_list(self.__class__(experiment))
-            return None
-
         for key in kwargs:
             if not hasattr(opts, key):
                 raise AttributeError("No argument called %s" % key)
@@ -231,16 +252,22 @@
         # Set up the experiment
         self.before_experiment_run("output")
 
+        # Goto the output directory
+        os.chdir(self.base_directory)
+
         try:
             self.run()
-        except:
+        except RuntimeError as e:
             # Clean up the tmp directory
             if self.suspend_on_error:
-                print "tmp-dir: %s" % self.tmp_directory.path
+                print(str(e))
+                print("tmp-dir: %s" % self.tmp_directory.path)
                 self.suspend_python()
             logging.error("Removing tmp directory")
             shutil.rmtree(self.tmp_directory.path)
-            raise
+            raise e
+        finally:
+            os.chdir(self.startup_directory)
 
         # Tear down the experiment
         self.after_experiment_run("output")
@@ -264,20 +291,6 @@
         os.kill(os.getpid(), signal.SIGSTOP)
 
 
-    def __do_list(self, experiment, indent = 0):
-        with open(os.path.join(experiment.base_directory, "metadata")) as fd:
-            content = fd.read()
-        d = eval(content)
-        content = experiment.__experiment_instance + "\n" + content
-        print "+%s%s" % ("-" * indent,
-                        content.strip().replace("\n", "\n|" + (" " * (indent+1))))
-        for dirname in d.values():
-            if type(dirname) != type(""):
-                continue
-            if os.path.exists(os.path.join(dirname, "metadata")) and \
-               os.path.realpath(dirname) != os.path.realpath(experiment.base_directory):
-                self.__do_list(Experiment(dirname), indent + 3)
-
     def before_experiment_run(self, parameter_type):
         # When experiment run as input, just run the normal input handlers
         if parameter_type == "input":
@@ -304,7 +317,8 @@
         self.subobjects.update()
 
         # Now set up the experiment tmp directory
-        self.__setup_tmp_directory()
+        self.tmp_directory = Directory(tempfile.mkdtemp())
+        self.subobjects["tmp_directory"] = self.tmp_directory
 
         for obj in self.inputs.values():
             obj.before_experiment_run("input")
@@ -320,15 +334,20 @@
         for name in self.inputs:
             metadata.update( self.inputs[name].inp_metadata() )
         m = hashlib.md5()
-        m.update("version %s" % str(self.version))
+        m.update(("version %s" % str(self.version)).encode())
         calc_metadata = self.filter_metadata(metadata)
         for key in sorted(calc_metadata.keys()):
-            m.update(key + " " + str(calc_metadata[key]))
+            m.update((key + " " + str(calc_metadata[key])).encode())
 
         self.__experiment_instance = "%s-%s" %(self.title, m.hexdigest())
-        self.base_directory = os.path.join(os.curdir, self.__experiment_instance)
+        if self.__opts.dummy_result:
+            base = self.tmp_directory.path
+        else:
+            base = self.__opts.base_dir
+        self.base_directory = os.path.join(base, self.__experiment_instance)
         self.base_directory = os.path.realpath(self.base_directory)
 
+
         if os.path.exists(self.base_directory):
             logging.info("Removing all files from existing output directory")
             for f in glob.glob(os.path.join(self.base_directory, '*')):
@@ -355,6 +374,14 @@
 
         self.__metadata = metadata
 
+
+    def symlink_name(self):
+        """If -s is given, this function returns the name of the symlink
+           object that is created in the base directory.
+
+        """
+        return self.title
+
     def after_experiment_run(self, parameter_type):
 
         if parameter_type == "output":
@@ -371,9 +398,13 @@
 
             shutil.rmtree(self.tmp_directory.path)
 
-            # Create a Symlink to the newsest result set
-            if self.__opts.do_symlink:
-                link = os.path.join(self.__opts.base_dir, self.title)
+            # Create a Symlink to the newsest result set, if it is not
+            # already removed (--dummy)
+            if self.__opts.do_symlink and os.path.exists(self.base_directory):
+                link = os.path.join(self.base_directory,
+                                    "..",
+                                    self.symlink_name())
+                link = os.path.abspath(link)
                 if os.path.islink(link):
                     os.unlink(link)
 
@@ -393,20 +424,25 @@
     def inp_extract_cmdline_parser(self, opts, args):
         self.__experiment_instance = self.inp_parser_extract(opts, None)
         if not self.__experiment_instance:
-            print "Missing argument for %s" % self.title
+            print("Missing argument for %s" % self.title)
             raise ExperimentError
 
         # Resolve symlink relative to the current directory
-        self.__experiment_instance = os.path.realpath(self.__experiment_instance)
-        self.__experiment_instance = self.__experiment_instance[len(os.path.realpath(os.curdir))+1:]
+        path = os.path.realpath(self.__experiment_instance)
+        path = os.path.abspath(path)
+        self.__experiment_instance = os.path.basename(path)
+
+        self.base_directory = path
+        assert os.path.exists(self.base_directory), \
+            "Base Directory does not exist"
 
-        self.base_directory = os.path.join(os.curdir, self.__experiment_instance)
-        self.base_directory = os.path.realpath(self.base_directory)
+        self.__reinit__(path)
 
         for (name, outp) in self.outputs.items():
             del self.subobjects[name]
             self.subobjects[name] = outp
 
+
     def inp_metadata(self):
         return {self.name: self.__experiment_instance}
 
@@ -470,8 +506,7 @@
              return inp
          elif outp != None:
              return outp
-         
+
          raise AttributeError("'%s' object has no attribute '%s'" %(\
              self.__class__.__name__,
              name))
-
diff -Nru python-versuchung-1.1/src/versuchung/files.py python-versuchung-1.3.3/src/versuchung/files.py
--- python-versuchung-1.1/src/versuchung/files.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/src/versuchung/files.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,25 +1,29 @@
 # This file is part of versuchung.
-# 
+#
 # versuchung is free software: you can redistribute it and/or modify it under the
 # terms of the GNU General Public License as published by the Free Software
 # Foundation, either version 3 of the License, or (at your option) any later
 # version.
-# 
+#
 # versuchung is distributed in the hope that it will be useful, but WITHOUT ANY
 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 # PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU General Public License along with
 # versuchung.  If not, see <http://www.gnu.org/licenses/>.
 
 
 from versuchung.types import InputParameter, OutputParameter, Type
-from versuchung.tools import before
-from cStringIO import StringIO
+import versuchung.archives
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from io import StringIO
 import shutil
 import csv
 import os, stat
 import hashlib
+import fnmatch
 
 class FilesystemObject(InputParameter, OutputParameter, Type):
     def __init__(self, default_name=""):
@@ -45,14 +49,20 @@
         if not self.__force_enclosing_directory:
             if self.parameter_type == "input":
                 if self.static_experiment == self.dynamic_experiment:
-                    self.__enclosing_directory = os.path.abspath(os.curdir)
+                    self.__enclosing_directory = self.dynamic_experiment.startup_directory
                 else:
                     self.__enclosing_directory = self.static_experiment.base_directory
             elif self.parameter_type == "output":
                 assert self.static_experiment == self.dynamic_experiment
                 self.__enclosing_directory = self.dynamic_experiment.base_directory
+            elif self.static_experiment is not None:
+                self.__enclosing_directory = self.static_experiment.base_directory
             else:
                 self.__enclosing_directory = os.path.abspath(os.curdir)
+
+        if os.path.isabs(self.__object_name):
+            return self.__object_name
+
         return os.path.join(self.__enclosing_directory, self.__object_name)
 
     @property
@@ -83,17 +93,24 @@
     disk before the experiment finishes.
     """
 
-    def __init__(self, default_filename=""):
+    def __init__(self, default_filename="", binary=False):
         FilesystemObject.__init__(self, default_filename)
         self.__value = None
 
+        self.__binary = binary
+        if binary:
+            self.__binary_mode = "b"
+        else:
+            self.__binary_mode = ""
+
+
     @property
     def value(self):
         """This attribute can be read and written and represent the
         content of the specified file"""
         if not self.__value:
             try:
-                with open(self.path) as fd:
+                with open(self.original_path, "r" + self.__binary_mode) as fd:
                     self.__value = self.after_read(fd.read())
             except IOError:
                 # File couldn't be read
@@ -104,6 +121,10 @@
     def value(self, value):
         self.__value = value
 
+    @property
+    def original_path(self):
+        return File.path.fget(self)
+
     def write(self, content, append = False):
         """Similar to the :attr:`value` property. If the parameter
         `append` is `False`, then the property :attr:`value` is reset
@@ -121,9 +142,9 @@
 
     def flush(self):
         """Flush the cached content of the file to disk"""
-        if not self.__value:
+        if self.__value == None:
             return
-        with open(self.path, "w+") as fd:
+        with open(self.original_path, "w" + self.__binary_mode + "+") as fd:
             v = self.before_write(self.value)
             if v is None:
                 v = ""
@@ -140,8 +161,8 @@
 
     def make_executable(self):
         """makes a file exectuable (chmod +x $file)"""
-        st = os.stat(self.path)
-        os.chmod(self.path, st.st_mode | stat.S_IEXEC)
+        st = os.stat(self.original_path)
+        os.chmod(self.original_path, st.st_mode | stat.S_IEXEC)
 
     def after_read(self, value):
         """To provide filtering of file contents in subclasses, overrwrite this method.
@@ -188,7 +209,7 @@
         raise NotImplementedError
 
     def inp_metadata(self):
-        return {self.name + "-md5": hashlib.md5(open(self.path).read()).hexdigest()}
+        return {self.name + "-md5": hashlib.md5(open(self.path, "rb").read()).hexdigest()}
 
     def execute(self, cmdline, *args):
         """Does start the executable with meth:`versuchung.execute.shell` and
@@ -213,45 +234,62 @@
 class Directory(FilesystemObject, Directory_op_with):
     """Can be used as: **input parameter** and **output parameter**
 
-    Represents the contents of directory. It can also be used with the
-    **with**-keyword to change the current working directory temporarily to this
-    directory::
+    Represents the contents of directory. The filename_filter is a
+    glob/fnmatch expression to filter the directories content and to
+    ensure that no file is generated that does not fit this pattern.
+    An useful example of this is an output Directory that matches only
+    *.log files and is directly located in the result directory:
+
+       outputs = {
+           "logs": Directory(".", filename_filter="*.log")
+       }
+
+    It can also be used with the **with**-keyword to change the
+    current working directory temporarily to this directory::
 
        with directory as dir:
           # Do something with adjusted current working directory
           print os.curdir
+
     """
 
-    def __init__(self, default_filename=""):
+    def __init__(self, default_filename="", filename_filter="*"):
         FilesystemObject.__init__(self, default_filename)
         Directory_op_with.__init__(self)
+        self.filename_filter = filename_filter
         self.__value = None
         self.__new_files = []
 
-    def ___ensure_dir_exists(self):
+    def __ensure_dir_exists(self):
         if not os.path.exists(self.path):
             os.mkdir(self.path)
 
-    # Ensure dir exists DECORATOR
-    __ensure_dir_exists = before(___ensure_dir_exists)
-
     @property
     def value(self):
         """:return: list -- directories and files in given directory"""
         if not self.__value:
             self.__value = os.listdir(self.path)
+            self.__value = [x for x in self.__value
+                            if fnmatch.fnmatch(x, self.filename_filter)]
         return self.__value
 
     def __iter__(self):
         for name in self.value:
             p = os.path.join(self.path, name)
+            if name in self.subobjects:
+                yield self.subobjects[name]
+                continue
+
             if os.path.isdir(p):
                 d = Directory(name)
                 d.set_path(self.path, p)
                 self.subobjects[name] = d
                 yield d
             else:
-                f = File(name)
+                if p.endswith(".gz"):
+                    f = versuchung.archives.GzipFile(name)
+                else:
+                    f = File(name)
                 f.set_path(self.path, p)
                 self.subobjects[name] = f
                 yield f
@@ -259,22 +297,32 @@
     def before_experiment_run(self, parameter_type):
         FilesystemObject.before_experiment_run(self, parameter_type)
         if parameter_type == "output":
-            self.___ensure_dir_exists()
+            self.__ensure_dir_exists()
 
-    @__ensure_dir_exists
-    def new_file(self, name):
+    def new_file(self, name, compressed=False):
         """Generate a new :class:`~versuchung.files.File` in the
         directory. It will be flushed automatically if the experiment
         is over."""
-        f = File(name)
+        if not fnmatch.fnmatch(name, self.filename_filter):
+            raise RuntimeError("Filename {} does not match filter {}".\
+                                 format(name, self.filename_filter))
+        self.__ensure_dir_exists()
+        if compressed:
+            f = versuchung.archives.GzipFile(name)
+        else:
+            f = File(name)
         f.set_path(self.path, name)
+        f.value = ""
         self.subobjects[name] = f
         return f
 
-    @__ensure_dir_exists
     def new_directory(self, name):
         """Generate a new :class:`~versuchung.files.Directory` in the
         directory. The directory <name> must not be present before"""
+        if not fnmatch.fnmatch(name, self.filename_filter):
+            raise RuntimeError("Filename {} does not match filter {}".\
+                                 format(name, self.filename_filter))
+        self.__ensure_dir_exists()
         f = Directory(name)
         f.set_path(self.path, name)
         os.mkdir(f.path)
@@ -282,7 +330,6 @@
         return f
 
 
-    @__ensure_dir_exists
     def mirror_directory(self, path, include_closure = None):
         """Copies the contents of the given directory to this
         directory.
@@ -291,6 +338,8 @@
         (absolute) path in the origin directory, if it is mirrored. If
         it is None, all files are included."""
 
+        self.__ensure_dir_exists()
+
         if not include_closure:
             include_closure = lambda arg: True
 
@@ -365,4 +414,3 @@
         if type(row) != list:
             raise TypeError("list of values required")
         self.value.append(row)
-
diff -Nru python-versuchung-1.1/src/versuchung/search.py python-versuchung-1.3.3/src/versuchung/search.py
--- python-versuchung-1.1/src/versuchung/search.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/src/versuchung/search.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,20 +1,24 @@
 # This file is part of versuchung.
-# 
+#
 # versuchung is free software: you can redistribute it and/or modify it under the
 # terms of the GNU General Public License as published by the Free Software
 # Foundation, either version 3 of the License, or (at your option) any later
 # version.
-# 
+#
 # versuchung is distributed in the hope that it will be useful, but WITHOUT ANY
 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 # PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU General Public License along with
 # versuchung.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import print_function
+
 import os
 import logging
 
+from versuchung.types import List
+
 def search_experiment_results(experiment_type, directory, selector = None):
     """In large experiment setups it is hard to keep track of all
     result sets, which were produced. Therefore a search on the
@@ -29,7 +33,7 @@
     :func:`search_selector_metadata`.
 
     >>> search_experiment_results(MyExperiment, ".", lambda e: "home" in e.path)
-      [<MyExperiment object at 0xb74805ec>]
+      List(MyExperiment, <MyExperiment object at 0xb74805ec>])
     """
     # Name -> Path
     experiment_map = {}
@@ -52,7 +56,8 @@
                     if exp.path not in [e.path for e in experiment_map.values()]:
                         experiment_map[experiment_name] = exp
 
-    return experiment_map.values()
+    return List(experiment_type, experiment_map.values())
+
 
 def search_experiment(experiment_type, directory, selector = None):
     """Like :func:`search_experiment_results`, but returns only one
@@ -109,8 +114,8 @@
     import sys
     from versuchung.experiment import Experiment
     if len(sys.argv) != 4:
-        print "%s <experiment-type> <field> <data>" % sys.argv[0]
+        print("%s <experiment-type> <field> <data>" % sys.argv[0])
         sys.exit(-1)
     Experiment.__name__ = sys.argv[1]
     for exp in search_experiment_results(Experiment, ".", {sys.argv[2]: sys.argv[3]}):
-        print exp.path
+        print(exp.path)
diff -Nru python-versuchung-1.1/src/versuchung/tex.py python-versuchung-1.3.3/src/versuchung/tex.py
--- python-versuchung-1.1/src/versuchung/tex.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/src/versuchung/tex.py	2018-08-22 17:23:14.000000000 +0200
@@ -12,6 +12,8 @@
 # You should have received a copy of the GNU General Public License along with
 # versuchung.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import print_function
+
 from versuchung.files import File
 import re
 import os
@@ -158,4 +160,4 @@
 
 if __name__ == '__main__':
     import sys
-    print PgfKeyDict(sys.argv[1])
+    print(PgfKeyDict(sys.argv[1]))
diff -Nru python-versuchung-1.1/src/versuchung/tools.py python-versuchung-1.3.3/src/versuchung/tools.py
--- python-versuchung-1.1/src/versuchung/tools.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/src/versuchung/tools.py	2018-08-22 17:23:14.000000000 +0200
@@ -13,6 +13,8 @@
 # versuchung.  If not, see <http://www.gnu.org/licenses/>.
 
 import logging
+import sys
+from functools import wraps
 
 class JavascriptStyleDictAccess(dict):
     def __init__(self, d):
@@ -42,33 +44,6 @@
 
     logging.basicConfig(level=l)
 
-def before(decorator_argument):
-    """Decorator for executing functions before other functions"""
-    def decorator(func):
-        def wrapped(self, *args, **kwargs):
-            # Late binding
-            inb4 = decorator_argument
-            if type(decorator_argument) == str:
-                inb4 = getattr(self, decorator_argument)
-
-            if "func_code" in dir(inb4):
-                argcount = inb4.func_code.co_argcount
-            else:
-                raise RuntimeError("Invalid argument to decorator")
-
-            if argcount == 1:
-                inb4(self)
-            elif argcount == 0:
-                inb4()
-            else:
-                raise RuntimeError("Unexpected parameter count")
-
-            return func(self, *args, **kwargs)
-        wrapped.__doc__ = func.__doc__
-        return wrapped
-    return decorator
-
-
 class Singleton(object):
     _instance = None
     def __new__(cls, *args, **kwargs):
@@ -96,9 +71,9 @@
     @staticmethod
     def advicable(func):
         """Decorator to mark a function as advicable"""
-        if not "func_name" in dir(func):
+        if not "__call__" in dir(func):
             raise ValueError("No function adviced")
-        full_name = "%s.%s" % (func.__module__, func.func_name)
+        full_name = "%s.%s" % (func.__module__, func.__name__)
 
         self = AdviceManager()
 
@@ -157,13 +132,22 @@
         if self.enabled:
             return
         # Hook only in if the methods are overwritten
-        if self.before.im_func != Advice.before.im_func:
-            am.before[self.method].append(self.before)
-        if self.around.im_func != Advice.around.im_func:
-            am.around[self.method].append(self.around)
-        if self.after.im_func != Advice.after.im_func:
-            am.after[self.method].append(self.after)
-        self.enabled = True
+        if sys.version_info[0] == 2:
+            if self.before.im_func != Advice.before.im_func:
+                am.before[self.method].append(self.before)
+            if self.around.im_func != Advice.around.im_func:
+                am.around[self.method].append(self.around)
+            if self.after.im_func != Advice.after.im_func:
+                am.after[self.method].append(self.after)
+            self.enabled = True
+        elif sys.version_info[0] == 3:
+            if self.before.__func__ != Advice.before:
+                am.before[self.method].append(self.before)
+            if self.around.__func__ != Advice.around:
+                am.around[self.method].append(self.around)
+            if self.after.__func__ != Advice.after:
+                am.after[self.method].append(self.after)
+            self.enabled = True
 
     def before(self, args, kwargs):
         return (args, kwargs)
diff -Nru python-versuchung-1.1/src/versuchung/types.py python-versuchung-1.3.3/src/versuchung/types.py
--- python-versuchung-1.1/src/versuchung/types.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/src/versuchung/types.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,36 +1,45 @@
 # This file is part of versuchung.
-# 
+#
 # versuchung is free software: you can redistribute it and/or modify it under the
 # terms of the GNU General Public License as published by the Free Software
 # Foundation, either version 3 of the License, or (at your option) any later
 # version.
-# 
+#
 # versuchung is distributed in the hope that it will be useful, but WITHOUT ANY
 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 # PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU General Public License along with
 # versuchung.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import print_function
+
 import os
 import csv
-from  cStringIO import StringIO
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from io import StringIO
 from optparse import OptionParser
 import copy
+import glob
 
 class SubObjects(dict):
     def __init__(self, type_object):
         dict.__init__(self)
         self.parent = type_object
+
     def __setitem__(self, key, value):
         assert not key in self or self[key] == value, "Duplicated object name: %s = %s" % (key, value)
         dict.__setitem__(self, key, value)
         value.parent_object = self.parent
         self.update()
+
     def update(self):
         if not "parent" in dir(self) and len(self) > 0:
-            print "You probably used python multiprocessing, this might break horrible"
+            print("You probably used python multiprocessing, this might break horrible")
             return
+
         for name, obj in self.items():
             if self.parent.name != None:
                 obj.name = "%s-%s" % (self.parent.name, name)
@@ -59,6 +68,7 @@
     def __init__(self):
         # We gather a list of objects that are used by us.
         self.subobjects = SubObjects(self)
+        self.__name = None
 
     def before_experiment_run(self, parameter_type):
         self.parameter_type = parameter_type
@@ -112,9 +122,16 @@
         assert self.dynamic_experiment, "Type is not used part of a running experiment"
         return self.dynamic_experiment.tmp_directory
 
+    def __repr__(self, value=None):
+        if value:
+            return "<%s %s '%s'>" %(self.__class__.__name__, self.__name, value)
+        return "<%s %s>" %(self.__class__.__name__, self.__name)
+
 
 
 class InputParameter:
+    is_restartable = False
+
     def __init__(self):
         pass
     def inp_setup_cmdline_parser(self, parser):
@@ -181,8 +198,12 @@
         Type.__init__(self)
         self.__value = default_value
 
+    def __reinit__(self, value):
+        self.__value = value
+
     def inp_setup_cmdline_parser(self, parser):
         self.inp_parser_add(parser, None, self.__value)
+
     def inp_extract_cmdline_parser(self, opts, args):
         self.__value = self.inp_parser_extract(opts, None)
 
@@ -190,7 +211,10 @@
         return {self.name: self.value}
 
     def __str__(self):
-        return self.value
+        return str(self.value)
+
+    def __repr__(self):
+        return Type.__repr__(self, self.__value)
 
     @property
     def value(self):
@@ -198,6 +222,7 @@
         or the parameter given on the command line"""
         return self.__value
 
+
 class Bool(InputParameter, Type):
     """Can be used as: **input parameter**
 
@@ -208,6 +233,9 @@
         Type.__init__(self)
         self.__value = default_value
 
+    def __reinit__(self, value):
+        self.__value = value
+
     def inp_setup_cmdline_parser(self, parser):
         self.inp_parser_add(parser, None, self.__value)
     def inp_extract_cmdline_parser(self, opts, args):
@@ -246,6 +274,10 @@
         Type.__init__(self)
         self.__value = default_value
 
+    def __reinit__(self, value):
+        self.__value = value
+
+
     def inp_setup_cmdline_parser(self, parser):
         self.inp_parser_add(parser, None, self.__value)
     def inp_extract_cmdline_parser(self, opts, args):
@@ -319,84 +351,97 @@
     inherits from ``list``), so it is really easy to iterate over it::
 
       for string in self.inputs.strings:
-          print string.value
+          print(string.value)
 
       for git in self.inputs.git:
           # Clone all given Git Archives
-          print git.path
+          print(git.path)
     """
 
     def __init__(self, datatype, default_value=[]):
         InputParameter.__init__(self)
         Type.__init__(self)
-        list.__init__(self)
-        self.__default_value = default_value
+        list.__init__(self, default_value)
         if type(datatype) != type:
             datatype = type(datatype)
         self.datatype = datatype
         self.__command_line_parsed = False
 
+    def __reinit__(self, values):
+        if hasattr(self.datatype, "__reinit__"):
+            self[:] = []
+            self.subobjects.clear()
+            for item in values:
+                # Intatiate Datatype
+                item = self.datatype(item)
+                self.subobjects["%d" % len(self)] = item
+                self.append(item)
+
+
+
     def inp_setup_cmdline_parser(self, parser):
-        self.inp_parser_add(parser, None, copy.deepcopy(self.__default_value), action="append",
+        self.inp_parser_add(parser, None, [], action="append",
                             help = "List parameter for type %s" %
                             self.datatype.__name__)
 
 
     def before_experiment_run(self, parameter_type):
-        if parameter_type == "input" and \
-                not self.__command_line_parsed:
-            count = 0
-            for i in self.__default_value:
-                self.subobjects["%d" % count] = i
-                self.append(i)
-                count += 1
+        for idx, value in enumerate(self):
+            self.subobjects[str(idx)] = value
 
         Type.before_experiment_run(self,parameter_type)
 
     def inp_extract_cmdline_parser(self, opts, args):
         import shlex
         args = self.inp_parser_extract(opts, None)
-
-        # No argument where given, us the default_values in before_experiment_run
-        if len(args) == len(self.__default_value) and len(args) > 0\
-           and type(args[0]) == type(args[0]) == self.datatype:
+        if not args:
             return
 
-        self.__command_line_parsed = True
-
-        if len(args) > len(self.__default_value):
-            args = [x for x in args if type(x) != self.datatype]
-
-        count = 0
-        for arg in args:
+        # Remove default values
+        self[:] = []
+        self.subobjects.clear()
+
+        while len(args) > 0:
+            arg = args.pop(0)
+            if hasattr(self.datatype, "path") and not os.path.exists(arg):
+                args = glob.glob(arg) + args
+                # Remove duplicated items caused by symlinks
+                args = list(set([os.path.realpath(x) for x in args]))
+                continue
             # Create Subtype and initialize its parser
             subtype = self.datatype()
-            self.subobjects["%d" % count] = subtype
-            count += 1
+            self.subobjects["%d" % len(self)] = subtype
             subtype_parser = OptionParser()
             subtype.inp_setup_cmdline_parser(subtype_parser)
 
             if not ":" in arg:
-                arg = ": " + arg
+                (opts, sub_args) = subtype_parser.parse_args(["--" + subtype.name, arg])
+            else:
+                arg = arg.replace(": ", "--" + subtype.name + " ")
+                arg = arg.replace(":", "--" + subtype.name + "-")
 
-            arg = arg.replace(": ", "--" + subtype.name + " ")
-            arg = arg.replace(":", "--" + subtype.name + "-")
+                arg = shlex.split(arg)
+                (opts, sub_args) = subtype_parser.parse_args(arg)
 
-            arg = shlex.split(arg)
+            subtype.inp_extract_cmdline_parser(opts,sub_args)
 
-            (opts, args) = subtype_parser.parse_args(arg)
-            subtype.inp_extract_cmdline_parser(opts,args)
             self.append(subtype)
 
+
+
     def inp_metadata(self):
-        metadata = {}
-        for item in self:
-            metadata.update(item.inp_metadata())
+        metadata = {self.name: []}
+        for idx, item in enumerate(self):
+            m = item.inp_metadata()
+            metadata[self.name].append(m["%s-%d" % (self.name, idx)])
+            metadata.update(m)
         return metadata
 
     @property
     def value(self):
         """Returns the object (which behaves like a list) itself. This
-        is only implemented for a coherent API."""
+           is only implemented for a coherent API."""
         return self
 
+    def __repr__(self):
+        return Type.__repr__(self, list.__repr__(self))
diff -Nru python-versuchung-1.1/tests/advices/test.py python-versuchung-1.3.3/tests/advices/test.py
--- python-versuchung-1.1/tests/advices/test.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/tests/advices/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,5 +1,7 @@
 #!/usr/bin/python
 
+from __future__ import print_function
+
 from versuchung.experiment import Experiment
 from versuchung.files import File
 from versuchung.execute import shell
@@ -23,5 +25,5 @@
 
     if dirname:
         shutil.rmtree(dirname)
-    print "success"
+    print("success")
 
diff -Nru python-versuchung-1.1/tests/basic_types/test.py python-versuchung-1.3.3/tests/basic_types/test.py
--- python-versuchung-1.1/tests/basic_types/test.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/tests/basic_types/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 from versuchung.experiment import Experiment
 from versuchung.types import String, Optional, Bool
 
@@ -12,7 +14,7 @@
 
         assert str(self.string) == "ABC"
         assert str(self.string) != repr(self.string)
-        assert "<versuchung.types.String" in repr(self.string)
+        assert "<String" in repr(self.string)
         assert "%s" % self.string == "ABC"
         assert self.bool.value == False
 
@@ -22,5 +24,8 @@
     import shutil
     t = BasicTypesTest()
     dirname = t(["--bool", "no"] + sys.argv)
+
+    assert BasicTypesTest(dirname).bool.value == t.bool.value
+
     shutil.rmtree(dirname)
-    print "success"
+    print("success")
diff -Nru python-versuchung-1.1/tests/csv_read_write/test.py python-versuchung-1.3.3/tests/csv_read_write/test.py
--- python-versuchung-1.1/tests/csv_read_write/test.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/tests/csv_read_write/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 from versuchung.experiment import Experiment
 from versuchung.files import CSV_File
 
@@ -19,4 +21,4 @@
 
     if dirname:
         shutil.rmtree(dirname)
-    print "success"
+    print("success")
diff -Nru python-versuchung-1.1/tests/database_basic/test.py python-versuchung-1.3.3/tests/database_basic/test.py
--- python-versuchung-1.1/tests/database_basic/test.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/tests/database_basic/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,5 +1,7 @@
 #!/usr/bin/python
 
+from __future__ import print_function
+
 from versuchung.experiment import Experiment
 from versuchung.database   import Database, TableDict, Table
 import os
@@ -37,12 +39,11 @@
     assert os.path.exists(os.path.join(r1, "foobar.db"))
 
     e2 = SimpleExperiment2()
-    r2 = e2(se=r1)
+    r2 = e2(se=os.path.abspath(r1))
 
     if r1:
         shutil.rmtree(r1)
 
     if r2:
         shutil.rmtree(r2)
-    print "success"
-
+    print("success")
diff -Nru python-versuchung-1.1/tests/database_mergetool/test.py python-versuchung-1.3.3/tests/database_mergetool/test.py
--- python-versuchung-1.1/tests/database_mergetool/test.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/tests/database_mergetool/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,5 +1,7 @@
 #!/usr/bin/python
 
+from __future__ import print_function
+
 from versuchung.experiment import Experiment
 from versuchung.types      import Integer
 from versuchung.database   import Database, TableDict, Table, Database_SQlite_Merger
@@ -95,5 +97,5 @@
 
     os.unlink("output.db")
 
-    print "success"
+    print("success")
 
diff -Nru python-versuchung-1.1/tests/directory_output/test.py python-versuchung-1.3.3/tests/directory_output/test.py
--- python-versuchung-1.1/tests/directory_output/test.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/tests/directory_output/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,11 +1,16 @@
+from __future__ import print_function
+
 from versuchung.experiment import Experiment
 from versuchung.types import String
-from versuchung.files import Directory
+from versuchung.files import Directory, File
+from versuchung.archives import GzipFile
 import os
 
 class SimpleExperiment(Experiment):
     outputs = {"dir1": Directory("d1"),
-               "dir2": Directory("d2")}
+               "dir2": Directory("d2"),
+               "filtered": Directory(".", filename_filter="*.log*"),
+              }
 
     def run(self):
         a = self.o.dir1.new_file("barfoo")
@@ -18,6 +23,22 @@
         self.o.dir2.mirror_directory(self.o.dir1.path,
                                      lambda x: True)
 
+        a = self.filtered.new_file("foo.log")
+        a.value = "xx"
+        try:
+            a = self.filtered.new_file("bar.xxx")
+            raise Exception("Filter does not work")
+        except RuntimeError as e:
+            pass # Everything is good
+
+        b = self.filtered.new_file("barfoo.log.gz", compressed=True)
+        b.value = "xx"
+
+        assert type(a) == File
+        assert type(b) == GzipFile
+
+
+
 if __name__ == "__main__":
     import shutil, sys,os
     experiment = SimpleExperiment()
@@ -27,6 +48,14 @@
     assert os.path.exists(experiment.o.dir2.path + "/barfoo")
     assert os.path.exists(experiment.o.dir2.path + "/tmpdir/foo")
 
+    N = Directory(experiment.path, "*.log*")
+    assert experiment.filtered.value == N.value
+    assert os.path.exists(experiment.path + "/foo.log")
+
+    contents = [x.value for x in N]
+    assert len(contents) == 2
+    assert contents[0] == contents[1], contents
+
     if dirname:
         shutil.rmtree(dirname)
-    print "success"
+    print("success")
diff -Nru python-versuchung-1.1/tests/event_log/test.py python-versuchung-1.3.3/tests/event_log/test.py
--- python-versuchung-1.1/tests/event_log/test.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/tests/event_log/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 from versuchung.experiment import Experiment
 from versuchung.events import EventLog
 
@@ -22,4 +24,4 @@
 
     if dirname:
         shutil.rmtree(dirname)
-    print "success"
+    print("success")
diff -Nru python-versuchung-1.1/tests/experiment_getattr/test.py python-versuchung-1.3.3/tests/experiment_getattr/test.py
--- python-versuchung-1.1/tests/experiment_getattr/test.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/tests/experiment_getattr/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,5 +1,7 @@
 #!/usr/bin/python
 
+from __future__ import print_function
+
 from versuchung.experiment import Experiment
 from versuchung.files import File
 from versuchung.types import String
@@ -34,5 +36,5 @@
 
     if dirname:
         shutil.rmtree(dirname)
-    print "success"
+    print("success")
 
diff -Nru python-versuchung-1.1/tests/file_output/test.py python-versuchung-1.3.3/tests/file_output/test.py
--- python-versuchung-1.1/tests/file_output/test.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/tests/file_output/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,11 +1,15 @@
+from __future__ import print_function
+
 from versuchung.experiment import Experiment
 from versuchung.types import String
-from versuchung.files import File
+from versuchung.files import File, Directory
+import os
 
 class SimpleExperiment(Experiment):
     inputs = {"input_key": String("default key"),
               "input_value": String("default value")}
-    outputs = {"output_file": File("output")}
+    outputs = {"output_file": File("output"),
+               "output_directory": Directory("output_directory")}
 
     def run(self):
         # Combine the input parameters
@@ -14,13 +18,16 @@
 
         # write the result to the output file
         self.outputs.output_file.value = content + "\n"
-
+        # New output directory
+        x = self.output_directory.new_directory("foo").new_file("lala")
 
 if __name__ == "__main__":
     import shutil, sys
     experiment = SimpleExperiment()
     dirname = experiment(sys.argv)
 
+    assert os.path.exists("%s/output_directory/foo/lala" % dirname)
+
     if dirname:
         shutil.rmtree(dirname)
-    print "success"
+    print("success")
diff -Nru python-versuchung-1.1/tests/git_archive/test.py python-versuchung-1.3.3/tests/git_archive/test.py
--- python-versuchung-1.1/tests/git_archive/test.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/tests/git_archive/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,11 +1,13 @@
 #!/usr/bin/python
 
+from __future__ import print_function
+
 from versuchung.experiment import Experiment
 from versuchung.archives import TarArchive, GitArchive
 import os
 
 class GitArchiveTest(Experiment):
-    inputs = {"git": GitArchive(TarArchive("origin.tar.gz")),
+    inputs = {"git":      GitArchive(TarArchive("origin.tar.gz")),
               "git_bare": GitArchive(TarArchive("origin.tar.gz"), shallow=True)
               }
 
@@ -20,7 +22,7 @@
             assert path == self.i.git.value.path
             assert os.path.abspath(os.curdir) == path
 
-        print "success"
+        print("success")
 
 
 if __name__ == "__main__":
@@ -28,4 +30,11 @@
     import shutil
     t = GitArchiveTest()
     dirname = t(sys.argv)
+
+    # Reinit of Git Archive must fail
+    reinit = GitArchiveTest(dirname)
+    assert reinit.inputs['git'] is None
+    assert reinit.inputs['git_bare'] is None
+
+
     shutil.rmtree(dirname)
diff -Nru python-versuchung-1.1/tests/gzip_file/test.py python-versuchung-1.3.3/tests/gzip_file/test.py
--- python-versuchung-1.1/tests/gzip_file/test.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/tests/gzip_file/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 from versuchung.experiment import Experiment
 from versuchung.archives import GzipFile
 from versuchung.files import Directory
@@ -9,7 +11,7 @@
     def run(self):
         with self.tmp_directory as d:
             assert self.tmp_directory.path in self.gz.path
-            assert self.gz.value.strip() == "CONTENT"
+            assert self.gz.value.strip() == "CONTENT", self.gz.value
 
             self.gz_out.value = "OUTPUT"
 
@@ -19,8 +21,8 @@
     experiment = SimpleExperiment()
     dirname = experiment(sys.argv)
 
-    assert len(open(experiment.gz_out.path).read()) > 0
+    assert len(open(experiment.gz_out.original_path, 'rb').read()) > 0
 
     if dirname:
         shutil.rmtree(dirname)
-    print "success"
+    print("success")
diff -Nru python-versuchung-1.1/tests/invalid_experiment/test.py python-versuchung-1.3.3/tests/invalid_experiment/test.py
--- python-versuchung-1.1/tests/invalid_experiment/test.py	1970-01-01 01:00:00.000000000 +0100
+++ python-versuchung-1.3.3/tests/invalid_experiment/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -0,0 +1,29 @@
+#!/usr/bin/python
+from __future__ import print_function
+import sys
+
+from versuchung.experiment import Experiment
+from versuchung.types import List, String
+from versuchung.files import File
+
+class OriginalExperiment(Experiment):
+    def run(self):
+        pass
+
+class TestExperiment(Experiment):
+    inputs = { 'experiment' : OriginalExperiment() }
+
+    def run(self):
+        pass
+
+
+if __name__ == "__main__":
+    experiment = TestExperiment()
+
+    try:
+        dirname = experiment(["--experiment", "Invalid"])
+    except:
+        print("success")
+        sys.exit(0)
+
+    assert False
diff -Nru python-versuchung-1.1/tests/list_parameter/test.py python-versuchung-1.3.3/tests/list_parameter/test.py
--- python-versuchung-1.1/tests/list_parameter/test.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/tests/list_parameter/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,10 +1,15 @@
+from __future__ import print_function
+
 from versuchung.experiment import Experiment
 from versuchung.types import String, List
 
 class SimpleExperiment(Experiment):
     inputs = {"strings": List(String(), default_value=[]),
               "default": List(String, default_value=[String("foo")]),
-              "default2": List(String, default_value=[String("foo")])}
+              "default2": List(String, default_value=[String("fox")]),
+              "default3": List(String, default_value=[String("a"), String("b")])
+    }
+
 
     def run(self):
         strings = [s.value for s in self.i.strings]
@@ -27,4 +32,4 @@
 
     if dirname:
         shutil.rmtree(dirname)
-    print "success"
+    print("success")
diff -Nru python-versuchung-1.1/tests/list_parameter_lambda/test.py python-versuchung-1.3.3/tests/list_parameter_lambda/test.py
--- python-versuchung-1.1/tests/list_parameter_lambda/test.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/tests/list_parameter_lambda/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 from versuchung.experiment import Experiment
 from versuchung.types import String, List
 from versuchung.files import File
@@ -57,4 +59,4 @@
 
     for dirname in dirs_to_del:
         shutil.rmtree(dirname)
-    print "success"
+    print("success")
diff -Nru python-versuchung-1.1/tests/machine_monitor/test.py python-versuchung-1.3.3/tests/machine_monitor/test.py
--- python-versuchung-1.1/tests/machine_monitor/test.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/tests/machine_monitor/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 from versuchung.experiment import Experiment
 from versuchung.execute import shell, MachineMonitor
 
@@ -14,10 +16,10 @@
     try:
         import psutil
         if not "phymem_usage" in dir(psutil):
-            print "skipped"
+            print("skipped")
             sys.exit(0)
     except:
-        print "skipped"
+        print("skipped")
         sys.exit(0)
 
     experiment = SimpleExperiment()
@@ -25,4 +27,4 @@
 
     if dirname:
         shutil.rmtree(dirname)
-    print "success"
+    print("success")
diff -Nru python-versuchung-1.1/tests/Makefile python-versuchung-1.3.3/tests/Makefile
--- python-versuchung-1.1/tests/Makefile	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/tests/Makefile	2018-08-22 17:23:14.000000000 +0200
@@ -1,19 +1,26 @@
 TESTS = $(patsubst ./%,%,$(shell find . -maxdepth 1 -mindepth 1 -type d))
 PHONY = $(TESTS)
 PWD   = $(shell pwd)
+PYTHON := python3
 
 check: $(TESTS)
 
 define test_cmd
-$(1): FORCE
-	@echo -n "Running test $(1)..."
-	@cd $(1); PYTHONPATH=$(PWD)/../src python test.py
+$(1): py2-$(1) py3-$(1)
+
+py2-$(1): FORCE
+	@echo -n "Running test python2: $(1)..."
+	@cd $(1); PYTHONPATH=$(PWD)/../src python2 test.py
+
+py3-$(1):
+	@echo -n "Running test python3: $(1)..."
+	@cd $(1); PYTHONPATH=$(PWD)/../src python3 test.py
 
 endef
 
 $(foreach test,$(TESTS),$(eval $(call test_cmd,$(test))))
 
-	
+
 FORCE:
 
 .PHONY: $(PHONY) FORCE
diff -Nru python-versuchung-1.1/tests/reinit_types/test.py python-versuchung-1.3.3/tests/reinit_types/test.py
--- python-versuchung-1.1/tests/reinit_types/test.py	1970-01-01 01:00:00.000000000 +0100
+++ python-versuchung-1.3.3/tests/reinit_types/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -0,0 +1,51 @@
+from __future__ import print_function
+
+from versuchung.experiment import Experiment
+from versuchung.types import String, Optional, Bool, List
+
+class ReinitTypesTest(Experiment):
+    inputs = {"string": String("A"),
+              "bool"  : Bool(True),
+              "string_optional": Optional(String()),
+              'list'  : List(String, [])}
+
+    def run(self):
+        assert self.string.value == "X"
+        assert self.bool.value == False
+        assert self.string_optional.value == None
+
+
+class DownstreamReinitTypesTest(Experiment):
+    inputs = {'reinit': ReinitTypesTest('ReinitTypesTest-Foobar') }
+
+    def run(self):
+        do_asserts(self.reinit)
+
+def do_asserts(reinit):
+    global t
+
+    for field in ('string', 'bool', 'string_optional'):
+        assert getattr(t, field).value == getattr(reinit, field).value,\
+            "Field %s not correctly reinitted" % field
+
+    assert [x.value for x in t.list.value] == [x.value for x in reinit.list.value], \
+        "Field list not correctly reinitted"
+
+
+if __name__ == "__main__":
+    import sys
+    import shutil
+    t = ReinitTypesTest()
+    dirname = t(["--string", "X", "--bool", "no", "--list", "a", "--list", "b"])
+
+    # Reinit without enclosing experiment
+    reinit = ReinitTypesTest(dirname)
+    do_asserts(reinit)
+
+    t2 = DownstreamReinitTypesTest()
+    dirname2 = t2(['--reinit', dirname])
+    reinit2 = DownstreamReinitTypesTest(dirname2)
+    do_asserts(reinit2.reinit)
+
+    shutil.rmtree(dirname)
+    print("success")
diff -Nru python-versuchung-1.1/tests/run_shell/test.py python-versuchung-1.3.3/tests/run_shell/test.py
--- python-versuchung-1.1/tests/run_shell/test.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/tests/run_shell/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,7 +1,12 @@
+from __future__ import print_function
+
 from versuchung.experiment import Experiment
 from versuchung.execute import shell, shell_failok, CommandFailed
 
 import sys
+import os
+
+experiment_file = os.path.abspath(__file__)
 
 class ShellExperiment(Experiment):
     def run(self):
@@ -21,12 +26,12 @@
 
         assert (['2 23'], 0) == shell("echo %(foo)s %(bar)s", {"foo": "2", "bar": "23"})
 
-        shell("cat %s", __file__)
+        shell("cat %s", experiment_file)
 
 if __name__ == "__main__":
     import shutil
     experiment = ShellExperiment()
     dirname = experiment(sys.argv)
-    print "success"
+    print("success")
     if dirname:
         shutil.rmtree(dirname)
diff -Nru python-versuchung-1.1/tests/search_experiments/test.py python-versuchung-1.3.3/tests/search_experiments/test.py
--- python-versuchung-1.1/tests/search_experiments/test.py	1970-01-01 01:00:00.000000000 +0100
+++ python-versuchung-1.3.3/tests/search_experiments/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -0,0 +1,35 @@
+#!/usr/bin/python
+
+from versuchung.experiment import Experiment
+from versuchung.types import *
+from versuchung.files import *
+from versuchung.files import Directory
+from versuchung.execute import *
+from versuchung.search import *
+import sys
+
+class Exp1(Experiment):
+    inputs = {"in": String("hello")}
+    outputs = {"out": CSV_File("results.csv", delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)}
+    def run(self):
+        pass
+
+class Exp2(Experiment):
+    inputs = {"inp": lambda self: search_experiment_results(Exp1, ".", selector=None)}
+    outputs = {"out": CSV_File("results.csv", delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)}
+    def run(self):
+        assert len(self.inp) == 1
+
+if __name__ == "__main__":
+    import sys
+
+    exp1 = Exp1()
+    dirname1 = exp1(sys.argv)
+
+    exp2 = Exp2()
+    dirname2 = exp2(sys.argv)
+
+    import shutil
+    shutil.rmtree(dirname1)
+    shutil.rmtree(dirname2)
+    print("success")
diff -Nru python-versuchung-1.1/tests/start_end_date/test.py python-versuchung-1.3.3/tests/start_end_date/test.py
--- python-versuchung-1.1/tests/start_end_date/test.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/tests/start_end_date/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 from versuchung.experiment import Experiment
 import time
 class SimpleExperiment(Experiment):
@@ -13,4 +15,4 @@
 
     if dirname:
         shutil.rmtree(dirname)
-    print "success"
+    print("success")
diff -Nru python-versuchung-1.1/tests/string_list/test.py python-versuchung-1.3.3/tests/string_list/test.py
--- python-versuchung-1.1/tests/string_list/test.py	1970-01-01 01:00:00.000000000 +0100
+++ python-versuchung-1.3.3/tests/string_list/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -0,0 +1,26 @@
+#!/usr/bin/python3
+from __future__ import print_function
+import shutil
+
+from versuchung.experiment import Experiment
+from versuchung.types import List, String
+from versuchung.files import File
+
+class TestExperiment(Experiment):
+    inputs = { 'stringlist' : List(String) }
+    outputs = { 'result' : File("result") }
+
+    def run(self):
+        for i in self.i.stringlist:
+            self.o.result.write(i.value)
+
+
+if __name__ == "__main__":
+    experiment = TestExperiment()
+
+    dirname = experiment(["--stringlist", "Hello world"])
+    with open("%s/result" % dirname) as fd:
+        assert fd.read() == "Hello world"
+
+    shutil.rmtree(dirname)
+    print("success")
diff -Nru python-versuchung-1.1/tests/tar_archive/test.py python-versuchung-1.3.3/tests/tar_archive/test.py
--- python-versuchung-1.1/tests/tar_archive/test.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/tests/tar_archive/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,5 +1,7 @@
 #!/usr/bin/python
 
+from __future__ import print_function
+
 from versuchung.experiment import Experiment
 from versuchung.archives import TarArchive
 
@@ -12,7 +14,7 @@
         assert len(directory.value) == 2
         assert "ABC" in directory.value
         assert "Hallo" in directory.value
-        print "success"
+        print("success")
 
 
 if __name__ == "__main__":
diff -Nru python-versuchung-1.1/tests/tex_output/test.py python-versuchung-1.3.3/tests/tex_output/test.py
--- python-versuchung-1.1/tests/tex_output/test.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/tests/tex_output/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 from versuchung.experiment import Experiment
 from versuchung.tex import *
 
@@ -40,4 +42,4 @@
     assert dref["foobar"] == "42"
 
     shutil.rmtree(dirname)
-    print "success"
+    print("success")
diff -Nru python-versuchung-1.1/tests/two_experiments/test.py python-versuchung-1.3.3/tests/two_experiments/test.py
--- python-versuchung-1.1/tests/two_experiments/test.py	2014-10-25 16:48:48.000000000 +0200
+++ python-versuchung-1.3.3/tests/two_experiments/test.py	2018-08-22 17:23:14.000000000 +0200
@@ -1,3 +1,5 @@
+from __future__ import print_function
+
 from versuchung.experiment import Experiment
 from versuchung.types import String
 from versuchung.files import File
@@ -31,6 +33,9 @@
     e1 = SimpleExperiment()
     r1 = e1([])
 
+    assert SimpleExperiment(r1).output_file.path == e1.output_file.path,\
+        "Default Constructor should set paths correct."
+
     e2 = SimpleExperiment2()
     r2 = e2(se=r1)
 
@@ -39,4 +44,4 @@
 
     if r2:
         shutil.rmtree(r2)
-    print "success"
+    print("success")

Reply via email to