We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Gracefully handle situations where TSV files only contain the column headers
`_from_tsv()` should probably raise an exception, warn, or return `None` if it can only find a header, but no actual data in the parsed TSV file.

Issue reported on the forum:
https://mne.discourse.group/t/errors-with-read-raw-bids-with-edf-files/4082

Affected OpenNeuro dataset:
https://openneuro.org/datasets/ds002720

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mne_bids/tsv_handler.py`
Content:
```
1 """Private functions to handle tabular data."""
2 import numpy as np
3 from collections import OrderedDict
4 from copy import deepcopy
5 
6 
7 def _combine_rows(data1, data2, drop_column=None):
8     """Add two OrderedDict's together and optionally drop repeated data.
9 
10     Parameters
11     ----------
12     data1 : collections.OrderedDict
13         Original OrderedDict.
14     data2 : collections.OrderedDict
15         New OrderedDict to be added to the original.
16     drop_column : str, optional
17         Name of the column to check for duplicate values in.
18         Any duplicates found will be dropped from the original data array (ie.
19         most recent value are kept).
20 
21     Returns
22     -------
23     data : collections.OrderedDict
24         The new combined data.
25     """
26     data = deepcopy(data1)
27     # next extend the values in data1 with values in data2
28     for key, value in data2.items():
29         data[key].extend(value)
30 
31     # Make sure that if there are any columns in data1 that didn't get new
32     # data they are populated with "n/a"'s.
33     for key in set(data1.keys()) - set(data2.keys()):
34         data[key].extend(["n/a"] * len(next(iter(data2.values()))))
35 
36     if drop_column is None:
37         return data
38 
39     # Find any repeated values and remove all but the most recent value.
40     n_rows = len(data[drop_column])
41     _, idxs = np.unique(data[drop_column][::-1], return_index=True)
42     for key in data:
43         data[key] = [data[key][n_rows - 1 - idx] for idx in idxs]
44 
45     return data
46 
47 
48 def _contains_row(data, row_data):
49     """Determine whether the specified row data exists in the OrderedDict.
50 
51     Parameters
52     ----------
53     data : collections.OrderedDict
54         OrderedDict to check.
55     row_data : dict
56         Dictionary with column names as keys, and values being the column value
57         to match within a row.
58 
59     Returns
60     -------
61     bool
62         True if `row_data` exists in `data`.
63 
64     Note
65     ----
66     This function will return True if the supplied `row_data` contains less
67     columns than the number of columns in the existing data but there is still
68     a match for the partial row data.
69 
70     """
71     mask = None
72     for key, row_value in row_data.items():
73         # if any of the columns don't even exist in the keys
74         # this data_value will return False
75         data_value = np.array(data.get(key))
76 
77         # Cast row_value to the same dtype as data_value to avoid a NumPy
78         # FutureWarning, see
79         # https://github.com/mne-tools/mne-bids/pull/372
80         row_value = np.array(row_value, dtype=data_value.dtype)
81 
82         column_mask = np.in1d(data_value, row_value)
83         mask = column_mask if mask is None else (mask & column_mask)
84     return np.any(mask)
85 
86 
87 def _drop(data, values, column):
88     """Remove rows from the OrderedDict.
89 
90     Parameters
91     ----------
92     data : collections.OrderedDict
93         Data to drop values from.
94     values : list
95         List of values to drop. Any row containing this value in the specified
96         column will be dropped.
97     column : string
98         Name of the column to check for the existence of `value` in.
99 
100     Returns
101     -------
102     new_data : collections.OrderedDict
103         Copy of the original data with 0 or more rows dropped.
104 
105     """
106     new_data = deepcopy(data)
107     new_data_col = np.array(new_data[column])
108 
109     # Cast `values` to the same dtype as `new_data_col` to avoid a NumPy
110     # FutureWarning, see
111     # https://github.com/mne-tools/mne-bids/pull/372
112     values = np.array(values, dtype=new_data_col.dtype)
113 
114     mask = np.in1d(new_data_col, values, invert=True)
115     for key in new_data.keys():
116         new_data[key] = np.array(new_data[key])[mask].tolist()
117     return new_data
118 
119 
120 def _from_tsv(fname, dtypes=None):
121     """Read a tsv file into an OrderedDict.
122 
123     Parameters
124     ----------
125     fname : str
126         Path to the file being loaded.
127     dtypes : list, optional
128         List of types to cast the values loaded as. This is specified column by
129         column.
130         Defaults to None. In this case all the data is loaded as strings.
131 
132     Returns
133     -------
134     data_dict : collections.OrderedDict
135         Keys are the column names, and values are the column data.
136 
137     """
138     data = np.loadtxt(fname, dtype=str, delimiter='\t', ndmin=2,
139                       comments=None, encoding='utf-8-sig')
140     column_names = data[0, :]
141     info = data[1:, :]
142     data_dict = OrderedDict()
143     if dtypes is None:
144         dtypes = [str] * info.shape[1]
145     if not isinstance(dtypes, (list, tuple)):
146         dtypes = [dtypes] * info.shape[1]
147     if not len(dtypes) == info.shape[1]:
148         raise ValueError('dtypes length mismatch. Provided: {0}, '
149                          'Expected: {1}'.format(len(dtypes), info.shape[1]))
150     for i, name in enumerate(column_names):
151         data_dict[name] = info[:, i].astype(dtypes[i]).tolist()
152     return data_dict
153 
154 
155 def _to_tsv(data, fname):
156     """Write an OrderedDict into a tsv file.
157 
158     Parameters
159     ----------
160     data : collections.OrderedDict
161         Ordered dictionary containing data to be written to a tsv file.
162     fname : str
163         Path to the file being written.
164 
165     """
166     n_rows = len(data[list(data.keys())[0]])
167     output = _tsv_to_str(data, n_rows)
168 
169     with open(fname, 'w', encoding='utf-8-sig') as f:
170         f.write(output)
171         f.write('\n')
172 
173 
174 def _tsv_to_str(data, rows=5):
175     """Return a string representation of the OrderedDict.
176 
177     Parameters
178     ----------
179     data : collections.OrderedDict
180         OrderedDict to return string representation of.
181     rows : int, optional
182         Maximum number of rows of data to output.
183 
184     Returns
185     -------
186     str
187         String representation of the first `rows` lines of `data`.
188 
189     """
190     col_names = list(data.keys())
191     n_rows = len(data[col_names[0]])
192     output = list()
193     # write headings.
194     output.append('\t'.join(col_names))
195 
196     # write column data.
197     max_rows = min(n_rows, rows)
198     for idx in range(max_rows):
199         row_data = list(str(data[key][idx]) for key in data)
200         output.append('\t'.join(row_data))
201 
202     return '\n'.join(output)
203
```
--- 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/mne_bids/tsv_handler.py b/mne_bids/tsv_handler.py
--- a/mne_bids/tsv_handler.py
+++ b/mne_bids/tsv_handler.py
@@ -1,8 +1,10 @@
 """Private functions to handle tabular data."""
-import numpy as np
 from collections import OrderedDict
 from copy import deepcopy
 
+from mne.utils import warn
+import numpy as np
+
 
 def _combine_rows(data1, data2, drop_column=None):
     """Add two OrderedDict's together and optionally drop repeated data.
@@ -109,7 +111,10 @@
     # Cast `values` to the same dtype as `new_data_col` to avoid a NumPy
     # FutureWarning, see
     # https://github.com/mne-tools/mne-bids/pull/372
-    values = np.array(values, dtype=new_data_col.dtype)
+    dtype = new_data_col.dtype
+    if new_data_col.shape == (0,):
+        dtype = np.array(values).dtype
+    values = np.array(values, dtype=dtype)
 
     mask = np.in1d(new_data_col, values, invert=True)
     for key in new_data.keys():
@@ -147,8 +152,16 @@
     if not len(dtypes) == info.shape[1]:
         raise ValueError('dtypes length mismatch. Provided: {0}, '
                          'Expected: {1}'.format(len(dtypes), info.shape[1]))
+    empty_cols = 0
     for i, name in enumerate(column_names):
-        data_dict[name] = info[:, i].astype(dtypes[i]).tolist()
+        values = info[:, i].astype(dtypes[i]).tolist()
+        data_dict[name] = values
+        if len(values) == 0:
+            empty_cols += 1
+
+    if empty_cols == len(column_names):
+        warn(f"TSV file is empty: '{fname}'")
+
     return data_dict

{"golden_diff": "diff --git a/mne_bids/tsv_handler.py b/mne_bids/tsv_handler.py\n--- a/mne_bids/tsv_handler.py\n+++ b/mne_bids/tsv_handler.py\n@@ -1,8 +1,10 @@\n \"\"\"Private functions to handle tabular data.\"\"\"\n-import numpy as np\n from collections import OrderedDict\n from copy import deepcopy\n \n+from mne.utils import warn\n+import numpy as np\n+\n \n def _combine_rows(data1, data2, drop_column=None):\n     \"\"\"Add two OrderedDict's together and optionally drop repeated data.\n@@ -109,7 +111,10 @@\n     # Cast `values` to the same dtype as `new_data_col` to avoid a NumPy\n     # FutureWarning, see\n     # https://github.com/mne-tools/mne-bids/pull/372\n-    values = np.array(values, dtype=new_data_col.dtype)\n+    dtype = new_data_col.dtype\n+    if new_data_col.shape == (0,):\n+        dtype = np.array(values).dtype\n+    values = np.array(values, dtype=dtype)\n \n     mask = np.in1d(new_data_col, values, invert=True)\n     for key in new_data.keys():\n@@ -147,8 +152,16 @@\n     if not len(dtypes) == info.shape[1]:\n         raise ValueError('dtypes length mismatch. Provided: {0}, '\n                          'Expected: {1}'.format(len(dtypes), info.shape[1]))\n+    empty_cols = 0\n     for i, name in enumerate(column_names):\n-        data_dict[name] = info[:, i].astype(dtypes[i]).tolist()\n+        values = info[:, i].astype(dtypes[i]).tolist()\n+        data_dict[name] = values\n+        if len(values) == 0:\n+            empty_cols += 1\n+\n+    if empty_cols == len(column_names):\n+        warn(f\"TSV file is empty: '{fname}'\")\n+\n     return data_dict\n", "issue": "Gracefully handle situations where TSV files only contain the column headers\n`_from_tsv()` should probably raise an exception, warn, or return `None` if it can only find a header, but no actual data in the parsed TSV file.\r\n\r\nIssue reported on the forum:\r\nhttps://mne.discourse.group/t/errors-with-read-raw-bids-with-edf-files/4082\r\n\r\nAffected OpenNeuro dataset:\r\nhttps://openneuro.org/datasets/ds002720\n", "before_files": [{"content": "\"\"\"Private functions to handle tabular data.\"\"\"\nimport numpy as np\nfrom collections import OrderedDict\nfrom copy import deepcopy\n\n\ndef _combine_rows(data1, data2, drop_column=None):\n    \"\"\"Add two OrderedDict's together and optionally drop repeated data.\n\n    Parameters\n    ----------\n    data1 : collections.OrderedDict\n        Original OrderedDict.\n    data2 : collections.OrderedDict\n        New OrderedDict to be added to the original.\n    drop_column : str, optional\n        Name of the column to check for duplicate values in.\n        Any duplicates found will be dropped from the original data array (ie.\n        most recent value are kept).\n\n    Returns\n    -------\n    data : collections.OrderedDict\n        The new combined data.\n    \"\"\"\n    data = deepcopy(data1)\n    # next extend the values in data1 with values in data2\n    for key, value in data2.items():\n        data[key].extend(value)\n\n    # Make sure that if there are any columns in data1 that didn't get new\n    # data they are populated with \"n/a\"'s.\n    for key in set(data1.keys()) - set(data2.keys()):\n        data[key].extend([\"n/a\"] * len(next(iter(data2.values()))))\n\n    if drop_column is None:\n        return data\n\n    # Find any repeated values and remove all but the most recent value.\n    n_rows = len(data[drop_column])\n    _, idxs = np.unique(data[drop_column][::-1], return_index=True)\n    for key in data:\n        data[key] = [data[key][n_rows - 1 - idx] for idx in idxs]\n\n    return data\n\n\ndef _contains_row(data, row_data):\n    \"\"\"Determine whether the specified row data exists in the OrderedDict.\n\n    Parameters\n    ----------\n    data : collections.OrderedDict\n        OrderedDict to check.\n    row_data : dict\n        Dictionary with column names as keys, and values being the column value\n        to match within a row.\n\n    Returns\n    -------\n    bool\n        True if `row_data` exists in `data`.\n\n    Note\n    ----\n    This function will return True if the supplied `row_data` contains less\n    columns than the number of columns in the existing data but there is still\n    a match for the partial row data.\n\n    \"\"\"\n    mask = None\n    for key, row_value in row_data.items():\n        # if any of the columns don't even exist in the keys\n        # this data_value will return False\n        data_value = np.array(data.get(key))\n\n        # Cast row_value to the same dtype as data_value to avoid a NumPy\n        # FutureWarning, see\n        # https://github.com/mne-tools/mne-bids/pull/372\n        row_value = np.array(row_value, dtype=data_value.dtype)\n\n        column_mask = np.in1d(data_value, row_value)\n        mask = column_mask if mask is None else (mask & column_mask)\n    return np.any(mask)\n\n\ndef _drop(data, values, column):\n    \"\"\"Remove rows from the OrderedDict.\n\n    Parameters\n    ----------\n    data : collections.OrderedDict\n        Data to drop values from.\n    values : list\n        List of values to drop. Any row containing this value in the specified\n        column will be dropped.\n    column : string\n        Name of the column to check for the existence of `value` in.\n\n    Returns\n    -------\n    new_data : collections.OrderedDict\n        Copy of the original data with 0 or more rows dropped.\n\n    \"\"\"\n    new_data = deepcopy(data)\n    new_data_col = np.array(new_data[column])\n\n    # Cast `values` to the same dtype as `new_data_col` to avoid a NumPy\n    # FutureWarning, see\n    # https://github.com/mne-tools/mne-bids/pull/372\n    values = np.array(values, dtype=new_data_col.dtype)\n\n    mask = np.in1d(new_data_col, values, invert=True)\n    for key in new_data.keys():\n        new_data[key] = np.array(new_data[key])[mask].tolist()\n    return new_data\n\n\ndef _from_tsv(fname, dtypes=None):\n    \"\"\"Read a tsv file into an OrderedDict.\n\n    Parameters\n    ----------\n    fname : str\n        Path to the file being loaded.\n    dtypes : list, optional\n        List of types to cast the values loaded as. This is specified column by\n        column.\n        Defaults to None. In this case all the data is loaded as strings.\n\n    Returns\n    -------\n    data_dict : collections.OrderedDict\n        Keys are the column names, and values are the column data.\n\n    \"\"\"\n    data = np.loadtxt(fname, dtype=str, delimiter='\\t', ndmin=2,\n                      comments=None, encoding='utf-8-sig')\n    column_names = data[0, :]\n    info = data[1:, :]\n    data_dict = OrderedDict()\n    if dtypes is None:\n        dtypes = [str] * info.shape[1]\n    if not isinstance(dtypes, (list, tuple)):\n        dtypes = [dtypes] * info.shape[1]\n    if not len(dtypes) == info.shape[1]:\n        raise ValueError('dtypes length mismatch. Provided: {0}, '\n                         'Expected: {1}'.format(len(dtypes), info.shape[1]))\n    for i, name in enumerate(column_names):\n        data_dict[name] = info[:, i].astype(dtypes[i]).tolist()\n    return data_dict\n\n\ndef _to_tsv(data, fname):\n    \"\"\"Write an OrderedDict into a tsv file.\n\n    Parameters\n    ----------\n    data : collections.OrderedDict\n        Ordered dictionary containing data to be written to a tsv file.\n    fname : str\n        Path to the file being written.\n\n    \"\"\"\n    n_rows = len(data[list(data.keys())[0]])\n    output = _tsv_to_str(data, n_rows)\n\n    with open(fname, 'w', encoding='utf-8-sig') as f:\n        f.write(output)\n        f.write('\\n')\n\n\ndef _tsv_to_str(data, rows=5):\n    \"\"\"Return a string representation of the OrderedDict.\n\n    Parameters\n    ----------\n    data : collections.OrderedDict\n        OrderedDict to return string representation of.\n    rows : int, optional\n        Maximum number of rows of data to output.\n\n    Returns\n    -------\n    str\n        String representation of the first `rows` lines of `data`.\n\n    \"\"\"\n    col_names = list(data.keys())\n    n_rows = len(data[col_names[0]])\n    output = list()\n    # write headings.\n    output.append('\\t'.join(col_names))\n\n    # write column data.\n    max_rows = min(n_rows, rows)\n    for idx in range(max_rows):\n        row_data = list(str(data[key][idx]) for key in data)\n        output.append('\\t'.join(row_data))\n\n    return '\\n'.join(output)\n", "path": "mne_bids/tsv_handler.py"}], "after_files": [{"content": "\"\"\"Private functions to handle tabular data.\"\"\"\nfrom collections import OrderedDict\nfrom copy import deepcopy\n\nfrom mne.utils import warn\nimport numpy as np\n\n\ndef _combine_rows(data1, data2, drop_column=None):\n    \"\"\"Add two OrderedDict's together and optionally drop repeated data.\n\n    Parameters\n    ----------\n    data1 : collections.OrderedDict\n        Original OrderedDict.\n    data2 : collections.OrderedDict\n        New OrderedDict to be added to the original.\n    drop_column : str, optional\n        Name of the column to check for duplicate values in.\n        Any duplicates found will be dropped from the original data array (ie.\n        most recent value are kept).\n\n    Returns\n    -------\n    data : collections.OrderedDict\n        The new combined data.\n    \"\"\"\n    data = deepcopy(data1)\n    # next extend the values in data1 with values in data2\n    for key, value in data2.items():\n        data[key].extend(value)\n\n    # Make sure that if there are any columns in data1 that didn't get new\n    # data they are populated with \"n/a\"'s.\n    for key in set(data1.keys()) - set(data2.keys()):\n        data[key].extend([\"n/a\"] * len(next(iter(data2.values()))))\n\n    if drop_column is None:\n        return data\n\n    # Find any repeated values and remove all but the most recent value.\n    n_rows = len(data[drop_column])\n    _, idxs = np.unique(data[drop_column][::-1], return_index=True)\n    for key in data:\n        data[key] = [data[key][n_rows - 1 - idx] for idx in idxs]\n\n    return data\n\n\ndef _contains_row(data, row_data):\n    \"\"\"Determine whether the specified row data exists in the OrderedDict.\n\n    Parameters\n    ----------\n    data : collections.OrderedDict\n        OrderedDict to check.\n    row_data : dict\n        Dictionary with column names as keys, and values being the column value\n        to match within a row.\n\n    Returns\n    -------\n    bool\n        True if `row_data` exists in `data`.\n\n    Note\n    ----\n    This function will return True if the supplied `row_data` contains less\n    columns than the number of columns in the existing data but there is still\n    a match for the partial row data.\n\n    \"\"\"\n    mask = None\n    for key, row_value in row_data.items():\n        # if any of the columns don't even exist in the keys\n        # this data_value will return False\n        data_value = np.array(data.get(key))\n\n        # Cast row_value to the same dtype as data_value to avoid a NumPy\n        # FutureWarning, see\n        # https://github.com/mne-tools/mne-bids/pull/372\n        row_value = np.array(row_value, dtype=data_value.dtype)\n\n        column_mask = np.in1d(data_value, row_value)\n        mask = column_mask if mask is None else (mask & column_mask)\n    return np.any(mask)\n\n\ndef _drop(data, values, column):\n    \"\"\"Remove rows from the OrderedDict.\n\n    Parameters\n    ----------\n    data : collections.OrderedDict\n        Data to drop values from.\n    values : list\n        List of values to drop. Any row containing this value in the specified\n        column will be dropped.\n    column : string\n        Name of the column to check for the existence of `value` in.\n\n    Returns\n    -------\n    new_data : collections.OrderedDict\n        Copy of the original data with 0 or more rows dropped.\n\n    \"\"\"\n    new_data = deepcopy(data)\n    new_data_col = np.array(new_data[column])\n\n    # Cast `values` to the same dtype as `new_data_col` to avoid a NumPy\n    # FutureWarning, see\n    # https://github.com/mne-tools/mne-bids/pull/372\n    dtype = new_data_col.dtype\n    if new_data_col.shape == (0,):\n        dtype = np.array(values).dtype\n    values = np.array(values, dtype=dtype)\n\n    mask = np.in1d(new_data_col, values, invert=True)\n    for key in new_data.keys():\n        new_data[key] = np.array(new_data[key])[mask].tolist()\n    return new_data\n\n\ndef _from_tsv(fname, dtypes=None):\n    \"\"\"Read a tsv file into an OrderedDict.\n\n    Parameters\n    ----------\n    fname : str\n        Path to the file being loaded.\n    dtypes : list, optional\n        List of types to cast the values loaded as. This is specified column by\n        column.\n        Defaults to None. In this case all the data is loaded as strings.\n\n    Returns\n    -------\n    data_dict : collections.OrderedDict\n        Keys are the column names, and values are the column data.\n\n    \"\"\"\n    data = np.loadtxt(fname, dtype=str, delimiter='\\t', ndmin=2,\n                      comments=None, encoding='utf-8-sig')\n    column_names = data[0, :]\n    info = data[1:, :]\n    data_dict = OrderedDict()\n    if dtypes is None:\n        dtypes = [str] * info.shape[1]\n    if not isinstance(dtypes, (list, tuple)):\n        dtypes = [dtypes] * info.shape[1]\n    if not len(dtypes) == info.shape[1]:\n        raise ValueError('dtypes length mismatch. Provided: {0}, '\n                         'Expected: {1}'.format(len(dtypes), info.shape[1]))\n    empty_cols = 0\n    for i, name in enumerate(column_names):\n        values = info[:, i].astype(dtypes[i]).tolist()\n        data_dict[name] = values\n        if len(values) == 0:\n            empty_cols += 1\n\n    if empty_cols == len(column_names):\n        warn(f\"TSV file is empty: '{fname}'\")\n\n    return data_dict\n\n\ndef _to_tsv(data, fname):\n    \"\"\"Write an OrderedDict into a tsv file.\n\n    Parameters\n    ----------\n    data : collections.OrderedDict\n        Ordered dictionary containing data to be written to a tsv file.\n    fname : str\n        Path to the file being written.\n\n    \"\"\"\n    n_rows = len(data[list(data.keys())[0]])\n    output = _tsv_to_str(data, n_rows)\n\n    with open(fname, 'w', encoding='utf-8-sig') as f:\n        f.write(output)\n        f.write('\\n')\n\n\ndef _tsv_to_str(data, rows=5):\n    \"\"\"Return a string representation of the OrderedDict.\n\n    Parameters\n    ----------\n    data : collections.OrderedDict\n        OrderedDict to return string representation of.\n    rows : int, optional\n        Maximum number of rows of data to output.\n\n    Returns\n    -------\n    str\n        String representation of the first `rows` lines of `data`.\n\n    \"\"\"\n    col_names = list(data.keys())\n    n_rows = len(data[col_names[0]])\n    output = list()\n    # write headings.\n    output.append('\\t'.join(col_names))\n\n    # write column data.\n    max_rows = min(n_rows, rows)\n    for idx in range(max_rows):\n        row_data = list(str(data[key][idx]) for key in data)\n        output.append('\\t'.join(row_data))\n\n    return '\\n'.join(output)\n", "path": "mne_bids/tsv_handler.py"}]}