Repository: mila-udem/fuel
Pull request #158 (2015-06-10 18:45:28)

Refactor fuel-convert output flag
Some converters can produce more than one format, and the current solution for dynamically assigning a default filename was getting awkward.

The way this is currently implemented is through a single default filename for all possible formats (e.g. `'svhn_format_{}.hdf5'`) which is formatted depending on the value of some command line arguments. This is awkward to maintain and prone to bugs.

This PR aims at solving this by allowing both file and directory names to be specified via the `-o` flag and using a custom `argparse.Action` subclass to split that into an `output_directory` and an (optional) `output_filename` argument. Converters now accept both arguments and can define a sane default filename, and they return the path pointing to the converted dataset.

jbornschein: Haven't looked in detail at the implementation yet -- but I think your approach makes a lot of sense. And from the users perspective it's even fully compatible with the current behavior.

jbornschein: 
I think it makes a lot of sense to let the user choose the output directory but not the actual filename. 

Would you simplify it in a way that a converter can create multiple datasets then? I don't think it's very important -- but it seems natural to allow it then.





rizar: +1 for only choosing the directory
vdumoulin: @jbornschein yes, in that case I think it would be straightforward to extend the logic to multiple files by changing the converter's return value from a string to a list of strings.

For convenience, you could add an `all` option to the subparser in your PR which loops over all formats and does the conversion.
vdumoulin: I've updated the code to allow multiple saved files. I think the PR would be ready for a final review, if anyone has other things to point out.

Patch:
diff --git a/bin/fuel-convert b/bin/fuel-convert
index a6f5e33..bf2aab0 100755
--- a/bin/fuel-convert
+++ b/bin/fuel-convert
@@ -10,6 +10,14 @@ from fuel.converters.base import MissingInputFiles
 from fuel.datasets import H5PYDataset
 
 
+class CheckDirectoryAction(argparse.Action):
+    def __call__(self, parser, namespace, values, option_string=None):
+        if os.path.isdir(values):
+            setattr(namespace, self.dest, values)
+        else:
+            raise ValueError('{} is not a existing directory'.format(values))
+
+
 if __name__ == "__main__":
     built_in_datasets = dict(converters.all_converters)
     parser = argparse.ArgumentParser(
@@ -24,9 +32,10 @@ if __name__ == "__main__":
             name, parents=[parent_parser],
             help='Convert the {} dataset'.format(name))
         subparser.add_argument(
-            "-o", "--output-file", help="where to save the dataset", type=str,
-            default=os.path.join(os.getcwd(), '{}.hdf5'.format(name)))
+            "-o", "--output-directory", help="where to save the dataset",
+            type=str, default=os.getcwd(), action=CheckDirectoryAction)
         subparser_fn(subparser)
+
     args = parser.parse_args()
     args_dict = vars(args)
     try:
@@ -35,21 +44,22 @@ if __name__ == "__main__":
         parser.print_usage()
         parser.exit()
     try:
-        func(**args_dict)
+        output_paths = func(**args_dict)
     except MissingInputFiles as e:
         intro = "The following required files were not found:\n"
         message = "\n".join([intro] + ["   * " + f for f in e.filenames])
         message += "\n\nDid you forget to run fuel-download?"
         parser.error(message)
 
-    # Tag the newly-created file with H5PYDataset version and command-line
+    # Tag the newly-created file(s) with H5PYDataset version and command-line
     # options
-    h5file = h5py.File(args.output_file, 'a')
-    interface_version = H5PYDataset.interface_version.encode('utf-8')
-    h5file.attrs['h5py_interface_version'] = interface_version
-    fuel_convert_version = converters.__version__.encode('utf-8')
-    h5file.attrs['fuel_convert_version'] = fuel_convert_version
-    command = [os.path.basename(sys.argv[0])] + sys.argv[1:]
-    h5file.attrs['fuel_convert_command'] = ' '.join(command).encode('utf-8')
-    h5file.flush()
-    h5file.close()
+    for output_path in output_paths:
+        h5file = h5py.File(output_path, 'a')
+        interface_version = H5PYDataset.interface_version.encode('utf-8')
+        h5file.attrs['h5py_interface_version'] = interface_version
+        fuel_convert_version = converters.__version__.encode('utf-8')
+        h5file.attrs['fuel_convert_version'] = fuel_convert_version
+        command = [os.path.basename(sys.argv[0])] + sys.argv[1:]
+        h5file.attrs['fuel_convert_command'] = ' '.join(command).encode('utf-8')
+        h5file.flush()
+        h5file.close()
diff --git a/docs/new_dataset.rst b/docs/new_dataset.rst
index f018f21..fdab37e 100644
--- a/docs/new_dataset.rst
+++ b/docs/new_dataset.rst
@@ -115,7 +115,10 @@ very similar to ``fuel-download``. The arguments to be aware of in the subparser
 are
 
 * ``directory`` : in which directory the input files reside
-* ``output-file`` : where to save the converted dataset
+* ``output-directory`` : where to save the converted dataset
+
+The converter function should return a tuple containing path(s) to the converted
+dataset(s).
 
 Put the following piece of code inside the ``fuel.converters.iris``
 module (you'll have to create it):
@@ -131,8 +134,9 @@ module (you'll have to create it):
     from fuel.converters.base import fill_hdf5_file
 
 
-    def convert_iris(directory, output_file):
-        h5file = h5py.File(output_file, mode='w')
+    def convert_iris(directory, output_directory, output_filename='iris.hdf5'):
+        output_path = os.path.join(output_directory, output_filename)
+        h5file = h5py.File(output_path, mode='w')
         classes = {'Iris-setosa': 0, 'Iris-versicolor': 1, 'Iris-virginica': 2}
         data = numpy.loadtxt(
             os.path.join(directory, 'iris.data'),
@@ -160,6 +164,8 @@ module (you'll have to create it):
         h5file.flush()
         h5file.close()
 
+        return (output_path,)
+
     def fill_subparser(subparser):
         subparser.set_defaults(func=convert_iris)
 
@@ -288,8 +294,9 @@ You can now use the Iris dataset like you would use any other built-in dataset:
     >>> import h5py
     >>> import numpy
     >>> from fuel.converters.base import fill_hdf5_file
-    >>> def iris_converter(directory, output_file):
-    ...     h5file = h5py.File(output_file, mode='w')
+    >>> def iris_converter(directory, output_directory, output_filename='iris.hdf5'):
+    ...     output_path = os.path.join(output_directory, output_filename)
+    ...     h5file = h5py.File(output_path, mode='w')
     ...     classes = {b'Iris-setosa': 0, b'Iris-versicolor': 1, b'Iris-virginica': 2}
     ...     data = numpy.loadtxt(
     ...         os.path.join(directory, 'iris.data'),
@@ -315,17 +322,19 @@ You can now use the Iris dataset like you would use any other built-in dataset:
     ...     h5file['targets'].dims[1].label = 'index'
     ...     h5file.flush()
     ...     h5file.close()
+    ...     return (output_path,)
     >>> def fill_converter_subparser(subparser):
     ...     subparser.set_defaults(func=iris_converter)
     >>> parser = argparse.ArgumentParser()
     >>> __ = parser.add_argument("--directory", type=str, default=os.getcwd())
-    >>> __ = parser.add_argument("--output-file", type=str, default='iris.hdf5')
+    >>> __ = parser.add_argument("--output-directory", type=str,
+    ...                          default=os.getcwd())
     >>> subparsers = parser.add_subparsers()
     >>> fill_converter_subparser(subparsers.add_parser('iris'))
     >>> args = parser.parse_args(['iris'])
     >>> args_dict = vars(args)
     >>> func = args_dict.pop('func')
-    >>> func(**args_dict)
+    >>> output_paths = func(**args_dict)
     >>> os.remove('iris.data')
 
 .. doctest::
diff --git a/fuel/converters/__init__.py b/fuel/converters/__init__.py
index 27866ac..259aceb 100644
--- a/fuel/converters/__init__.py
+++ b/fuel/converters/__init__.py
@@ -16,7 +16,7 @@ from fuel.converters import cifar100
 from fuel.converters import mnist
 from fuel.converters import svhn
 
-__version__ = '0.1'
+__version__ = '0.2'
 all_converters = (
     ('binarized_mnist', binarized_mnist.fill_subparser),
     ('cifar10', cifar10.fill_subparser),
diff --git a/fuel/converters/binarized_mnist.py b/fuel/converters/binarized_mnist.py
index 087a0f4..7a77292 100644
--- a/fuel/converters/binarized_mnist.py
+++ b/fuel/converters/binarized_mnist.py
@@ -14,7 +14,8 @@ ALL_FILES = [TRAIN_FILE, VALID_FILE, TEST_FILE]
 
 
 @check_exists(required_files=ALL_FILES)
-def convert_binarized_mnist(directory, output_file):
+def convert_binarized_mnist(directory, output_directory,
+                            output_filename='binarized_mnist.hdf5'):
     """Converts the binarized MNIST dataset to HDF5.
 
     Converts the binarized MNIST dataset used in R. Salakhutdinov's DBN
@@ -37,11 +38,19 @@ def convert_binarized_mnist(directory, output_file):
     ----------
     directory : str
         Directory in which input files reside.
-    output_file : str
-        Where to save the converted dataset.
+    output_directory : str
+        Directory in which to save the converted dataset.
+    output_filename : str, optional
+        Name of the saved dataset. Defaults to 'binarized_mnist.hdf5'.
+
+    Returns
+    -------
+    output_paths : tuple of str
+        Single-element tuple containing the path to the converted dataset.
 
     """
-    h5file = h5py.File(output_file, mode='w')
+    output_path = os.path.join(output_directory, output_filename)
+    h5file = h5py.File(output_path, mode='w')
 
     train_set = numpy.loadtxt(
         os.path.join(directory, TRAIN_FILE)).reshape(
@@ -62,6 +71,8 @@ def convert_binarized_mnist(directory, output_file):
     h5file.flush()
     h5file.close()
 
+    return (output_path,)
+
 
 def fill_subparser(subparser):
     """Sets up a subparser to convert the binarized MNIST dataset files.
diff --git a/fuel/converters/cifar10.py b/fuel/converters/cifar10.py
index 3c567ba..9aaa177 100644
--- a/fuel/converters/cifar10.py
+++ b/fuel/converters/cifar10.py
@@ -12,7 +12,8 @@ DISTRIBUTION_FILE = 'cifar-10-python.tar.gz'
 
 
 @check_exists(required_files=[DISTRIBUTION_FILE])
-def convert_cifar10(directory, output_file):
+def convert_cifar10(directory, output_directory,
+                    output_filename='cifar10.hdf5'):
     """Converts the CIFAR-10 dataset to HDF5.
 
     Converts the CIFAR-10 dataset to an HDF5 dataset compatible with
@@ -27,11 +28,19 @@ def convert_cifar10(directory, output_file):
     ----------
     directory : str
         Directory in which input files reside.
-    output_file : str
-        Where to save the converted dataset.
+    output_directory : str
+        Directory in which to save the converted dataset.
+    output_filename : str, optional
+        Name of the saved dataset. Defaults to 'cifar10.hdf5'.
+
+    Returns
+    -------
+    output_paths : tuple of str
+        Single-element tuple containing the path to the converted dataset.
 
     """
-    h5file = h5py.File(output_file, mode='w')
+    output_path = os.path.join(output_directory, output_filename)
+    h5file = h5py.File(output_path, mode='w')
     input_file = os.path.join(directory, DISTRIBUTION_FILE)
     tar_file = tarfile.open(input_file, 'r:gz')
 
@@ -85,6 +94,8 @@ def convert_cifar10(directory, output_file):
     h5file.flush()
     h5file.close()
 
+    return (output_path,)
+
 
 def fill_subparser(subparser):
     """Sets up a subparser to convert the CIFAR10 dataset files.
diff --git a/fuel/converters/cifar100.py b/fuel/converters/cifar100.py
index 4ff549d..23bcbc1 100644
--- a/fuel/converters/cifar100.py
+++ b/fuel/converters/cifar100.py
@@ -12,7 +12,8 @@ DISTRIBUTION_FILE = 'cifar-100-python.tar.gz'
 
 
 @check_exists(required_files=[DISTRIBUTION_FILE])
-def convert_cifar100(directory, output_file):
+def convert_cifar100(directory, output_directory,
+                     output_filename='cifar100.hdf5'):
     """Converts the CIFAR-100 dataset to HDF5.
 
     Converts the CIFAR-100 dataset to an HDF5 dataset compatible with
@@ -26,11 +27,19 @@ def convert_cifar100(directory, output_file):
     ----------
     directory : str
         Directory in which the required input files reside.
-    output_file : str
-        Where to save the converted dataset.
+    output_directory : str
+        Directory in which to save the converted dataset.
+    output_filename : str, optional
+        Name of the saved dataset. Defaults to 'cifar100.hdf5'.
+
+    Returns
+    -------
+    output_paths : tuple of str
+        Single-element tuple containing the path to the converted dataset.
 
     """
-    h5file = h5py.File(output_file, mode="w")
+    output_path = os.path.join(output_directory, output_filename)
+    h5file = h5py.File(output_path, mode="w")
     input_file = os.path.join(directory, 'cifar-100-python.tar.gz')
     tar_file = tarfile.open(input_file, 'r:gz')
 
@@ -83,6 +92,8 @@ def convert_cifar100(directory, output_file):
     h5file.flush()
     h5file.close()
 
+    return (output_path,)
+
 
 def fill_subparser(subparser):
     """Sets up a subparser to convert the CIFAR100 dataset files.
diff --git a/fuel/converters/mnist.py b/fuel/converters/mnist.py
index d5e83dd..4f4d3ab 100644
--- a/fuel/converters/mnist.py
+++ b/fuel/converters/mnist.py
@@ -19,7 +19,8 @@ ALL_FILES = [TRAIN_IMAGES, TRAIN_LABELS, TEST_IMAGES, TEST_LABELS]
 
 
 @check_exists(required_files=ALL_FILES)
-def convert_mnist(directory, output_file, dtype=None):
+def convert_mnist(directory, output_directory, output_filename=None,
+                  dtype=None):
     """Converts the MNIST dataset to HDF5.
 
     Converts the MNIST dataset to an HDF5 dataset compatible with
@@ -41,15 +42,29 @@ def convert_mnist(directory, output_file, dtype=None):
     ----------
     directory : str
         Directory in which input files reside.
-    output_file : str
-        Where to save the converted dataset.
+    output_directory : str
+        Directory in which to save the converted dataset.
+    output_filename : str, optional
+        Name of the saved dataset. Defaults to `None`, in which case a name
+        based on `dtype` will be used.
     dtype : str, optional
         Either 'float32', 'float64', or 'bool'. Defaults to `None`,
         in which case images will be returned in their original
         unsigned byte format.
 
+    Returns
+    -------
+    output_paths : tuple of str
+        Single-element tuple containing the path to the converted dataset.
+
     """
-    h5file = h5py.File(output_file, mode='w')
+    if not output_filename:
+        if dtype:
+            output_filename = 'mnist_{}.hdf5'.format(dtype)
+        else:
+            output_filename = 'mnist.hdf5'
+    output_path = os.path.join(output_directory, output_filename)
+    h5file = h5py.File(output_path, mode='w')
 
     train_feat_path = os.path.join(directory, TRAIN_IMAGES)
     train_features = read_mnist_images(train_feat_path, dtype)
@@ -74,6 +89,8 @@ def convert_mnist(directory, output_file, dtype=None):
     h5file.flush()
     h5file.close()
 
+    return (output_path,)
+
 
 def fill_subparser(subparser):
     """Sets up a subparser to convert the MNIST dataset files.
diff --git a/fuel/converters/svhn.py b/fuel/converters/svhn.py
index 605fbf8..1aa9e5d 100644
--- a/fuel/converters/svhn.py
+++ b/fuel/converters/svhn.py
@@ -22,7 +22,8 @@ FORMAT_2_TRAIN_FILE, FORMAT_2_TEST_FILE, FORMAT_2_EXTRA_FILE = FORMAT_2_FILES
 
 
 @check_exists(required_files=FORMAT_1_FILES)
-def convert_svhn_format_1(directory, output_file):
+def convert_svhn_format_1(directory, output_directory,
+                          output_filename='svhn_format_1.hdf5'):
     """Converts the SVHN dataset (format 1) to HDF5.
 
     This method assumes the existence of the files
@@ -35,12 +36,20 @@ def convert_svhn_format_1(directory, output_file):
     ----------
     directory : str
         Directory in which input files reside.
-    output_file : str
-        Where to save the converted dataset.
+    output_directory : str
+        Directory in which to save the converted dataset.
+    output_filename : str, optional
+        Name of the saved dataset. Defaults to 'svhn_format_1.hdf5'.
+
+    Returns
+    -------
+    output_paths : tuple of str
+        Single-element tuple containing the path to the converted dataset.
 
     """
     try:
-        h5file = h5py.File(output_file, mode='w')
+        output_path = os.path.join(output_directory, output_filename)
+        h5file = h5py.File(output_path, mode='w')
         TMPDIR = tempfile.mkdtemp()
 
         # Every image has three channels (RGB) and variable height and width.
@@ -249,9 +258,12 @@ def convert_svhn_format_1(directory, output_file):
         h5file.flush()
         h5file.close()
 
+    return (output_path,)
+
 
 @check_exists(required_files=FORMAT_2_FILES)
-def convert_svhn_format_2(directory, output_file):
+def convert_svhn_format_2(directory, output_directory,
+                          output_filename='svhn_format_2.hdf5'):
     """Converts the SVHN dataset (format 2) to HDF5.
 
     This method assumes the existence of the files
@@ -264,11 +276,19 @@ def convert_svhn_format_2(directory, output_file):
     ----------
     directory : str
         Directory in which input files reside.
-    output_file : str
-        Where to save the converted dataset.
+    output_directory : str
+        Directory in which to save the converted dataset.
+    output_filename : str, optional
+        Name of the saved dataset. Defaults to 'svhn_format_2.hdf5'.
+
+    Returns
+    -------
+    output_paths : tuple of str
+        Single-element tuple containing the path to the converted dataset.
 
     """
-    h5file = h5py.File(output_file, mode='w')
+    output_path = os.path.join(output_directory, output_filename)
+    h5file = h5py.File(output_path, mode='w')
 
     train_set = loadmat(os.path.join(directory, FORMAT_2_TRAIN_FILE))
     train_features = train_set['X'].transpose(3, 2, 0, 1)
@@ -297,8 +317,11 @@ def convert_svhn_format_2(directory, output_file):
     h5file.flush()
     h5file.close()
 
+    return (output_path,)
 
-def convert_svhn(which_format, directory, output_file):
+
+def convert_svhn(which_format, directory, output_directory,
+                 output_filename=None):
     """Converts the SVHN dataset to HDF5.
 
     Converts the SVHN dataset [SVHN] to an HDF5 dataset compatible
@@ -318,17 +341,28 @@ def convert_svhn(which_format, directory, output_file):
         or format 2: cropped digits) to convert.
     directory : str
         Directory in which input files reside.
-    output_file : str
-        Where to save the converted dataset.
+    output_directory : str
+        Directory in which to save the converted dataset.
+    output_filename : str, optional
+        Name of the saved dataset. Defaults to 'svhn_format_1.hdf5' or
+        'svhn_format_2.hdf5', depending on `which_format`.
+
+    Returns
+    -------
+    output_paths : tuple of str
+        Single-element tuple containing the path to the converted dataset.
 
     """
     if which_format not in (1, 2):
         raise ValueError("SVHN format needs to be either 1 or 2.")
-    output_file = output_file.format(which_format)
+    if not output_filename:
+        output_filename = 'svhn_format_{}.hdf5'.format(which_format)
     if which_format == 1:
-        convert_svhn_format_1(directory, output_file)
+        return convert_svhn_format_1(
+            directory, output_directory, output_filename)
     else:
-        convert_svhn_format_2(directory, output_file)
+        return convert_svhn_format_2(
+            directory, output_directory, output_filename)
 
 
 def fill_subparser(subparser):
@@ -342,6 +376,4 @@ def fill_subparser(subparser):
     """
     subparser.add_argument(
         "which_format", help="which dataset format", type=int, choices=(1, 2))
-    subparser.set_defaults(
-        func=convert_svhn,
-        output_file=os.path.join(os.getcwd(), 'svhn_format_{}.hdf5'))
+    subparser.set_defaults(func=convert_svhn)


Test patch:
diff --git a/tests/test_converters.py b/tests/test_converters.py
index f558c35..0e94617 100644
--- a/tests/test_converters.py
+++ b/tests/test_converters.py
@@ -132,17 +132,17 @@ class TestMNIST(object):
         shutil.rmtree(self.tempdir)
 
     def test_converter(self):
-        filename = os.path.join(self.tempdir, 'mock_mnist.hdf5')
         parser = argparse.ArgumentParser()
         subparsers = parser.add_subparsers()
         subparser = subparsers.add_parser('mnist')
         subparser.set_defaults(
-            directory=self.tempdir, output_file=filename)
+            directory=self.tempdir, output_directory=self.tempdir,
+            output_filename='mock_mnist.hdf5')
         mnist.fill_subparser(subparser)
         args = parser.parse_args(['mnist'])
         args_dict = vars(args)
         func = args_dict.pop('func')
-        func(**args_dict)
+        filename, = func(**args_dict)
         h5file = h5py.File(filename, mode='r')
         assert_equal(
             h5file['features'][...],
@@ -158,6 +158,32 @@ class TestMNIST(object):
         assert_equal(tuple(dim.label for dim in h5file['targets'].dims),
                      ('batch', 'index'))
 
+    def test_converter_no_filename_no_dtype(self):
+        parser = argparse.ArgumentParser()
+        subparsers = parser.add_subparsers()
+        subparser = subparsers.add_parser('mnist')
+        subparser.set_defaults(
+            directory=self.tempdir, output_directory=self.tempdir)
+        mnist.fill_subparser(subparser)
+        args = parser.parse_args(['mnist'])
+        args_dict = vars(args)
+        func = args_dict.pop('func')
+        filename, = func(**args_dict)
+        assert_equal(os.path.basename(filename), 'mnist.hdf5')
+
+    def test_converter_no_filename(self):
+        parser = argparse.ArgumentParser()
+        subparsers = parser.add_subparsers()
+        subparser = subparsers.add_parser('mnist')
+        subparser.set_defaults(
+            directory=self.tempdir, output_directory=self.tempdir)
+        mnist.fill_subparser(subparser)
+        args = parser.parse_args(['mnist', '--dtype', 'bool'])
+        args_dict = vars(args)
+        func = args_dict.pop('func')
+        filename, = func(**args_dict)
+        assert_equal(os.path.basename(filename), 'mnist_bool.hdf5')
+
     def test_wrong_image_magic(self):
         assert_raises(
             ValueError, mnist.read_mnist_images, self.wrong_images_path)
@@ -201,16 +227,17 @@ class TestBinarizedMNIST(object):
         shutil.rmtree(self.tempdir)
 
     def test_converter(self):
-        filename = os.path.join(self.tempdir, 'mock_binarized_mnist.hdf5')
         parser = argparse.ArgumentParser()
         subparsers = parser.add_subparsers()
         subparser = subparsers.add_parser('binarized_mnist')
-        subparser.set_defaults(directory=self.tempdir, output_file=filename)
+        subparser.set_defaults(
+            directory=self.tempdir, output_directory=self.tempdir,
+            output_filename='mock_binarized_mnist.hdf5')
         binarized_mnist.fill_subparser(subparser)
         args = parser.parse_args(['binarized_mnist'])
         args_dict = vars(args)
         func = args_dict.pop('func')
-        func(**args_dict)
+        filename, = func(**args_dict)
         h5file = h5py.File(filename, mode='r')
         assert_equal(h5file['features'][...],
                      numpy.vstack([self.train_mock, self.valid_mock,
@@ -256,16 +283,17 @@ class TestCIFAR10(object):
         shutil.rmtree(self.tempdir)
 
     def test_converter(self):
-        filename = os.path.join(self.tempdir, 'mock_cifar10.hdf5')
         parser = argparse.ArgumentParser()
         subparsers = parser.add_subparsers()
         subparser = subparsers.add_parser('cifar10')
-        subparser.set_defaults(directory=self.tempdir, output_file=filename)
+        subparser.set_defaults(
+            directory=self.tempdir, output_directory=self.tempdir,
+            output_filename='mock_cifar10.hdf5')
         cifar10.fill_subparser(subparser)
         args = parser.parse_args(['cifar10'])
         args_dict = vars(args)
         func = args_dict.pop('func')
-        func(**args_dict)
+        filename, = func(**args_dict)
         h5file = h5py.File(filename, mode='r')
         assert_equal(
             h5file['features'][...],
@@ -320,16 +348,17 @@ class TestCIFAR100(object):
         shutil.rmtree(self.tempdir)
 
     def test_converter(self):
-        filename = os.path.join(self.tempdir, 'mock_cifar100.hdf5')
         parser = argparse.ArgumentParser()
         subparsers = parser.add_subparsers()
         subparser = subparsers.add_parser('cifar100')
-        subparser.set_defaults(directory=self.tempdir, output_file=filename)
+        subparser.set_defaults(
+            directory=self.tempdir, output_directory=self.tempdir,
+            output_filename='mock_cifar100.hdf5')
         cifar100.fill_subparser(subparser)
         args = parser.parse_args(['cifar100'])
         args_dict = vars(args)
         func = args_dict.pop('func')
-        func(**args_dict)
+        filename, = func(**args_dict)
         h5file = h5py.File(filename, mode='r')
         assert_equal(
             h5file['features'][...],
@@ -427,16 +456,17 @@ class TestSVHN(object):
         shutil.rmtree(self.tempdir)
 
     def test_format_1_converter(self):
-        filename = os.path.join(self.tempdir, 'svhn_format_1.hdf5')
         parser = argparse.ArgumentParser()
         subparsers = parser.add_subparsers()
         subparser = subparsers.add_parser('svhn')
         svhn.fill_subparser(subparser)
-        subparser.set_defaults(directory=self.tempdir, output_file=filename)
+        subparser.set_defaults(
+            directory=self.tempdir, output_directory=self.tempdir,
+            output_filename='svhn_format_1.hdf5')
         args = parser.parse_args(['svhn', '1'])
         args_dict = vars(args)
         func = args_dict.pop('func')
-        func(**args_dict)
+        filename, = func(**args_dict)
         h5file = h5py.File(filename, mode='r')
 
         expected_features = sum((self.f1_mock[split]['image']
@@ -456,16 +486,17 @@ class TestSVHN(object):
             assert_equal(val, truth)
 
     def test_format_2_converter(self):
-        filename = os.path.join(self.tempdir, 'svhn_format_2.hdf5')
         parser = argparse.ArgumentParser()
         subparsers = parser.add_subparsers()
         subparser = subparsers.add_parser('svhn')
         svhn.fill_subparser(subparser)
-        subparser.set_defaults(directory=self.tempdir, output_file=filename)
+        subparser.set_defaults(
+            directory=self.tempdir, output_directory=self.tempdir,
+            output_filename='svhn_format_2.hdf5')
         args = parser.parse_args(['svhn', '2'])
         args_dict = vars(args)
         func = args_dict.pop('func')
-        func(**args_dict)
+        filename, = func(**args_dict)
         h5file = h5py.File(filename, mode='r')
         assert_equal(
             h5file['features'][...],
@@ -485,14 +516,10 @@ class TestSVHN(object):
                      ('batch', 'index'))
 
     @mock.patch('fuel.converters.svhn.convert_svhn_format_1')
-    def test_converter_call_format_1(self, mock_converter_format_1):
-        svhn.convert_svhn(1, './', 'svhn_format_{}.hdf5')
-        mock_converter_format_1.assert_called_with('./', 'svhn_format_1.hdf5')
-
-    @mock.patch('fuel.converters.svhn.convert_svhn_format_2')
-    def test_converter_call_format_2(self, mock_converter_format_2):
-        svhn.convert_svhn(2, './', 'svhn_format_{}.hdf5')
-        mock_converter_format_2.assert_called_with('./', 'svhn_format_2.hdf5')
+    def test_converter_default_filename(self, mock_converter_format_1):
+        svhn.convert_svhn(1, './', './')
+        mock_converter_format_1.assert_called_with(
+            './', './', 'svhn_format_1.hdf5')
 
     def test_converter_error_wrong_format(self):
         assert_raises(ValueError, svhn.convert_svhn, 3, './', 'mock.hdf5')