We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Crash loading parallel inputs with --data_dir
I found the next issue if I follow the tutorial and try to do

data:
  train_features_file:
    - train_source_1.records
    - train_source_2.txt
    - train_source_3.txt

in main.py at the method _prefix_paths
new_path = os.path.join(prefix, path) 
will crash because paths is a list and join can't be done on a list.

The fix should be just check the instance type at paths and iterate

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `opennmt/bin/main.py`
Content:
```
1 """Main script."""
2 
3 import argparse
4 import json
5 import os
6 import six
7 
8 import tensorflow as tf
9 
10 from opennmt.models import catalog
11 from opennmt.runner import Runner
12 from opennmt.config import load_model, load_config
13 from opennmt.utils.misc import classes_in_module
14 
15 
16 def _prefix_paths(prefix, paths):
17   """Recursively prefix paths.
18 
19   Args:
20     prefix: The prefix to apply.
21     data: A dict of relative paths.
22 
23   Returns:
24     The updated dict.
25   """
26   if isinstance(paths, dict):
27     for key, path in six.iteritems(paths):
28       paths[key] = _prefix_paths(prefix, path)
29     return paths
30   else:
31     path = paths
32     new_path = os.path.join(prefix, path)
33     if os.path.isfile(new_path):
34       return new_path
35     else:
36       return path
37 
38 def main():
39   parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
40   parser.add_argument("run",
41                       choices=["train_and_eval", "train", "eval", "infer", "export", "score"],
42                       help="Run type.")
43   parser.add_argument("--config", required=True, nargs="+",
44                       help="List of configuration files.")
45   parser.add_argument("--model_type", default="", choices=list(classes_in_module(catalog)),
46                       help="Model type from the catalog.")
47   parser.add_argument("--model", default="",
48                       help="Custom model configuration file.")
49   parser.add_argument("--run_dir", default="",
50                       help="If set, model_dir will be created relative to this location.")
51   parser.add_argument("--data_dir", default="",
52                       help="If set, data files are expected to be relative to this location.")
53   parser.add_argument("--features_file", default=[], nargs="+",
54                       help="Run inference on this file.")
55   parser.add_argument("--predictions_file", default="",
56                       help=("File used to save predictions. If not set, predictions are printed "
57                             "on the standard output."))
58   parser.add_argument("--log_prediction_time", default=False, action="store_true",
59                       help="Logs some prediction time metrics.")
60   parser.add_argument("--checkpoint_path", default=None,
61                       help=("Checkpoint or directory to use for inference or export "
62                             "(when a directory is set, the latest checkpoint is used)."))
63   parser.add_argument("--num_gpus", type=int, default=1,
64                       help="Number of GPUs to use for in-graph replication.")
65   parser.add_argument("--chief_host", default="",
66                       help="hostname:port of the chief worker (for distributed training).")
67   parser.add_argument("--worker_hosts", default="",
68                       help=("Comma-separated list of hostname:port of workers "
69                             "(for distributed training)."))
70   parser.add_argument("--ps_hosts", default="",
71                       help=("Comma-separated list of hostname:port of parameter servers "
72                             "(for distributed training)."))
73   parser.add_argument("--task_type", default="chief",
74                       choices=["chief", "worker", "ps", "evaluator"],
75                       help="Type of the task to run (for distributed training).")
76   parser.add_argument("--task_index", type=int, default=0,
77                       help="ID of the task (for distributed training).")
78   parser.add_argument("--log_level", default="INFO",
79                       choices=["DEBUG", "ERROR", "FATAL", "INFO", "WARN"],
80                       help="Logs verbosity.")
81   parser.add_argument("--seed", type=int, default=None,
82                       help="Random seed.")
83   parser.add_argument("--gpu_allow_growth", default=False, action="store_true",
84                       help="Allocate GPU memory dynamically.")
85   parser.add_argument("--intra_op_parallelism_threads", type=int, default=0,
86                       help=("Number of intra op threads (0 means the system picks "
87                             "an appropriate number)."))
88   parser.add_argument("--inter_op_parallelism_threads", type=int, default=0,
89                       help=("Number of inter op threads (0 means the system picks "
90                             "an appropriate number)."))
91   args = parser.parse_args()
92 
93   tf.logging.set_verbosity(getattr(tf.logging, args.log_level))
94 
95   # Setup cluster if defined.
96   if args.chief_host:
97     os.environ["TF_CONFIG"] = json.dumps({
98         "cluster": {
99             "chief": [args.chief_host],
100             "worker": args.worker_hosts.split(","),
101             "ps": args.ps_hosts.split(",")
102         },
103         "task": {
104             "type": args.task_type,
105             "index": args.task_index
106         }
107     })
108 
109   # Load and merge run configurations.
110   config = load_config(args.config)
111   if args.run_dir:
112     config["model_dir"] = os.path.join(args.run_dir, config["model_dir"])
113   if args.data_dir:
114     config["data"] = _prefix_paths(args.data_dir, config["data"])
115 
116   if not os.path.isdir(config["model_dir"]):
117     tf.logging.info("Creating model directory %s", config["model_dir"])
118     os.makedirs(config["model_dir"])
119 
120   model = load_model(config["model_dir"], model_file=args.model, model_name=args.model_type)
121   session_config = tf.ConfigProto(
122       intra_op_parallelism_threads=args.intra_op_parallelism_threads,
123       inter_op_parallelism_threads=args.inter_op_parallelism_threads)
124   runner = Runner(
125       model,
126       config,
127       seed=args.seed,
128       num_devices=args.num_gpus,
129       gpu_allow_growth=args.gpu_allow_growth,
130       session_config=session_config)
131 
132   if args.run == "train_and_eval":
133     runner.train_and_evaluate()
134   elif args.run == "train":
135     runner.train()
136   elif args.run == "eval":
137     runner.evaluate(checkpoint_path=args.checkpoint_path)
138   elif args.run == "infer":
139     if not args.features_file:
140       parser.error("--features_file is required for inference.")
141     elif len(args.features_file) == 1:
142       args.features_file = args.features_file[0]
143     runner.infer(
144         args.features_file,
145         predictions_file=args.predictions_file,
146         checkpoint_path=args.checkpoint_path,
147         log_time=args.log_prediction_time)
148   elif args.run == "export":
149     runner.export(checkpoint_path=args.checkpoint_path)
150   elif args.run == "score":
151     if not args.features_file:
152       parser.error("--features_file is required for scoring.")
153     if not args.predictions_file:
154       parser.error("--predictions_file is required for scoring.")
155     runner.score(
156         args.features_file,
157         args.predictions_file,
158         checkpoint_path=args.checkpoint_path)
159 
160 
161 if __name__ == "__main__":
162   main()
163
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/opennmt/bin/main.py b/opennmt/bin/main.py
--- a/opennmt/bin/main.py
+++ b/opennmt/bin/main.py
@@ -27,6 +27,10 @@
     for key, path in six.iteritems(paths):
       paths[key] = _prefix_paths(prefix, path)
     return paths
+  elif isinstance(paths, list):
+    for i, path in enumerate(paths):
+      paths[i] = _prefix_paths(prefix, path)
+    return paths
   else:
     path = paths
     new_path = os.path.join(prefix, path)

{"golden_diff": "diff --git a/opennmt/bin/main.py b/opennmt/bin/main.py\n--- a/opennmt/bin/main.py\n+++ b/opennmt/bin/main.py\n@@ -27,6 +27,10 @@\n     for key, path in six.iteritems(paths):\n       paths[key] = _prefix_paths(prefix, path)\n     return paths\n+  elif isinstance(paths, list):\n+    for i, path in enumerate(paths):\n+      paths[i] = _prefix_paths(prefix, path)\n+    return paths\n   else:\n     path = paths\n     new_path = os.path.join(prefix, path)\n", "issue": "Crash loading parallel inputs with --data_dir\nI found the next issue if I follow the tutorial and try to do\r\n\r\ndata:\r\n  train_features_file:\r\n    - train_source_1.records\r\n    - train_source_2.txt\r\n    - train_source_3.txt\r\n\r\nin main.py at the method _prefix_paths\r\nnew_path = os.path.join(prefix, path) \r\nwill crash because paths is a list and join can't be done on a list.\r\n\r\nThe fix should be just check the instance type at paths and iterate\n", "before_files": [{"content": "\"\"\"Main script.\"\"\"\n\nimport argparse\nimport json\nimport os\nimport six\n\nimport tensorflow as tf\n\nfrom opennmt.models import catalog\nfrom opennmt.runner import Runner\nfrom opennmt.config import load_model, load_config\nfrom opennmt.utils.misc import classes_in_module\n\n\ndef _prefix_paths(prefix, paths):\n  \"\"\"Recursively prefix paths.\n\n  Args:\n    prefix: The prefix to apply.\n    data: A dict of relative paths.\n\n  Returns:\n    The updated dict.\n  \"\"\"\n  if isinstance(paths, dict):\n    for key, path in six.iteritems(paths):\n      paths[key] = _prefix_paths(prefix, path)\n    return paths\n  else:\n    path = paths\n    new_path = os.path.join(prefix, path)\n    if os.path.isfile(new_path):\n      return new_path\n    else:\n      return path\n\ndef main():\n  parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)\n  parser.add_argument(\"run\",\n                      choices=[\"train_and_eval\", \"train\", \"eval\", \"infer\", \"export\", \"score\"],\n                      help=\"Run type.\")\n  parser.add_argument(\"--config\", required=True, nargs=\"+\",\n                      help=\"List of configuration files.\")\n  parser.add_argument(\"--model_type\", default=\"\", choices=list(classes_in_module(catalog)),\n                      help=\"Model type from the catalog.\")\n  parser.add_argument(\"--model\", default=\"\",\n                      help=\"Custom model configuration file.\")\n  parser.add_argument(\"--run_dir\", default=\"\",\n                      help=\"If set, model_dir will be created relative to this location.\")\n  parser.add_argument(\"--data_dir\", default=\"\",\n                      help=\"If set, data files are expected to be relative to this location.\")\n  parser.add_argument(\"--features_file\", default=[], nargs=\"+\",\n                      help=\"Run inference on this file.\")\n  parser.add_argument(\"--predictions_file\", default=\"\",\n                      help=(\"File used to save predictions. If not set, predictions are printed \"\n                            \"on the standard output.\"))\n  parser.add_argument(\"--log_prediction_time\", default=False, action=\"store_true\",\n                      help=\"Logs some prediction time metrics.\")\n  parser.add_argument(\"--checkpoint_path\", default=None,\n                      help=(\"Checkpoint or directory to use for inference or export \"\n                            \"(when a directory is set, the latest checkpoint is used).\"))\n  parser.add_argument(\"--num_gpus\", type=int, default=1,\n                      help=\"Number of GPUs to use for in-graph replication.\")\n  parser.add_argument(\"--chief_host\", default=\"\",\n                      help=\"hostname:port of the chief worker (for distributed training).\")\n  parser.add_argument(\"--worker_hosts\", default=\"\",\n                      help=(\"Comma-separated list of hostname:port of workers \"\n                            \"(for distributed training).\"))\n  parser.add_argument(\"--ps_hosts\", default=\"\",\n                      help=(\"Comma-separated list of hostname:port of parameter servers \"\n                            \"(for distributed training).\"))\n  parser.add_argument(\"--task_type\", default=\"chief\",\n                      choices=[\"chief\", \"worker\", \"ps\", \"evaluator\"],\n                      help=\"Type of the task to run (for distributed training).\")\n  parser.add_argument(\"--task_index\", type=int, default=0,\n                      help=\"ID of the task (for distributed training).\")\n  parser.add_argument(\"--log_level\", default=\"INFO\",\n                      choices=[\"DEBUG\", \"ERROR\", \"FATAL\", \"INFO\", \"WARN\"],\n                      help=\"Logs verbosity.\")\n  parser.add_argument(\"--seed\", type=int, default=None,\n                      help=\"Random seed.\")\n  parser.add_argument(\"--gpu_allow_growth\", default=False, action=\"store_true\",\n                      help=\"Allocate GPU memory dynamically.\")\n  parser.add_argument(\"--intra_op_parallelism_threads\", type=int, default=0,\n                      help=(\"Number of intra op threads (0 means the system picks \"\n                            \"an appropriate number).\"))\n  parser.add_argument(\"--inter_op_parallelism_threads\", type=int, default=0,\n                      help=(\"Number of inter op threads (0 means the system picks \"\n                            \"an appropriate number).\"))\n  args = parser.parse_args()\n\n  tf.logging.set_verbosity(getattr(tf.logging, args.log_level))\n\n  # Setup cluster if defined.\n  if args.chief_host:\n    os.environ[\"TF_CONFIG\"] = json.dumps({\n        \"cluster\": {\n            \"chief\": [args.chief_host],\n            \"worker\": args.worker_hosts.split(\",\"),\n            \"ps\": args.ps_hosts.split(\",\")\n        },\n        \"task\": {\n            \"type\": args.task_type,\n            \"index\": args.task_index\n        }\n    })\n\n  # Load and merge run configurations.\n  config = load_config(args.config)\n  if args.run_dir:\n    config[\"model_dir\"] = os.path.join(args.run_dir, config[\"model_dir\"])\n  if args.data_dir:\n    config[\"data\"] = _prefix_paths(args.data_dir, config[\"data\"])\n\n  if not os.path.isdir(config[\"model_dir\"]):\n    tf.logging.info(\"Creating model directory %s\", config[\"model_dir\"])\n    os.makedirs(config[\"model_dir\"])\n\n  model = load_model(config[\"model_dir\"], model_file=args.model, model_name=args.model_type)\n  session_config = tf.ConfigProto(\n      intra_op_parallelism_threads=args.intra_op_parallelism_threads,\n      inter_op_parallelism_threads=args.inter_op_parallelism_threads)\n  runner = Runner(\n      model,\n      config,\n      seed=args.seed,\n      num_devices=args.num_gpus,\n      gpu_allow_growth=args.gpu_allow_growth,\n      session_config=session_config)\n\n  if args.run == \"train_and_eval\":\n    runner.train_and_evaluate()\n  elif args.run == \"train\":\n    runner.train()\n  elif args.run == \"eval\":\n    runner.evaluate(checkpoint_path=args.checkpoint_path)\n  elif args.run == \"infer\":\n    if not args.features_file:\n      parser.error(\"--features_file is required for inference.\")\n    elif len(args.features_file) == 1:\n      args.features_file = args.features_file[0]\n    runner.infer(\n        args.features_file,\n        predictions_file=args.predictions_file,\n        checkpoint_path=args.checkpoint_path,\n        log_time=args.log_prediction_time)\n  elif args.run == \"export\":\n    runner.export(checkpoint_path=args.checkpoint_path)\n  elif args.run == \"score\":\n    if not args.features_file:\n      parser.error(\"--features_file is required for scoring.\")\n    if not args.predictions_file:\n      parser.error(\"--predictions_file is required for scoring.\")\n    runner.score(\n        args.features_file,\n        args.predictions_file,\n        checkpoint_path=args.checkpoint_path)\n\n\nif __name__ == \"__main__\":\n  main()\n", "path": "opennmt/bin/main.py"}], "after_files": [{"content": "\"\"\"Main script.\"\"\"\n\nimport argparse\nimport json\nimport os\nimport six\n\nimport tensorflow as tf\n\nfrom opennmt.models import catalog\nfrom opennmt.runner import Runner\nfrom opennmt.config import load_model, load_config\nfrom opennmt.utils.misc import classes_in_module\n\n\ndef _prefix_paths(prefix, paths):\n  \"\"\"Recursively prefix paths.\n\n  Args:\n    prefix: The prefix to apply.\n    data: A dict of relative paths.\n\n  Returns:\n    The updated dict.\n  \"\"\"\n  if isinstance(paths, dict):\n    for key, path in six.iteritems(paths):\n      paths[key] = _prefix_paths(prefix, path)\n    return paths\n  elif isinstance(paths, list):\n    for i, path in enumerate(paths):\n      paths[i] = _prefix_paths(prefix, path)\n    return paths\n  else:\n    path = paths\n    new_path = os.path.join(prefix, path)\n    if os.path.isfile(new_path):\n      return new_path\n    else:\n      return path\n\ndef main():\n  parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)\n  parser.add_argument(\"run\",\n                      choices=[\"train_and_eval\", \"train\", \"eval\", \"infer\", \"export\", \"score\"],\n                      help=\"Run type.\")\n  parser.add_argument(\"--config\", required=True, nargs=\"+\",\n                      help=\"List of configuration files.\")\n  parser.add_argument(\"--model_type\", default=\"\", choices=list(classes_in_module(catalog)),\n                      help=\"Model type from the catalog.\")\n  parser.add_argument(\"--model\", default=\"\",\n                      help=\"Custom model configuration file.\")\n  parser.add_argument(\"--run_dir\", default=\"\",\n                      help=\"If set, model_dir will be created relative to this location.\")\n  parser.add_argument(\"--data_dir\", default=\"\",\n                      help=\"If set, data files are expected to be relative to this location.\")\n  parser.add_argument(\"--features_file\", default=[], nargs=\"+\",\n                      help=\"Run inference on this file.\")\n  parser.add_argument(\"--predictions_file\", default=\"\",\n                      help=(\"File used to save predictions. If not set, predictions are printed \"\n                            \"on the standard output.\"))\n  parser.add_argument(\"--log_prediction_time\", default=False, action=\"store_true\",\n                      help=\"Logs some prediction time metrics.\")\n  parser.add_argument(\"--checkpoint_path\", default=None,\n                      help=(\"Checkpoint or directory to use for inference or export \"\n                            \"(when a directory is set, the latest checkpoint is used).\"))\n  parser.add_argument(\"--num_gpus\", type=int, default=1,\n                      help=\"Number of GPUs to use for in-graph replication.\")\n  parser.add_argument(\"--chief_host\", default=\"\",\n                      help=\"hostname:port of the chief worker (for distributed training).\")\n  parser.add_argument(\"--worker_hosts\", default=\"\",\n                      help=(\"Comma-separated list of hostname:port of workers \"\n                            \"(for distributed training).\"))\n  parser.add_argument(\"--ps_hosts\", default=\"\",\n                      help=(\"Comma-separated list of hostname:port of parameter servers \"\n                            \"(for distributed training).\"))\n  parser.add_argument(\"--task_type\", default=\"chief\",\n                      choices=[\"chief\", \"worker\", \"ps\", \"evaluator\"],\n                      help=\"Type of the task to run (for distributed training).\")\n  parser.add_argument(\"--task_index\", type=int, default=0,\n                      help=\"ID of the task (for distributed training).\")\n  parser.add_argument(\"--log_level\", default=\"INFO\",\n                      choices=[\"DEBUG\", \"ERROR\", \"FATAL\", \"INFO\", \"WARN\"],\n                      help=\"Logs verbosity.\")\n  parser.add_argument(\"--seed\", type=int, default=None,\n                      help=\"Random seed.\")\n  parser.add_argument(\"--gpu_allow_growth\", default=False, action=\"store_true\",\n                      help=\"Allocate GPU memory dynamically.\")\n  parser.add_argument(\"--intra_op_parallelism_threads\", type=int, default=0,\n                      help=(\"Number of intra op threads (0 means the system picks \"\n                            \"an appropriate number).\"))\n  parser.add_argument(\"--inter_op_parallelism_threads\", type=int, default=0,\n                      help=(\"Number of inter op threads (0 means the system picks \"\n                            \"an appropriate number).\"))\n  args = parser.parse_args()\n\n  tf.logging.set_verbosity(getattr(tf.logging, args.log_level))\n\n  # Setup cluster if defined.\n  if args.chief_host:\n    os.environ[\"TF_CONFIG\"] = json.dumps({\n        \"cluster\": {\n            \"chief\": [args.chief_host],\n            \"worker\": args.worker_hosts.split(\",\"),\n            \"ps\": args.ps_hosts.split(\",\")\n        },\n        \"task\": {\n            \"type\": args.task_type,\n            \"index\": args.task_index\n        }\n    })\n\n  # Load and merge run configurations.\n  config = load_config(args.config)\n  if args.run_dir:\n    config[\"model_dir\"] = os.path.join(args.run_dir, config[\"model_dir\"])\n  if args.data_dir:\n    config[\"data\"] = _prefix_paths(args.data_dir, config[\"data\"])\n\n  if not os.path.isdir(config[\"model_dir\"]):\n    tf.logging.info(\"Creating model directory %s\", config[\"model_dir\"])\n    os.makedirs(config[\"model_dir\"])\n\n  model = load_model(config[\"model_dir\"], model_file=args.model, model_name=args.model_type)\n  session_config = tf.ConfigProto(\n      intra_op_parallelism_threads=args.intra_op_parallelism_threads,\n      inter_op_parallelism_threads=args.inter_op_parallelism_threads)\n  runner = Runner(\n      model,\n      config,\n      seed=args.seed,\n      num_devices=args.num_gpus,\n      gpu_allow_growth=args.gpu_allow_growth,\n      session_config=session_config)\n\n  if args.run == \"train_and_eval\":\n    runner.train_and_evaluate()\n  elif args.run == \"train\":\n    runner.train()\n  elif args.run == \"eval\":\n    runner.evaluate(checkpoint_path=args.checkpoint_path)\n  elif args.run == \"infer\":\n    if not args.features_file:\n      parser.error(\"--features_file is required for inference.\")\n    elif len(args.features_file) == 1:\n      args.features_file = args.features_file[0]\n    runner.infer(\n        args.features_file,\n        predictions_file=args.predictions_file,\n        checkpoint_path=args.checkpoint_path,\n        log_time=args.log_prediction_time)\n  elif args.run == \"export\":\n    runner.export(checkpoint_path=args.checkpoint_path)\n  elif args.run == \"score\":\n    if not args.features_file:\n      parser.error(\"--features_file is required for scoring.\")\n    if not args.predictions_file:\n      parser.error(\"--predictions_file is required for scoring.\")\n    runner.score(\n        args.features_file,\n        args.predictions_file,\n        checkpoint_path=args.checkpoint_path)\n\n\nif __name__ == \"__main__\":\n  main()\n", "path": "opennmt/bin/main.py"}]}