We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[INF] Allow `import_message()` to be Python distribution flexible
# Brief Description

<!-- Please provide a brief description of what you'd like to propose. -->

Currently, if a user attempts to user a feature of an optional external package (`rdkit`, `biopython`, `unyt`, `pyspark`) which is not installed, the user receives an error that directs them on how to install it. The error message is displayed by `import_message()` which passes instructions on how to install it. Ex:
```
To use the janitor submodule spark, you need to install pyspark.
To do so, use the following command:
    conda install -c conda-forge pyspark
```
With the exception of `rdkit`, I think all of these packages are `pip` installable. It would be nice if this message could decide whether to provide `conda` vs `pip` instructions to the user. Or tell them that the package can only be installed with `conda`. 

This is how the function is currently called:
```python
import_message(submodule="spark", package="pyspark", 
    installation="conda install -c conda-forge pyspark")
```

Not all `conda` installs will use the same channel. One option is to provide both `conda` and `pip` instructions as arguments in the call, and it figures out which to send to the user. If either are `None`, then it is understood to be `pip` or `conda` only.

# Example API

One verbose option would be to extend what currently exists:
```python
import_message(submodule="spark", package="pyspark", 
    conda_installation="conda install -c conda-forge pyspark", 
    pip_installation="pip install pyspark")
```

A more succinct version could be:
```python
import_message(submodule="spark", package="pyspark", 
    conda_channel="conda-forge", pip_install=True)
```
which would use the provided `package` argument, and `conda_channel` could be `None` if it doesn't exist on `conda`.

--- 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/spark/functions.py`
Content:
```
1 """
2 General purpose data cleaning functions for pyspark.
3 """
4 
5 import re
6 
7 from .. import functions as janitor_func
8 from .. import utils as janitor_utils
9 from . import backend
10 
11 try:
12     from pyspark.sql import DataFrame
13 except ImportError:
14     import_message(
15         submodule="spark",
16         package="pyspark",
17         conda_channel="conda-forge",
18         pip_install=True,
19     )
20 
21 
22 @backend.register_dataframe_method
23 def clean_names(
24     df: DataFrame,
25     case_type: str = "lower",
26     remove_special: bool = False,
27     strip_underscores: str = None,
28 ) -> DataFrame:
29     """
30     Clean column names for pyspark dataframe.
31 
32     Takes all column names, converts them to lowercase, then replaces all
33     spaces with underscores.
34 
35     This method does not mutate the original DataFrame.
36 
37     Functional usage example:
38 
39     .. code-block:: python
40 
41         df = clean_names(df)
42 
43     Method chaining example:
44 
45     .. code-block:: python
46 
47         from pyspark.sql import DataFrame
48         import janitor.spark
49         df = DataFrame(...).clean_names()
50 
51     :Example of transformation:
52 
53     .. code-block:: python
54 
55         Columns before: First Name, Last Name, Employee Status, Subject
56         Columns after: first_name, last_name, employee_status, subject
57 
58     :param df: Spark DataFrame object.
59     :param strip_underscores: (optional) Removes the outer underscores from all
60         column names. Default None keeps outer underscores. Values can be
61         either 'left', 'right' or 'both' or the respective shorthand 'l', 'r'
62         and True.
63     :param case_type: (optional) Whether to make columns lower or uppercase.
64         Current case may be preserved with 'preserve',
65         while snake case conversion (from CamelCase or camelCase only)
66         can be turned on using "snake".
67         Default 'lower' makes all characters lowercase.
68     :param remove_special: (optional) Remove special characters from columns.
69         Only letters, numbers and underscores are preserved.
70     :returns: A Spark DataFrame.
71     """
72 
73     cols = df.columns
74 
75     cols = [janitor_func._change_case(col, case_type) for col in cols]
76 
77     cols = [janitor_func._normalize_1(col) for col in cols]
78 
79     if remove_special:
80         cols = [janitor_func._remove_special(col) for col in cols]
81 
82     cols = [re.sub("_+", "_", col) for col in cols]
83 
84     cols = [
85         janitor_utils._strip_underscores_func(col, strip_underscores)
86         for col in cols
87     ]
88 
89     cols = [
90         f"`{col}` AS `{new_col}`" for col, new_col in zip(df.columns, cols)
91     ]
92 
93     return df.selectExpr(*cols)
94
```
--- 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/spark/functions.py b/janitor/spark/functions.py
--- a/janitor/spark/functions.py
+++ b/janitor/spark/functions.py
@@ -11,7 +11,7 @@
 try:
     from pyspark.sql import DataFrame
 except ImportError:
-    import_message(
+    janitor_utils.import_message(
         submodule="spark",
         package="pyspark",
         conda_channel="conda-forge",

{"golden_diff": "diff --git a/janitor/spark/functions.py b/janitor/spark/functions.py\n--- a/janitor/spark/functions.py\n+++ b/janitor/spark/functions.py\n@@ -11,7 +11,7 @@\n try:\n     from pyspark.sql import DataFrame\n except ImportError:\n-    import_message(\n+    janitor_utils.import_message(\n         submodule=\"spark\",\n         package=\"pyspark\",\n         conda_channel=\"conda-forge\",\n", "issue": "[INF] Allow `import_message()` to be Python distribution flexible\n# Brief Description\r\n\r\n<!-- Please provide a brief description of what you'd like to propose. -->\r\n\r\nCurrently, if a user attempts to user a feature of an optional external package (`rdkit`, `biopython`, `unyt`, `pyspark`) which is not installed, the user receives an error that directs them on how to install it. The error message is displayed by `import_message()` which passes instructions on how to install it. Ex:\r\n```\r\nTo use the janitor submodule spark, you need to install pyspark.\r\nTo do so, use the following command:\r\n    conda install -c conda-forge pyspark\r\n```\r\nWith the exception of `rdkit`, I think all of these packages are `pip` installable. It would be nice if this message could decide whether to provide `conda` vs `pip` instructions to the user. Or tell them that the package can only be installed with `conda`. \r\n\r\nThis is how the function is currently called:\r\n```python\r\nimport_message(submodule=\"spark\", package=\"pyspark\", \r\n    installation=\"conda install -c conda-forge pyspark\")\r\n```\r\n\r\nNot all `conda` installs will use the same channel. One option is to provide both `conda` and `pip` instructions as arguments in the call, and it figures out which to send to the user. If either are `None`, then it is understood to be `pip` or `conda` only.\r\n\r\n# Example API\r\n\r\nOne verbose option would be to extend what currently exists:\r\n```python\r\nimport_message(submodule=\"spark\", package=\"pyspark\", \r\n    conda_installation=\"conda install -c conda-forge pyspark\", \r\n    pip_installation=\"pip install pyspark\")\r\n```\r\n\r\nA more succinct version could be:\r\n```python\r\nimport_message(submodule=\"spark\", package=\"pyspark\", \r\n    conda_channel=\"conda-forge\", pip_install=True)\r\n```\r\nwhich would use the provided `package` argument, and `conda_channel` could be `None` if it doesn't exist on `conda`.\n", "before_files": [{"content": "\"\"\"\nGeneral purpose data cleaning functions for pyspark.\n\"\"\"\n\nimport re\n\nfrom .. import functions as janitor_func\nfrom .. import utils as janitor_utils\nfrom . import backend\n\ntry:\n    from pyspark.sql import DataFrame\nexcept ImportError:\n    import_message(\n        submodule=\"spark\",\n        package=\"pyspark\",\n        conda_channel=\"conda-forge\",\n        pip_install=True,\n    )\n\n\n@backend.register_dataframe_method\ndef clean_names(\n    df: DataFrame,\n    case_type: str = \"lower\",\n    remove_special: bool = False,\n    strip_underscores: str = None,\n) -> DataFrame:\n    \"\"\"\n    Clean column names for pyspark dataframe.\n\n    Takes all column names, converts them to lowercase, then replaces all\n    spaces with underscores.\n\n    This method does not mutate the original DataFrame.\n\n    Functional usage example:\n\n    .. code-block:: python\n\n        df = clean_names(df)\n\n    Method chaining example:\n\n    .. code-block:: python\n\n        from pyspark.sql import DataFrame\n        import janitor.spark\n        df = DataFrame(...).clean_names()\n\n    :Example of transformation:\n\n    .. code-block:: python\n\n        Columns before: First Name, Last Name, Employee Status, Subject\n        Columns after: first_name, last_name, employee_status, subject\n\n    :param df: Spark DataFrame object.\n    :param strip_underscores: (optional) Removes the outer underscores from all\n        column names. Default None keeps outer underscores. Values can be\n        either 'left', 'right' or 'both' or the respective shorthand 'l', 'r'\n        and True.\n    :param case_type: (optional) Whether to make columns lower or uppercase.\n        Current case may be preserved with 'preserve',\n        while snake case conversion (from CamelCase or camelCase only)\n        can be turned on using \"snake\".\n        Default 'lower' makes all characters lowercase.\n    :param remove_special: (optional) Remove special characters from columns.\n        Only letters, numbers and underscores are preserved.\n    :returns: A Spark DataFrame.\n    \"\"\"\n\n    cols = df.columns\n\n    cols = [janitor_func._change_case(col, case_type) for col in cols]\n\n    cols = [janitor_func._normalize_1(col) for col in cols]\n\n    if remove_special:\n        cols = [janitor_func._remove_special(col) for col in cols]\n\n    cols = [re.sub(\"_+\", \"_\", col) for col in cols]\n\n    cols = [\n        janitor_utils._strip_underscores_func(col, strip_underscores)\n        for col in cols\n    ]\n\n    cols = [\n        f\"`{col}` AS `{new_col}`\" for col, new_col in zip(df.columns, cols)\n    ]\n\n    return df.selectExpr(*cols)\n", "path": "janitor/spark/functions.py"}], "after_files": [{"content": "\"\"\"\nGeneral purpose data cleaning functions for pyspark.\n\"\"\"\n\nimport re\n\nfrom .. import functions as janitor_func\nfrom .. import utils as janitor_utils\nfrom . import backend\n\ntry:\n    from pyspark.sql import DataFrame\nexcept ImportError:\n    janitor_utils.import_message(\n        submodule=\"spark\",\n        package=\"pyspark\",\n        conda_channel=\"conda-forge\",\n        pip_install=True,\n    )\n\n\n@backend.register_dataframe_method\ndef clean_names(\n    df: DataFrame,\n    case_type: str = \"lower\",\n    remove_special: bool = False,\n    strip_underscores: str = None,\n) -> DataFrame:\n    \"\"\"\n    Clean column names for pyspark dataframe.\n\n    Takes all column names, converts them to lowercase, then replaces all\n    spaces with underscores.\n\n    This method does not mutate the original DataFrame.\n\n    Functional usage example:\n\n    .. code-block:: python\n\n        df = clean_names(df)\n\n    Method chaining example:\n\n    .. code-block:: python\n\n        from pyspark.sql import DataFrame\n        import janitor.spark\n        df = DataFrame(...).clean_names()\n\n    :Example of transformation:\n\n    .. code-block:: python\n\n        Columns before: First Name, Last Name, Employee Status, Subject\n        Columns after: first_name, last_name, employee_status, subject\n\n    :param df: Spark DataFrame object.\n    :param strip_underscores: (optional) Removes the outer underscores from all\n        column names. Default None keeps outer underscores. Values can be\n        either 'left', 'right' or 'both' or the respective shorthand 'l', 'r'\n        and True.\n    :param case_type: (optional) Whether to make columns lower or uppercase.\n        Current case may be preserved with 'preserve',\n        while snake case conversion (from CamelCase or camelCase only)\n        can be turned on using \"snake\".\n        Default 'lower' makes all characters lowercase.\n    :param remove_special: (optional) Remove special characters from columns.\n        Only letters, numbers and underscores are preserved.\n    :returns: A Spark DataFrame.\n    \"\"\"\n\n    cols = df.columns\n\n    cols = [janitor_func._change_case(col, case_type) for col in cols]\n\n    cols = [janitor_func._normalize_1(col) for col in cols]\n\n    if remove_special:\n        cols = [janitor_func._remove_special(col) for col in cols]\n\n    cols = [re.sub(\"_+\", \"_\", col) for col in cols]\n\n    cols = [\n        janitor_utils._strip_underscores_func(col, strip_underscores)\n        for col in cols\n    ]\n\n    cols = [\n        f\"`{col}` AS `{new_col}`\" for col, new_col in zip(df.columns, cols)\n    ]\n\n    return df.selectExpr(*cols)\n", "path": "janitor/spark/functions.py"}]}