def excel_column_name(index):
    """
    Converts 1-based index to the Excel column name.

    Parameters
    ----------
    index : int
        The column number. Must be 1-based, ie. the first column
        number is 1 rather than 0.

    Returns
    -------
    col_name : str
        The column name for the input index, eg. an index of 1 returns 'A'.

    Raises
    ------
    ValueError
        Raised if the input index is not in the range 1 <= index <= 18278,
        meaning the column name is not within 'A'...'ZZZ'.

    Notes
    -----
    Caches the result so that any repeated index lookups are faster,
    and uses recursion to make better usage of the cache.

    chr(64 + remainder) converts the remainder to a character, where
    64 denotes ord('A') - 1, so if remainder = 1, chr(65) = 'A'.

    """

    if not 1 <= index <= 18278: # ensures column is between 'A' and 'ZZZ'.
        raise ValueError(f'Column index {index} must be between 1 and 18278.')

    col_num, remainder = divmod(index, 26)
    # ensure remainder is between 1 and 26
    if remainder == 0:
        remainder = 26
        col_num -= 1

    if col_num > 0:
        return excel_column_name(col_num) + chr(64 + remainder)
    else:
        return chr(64 + remainder)