Detects and opens compressed file for reading and writing.

    Args:
        fileobj (File): any File-like object supported by an underlying
                        compression algorithm

        mode (unicode): mode to open fileobj with

        **kwargs: keyword-arguments to pass to the compression algorithm

    Returns:
        File: TextWrapper if no compression, else returns appropriate
              wrapper for the compression type

    Example:
        .. code-block:: Python

            >>> from tempfile import NamedTemporaryFile
            >>> # Write compressed file
            >>> temp = NamedTemporaryFile(delete=False, suffix='.bz2')
            >>> test_bz2 = copen(temp.name, 'wb')
            >>> test_bz2.write(b'bzip2')
            >>> test_bz2.close()
            >>> # Read compressed bzip file
            >>> test_bz2 = copen(temp.name, 'rb')
            >>> test_bz2.read()
            b'bzip2'

def copen(fileobj, mode='rb', **kwargs):
    """Detects and opens compressed file for reading and writing.

    Args:
        fileobj (File): any File-like object supported by an underlying
                        compression algorithm

        mode (unicode): mode to open fileobj with

        **kwargs: keyword-arguments to pass to the compression algorithm

    Returns:
        File: TextWrapper if no compression, else returns appropriate
              wrapper for the compression type

    Example:
        .. code-block:: Python

            >>> from tempfile import NamedTemporaryFile
            >>> # Write compressed file
            >>> temp = NamedTemporaryFile(delete=False, suffix='.bz2')
            >>> test_bz2 = copen(temp.name, 'wb')
            >>> test_bz2.write(b'bzip2')
            >>> test_bz2.close()
            >>> # Read compressed bzip file
            >>> test_bz2 = copen(temp.name, 'rb')
            >>> test_bz2.read()
            b'bzip2'
    """

    algo = io.open  # Only used as io.open in write mode
    mode = mode.lower().strip()
    modules = {}  # Later populated by compression algorithms
    write_mode = False if mode.lstrip('U')[0] == 'r' else True
    kwargs['mode'] = mode

    # Currently supported compression algorithms
    modules_to_import = {
        'bz2': 'BZ2File',
        'gzip': 'GzipFile',
        'lzma': 'LZMAFile'
    }

    # Dynamically import compression libraries and warn about failures
    for mod, _class in modules_to_import.items():
        try:
            modules[_class] = getattr(import_module(mod), _class)
        except (ImportError, AttributeError) as e:
            modules[_class] = open
            warn('Cannot process {0} files due to following error:'
                 '{1}{2}{1}You will need to install the {0} library to '
                 'properly use these files. Currently, such files will '
                 'open in "text" mode.'.format(mod, linesep, e))

    # Write mode
    if write_mode is True:

        # Map file extensions to decompression classes
        algo_map = {
            'bz2': modules['BZ2File'],
            'gz':  modules['GzipFile'],
            'xz':  modules['LZMAFile']
        }

        # Determine the compression algorithm via the file extension
        ext = fileobj.split('.')[-1]
        try:
            algo = algo_map[ext]
        except KeyError:
            pass

    # Read mode
    else:

        algo = io.TextIOWrapper  # Default to plaintext buffer

        # Magic headers of encryption formats
        file_sigs = {
            b'\x42\x5a\x68': modules['BZ2File'],
            b'\x1f\x8b\x08': modules['GzipFile'],
            b'\xfd7zXZ\x00': modules['LZMAFile']
        }

        # Open the file, buffer it, and identify the compression algorithm
        fileobj = io.BufferedReader(io.open(fileobj, 'rb'))
        max_len = max(len(x) for x in file_sigs.keys())
        start = fileobj.peek(max_len)
        for sig in file_sigs.keys():
            if start.startswith(sig):
                algo = file_sigs[sig]
                break  # Stop iterating once a good signature is found

    # Filter all **kwargs by the args accepted by the compression algorithm
    algo_args = set(getfullargspec(algo).args)
    good_args = set(kwargs.keys()).intersection(algo_args)
    _kwargs = {arg: kwargs[arg] for arg in good_args}

    # Open the file using parameters defined above and store in namespace
    if write_mode is True:
        handle = algo(fileobj, **_kwargs)
    else:
        try:  # For algorithms that need to be explicitly given a fileobj
            handle = algo(fileobj=fileobj, **_kwargs)
        except TypeError:  # For algorithms that detect file objects
            handle = algo(fileobj, **_kwargs)

    return handle