# -*- coding: utf-8 -*-

"""API functions for PyStow."""

import sqlite3
from contextlib import contextmanager
from pathlib import Path
from typing import TYPE_CHECKING, Any, Generator, Mapping, Optional, Sequence, Union

from .constants import JSON, BytesOpener, Opener, Provider
from .impl import Module

if TYPE_CHECKING:
    import lxml.etree
    import numpy.typing
    import pandas as pd
    import rdflib

__all__ = [
    "module",
    "join",
    "joinpath_sqlite",
    # Opener functions
    "open",
    "open_gz",
    # Loader functions
    "load_df",
    "load_json",
    "load_pickle",
    "load_pickle_gz",
    "load_rdf",
    "load_xml",
    # Dump functions
    "dump_df",
    "dump_json",
    "dump_pickle",
    "dump_rdf",
    "dump_xml",
    # Downloader functions
    "ensure",
    "ensure_from_s3",
    "ensure_from_google",
    # Downloader functions with postprocessing
    "ensure_untar",
    "ensure_gunzip",
    # Downloader + opener functions
    "ensure_open",
    "ensure_open_gz",
    "ensure_open_bz2",
    "ensure_open_lzma",
    "ensure_open_tarfile",
    "ensure_open_zip",
    "ensure_open_sqlite",
    "ensure_open_sqlite_gz",
    # Processors
    "ensure_csv",
    "ensure_custom",
    "ensure_json",
    "ensure_json_bz2",
    "ensure_pickle",
    "ensure_pickle_gz",
    "ensure_excel",
    "ensure_xml",
    "ensure_rdf",
    "ensure_tar_df",
    "ensure_tar_xml",
    "ensure_zip_df",
    "ensure_zip_np",
]


def module(key: str, *subkeys: str, ensure_exists: bool = True) -> Module:
    """Return a module for the application.

    :param key:
        The name of the module. No funny characters. The envvar
        <key>_HOME where key is uppercased is checked first before using
        the default home directory.
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param ensure_exists:
        Should all directories be created automatically?
        Defaults to true.
    :return:
        The module object that manages getting and ensuring
    """
    return Module.from_key(key, *subkeys, ensure_exists=ensure_exists)


def join(key: str, *subkeys: str, name: Optional[str] = None, ensure_exists: bool = True) -> Path:
    """Return the home data directory for the given module.

    :param key:
        The name of the module. No funny characters. The envvar
        <key>_HOME where key is uppercased is checked first before using
        the default home directory.
    :param subkeys:
        A sequence of additional strings to join
    :param name:
        The name of the file (optional) inside the folder
    :param ensure_exists:
        Should all directories be created automatically?
        Defaults to true.
    :return:
        The path of the directory or subdirectory for the given module.
    """
    _module = Module.from_key(key, ensure_exists=ensure_exists)
    return _module.join(*subkeys, name=name, ensure_exists=ensure_exists)


@contextmanager
def open(
    key: str,
    *subkeys: str,
    name: str,
    mode: str = "r",
    open_kwargs: Optional[Mapping[str, Any]] = None,
) -> Opener:
    """Open a file that exists already.

    :param key:
        The name of the module. No funny characters. The envvar
        <key>_HOME where key is uppercased is checked first before using
        the default home directory.
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param name: The name of the file to open
    :param mode: The read mode, passed to :func:`open`
    :param open_kwargs: Additional keyword arguments passed to :func:`open`

    :yields: An open file object
    """
    _module = Module.from_key(key, ensure_exists=True)
    with _module.open(*subkeys, name=name, mode=mode, open_kwargs=open_kwargs) as file:
        yield file


@contextmanager
def open_gz(
    key: str,
    *subkeys: str,
    name: str,
    mode: str = "rt",
    open_kwargs: Optional[Mapping[str, Any]] = None,
) -> Opener:
    """Open a gzipped file that exists already.

    :param key:
        The name of the module. No funny characters. The envvar
        <key>_HOME where key is uppercased is checked first before using
        the default home directory.
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param name: The name of the file to open
    :param mode: The read mode, passed to :func:`gzip.open`
    :param open_kwargs: Additional keyword arguments passed to :func:`gzip.open`

    :yields: An open file object
    """
    _module = Module.from_key(key, ensure_exists=True)
    with _module.open_gz(*subkeys, name=name, mode=mode, open_kwargs=open_kwargs) as file:
        yield file


def ensure(
    key: str,
    *subkeys: str,
    url: str,
    name: Optional[str] = None,
    force: bool = False,
    download_kwargs: Optional[Mapping[str, Any]] = None,
) -> Path:
    """Ensure a file is downloaded.

    :param key:
        The name of the module. No funny characters. The envvar
        <key>_HOME where key is uppercased is checked first before using
        the default home directory.
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
    :return:
        The path of the file that has been downloaded (or already exists)
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.ensure(
        *subkeys, url=url, name=name, force=force, download_kwargs=download_kwargs
    )


def ensure_custom(
    key: str,
    *subkeys: str,
    name: str,
    force: bool = False,
    provider: Provider,
    **kwargs: Any,
) -> Path:
    """Ensure a file is present, and run a custom create function otherwise.

    :param key:
        The name of the module. No funny characters. The envvar
        <key>_HOME where key is uppercased is checked first before using
        the default home directory.
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param name:
        The file name.
    :param force:
        Should the file be re-created, even if the path already exists?
    :param provider:
        The file provider. Will be run with the path as the first positional argument,
        if the file needs to be generated.
    :param kwargs:
        Additional keyword-based parameters passed to the provider.

    :return:
        The path of the file that has been created (or already exists)
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.ensure_custom(*subkeys, name=name, force=force, provider=provider, **kwargs)


def ensure_untar(
    key: str,
    *subkeys: str,
    url: str,
    name: Optional[str] = None,
    directory: Optional[str] = None,
    force: bool = False,
    download_kwargs: Optional[Mapping[str, Any]] = None,
    extract_kwargs: Optional[Mapping[str, Any]] = None,
) -> Path:
    """Ensure a file is downloaded and untarred.

    :param key:
        The name of the module. No funny characters. The envvar
        <key>_HOME where key is uppercased is checked first before using
        the default home directory.
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param directory:
        Overrides the name of the directory into which the tar archive is extracted.
        If none given, will use the stem of the file name that gets downloaded.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
    :param extract_kwargs: Keyword arguments to pass to :meth:`tarfile.TarFile.extract_all`.
    :return:
        The path of the directory where the file that has been downloaded
        gets extracted to
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.ensure_untar(
        *subkeys,
        url=url,
        name=name,
        directory=directory,
        force=force,
        download_kwargs=download_kwargs,
        extract_kwargs=extract_kwargs,
    )


def ensure_gunzip(
    key: str,
    *subkeys: str,
    url: str,
    name: Optional[str] = None,
    force: bool = False,
    autoclean: bool = True,
    download_kwargs: Optional[Mapping[str, Any]] = None,
) -> Path:
    """Ensure a file is downloaded and gunzipped.

    :param key:
        The name of the module. No funny characters. The envvar
        <key>_HOME where key is uppercased is checked first before using
        the default home directory.
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param autoclean: Should the zipped file be deleted?
    :param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
    :return:
        The path of the directory where the file that has been downloaded
        gets extracted to
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.ensure_gunzip(
        *subkeys,
        url=url,
        name=name,
        force=force,
        autoclean=autoclean,
        download_kwargs=download_kwargs,
    )


@contextmanager
def ensure_open(
    key: str,
    *subkeys: str,
    url: str,
    name: Optional[str] = None,
    force: bool = False,
    download_kwargs: Optional[Mapping[str, Any]] = None,
    mode: str = "r",
    open_kwargs: Optional[Mapping[str, Any]] = None,
) -> Opener:
    """Ensure a file is downloaded and open it.

    :param key:
        The name of the module. No funny characters. The envvar
        `<key>_HOME` where key is uppercased is checked first before using
        the default home directory.
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
    :param mode: The read mode, passed to :func:`lzma.open`
    :param open_kwargs: Additional keyword arguments passed to :func:`lzma.open`

    :yields: An open file object
    """
    _module = Module.from_key(key, ensure_exists=True)
    with _module.ensure_open(
        *subkeys,
        url=url,
        name=name,
        force=force,
        download_kwargs=download_kwargs,
        mode=mode,
        open_kwargs=open_kwargs,
    ) as yv:
        yield yv


@contextmanager
def ensure_open_zip(
    key: str,
    *subkeys: str,
    url: str,
    inner_path: str,
    name: Optional[str] = None,
    force: bool = False,
    download_kwargs: Optional[Mapping[str, Any]] = None,
    mode: str = "r",
    open_kwargs: Optional[Mapping[str, Any]] = None,
) -> BytesOpener:
    """Ensure a file is downloaded then open it with :mod:`zipfile`.

    :param key:
        The name of the module. No funny characters. The envvar
        `<key>_HOME` where key is uppercased is checked first before using
        the default home directory.
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param inner_path:
        The relative path to the file inside the archive
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
    :param mode: The read mode, passed to :func:`zipfile.open`
    :param open_kwargs: Additional keyword arguments passed to :func:`zipfile.open`

    :yields: An open file object
    """
    _module = Module.from_key(key, ensure_exists=True)
    with _module.ensure_open_zip(
        *subkeys,
        url=url,
        inner_path=inner_path,
        name=name,
        force=force,
        download_kwargs=download_kwargs,
        mode=mode,
        open_kwargs=open_kwargs,
    ) as yv:
        yield yv


@contextmanager
def ensure_open_lzma(
    key: str,
    *subkeys: str,
    url: str,
    name: Optional[str] = None,
    force: bool = False,
    download_kwargs: Optional[Mapping[str, Any]] = None,
    mode: str = "r",
    open_kwargs: Optional[Mapping[str, Any]] = None,
) -> Opener:
    """Ensure a LZMA-compressed file is downloaded and open a file inside it.

    :param key:
        The name of the module. No funny characters. The envvar
        `<key>_HOME` where key is uppercased is checked first before using
        the default home directory.
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
    :param mode: The read mode, passed to :func:`lzma.open`
    :param open_kwargs: Additional keyword arguments passed to :func:`lzma.open`

    :yields: An open file object
    """
    _module = Module.from_key(key, ensure_exists=True)
    with _module.ensure_open_lzma(
        *subkeys,
        url=url,
        name=name,
        force=force,
        download_kwargs=download_kwargs,
        mode=mode,
        open_kwargs=open_kwargs,
    ) as yv:
        yield yv


@contextmanager
def ensure_open_tarfile(
    key: str,
    *subkeys: str,
    url: str,
    inner_path: str,
    name: Optional[str] = None,
    force: bool = False,
    download_kwargs: Optional[Mapping[str, Any]] = None,
    mode: str = "r",
    open_kwargs: Optional[Mapping[str, Any]] = None,
) -> BytesOpener:
    """Ensure a tar file is downloaded and open a file inside it.

    :param key:
        The name of the module. No funny characters. The envvar
        `<key>_HOME` where key is uppercased is checked first before using
        the default home directory.
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param inner_path:
        The relative path to the file inside the archive
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
    :param mode: The read mode, passed to :func:`tarfile.open`
    :param open_kwargs: Additional keyword arguments passed to :func:`tarfile.open`

    :yields: An open file object
    """
    _module = Module.from_key(key, ensure_exists=True)
    with _module.ensure_open_tarfile(
        *subkeys,
        url=url,
        inner_path=inner_path,
        name=name,
        force=force,
        download_kwargs=download_kwargs,
        mode=mode,
        open_kwargs=open_kwargs,
    ) as yv:
        yield yv


@contextmanager
def ensure_open_gz(
    key: str,
    *subkeys: str,
    url: str,
    name: Optional[str] = None,
    force: bool = False,
    download_kwargs: Optional[Mapping[str, Any]] = None,
    mode: str = "rb",
    open_kwargs: Optional[Mapping[str, Any]] = None,
) -> Opener:
    """Ensure a gzipped file is downloaded and open a file inside it.

    :param key:
        The name of the module. No funny characters. The envvar
        `<key>_HOME` where key is uppercased is checked first before using
        the default home directory.
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
    :param mode: The read mode, passed to :func:`gzip.open`
    :param open_kwargs: Additional keyword arguments passed to :func:`gzip.open`

    :yields: An open file object
    """
    _module = Module.from_key(key, ensure_exists=True)
    with _module.ensure_open_gz(
        *subkeys,
        url=url,
        name=name,
        force=force,
        download_kwargs=download_kwargs,
        mode=mode,
        open_kwargs=open_kwargs,
    ) as yv:
        yield yv


@contextmanager
def ensure_open_bz2(
    key: str,
    *subkeys: str,
    url: str,
    name: Optional[str] = None,
    force: bool = False,
    download_kwargs: Optional[Mapping[str, Any]] = None,
    mode: str = "rb",
    open_kwargs: Optional[Mapping[str, Any]] = None,
) -> Opener:
    """Ensure a BZ2-compressed file is downloaded and open a file inside it.

    :param key:
        The name of the module. No funny characters. The envvar
        `<key>_HOME` where key is uppercased is checked first before using
        the default home directory.
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
    :param mode: The read mode, passed to :func:`bz2.open`
    :param open_kwargs: Additional keyword arguments passed to :func:`bz2.open`

    :yields: An open file object
    """
    _module = Module.from_key(key, ensure_exists=True)
    with _module.ensure_open_bz2(
        *subkeys,
        url=url,
        name=name,
        force=force,
        download_kwargs=download_kwargs,
        mode=mode,
        open_kwargs=open_kwargs,
    ) as yv:
        yield yv


def ensure_csv(
    key: str,
    *subkeys: str,
    url: str,
    name: Optional[str] = None,
    force: bool = False,
    download_kwargs: Optional[Mapping[str, Any]] = None,
    read_csv_kwargs: Optional[Mapping[str, Any]] = None,
) -> "pd.DataFrame":
    """Download a CSV and open as a dataframe with :mod:`pandas`.

    :param key: The module name
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
    :param read_csv_kwargs: Keyword arguments to pass through to :func:`pandas.read_csv`.

        .. note::

            It is assumed that the CSV uses tab separators, as this is the
            only safe option. For more information, see
            `Wikipedia <https://en.wikipedia.org/wiki/Comma-separated_values>`_ and
            `Issue #51 <https://github.com/cthoyt/pystow/issues/51>`_. To override this
            behavior and load using the comma separator, specify ``read_csv_kwargs=dict(sep=",")``.

    :return: A pandas DataFrame

    Example usage::

    >>> import pystow
    >>> import pandas as pd
    >>> url = 'https://raw.githubusercontent.com/pykeen/pykeen/master/src/pykeen/datasets/nations/test.txt'
    >>> df: pd.DataFrame = pystow.ensure_csv('pykeen', 'datasets', 'nations', url=url)
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.ensure_csv(
        *subkeys,
        url=url,
        name=name,
        force=force,
        download_kwargs=download_kwargs,
        read_csv_kwargs=read_csv_kwargs,
    )


def load_df(
    key: str,
    *subkeys: str,
    name: str,
    read_csv_kwargs: Optional[Mapping[str, Any]] = None,
) -> "pd.DataFrame":
    """Open a pre-existing CSV as a dataframe with :mod:`pandas`.

    :param key: The module name
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param read_csv_kwargs: Keyword arguments to pass through to :func:`pandas.read_csv`.
    :return: A pandas DataFrame

    Example usage::

    >>> import pystow
    >>> import pandas as pd
    >>> url = 'https://raw.githubusercontent.com/pykeen/pykeen/master/src/pykeen/datasets/nations/test.txt'
    >>> pystow.ensure_csv('pykeen', 'datasets', 'nations', url=url)
    >>> df: pd.DataFrame = pystow.load_df('pykeen', 'datasets', 'nations', name='test.txt')
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.load_df(
        *subkeys,
        name=name,
        read_csv_kwargs=read_csv_kwargs,
    )


def dump_df(
    key: str,
    *subkeys: str,
    name: str,
    obj: "pd.DataFrame",
    sep: str = "\t",
    index: bool = False,
    to_csv_kwargs: Optional[Mapping[str, Any]] = None,
) -> None:
    """Dump a dataframe to a TSV file with :mod:`pandas`.

    :param key: The module name
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param obj: The dataframe to dump
    :param sep: The separator to use, defaults to a tab
    :param index: Should the index be dumped? Defaults to false.
    :param to_csv_kwargs: Keyword arguments to pass through to :meth:`pandas.DataFrame.to_csv`.
    """
    _module = Module.from_key(key, ensure_exists=True)
    _module.dump_df(
        *subkeys,
        name=name,
        obj=obj,
        sep=sep,
        index=index,
        to_csv_kwargs=to_csv_kwargs,
    )


def ensure_json(
    key: str,
    *subkeys: str,
    url: str,
    name: Optional[str] = None,
    force: bool = False,
    download_kwargs: Optional[Mapping[str, Any]] = None,
    open_kwargs: Optional[Mapping[str, Any]] = None,
    json_load_kwargs: Optional[Mapping[str, Any]] = None,
) -> JSON:
    """Download JSON and open with :mod:`json`.

    :param key: The module name
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
    :param open_kwargs: Additional keyword arguments passed to :func:`open`
    :param json_load_kwargs: Keyword arguments to pass through to :func:`json.load`.
    :returns: A JSON object (list, dict, etc.)

    Example usage::

    >>> import pystow
    >>> url = 'https://maayanlab.cloud/CREEDS/download/single_gene_perturbations-v1.0.json'
    >>> perturbations = pystow.ensure_json('bio', 'creeds', '1.0', url=url)
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.ensure_json(
        *subkeys,
        url=url,
        name=name,
        force=force,
        download_kwargs=download_kwargs,
        open_kwargs=open_kwargs,
        json_load_kwargs=json_load_kwargs,
    )


def ensure_json_bz2(
    key: str,
    *subkeys: str,
    url: str,
    name: Optional[str] = None,
    force: bool = False,
    download_kwargs: Optional[Mapping[str, Any]] = None,
    open_kwargs: Optional[Mapping[str, Any]] = None,
    json_load_kwargs: Optional[Mapping[str, Any]] = None,
) -> JSON:
    """Download BZ2-compressed JSON and open with :mod:`json`.

    :param key: The module name
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
    :param open_kwargs: Additional keyword arguments passed to :func:`bz2.open`
    :param json_load_kwargs: Keyword arguments to pass through to :func:`json.load`.
    :returns: A JSON object (list, dict, etc.)

    Example usage::

    >>> import pystow
    >>> url = 'https://github.com/hetio/hetionet/raw/master/hetnet/json/hetionet-v1.0.json.bz2'
    >>> hetionet = pystow.ensure_json_bz2('bio', 'hetionet', '1.0', url=url)
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.ensure_json_bz2(
        *subkeys,
        url=url,
        name=name,
        force=force,
        download_kwargs=download_kwargs,
        open_kwargs=open_kwargs,
        json_load_kwargs=json_load_kwargs,
    )


def load_json(
    key: str,
    *subkeys: str,
    name: str,
    json_load_kwargs: Optional[Mapping[str, Any]] = None,
) -> JSON:
    """Open a JSON file :mod:`json`.

    :param key: The module name
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param name: The name of the file to open
    :param json_load_kwargs: Keyword arguments to pass through to :func:`json.load`.
    :returns: A JSON object (list, dict, etc.)
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.load_json(*subkeys, name=name, json_load_kwargs=json_load_kwargs)


def dump_json(
    key: str,
    *subkeys: str,
    name: str,
    obj: JSON,
    open_kwargs: Optional[Mapping[str, Any]] = None,
    json_dump_kwargs: Optional[Mapping[str, Any]] = None,
) -> None:
    """Dump an object to a file with :mod:`json`.

    :param key: The module name
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param name: The name of the file to open
    :param obj: The object to dump
    :param open_kwargs: Additional keyword arguments passed to :func:`open`
    :param json_dump_kwargs: Keyword arguments to pass through to :func:`json.dump`.
    """
    _module = Module.from_key(key, ensure_exists=True)
    _module.dump_json(
        *subkeys, name=name, obj=obj, open_kwargs=open_kwargs, json_dump_kwargs=json_dump_kwargs
    )


def ensure_pickle(
    key: str,
    *subkeys: str,
    url: str,
    name: Optional[str] = None,
    force: bool = False,
    download_kwargs: Optional[Mapping[str, Any]] = None,
    mode: str = "rb",
    open_kwargs: Optional[Mapping[str, Any]] = None,
    pickle_load_kwargs: Optional[Mapping[str, Any]] = None,
) -> Any:
    """Download a pickle file and open with :mod:`pickle`.

    :param key: The module name
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
    :param mode: The read mode, passed to :func:`open`
    :param open_kwargs: Additional keyword arguments passed to :func:`open`
    :param pickle_load_kwargs: Keyword arguments to pass through to :func:`pickle.load`.
    :returns: Any object
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.ensure_pickle(
        *subkeys,
        url=url,
        name=name,
        force=force,
        download_kwargs=download_kwargs,
        mode=mode,
        open_kwargs=open_kwargs,
        pickle_load_kwargs=pickle_load_kwargs,
    )


def load_pickle(
    key: str,
    *subkeys: str,
    name: str,
    mode: str = "rb",
    open_kwargs: Optional[Mapping[str, Any]] = None,
    pickle_load_kwargs: Optional[Mapping[str, Any]] = None,
) -> Any:
    """Open a pickle file with :mod:`pickle`.

    :param key: The module name
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param name: The name of the file to open
    :param mode: The read mode, passed to :func:`open`
    :param open_kwargs: Additional keyword arguments passed to :func:`open`
    :param pickle_load_kwargs: Keyword arguments to pass through to :func:`pickle.load`.
    :returns: Any object
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.load_pickle(
        *subkeys,
        name=name,
        mode=mode,
        open_kwargs=open_kwargs,
        pickle_load_kwargs=pickle_load_kwargs,
    )


def dump_pickle(
    key: str,
    *subkeys: str,
    name: str,
    obj: Any,
    mode: str = "wb",
    open_kwargs: Optional[Mapping[str, Any]] = None,
    pickle_dump_kwargs: Optional[Mapping[str, Any]] = None,
) -> None:
    """Dump an object to a file with :mod:`pickle`.

    :param key: The module name
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param name: The name of the file to open
    :param obj: The object to dump
    :param mode: The read mode, passed to :func:`open`
    :param open_kwargs: Additional keyword arguments passed to :func:`open`
    :param pickle_dump_kwargs: Keyword arguments to pass through to :func:`pickle.dump`.
    """
    _module = Module.from_key(key, ensure_exists=True)
    _module.dump_pickle(
        *subkeys,
        name=name,
        obj=obj,
        mode=mode,
        open_kwargs=open_kwargs,
        pickle_dump_kwargs=pickle_dump_kwargs,
    )


def ensure_pickle_gz(
    key: str,
    *subkeys: str,
    url: str,
    name: Optional[str] = None,
    force: bool = False,
    download_kwargs: Optional[Mapping[str, Any]] = None,
    mode: str = "rb",
    open_kwargs: Optional[Mapping[str, Any]] = None,
    pickle_load_kwargs: Optional[Mapping[str, Any]] = None,
) -> Any:
    """Download a gzipped pickle file and open with :mod:`pickle`.

    :param key: The module name
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
    :param mode: The read mode, passed to :func:`gzip.open`
    :param open_kwargs: Additional keyword arguments passed to :func:`gzip.open`
    :param pickle_load_kwargs: Keyword arguments to pass through to :func:`pickle.load`.
    :returns: Any object
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.ensure_pickle_gz(
        *subkeys,
        url=url,
        name=name,
        force=force,
        download_kwargs=download_kwargs,
        mode=mode,
        open_kwargs=open_kwargs,
        pickle_load_kwargs=pickle_load_kwargs,
    )


def load_pickle_gz(
    key: str,
    *subkeys: str,
    name: str,
    mode: str = "rb",
    open_kwargs: Optional[Mapping[str, Any]] = None,
    pickle_load_kwargs: Optional[Mapping[str, Any]] = None,
) -> Any:
    """Open a gzipped pickle file with :mod:`pickle`.

    :param key: The module name
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param name: The name of the file to open
    :param mode: The read mode, passed to :func:`open`
    :param open_kwargs: Additional keyword arguments passed to :func:`gzip.open`
    :param pickle_load_kwargs: Keyword arguments to pass through to :func:`pickle.load`.
    :returns: Any object
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.load_pickle_gz(
        *subkeys,
        name=name,
        mode=mode,
        open_kwargs=open_kwargs,
        pickle_load_kwargs=pickle_load_kwargs,
    )


def ensure_xml(
    key: str,
    *subkeys: str,
    url: str,
    name: Optional[str] = None,
    force: bool = False,
    download_kwargs: Optional[Mapping[str, Any]] = None,
    parse_kwargs: Optional[Mapping[str, Any]] = None,
) -> "lxml.etree.ElementTree":
    """Download an XML file and open it with :mod:`lxml`.

    :param key: The module name
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
    :param parse_kwargs: Keyword arguments to pass through to :func:`lxml.etree.parse`.
    :returns: An ElementTree object

    .. warning:: If you have lots of files to read in the same archive, it's better just to unzip first.
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.ensure_xml(
        *subkeys,
        name=name,
        url=url,
        force=force,
        download_kwargs=download_kwargs,
        parse_kwargs=parse_kwargs,
    )


def load_xml(
    key: str,
    *subkeys: str,
    name: str,
    parse_kwargs: Optional[Mapping[str, Any]] = None,
) -> "lxml.etree.ElementTree":
    """Load an XML file with :mod:`lxml`.

    :param key: The module name
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param name: The name of the file to open
    :param parse_kwargs: Keyword arguments to pass through to :func:`lxml.etree.parse`.
    :returns: An ElementTree object

    .. warning:: If you have lots of files to read in the same archive, it's better just to unzip first.
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.load_xml(
        *subkeys,
        name=name,
        parse_kwargs=parse_kwargs,
    )


def dump_xml(
    key: str,
    *subkeys: str,
    name: str,
    obj: "lxml.etree.ElementTree",
    open_kwargs: Optional[Mapping[str, Any]] = None,
    write_kwargs: Optional[Mapping[str, Any]] = None,
) -> None:
    """Dump an XML element tree to a file with :mod:`lxml`.

    :param key: The module name
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param name: The name of the file to open
    :param obj: The object to dump
    :param open_kwargs: Additional keyword arguments passed to :func:`open`
    :param write_kwargs: Keyword arguments to pass through to :func:`lxml.etree.ElementTree.write`.
    """
    _module = Module.from_key(key, ensure_exists=True)
    _module.dump_xml(
        *subkeys,
        name=name,
        obj=obj,
        open_kwargs=open_kwargs,
        write_kwargs=write_kwargs,
    )


def ensure_excel(
    key: str,
    *subkeys: str,
    url: str,
    name: Optional[str] = None,
    force: bool = False,
    download_kwargs: Optional[Mapping[str, Any]] = None,
    read_excel_kwargs: Optional[Mapping[str, Any]] = None,
) -> "pd.DataFrame":
    """Download an excel file and open as a dataframe with :mod:`pandas`.

    :param key: The module name
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
    :param read_excel_kwargs: Keyword arguments to pass through to :func:`pandas.read_excel`.
    :return: A pandas DataFrame
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.ensure_excel(
        *subkeys,
        url=url,
        name=name,
        force=force,
        download_kwargs=download_kwargs,
        read_excel_kwargs=read_excel_kwargs,
    )


def ensure_tar_df(
    key: str,
    *subkeys: str,
    url: str,
    inner_path: str,
    name: Optional[str] = None,
    force: bool = False,
    download_kwargs: Optional[Mapping[str, Any]] = None,
    read_csv_kwargs: Optional[Mapping[str, Any]] = None,
) -> "pd.DataFrame":
    """Download a tar file and open an inner file as a dataframe with :mod:`pandas`.

    :param key: The module name
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param inner_path:
        The relative path to the file inside the archive
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
    :param read_csv_kwargs: Keyword arguments to pass through to :func:`pandas.read_csv`.
    :returns: A dataframe

    .. warning:: If you have lots of files to read in the same archive, it's better just to unzip first.
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.ensure_tar_df(
        *subkeys,
        url=url,
        name=name,
        force=force,
        inner_path=inner_path,
        download_kwargs=download_kwargs,
        read_csv_kwargs=read_csv_kwargs,
    )


def ensure_tar_xml(
    key: str,
    *subkeys: str,
    url: str,
    inner_path: str,
    name: Optional[str] = None,
    force: bool = False,
    download_kwargs: Optional[Mapping[str, Any]] = None,
    parse_kwargs: Optional[Mapping[str, Any]] = None,
) -> "lxml.etree.ElementTree":
    """Download a tar file and open an inner file as an XML with :mod:`lxml`.

    :param key: The module name
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param inner_path:
        The relative path to the file inside the archive
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
    :param parse_kwargs: Keyword arguments to pass through to :func:`lxml.etree.parse`.
    :returns: An ElementTree object

    .. warning:: If you have lots of files to read in the same archive, it's better just to unzip first.
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.ensure_tar_xml(
        *subkeys,
        url=url,
        name=name,
        force=force,
        inner_path=inner_path,
        download_kwargs=download_kwargs,
        parse_kwargs=parse_kwargs,
    )


def ensure_zip_df(
    key: str,
    *subkeys: str,
    url: str,
    inner_path: str,
    name: Optional[str] = None,
    force: bool = False,
    download_kwargs: Optional[Mapping[str, Any]] = None,
    read_csv_kwargs: Optional[Mapping[str, Any]] = None,
) -> "pd.DataFrame":
    """Download a zip file and open an inner file as a dataframe with :mod:`pandas`.

    :param key: The module name
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param inner_path:
        The relative path to the file inside the archive
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
    :param read_csv_kwargs: Keyword arguments to pass through to :func:`pandas.read_csv`.
    :return: A pandas DataFrame
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.ensure_zip_df(
        *subkeys,
        url=url,
        name=name,
        force=force,
        inner_path=inner_path,
        download_kwargs=download_kwargs,
        read_csv_kwargs=read_csv_kwargs,
    )


def ensure_zip_np(
    key: str,
    *subkeys: str,
    url: str,
    inner_path: str,
    name: Optional[str] = None,
    force: bool = False,
    download_kwargs: Optional[Mapping[str, Any]] = None,
    load_kwargs: Optional[Mapping[str, Any]] = None,
) -> "numpy.typing.ArrayLike":
    """Download a zip file and open an inner file as an array-like with :mod:`numpy`.

    :param key: The module name
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param inner_path:
        The relative path to the file inside the archive
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param download_kwargs:
        Keyword arguments to pass through to :func:`pystow.utils.download`.
    :param load_kwargs:
        Additional keyword arguments that are passed through to :func:`read_zip_np`
        and transitively to :func:`numpy.load`.
    :returns: An array-like object
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.ensure_zip_np(
        *subkeys,
        url=url,
        name=name,
        force=force,
        inner_path=inner_path,
        download_kwargs=download_kwargs,
        load_kwargs=load_kwargs,
    )


def ensure_rdf(
    key: str,
    *subkeys: str,
    url: str,
    name: Optional[str] = None,
    force: bool = False,
    download_kwargs: Optional[Mapping[str, Any]] = None,
    precache: bool = True,
    parse_kwargs: Optional[Mapping[str, Any]] = None,
) -> "rdflib.Graph":
    """Download a RDF file and open with :mod:`rdflib`.

    :param key: The module name
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
    :param precache: Should the parsed :class:`rdflib.Graph` be stored as a pickle for fast loading?
    :param parse_kwargs:
        Keyword arguments to pass through to :func:`pystow.utils.read_rdf` and transitively to
        :func:`rdflib.Graph.parse`.
    :return: An RDF graph

    Example usage::

    >>> import pystow
    >>> import rdflib
    >>> url = 'https://ftp.expasy.org/databases/rhea/rdf/rhea.rdf.gz'
    >>> rdf_graph: rdflib.Graph = pystow.ensure_rdf('rhea', url=url)

    If :mod:`rdflib` fails to guess the format, you can explicitly specify it using the `parse_kwargs` argument:

    >>> import pystow
    >>> import rdflib
    >>> url = "http://oaei.webdatacommons.org/tdrs/testdata/persistent/knowledgegraph" \
    ... "/v3/suite/memoryalpha-stexpanded/component/reference.xml"
    >>> rdf_graph: rdflib.Graph = pystow.ensure_rdf("memoryalpha-stexpanded", url=url, parse_kwargs={"format": "xml"})
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.ensure_rdf(
        *subkeys,
        url=url,
        name=name,
        force=force,
        download_kwargs=download_kwargs,
        precache=precache,
        parse_kwargs=parse_kwargs,
    )


def load_rdf(
    key: str,
    *subkeys: str,
    name: Optional[str] = None,
    parse_kwargs: Optional[Mapping[str, Any]] = None,
) -> "rdflib.Graph":
    """Open an RDF file with :mod:`rdflib`.

    :param key:
        The name of the module. No funny characters. The envvar
        <key>_HOME where key is uppercased is checked first before using
        the default home directory.
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param name: The name of the file to open
    :param parse_kwargs:
        Keyword arguments to pass through to :func:`pystow.utils.read_rdf` and transitively to
        :func:`rdflib.Graph.parse`.
    :return: An RDF graph
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.load_rdf(*subkeys, name=name, parse_kwargs=parse_kwargs)


def dump_rdf(
    key: str,
    *subkeys: str,
    name: str,
    obj: "rdflib.Graph",
    format: str = "turtle",
    serialize_kwargs: Optional[Mapping[str, Any]] = None,
) -> None:
    """Dump an RDF graph to a file with :mod:`rdflib`.

    :param key:
        The name of the module. No funny characters. The envvar
        <key>_HOME where key is uppercased is checked first before using
        the default home directory.
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param name: The name of the file to open
    :param obj: The object to dump
    :param format: The format to dump in
    :param serialize_kwargs:
        Keyword arguments to through to :func:`rdflib.Graph.serialize`.
    """
    _module = Module.from_key(key, ensure_exists=True)
    _module.dump_rdf(*subkeys, name=name, obj=obj, format=format, serialize_kwargs=serialize_kwargs)


def ensure_from_s3(
    key: str,
    *subkeys: str,
    s3_bucket: str,
    s3_key: Union[str, Sequence[str]],
    name: Optional[str] = None,
    force: bool = False,
    **kwargs: Any,
) -> Path:
    """Ensure a file is downloaded.

    :param key:
        The name of the module. No funny characters. The envvar
        <key>_HOME where key is uppercased is checked first before using
        the default home directory.
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param s3_bucket:
        The S3 bucket name
    :param s3_key:
        The S3 key name
    :param name:
        Overrides the name of the file at the end of the S3 key, if given.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param kwargs:
        Remaining kwargs to forwrad to :class:`Module.ensure_from_s3`.
    :return:
        The path of the file that has been downloaded (or already exists)

    Example downloading ProtMapper 0.0.21:

    >>> version = '0.0.21'
    >>> ensure_from_s3('test', version, s3_bucket='bigmech', s3_key=f'protmapper/{version}/refseq_uniprot.csv')
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.ensure_from_s3(
        *subkeys, s3_bucket=s3_bucket, s3_key=s3_key, name=name, force=force, **kwargs
    )


def ensure_from_google(
    key: str,
    *subkeys: str,
    name: str,
    file_id: str,
    force: bool = False,
) -> Path:
    """Ensure a file is downloaded from google drive.

    :param key:
        The name of the module. No funny characters. The envvar
        <key>_HOME where key is uppercased is checked first before using
        the default home directory.
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param name:
        The name of the file
    :param file_id:
        The file identifier of the google file. If your share link is
        https://drive.google.com/file/d/1AsPPU4ka1Rc9u-XYMGWtvV65hF3egi0z/view, then your file id is
        ``1AsPPU4ka1Rc9u-XYMGWtvV65hF3egi0z``.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :return:
        The path of the file that has been downloaded (or already exists)

    Example downloading the WK3l-15k dataset as motivated by
    https://github.com/pykeen/pykeen/pull/403:

    >>> ensure_from_google('test', name='wk3l15k.zip', file_id='1AsPPU4ka1Rc9u-XYMGWtvV65hF3egi0z')
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.ensure_from_google(*subkeys, name=name, file_id=file_id, force=force)


def joinpath_sqlite(key: str, *subkeys: str, name: str) -> str:
    """Get an SQLite database connection string.

    :param key:
        The name of the module. No funny characters. The envvar
        `<key>_HOME` where key is uppercased is checked first before using
        the default home directory.
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param name: The name of the database file.
    :return: A SQLite path string.
    """
    _module = Module.from_key(key, ensure_exists=True)
    return _module.joinpath_sqlite(*subkeys, name=name)


@contextmanager
def ensure_open_sqlite(
    key: str,
    *subkeys: str,
    url: str,
    name: Optional[str] = None,
    force: bool = False,
    download_kwargs: Optional[Mapping[str, Any]] = None,
) -> Generator[sqlite3.Connection, None, None]:
    """Ensure and connect to a SQLite database.

    :param key:
        The name of the module. No funny characters. The envvar
        `<key>_HOME` where key is uppercased is checked first before using
        the default home directory.
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
    :yields: An instance of :class:`sqlite3.Connection` from :func:`sqlite3.connect`

    Example usage:
    >>> import pystow
    >>> import pandas as pd
    >>> url = "https://s3.amazonaws.com/bbop-sqlite/hp.db"
    >>> sql = "SELECT * FROM entailed_edge LIMIT 10"
    >>> with pystow.ensure_open_sqlite("test", url=url) as conn:
    >>>     df = pd.read_sql(sql, conn)
    """
    _module = Module.from_key(key, ensure_exists=True)
    with _module.ensure_open_sqlite(
        *subkeys, url=url, name=name, force=force, download_kwargs=download_kwargs
    ) as yv:
        yield yv


@contextmanager
def ensure_open_sqlite_gz(
    key: str,
    *subkeys: str,
    url: str,
    name: Optional[str] = None,
    force: bool = False,
    download_kwargs: Optional[Mapping[str, Any]] = None,
) -> Generator[sqlite3.Connection, None, None]:
    """Ensure and connect to a gzipped SQLite database.

    :param key:
        The name of the module. No funny characters. The envvar
        `<key>_HOME` where key is uppercased is checked first before using
        the default home directory.
    :param subkeys:
        A sequence of additional strings to join. If none are given,
        returns the directory for this module.
    :param url:
        The URL to download.
    :param name:
        Overrides the name of the file at the end of the URL, if given. Also
        useful for URLs that don't have proper filenames with extensions.
    :param force:
        Should the download be done again, even if the path already exists?
        Defaults to false.
    :param download_kwargs: Keyword arguments to pass through to :func:`pystow.utils.download`.
    :yields: An instance of :class:`sqlite3.Connection` from :func:`sqlite3.connect`

    Example usage:
    >>> import pystow
    >>> import pandas as pd
    >>> url = "https://s3.amazonaws.com/bbop-sqlite/hp.db.gz"
    >>> sql = "SELECT * FROM entailed_edge LIMIT 10"
    >>> with pystow.ensure_open_sqlite_gz("test", url=url) as conn:
    >>>     df = pd.read_sql(sql, conn)
    """
    _module = Module.from_key(key, ensure_exists=True)
    with _module.ensure_open_sqlite_gz(
        *subkeys, url=url, name=name, force=force, download_kwargs=download_kwargs
    ) as yv:
        yield yv
