prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <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`.
  
  </issue>
  <code>
  [start of janitor/spark/functions.py]
  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 of janitor/spark/functions.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: 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",
  
verification_info: {"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"}]}