def to_df(obj, cfg={}, raise_on_error=True, silent=False, verbose=False):
    """Convert obj to pandas.DataFrame, determine parser from filename.
    Falls back to odo, esp. for database uri's.
    """
    
    if type(obj) == str:
        name = obj
    else:
        name = obj.name
    name = name[name.rfind('/') + 1:]
    
    if not raise_on_error:
        try:
            return to_df(obj=obj, cfg=cfg, raise_on_error=True)
        except (pd.parser.CParserError, AttributeError, ValueError, TypeError, IOError) as e:
            if not silent:
                print('WARNING in {}: {} {}'.format(name, e.__class__, e))
            return None
        except:
            if not silent:
                print('WARNING in {}: {}'.format(name, sys.exc_info()[0]))
            return None

    params = {}
    if 'default' in cfg:
        params = deepcopy(cfg['default'])
    if name in cfg:
        for e in cfg[name]:
            params[e] = deepcopy(cfg[name][e])
    if 'custom_date_parser' in params:
        params['date_parser'] = DtParser(params['custom_date_parser']).parse
        del params['custom_date_parser']

    if verbose:
        print(name, params)
    
    if name.lower().startswith('s3:'):
        if not find_spec('boto'):
            raise ImportError('reading from aws-s3 requires the boto package to be installed')
    
    if '.csv' in name.lower():
        # name can be .csv.gz or .csv.bz2
        return pd.read_csv(obj, **params)
        
    elif '.tsv' in name.lower() or '.txt' in name.lower():
        # name can be .tsv.gz or .txt.bz2
        return pd.read_table(obj, **params)
    
    elif name.lower().endswith(('.htm', '.html')):
        if not find_spec('lxml'):
            params['flavor'] = 'bs4'
            if not find_spec('bs4') and not find_spec('html5lib'):
                raise ImportError('reading html requires the lxml or bs4 + html5lib packages to be installed')

        if 'nrows' in params:
            del params['nrows']
        
        if type(obj) == zipfile.ZipExtFile:
            obj = obj.read()
        data = pd.read_html(obj, **params)
        data = {str(i): data[i] for i in range(len(data))}
        return data
    
    elif name.lower().endswith('.xml'):
        if 'nrows' in params:
            del params['nrows']
        
        from icy.utils import xml_to_json
        
        with open(obj) as f:
            json = xml_to_json(str(f.read()))
        
        return pd.read_json(json, **params)
        
    elif name.lower().endswith('.json'):
        if 'nrows' in params:
            del params['nrows']
        
        return pd.read_json(obj, **params)
    
    elif name.lower().endswith(('.xls', '.xlsx')):
        if not find_spec('xlrd'):
            raise ImportError('reading excel files requires the xlrd package to be installed')
        
        if 'nrows' in params:
            del params['nrows']
        
        data = {}
        xls = pd.ExcelFile(obj)
        for key in xls.sheet_names:
            data[key] = xls.parse(key, **params)
        return data
    
    elif name.lower().endswith(('.h5', '.hdf5')):
        if not find_spec('tables'):
            raise ImportError('reading hdf5 files requires the pytables package to be installed')
        
        if 'nrows' in params:
            del params['nrows']
            # params['chunksize'] = params.pop('nrows') # returns iterator
        
        with pd.HDFStore(obj) as store:
            data = {}
            for key in store.keys():
                data[key[1:]] = store[key]
        return data
    
    elif name.lower().endswith(('.sqlite', '.sql', '.db')):
        import sqlite3
        if type(obj) != str:
            raise IOError('sqlite-database must be decompressed before import')
        
        if 'nrows' in params:
            del params['nrows']
            # params['chunksize'] = params.pop('nrows') # returns iterator
        
        with sqlite3.connect(obj) as con:
            data = {}
            cursor = con.cursor()
            cursor.execute('SELECT name FROM sqlite_master WHERE type="table"')
            tables = [t[0] for t in cursor.fetchall()]
            for t in tables:
                sql = 'SELECT * FROM ' + t
                data[t] = pd.read_sql_query(sql, con, **params)
        return data
    
    else:
        try:
            data = {name: odo(obj, pd.DataFrame)}
            if type(data[name]) == pd.DataFrame:
                return data
        except NotImplementedError:
            pass
        raise NotImplementedError('Error creating DataFrame from object', obj)