Wraps pandas.IO & odo to create a dictionary of pandas.DataFrames from multiple different sources
    
    Parameters
    ----------
    path : str
        Location of file, folder or zip-file to be parsed. Can include globbing (e.g. `*.csv`).
        Can be remote with URI-notation beginning with e.g. http://, https://, file://, ftp://, s3:// and ssh://.
        Can be odo-supported database (SQL, MongoDB, Hadoop, Spark) if dependencies are available.
        Parser will be selected based on file extension.
    cfg : dict or str, optional
        Dictionary of kwargs to be provided to the pandas parser (http://pandas.pydata.org/pandas-docs/stable/api.html#input-output)
        or str with path to YAML, that will be parsed.
        
        Special keys:
        
        **filters** : str or list of strings, optional. For a file to be processed, it must contain one of the Strings (e.g. ['.csv', '.tsv'])
        
        **default** : kwargs to be used for every file
        
        **custom_date_parser** : strptime-format string (https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior), generates a parser that used as the *date_parser* argument
        
        If filename in keys, use kwargs from that key in addition to or overwriting *default* kwargs.
    silent : boolean, optional
        If True, doesn't print to stdout.
    verbose : boolean, optional
        If True, prints parsing arguments for each file processed to stdout.
    raise_on_error : boolean, optional
        Raise exception or only display warning, if a file cannot be parsed successfully.
    return_errors : boolean, optional
        If True, read() returns (data, errors) tuple instead of only data, with errors as a list of all files that could not be parsed.
        
    Returns
    -------
    data : dict
        Dictionary of parsed pandas.DataFrames, with file names as keys.
    
    Notes
    -----
    - Start with basic cfg and tune until the desired parsing result is achieved.
    - File extensions are critical to determine the parser, make sure they are *common*.
    - Avoid files named 'default' or 'filters'.
    - Avoid duplicate file names.
    - Subfolders and file names beginning with '.' or '_' are ignored.
    - If an https:// URI isn't correctly processed, try http:// instead.
    - To connect to a database or s3-bucket, make sure the required dependencies like sqlalchemy, pymongo, pyspark or boto are available in the active environment.

def read(path, cfg={}, raise_on_error=False, silent=False, verbose=False, return_errors=False):
    """Wraps pandas.IO & odo to create a dictionary of pandas.DataFrames from multiple different sources
    
    Parameters
    ----------
    path : str
        Location of file, folder or zip-file to be parsed. Can include globbing (e.g. `*.csv`).
        Can be remote with URI-notation beginning with e.g. http://, https://, file://, ftp://, s3:// and ssh://.
        Can be odo-supported database (SQL, MongoDB, Hadoop, Spark) if dependencies are available.
        Parser will be selected based on file extension.
    cfg : dict or str, optional
        Dictionary of kwargs to be provided to the pandas parser (http://pandas.pydata.org/pandas-docs/stable/api.html#input-output)
        or str with path to YAML, that will be parsed.
        
        Special keys:
        
        **filters** : str or list of strings, optional. For a file to be processed, it must contain one of the Strings (e.g. ['.csv', '.tsv'])
        
        **default** : kwargs to be used for every file
        
        **custom_date_parser** : strptime-format string (https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior), generates a parser that used as the *date_parser* argument
        
        If filename in keys, use kwargs from that key in addition to or overwriting *default* kwargs.
    silent : boolean, optional
        If True, doesn't print to stdout.
    verbose : boolean, optional
        If True, prints parsing arguments for each file processed to stdout.
    raise_on_error : boolean, optional
        Raise exception or only display warning, if a file cannot be parsed successfully.
    return_errors : boolean, optional
        If True, read() returns (data, errors) tuple instead of only data, with errors as a list of all files that could not be parsed.
        
    Returns
    -------
    data : dict
        Dictionary of parsed pandas.DataFrames, with file names as keys.
    
    Notes
    -----
    - Start with basic cfg and tune until the desired parsing result is achieved.
    - File extensions are critical to determine the parser, make sure they are *common*.
    - Avoid files named 'default' or 'filters'.
    - Avoid duplicate file names.
    - Subfolders and file names beginning with '.' or '_' are ignored.
    - If an https:// URI isn't correctly processed, try http:// instead.
    - To connect to a database or s3-bucket, make sure the required dependencies like sqlalchemy, pymongo, pyspark or boto are available in the active environment.
    """
    
    if type(cfg) == str:
        cfg = os.path.abspath(os.path.expanduser(cfg))
        yml = _read_yaml(cfg)
        if yml == None:
            if not silent:
                print('creating read.yml config file draft ...')
            cfg = {'filters': ['.csv'], 'default': {'sep': ',', 'parse_dates': []}}
            with open('local/read.yml', 'xt') as f:
                yaml.dump(cfg, f)
            yml = _read_yaml('local/read.yml')
        if 'filters' in yml:
            filters = yml['filters']
            if type(filters) == str:
                filters = [filters]
            del yml['filters']
        else:
            filters = []
        cfg = yml
    data = {}
    errors = []
    
    if not silent:
        print('processing', path, '...')
    
    for f in _path_to_objs(path):
        if type(f) == str:
            fname = os.path.basename(f)
        elif type(f) == zipfile.ZipExtFile:
            fname = f.name
        else:
            raise RuntimeError('_path_to_objs() returned unknown type', f)
        
        data, errors = _read_append(data=data, errors=errors, path=f, fname=fname, \
            cfg=cfg, raise_on_error=raise_on_error, silent=silent, verbose=verbose)
        
    if raise_on_error and data == {}:
        raise IOError('path is invalid or empty')
    
    if not silent:
        print('imported {} DataFrames'.format(len(data)))
        if len(data) > 0:
            print('total memory usage: {}'.format(mem(data)))
        if len(errors) > 0:
            print('import errors in files: {}'.format(', '.join(errors)))

    if return_errors:
        return data, errors
    else:
        return data