We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[BUG] Extend `fill_empty`'s `column_names` type range
# Brief Description

<!-- Please provide a brief description of your bug. Do NOT paste the stack trace here. -->

https://github.com/pyjanitor-devs/pyjanitor/blob/3fab49e8c89f1a5e4ca7a6e4fdbbe8e2f7b89c66/janitor/functions/fill.py#L148-L152

Quickly fix this, could add `pd.Index`.

And a little bit more thinking, using `Iterable` is better?
Because `check_column`'s `column_names` also support `Iterable`.
And `pd.Index`'s instance is also `Iterable`.
So it would be `@dispatch(pd.DataFrame, Iterable)`

The `Iterable` is not `typing.Iterable` is `collections.abc.Iterable`.
Or else using `typing.Iterable` would get another error.

# Minimally Reproducible Code

<!-- If you provide minimal code that reproduces the problem, this makes it easier for us to debug what's going on.

Minimal code should be trivially copy/pastable into a Python interpreter in its entirety. Be sure to include imports.
-->

```python
>>> import pandas as pd
>>> import janitor  # noqa

>>> df = pd.DataFrame({"attr":[None, None]})
>>> df.fill_empty(df.columns, 0)
Traceback (most recent call last):
  File "C:\Software\miniforge3\envs\work\lib\site-packages\multipledispatch\dispatcher.py", line 269, in __call__
    func = self._cache[types]
KeyError: (<class 'pandas.core.frame.DataFrame'>, <class 'pandas.core.indexes.base.Index'>)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Software\miniforge3\envs\work\lib\site-packages\pandas_flavor\register.py", line 29, in __call__
    return method(self._obj, *args, **kwargs)
  File "C:\Software\miniforge3\envs\work\lib\site-packages\janitor\utils.py", line 231, in wrapper
    return func(*args, **kwargs)
  File "C:\Software\miniforge3\envs\work\lib\site-packages\janitor\functions\fill.py", line 199, in fill_empty
    return _fill_empty(df, column_names, value=value)
  File "C:\Software\miniforge3\envs\work\lib\site-packages\multipledispatch\dispatcher.py", line 273, in __call__
    raise NotImplementedError(
NotImplementedError: Could not find signature for _fill_empty: <DataFrame, Index>
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `janitor/functions/fill.py`
Content:
```
1 from enum import Enum
2 from operator import methodcaller
3 from typing import Hashable, Iterable, Union
4 
5 import pandas as pd
6 import pandas_flavor as pf
7 from janitor.utils import check, check_column, deprecated_alias
8 from multipledispatch import dispatch
9 
10 
11 @pf.register_dataframe_method
12 def fill_direction(df: pd.DataFrame, **kwargs) -> pd.DataFrame:
13     """
14     Provide a method-chainable function for filling missing values
15     in selected columns.
16 
17     It is a wrapper for `pd.Series.ffill` and `pd.Series.bfill`,
18     and pairs the column name with one of `up`, `down`, `updown`,
19     and `downup`.
20 
21 
22     Functional usage syntax:
23 
24     ```python
25     import pandas as pd
26     import janitor as jn
27 
28     df = pd.DataFrame(...)
29     df = jn.fill_direction(
30                 df = df,
31                 column_1 = direction_1,
32                 column_2 = direction_2,
33             )
34     ```
35 
36     Method-chaining usage syntax:
37 
38     ```python
39     import pandas as pd
40     import janitor as jn
41 
42     df = pd.DataFrame(...)
43             .fill_direction(
44                 column_1 = direction_1,
45                 column_2 = direction_2,
46             )
47     ```
48 
49     :param df: A pandas DataFrame.
50     :param kwargs: Key - value pairs of columns and directions.
51         Directions can be either `down`, `up`, `updown`
52         (fill up then down) and `downup` (fill down then up).
53     :returns: A pandas DataFrame with modified column(s).
54     :raises ValueError: if direction supplied is not one of `down`, `up`,
55         `updown`, or `downup`.
56     """
57 
58     if not kwargs:
59         return df
60 
61     fill_types = {fill.name for fill in _FILLTYPE}
62     for column_name, fill_type in kwargs.items():
63         check("column_name", column_name, [str])
64         check("fill_type", fill_type, [str])
65         if fill_type.upper() not in fill_types:
66             raise ValueError(
67                 """
68                 fill_type should be one of
69                 up, down, updown, or downup.
70                 """
71             )
72 
73     check_column(df, kwargs)
74 
75     new_values = {}
76     for column_name, fill_type in kwargs.items():
77         direction = _FILLTYPE[f"{fill_type.upper()}"].value
78         if len(direction) == 1:
79             direction = methodcaller(direction[0])
80             output = direction(df[column_name])
81         else:
82             direction = [methodcaller(entry) for entry in direction]
83             output = _chain_func(df[column_name], *direction)
84         new_values[column_name] = output
85 
86     return df.assign(**new_values)
87 
88 
89 def _chain_func(column: pd.Series, *funcs):
90     """
91     Apply series of functions consecutively
92     to a Series.
93     https://blog.finxter.com/how-to-chain-multiple-function-calls-in-python/
94     """
95     new_value = column.copy()
96     for func in funcs:
97         new_value = func(new_value)
98     return new_value
99 
100 
101 class _FILLTYPE(Enum):
102     """List of fill types for fill_direction."""
103 
104     UP = ("bfill",)
105     DOWN = ("ffill",)
106     UPDOWN = "bfill", "ffill"
107     DOWNUP = "ffill", "bfill"
108 
109 
110 @pf.register_dataframe_method
111 @deprecated_alias(columns="column_names")
112 def fill_empty(
113     df: pd.DataFrame, column_names: Union[str, Iterable[str], Hashable], value
114 ) -> pd.DataFrame:
115     """
116     Fill `NaN` values in specified columns with a given value.
117 
118     Super sugary syntax that wraps `pandas.DataFrame.fillna`.
119 
120     This method mutates the original DataFrame.
121 
122     Functional usage syntax:
123 
124     ```python
125         df = fill_empty(df, column_names=[col1, col2], value=0)
126     ```
127 
128     Method chaining syntax:
129 
130     ```python
131         import pandas as pd
132         import janitor
133         df = pd.DataFrame(...).fill_empty(column_names=col1, value=0)
134     ```
135 
136     :param df: A pandas DataFrame.
137     :param column_names: column_names: A column name or an iterable (list
138         or tuple) of column names. If a single column name is passed in, then
139         only that column will be filled; if a list or tuple is passed in, then
140         those columns will all be filled with the same value.
141     :param value: The value that replaces the `NaN` values.
142     :returns: A pandas DataFrame with `NaN` values filled.
143     """
144     check_column(df, column_names)
145     return _fill_empty(df, column_names, value=value)
146 
147 
148 @dispatch(pd.DataFrame, (list, tuple))
149 def _fill_empty(df, column_names, value=None):
150     """Fill empty function for the case that column_names is list or tuple."""
151     fill_mapping = {c: value for c in column_names}
152     return df.fillna(value=fill_mapping)
153 
154 
155 @dispatch(pd.DataFrame, str)  # noqa: F811
156 def _fill_empty(df, column_names, value=None):  # noqa: F811
157     """Fill empty function for the case that column_names is a string."""
158     fill_mapping = {column_names: value}
159     return df.fillna(value=fill_mapping)
160
```
--- 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/janitor/functions/fill.py b/janitor/functions/fill.py
--- a/janitor/functions/fill.py
+++ b/janitor/functions/fill.py
@@ -1,3 +1,4 @@
+from collections.abc import Iterable as abcIterable
 from enum import Enum
 from operator import methodcaller
 from typing import Hashable, Iterable, Union
@@ -145,7 +146,7 @@
     return _fill_empty(df, column_names, value=value)
 
 
-@dispatch(pd.DataFrame, (list, tuple))
+@dispatch(pd.DataFrame, abcIterable)
 def _fill_empty(df, column_names, value=None):
     """Fill empty function for the case that column_names is list or tuple."""
     fill_mapping = {c: value for c in column_names}

{"golden_diff": "diff --git a/janitor/functions/fill.py b/janitor/functions/fill.py\n--- a/janitor/functions/fill.py\n+++ b/janitor/functions/fill.py\n@@ -1,3 +1,4 @@\n+from collections.abc import Iterable as abcIterable\n from enum import Enum\n from operator import methodcaller\n from typing import Hashable, Iterable, Union\n@@ -145,7 +146,7 @@\n     return _fill_empty(df, column_names, value=value)\n \n \n-@dispatch(pd.DataFrame, (list, tuple))\n+@dispatch(pd.DataFrame, abcIterable)\n def _fill_empty(df, column_names, value=None):\n     \"\"\"Fill empty function for the case that column_names is list or tuple.\"\"\"\n     fill_mapping = {c: value for c in column_names}\n", "issue": "[BUG] Extend `fill_empty`'s `column_names` type range\n# Brief Description\r\n\r\n<!-- Please provide a brief description of your bug. Do NOT paste the stack trace here. -->\r\n\r\nhttps://github.com/pyjanitor-devs/pyjanitor/blob/3fab49e8c89f1a5e4ca7a6e4fdbbe8e2f7b89c66/janitor/functions/fill.py#L148-L152\r\n\r\nQuickly fix this, could add `pd.Index`.\r\n\r\nAnd a little bit more thinking, using `Iterable` is better?\r\nBecause `check_column`'s `column_names` also support `Iterable`.\r\nAnd `pd.Index`'s instance is also `Iterable`.\r\nSo it would be `@dispatch(pd.DataFrame, Iterable)`\r\n\r\nThe `Iterable` is not `typing.Iterable` is `collections.abc.Iterable`.\r\nOr else using `typing.Iterable` would get another error.\r\n\r\n# Minimally Reproducible Code\r\n\r\n<!-- If you provide minimal code that reproduces the problem, this makes it easier for us to debug what's going on.\r\n\r\nMinimal code should be trivially copy/pastable into a Python interpreter in its entirety. Be sure to include imports.\r\n-->\r\n\r\n```python\r\n>>> import pandas as pd\r\n>>> import janitor  # noqa\r\n\r\n>>> df = pd.DataFrame({\"attr\":[None, None]})\r\n>>> df.fill_empty(df.columns, 0)\r\nTraceback (most recent call last):\r\n  File \"C:\\Software\\miniforge3\\envs\\work\\lib\\site-packages\\multipledispatch\\dispatcher.py\", line 269, in __call__\r\n    func = self._cache[types]\r\nKeyError: (<class 'pandas.core.frame.DataFrame'>, <class 'pandas.core.indexes.base.Index'>)\r\n\r\nDuring handling of the above exception, another exception occurred:\r\n\r\nTraceback (most recent call last):\r\n  File \"<stdin>\", line 1, in <module>\r\n  File \"C:\\Software\\miniforge3\\envs\\work\\lib\\site-packages\\pandas_flavor\\register.py\", line 29, in __call__\r\n    return method(self._obj, *args, **kwargs)\r\n  File \"C:\\Software\\miniforge3\\envs\\work\\lib\\site-packages\\janitor\\utils.py\", line 231, in wrapper\r\n    return func(*args, **kwargs)\r\n  File \"C:\\Software\\miniforge3\\envs\\work\\lib\\site-packages\\janitor\\functions\\fill.py\", line 199, in fill_empty\r\n    return _fill_empty(df, column_names, value=value)\r\n  File \"C:\\Software\\miniforge3\\envs\\work\\lib\\site-packages\\multipledispatch\\dispatcher.py\", line 273, in __call__\r\n    raise NotImplementedError(\r\nNotImplementedError: Could not find signature for _fill_empty: <DataFrame, Index>\r\n```\n", "before_files": [{"content": "from enum import Enum\nfrom operator import methodcaller\nfrom typing import Hashable, Iterable, Union\n\nimport pandas as pd\nimport pandas_flavor as pf\nfrom janitor.utils import check, check_column, deprecated_alias\nfrom multipledispatch import dispatch\n\n\n@pf.register_dataframe_method\ndef fill_direction(df: pd.DataFrame, **kwargs) -> pd.DataFrame:\n    \"\"\"\n    Provide a method-chainable function for filling missing values\n    in selected columns.\n\n    It is a wrapper for `pd.Series.ffill` and `pd.Series.bfill`,\n    and pairs the column name with one of `up`, `down`, `updown`,\n    and `downup`.\n\n\n    Functional usage syntax:\n\n    ```python\n    import pandas as pd\n    import janitor as jn\n\n    df = pd.DataFrame(...)\n    df = jn.fill_direction(\n                df = df,\n                column_1 = direction_1,\n                column_2 = direction_2,\n            )\n    ```\n\n    Method-chaining usage syntax:\n\n    ```python\n    import pandas as pd\n    import janitor as jn\n\n    df = pd.DataFrame(...)\n            .fill_direction(\n                column_1 = direction_1,\n                column_2 = direction_2,\n            )\n    ```\n\n    :param df: A pandas DataFrame.\n    :param kwargs: Key - value pairs of columns and directions.\n        Directions can be either `down`, `up`, `updown`\n        (fill up then down) and `downup` (fill down then up).\n    :returns: A pandas DataFrame with modified column(s).\n    :raises ValueError: if direction supplied is not one of `down`, `up`,\n        `updown`, or `downup`.\n    \"\"\"\n\n    if not kwargs:\n        return df\n\n    fill_types = {fill.name for fill in _FILLTYPE}\n    for column_name, fill_type in kwargs.items():\n        check(\"column_name\", column_name, [str])\n        check(\"fill_type\", fill_type, [str])\n        if fill_type.upper() not in fill_types:\n            raise ValueError(\n                \"\"\"\n                fill_type should be one of\n                up, down, updown, or downup.\n                \"\"\"\n            )\n\n    check_column(df, kwargs)\n\n    new_values = {}\n    for column_name, fill_type in kwargs.items():\n        direction = _FILLTYPE[f\"{fill_type.upper()}\"].value\n        if len(direction) == 1:\n            direction = methodcaller(direction[0])\n            output = direction(df[column_name])\n        else:\n            direction = [methodcaller(entry) for entry in direction]\n            output = _chain_func(df[column_name], *direction)\n        new_values[column_name] = output\n\n    return df.assign(**new_values)\n\n\ndef _chain_func(column: pd.Series, *funcs):\n    \"\"\"\n    Apply series of functions consecutively\n    to a Series.\n    https://blog.finxter.com/how-to-chain-multiple-function-calls-in-python/\n    \"\"\"\n    new_value = column.copy()\n    for func in funcs:\n        new_value = func(new_value)\n    return new_value\n\n\nclass _FILLTYPE(Enum):\n    \"\"\"List of fill types for fill_direction.\"\"\"\n\n    UP = (\"bfill\",)\n    DOWN = (\"ffill\",)\n    UPDOWN = \"bfill\", \"ffill\"\n    DOWNUP = \"ffill\", \"bfill\"\n\n\n@pf.register_dataframe_method\n@deprecated_alias(columns=\"column_names\")\ndef fill_empty(\n    df: pd.DataFrame, column_names: Union[str, Iterable[str], Hashable], value\n) -> pd.DataFrame:\n    \"\"\"\n    Fill `NaN` values in specified columns with a given value.\n\n    Super sugary syntax that wraps `pandas.DataFrame.fillna`.\n\n    This method mutates the original DataFrame.\n\n    Functional usage syntax:\n\n    ```python\n        df = fill_empty(df, column_names=[col1, col2], value=0)\n    ```\n\n    Method chaining syntax:\n\n    ```python\n        import pandas as pd\n        import janitor\n        df = pd.DataFrame(...).fill_empty(column_names=col1, value=0)\n    ```\n\n    :param df: A pandas DataFrame.\n    :param column_names: column_names: A column name or an iterable (list\n        or tuple) of column names. If a single column name is passed in, then\n        only that column will be filled; if a list or tuple is passed in, then\n        those columns will all be filled with the same value.\n    :param value: The value that replaces the `NaN` values.\n    :returns: A pandas DataFrame with `NaN` values filled.\n    \"\"\"\n    check_column(df, column_names)\n    return _fill_empty(df, column_names, value=value)\n\n\n@dispatch(pd.DataFrame, (list, tuple))\ndef _fill_empty(df, column_names, value=None):\n    \"\"\"Fill empty function for the case that column_names is list or tuple.\"\"\"\n    fill_mapping = {c: value for c in column_names}\n    return df.fillna(value=fill_mapping)\n\n\n@dispatch(pd.DataFrame, str)  # noqa: F811\ndef _fill_empty(df, column_names, value=None):  # noqa: F811\n    \"\"\"Fill empty function for the case that column_names is a string.\"\"\"\n    fill_mapping = {column_names: value}\n    return df.fillna(value=fill_mapping)\n", "path": "janitor/functions/fill.py"}], "after_files": [{"content": "from collections.abc import Iterable as abcIterable\nfrom enum import Enum\nfrom operator import methodcaller\nfrom typing import Hashable, Iterable, Union\n\nimport pandas as pd\nimport pandas_flavor as pf\nfrom janitor.utils import check, check_column, deprecated_alias\nfrom multipledispatch import dispatch\n\n\n@pf.register_dataframe_method\ndef fill_direction(df: pd.DataFrame, **kwargs) -> pd.DataFrame:\n    \"\"\"\n    Provide a method-chainable function for filling missing values\n    in selected columns.\n\n    It is a wrapper for `pd.Series.ffill` and `pd.Series.bfill`,\n    and pairs the column name with one of `up`, `down`, `updown`,\n    and `downup`.\n\n\n    Functional usage syntax:\n\n    ```python\n    import pandas as pd\n    import janitor as jn\n\n    df = pd.DataFrame(...)\n    df = jn.fill_direction(\n                df = df,\n                column_1 = direction_1,\n                column_2 = direction_2,\n            )\n    ```\n\n    Method-chaining usage syntax:\n\n    ```python\n    import pandas as pd\n    import janitor as jn\n\n    df = pd.DataFrame(...)\n            .fill_direction(\n                column_1 = direction_1,\n                column_2 = direction_2,\n            )\n    ```\n\n    :param df: A pandas DataFrame.\n    :param kwargs: Key - value pairs of columns and directions.\n        Directions can be either `down`, `up`, `updown`\n        (fill up then down) and `downup` (fill down then up).\n    :returns: A pandas DataFrame with modified column(s).\n    :raises ValueError: if direction supplied is not one of `down`, `up`,\n        `updown`, or `downup`.\n    \"\"\"\n\n    if not kwargs:\n        return df\n\n    fill_types = {fill.name for fill in _FILLTYPE}\n    for column_name, fill_type in kwargs.items():\n        check(\"column_name\", column_name, [str])\n        check(\"fill_type\", fill_type, [str])\n        if fill_type.upper() not in fill_types:\n            raise ValueError(\n                \"\"\"\n                fill_type should be one of\n                up, down, updown, or downup.\n                \"\"\"\n            )\n\n    check_column(df, kwargs)\n\n    new_values = {}\n    for column_name, fill_type in kwargs.items():\n        direction = _FILLTYPE[f\"{fill_type.upper()}\"].value\n        if len(direction) == 1:\n            direction = methodcaller(direction[0])\n            output = direction(df[column_name])\n        else:\n            direction = [methodcaller(entry) for entry in direction]\n            output = _chain_func(df[column_name], *direction)\n        new_values[column_name] = output\n\n    return df.assign(**new_values)\n\n\ndef _chain_func(column: pd.Series, *funcs):\n    \"\"\"\n    Apply series of functions consecutively\n    to a Series.\n    https://blog.finxter.com/how-to-chain-multiple-function-calls-in-python/\n    \"\"\"\n    new_value = column.copy()\n    for func in funcs:\n        new_value = func(new_value)\n    return new_value\n\n\nclass _FILLTYPE(Enum):\n    \"\"\"List of fill types for fill_direction.\"\"\"\n\n    UP = (\"bfill\",)\n    DOWN = (\"ffill\",)\n    UPDOWN = \"bfill\", \"ffill\"\n    DOWNUP = \"ffill\", \"bfill\"\n\n\n@pf.register_dataframe_method\n@deprecated_alias(columns=\"column_names\")\ndef fill_empty(\n    df: pd.DataFrame, column_names: Union[str, Iterable[str], Hashable], value\n) -> pd.DataFrame:\n    \"\"\"\n    Fill `NaN` values in specified columns with a given value.\n\n    Super sugary syntax that wraps `pandas.DataFrame.fillna`.\n\n    This method mutates the original DataFrame.\n\n    Functional usage syntax:\n\n    ```python\n        df = fill_empty(df, column_names=[col1, col2], value=0)\n    ```\n\n    Method chaining syntax:\n\n    ```python\n        import pandas as pd\n        import janitor\n        df = pd.DataFrame(...).fill_empty(column_names=col1, value=0)\n    ```\n\n    :param df: A pandas DataFrame.\n    :param column_names: column_names: A column name or an iterable (list\n        or tuple) of column names. If a single column name is passed in, then\n        only that column will be filled; if a list or tuple is passed in, then\n        those columns will all be filled with the same value.\n    :param value: The value that replaces the `NaN` values.\n    :returns: A pandas DataFrame with `NaN` values filled.\n    \"\"\"\n    check_column(df, column_names)\n    return _fill_empty(df, column_names, value=value)\n\n\n@dispatch(pd.DataFrame, abcIterable)\ndef _fill_empty(df, column_names, value=None):\n    \"\"\"Fill empty function for the case that column_names is list or tuple.\"\"\"\n    fill_mapping = {c: value for c in column_names}\n    return df.fillna(value=fill_mapping)\n\n\n@dispatch(pd.DataFrame, str)  # noqa: F811\ndef _fill_empty(df, column_names, value=None):  # noqa: F811\n    \"\"\"Fill empty function for the case that column_names is a string.\"\"\"\n    fill_mapping = {column_names: value}\n    return df.fillna(value=fill_mapping)\n", "path": "janitor/functions/fill.py"}]}