Read from the store, close it if we opened it.

    Retrieve pandas object stored in file, optionally based on where
    criteria

    Parameters
    ----------
    path_or_buf : string, buffer or path object
        Path to the file to open, or an open :class:`pandas.HDFStore` object.
        Supports any object implementing the ``__fspath__`` protocol.
        This includes :class:`pathlib.Path` and py._path.local.LocalPath
        objects.

        .. versionadded:: 0.19.0 support for pathlib, py.path.
        .. versionadded:: 0.21.0 support for __fspath__ protocol.

    key : object, optional
        The group identifier in the store. Can be omitted if the HDF file
        contains a single pandas object.
    mode : {'r', 'r+', 'a'}, optional
        Mode to use when opening the file. Ignored if path_or_buf is a
        :class:`pandas.HDFStore`. Default is 'r'.
    where : list, optional
        A list of Term (or convertible) objects.
    start : int, optional
        Row number to start selection.
    stop  : int, optional
        Row number to stop selection.
    columns : list, optional
        A list of columns names to return.
    iterator : bool, optional
        Return an iterator object.
    chunksize : int, optional
        Number of rows to include in an iteration when using an iterator.
    errors : str, default 'strict'
        Specifies how encoding and decoding errors are to be handled.
        See the errors argument for :func:`open` for a full list
        of options.
    **kwargs
        Additional keyword arguments passed to HDFStore.

    Returns
    -------
    item : object
        The selected object. Return type depends on the object stored.

    See Also
    --------
    DataFrame.to_hdf : Write a HDF file from a DataFrame.
    HDFStore : Low-level access to HDF files.

    Examples
    --------
    >>> df = pd.DataFrame([[1, 1.0, 'a']], columns=['x', 'y', 'z'])
    >>> df.to_hdf('./store.h5', 'data')
    >>> reread = pd.read_hdf('./store.h5')

def read_hdf(path_or_buf, key=None, mode='r', **kwargs):
    """
    Read from the store, close it if we opened it.

    Retrieve pandas object stored in file, optionally based on where
    criteria

    Parameters
    ----------
    path_or_buf : string, buffer or path object
        Path to the file to open, or an open :class:`pandas.HDFStore` object.
        Supports any object implementing the ``__fspath__`` protocol.
        This includes :class:`pathlib.Path` and py._path.local.LocalPath
        objects.

        .. versionadded:: 0.19.0 support for pathlib, py.path.
        .. versionadded:: 0.21.0 support for __fspath__ protocol.

    key : object, optional
        The group identifier in the store. Can be omitted if the HDF file
        contains a single pandas object.
    mode : {'r', 'r+', 'a'}, optional
        Mode to use when opening the file. Ignored if path_or_buf is a
        :class:`pandas.HDFStore`. Default is 'r'.
    where : list, optional
        A list of Term (or convertible) objects.
    start : int, optional
        Row number to start selection.
    stop  : int, optional
        Row number to stop selection.
    columns : list, optional
        A list of columns names to return.
    iterator : bool, optional
        Return an iterator object.
    chunksize : int, optional
        Number of rows to include in an iteration when using an iterator.
    errors : str, default 'strict'
        Specifies how encoding and decoding errors are to be handled.
        See the errors argument for :func:`open` for a full list
        of options.
    **kwargs
        Additional keyword arguments passed to HDFStore.

    Returns
    -------
    item : object
        The selected object. Return type depends on the object stored.

    See Also
    --------
    DataFrame.to_hdf : Write a HDF file from a DataFrame.
    HDFStore : Low-level access to HDF files.

    Examples
    --------
    >>> df = pd.DataFrame([[1, 1.0, 'a']], columns=['x', 'y', 'z'])
    >>> df.to_hdf('./store.h5', 'data')
    >>> reread = pd.read_hdf('./store.h5')
    """

    if mode not in ['r', 'r+', 'a']:
        raise ValueError('mode {0} is not allowed while performing a read. '
                         'Allowed modes are r, r+ and a.'.format(mode))
    # grab the scope
    if 'where' in kwargs:
        kwargs['where'] = _ensure_term(kwargs['where'], scope_level=1)

    if isinstance(path_or_buf, HDFStore):
        if not path_or_buf.is_open:
            raise IOError('The HDFStore must be open for reading.')

        store = path_or_buf
        auto_close = False
    else:
        path_or_buf = _stringify_path(path_or_buf)
        if not isinstance(path_or_buf, str):
            raise NotImplementedError('Support for generic buffers has not '
                                      'been implemented.')
        try:
            exists = os.path.exists(path_or_buf)

        # if filepath is too long
        except (TypeError, ValueError):
            exists = False

        if not exists:
            raise FileNotFoundError(
                'File {path} does not exist'.format(path=path_or_buf))

        store = HDFStore(path_or_buf, mode=mode, **kwargs)
        # can't auto open/close if we are using an iterator
        # so delegate to the iterator
        auto_close = True

    try:
        if key is None:
            groups = store.groups()
            if len(groups) == 0:
                raise ValueError('No dataset in HDF5 file.')
            candidate_only_group = groups[0]

            # For the HDF file to have only one dataset, all other groups
            # should then be metadata groups for that candidate group. (This
            # assumes that the groups() method enumerates parent groups
            # before their children.)
            for group_to_check in groups[1:]:
                if not _is_metadata_of(group_to_check, candidate_only_group):
                    raise ValueError('key must be provided when HDF5 file '
                                     'contains multiple datasets.')
            key = candidate_only_group._v_pathname
        return store.select(key, auto_close=auto_close, **kwargs)
    except (ValueError, TypeError, KeyError):
        # if there is an error, close the store
        try:
            store.close()
        except AttributeError:
            pass

        raise