# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/998_Internal_Helpers.ipynb.

# %% auto 0
__all__ = ['logger', 'F', 'potential_jdk_path', 'kafka_version', 'kafka_fname', 'kafka_url', 'local_path', 'tgz_path',
           'kafka_path', 'in_notebook', 'combine_params', 'delegates_using_docstring', 'use_parameters_of',
           'generate_app_src', 'change_dir', 'ImportFromStringError', 'filter_using_signature', 'check_java',
           'check_kafka']

# %% ../../nbs/998_Internal_Helpers.ipynb 2
def in_notebook() -> bool:
    try:
        from IPython import get_ipython

        if "IPKernelApp" not in get_ipython().config:
            return False
    except ImportError:
        return False
    except AttributeError:
        return False
    return True

# %% ../../nbs/998_Internal_Helpers.ipynb 4
import contextlib
import importlib
import os
import shutil
import sys
import tarfile
import textwrap
from functools import wraps
from inspect import signature
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import *

import docstring_parser
import jdk
import requests
import typer
from fastcore.meta import delegates

if in_notebook():
    from tqdm.notebook import tqdm, trange
else:
    from tqdm import tqdm, trange

import nbformat
from nbconvert import PythonExporter

from .logger import get_logger, supress_timestamps

# %% ../../nbs/998_Internal_Helpers.ipynb 6
logger = get_logger(__name__)

# %% ../../nbs/998_Internal_Helpers.ipynb 8
F = TypeVar("F", bound=Callable[..., Any])


def _format_args(xs: List[docstring_parser.DocstringParam]) -> str:
    return "\nArgs:\n - " + "\n - ".join(
        [f"{x.arg_name} ({x.type_name}): {x.description}" for x in xs]
    )


def combine_params(f: F, o: Union[Type, Callable[..., Any]]) -> F:
    """Combines docstring arguments of a function and another object or function

    Args:
        f: destination functions where combined arguments will end up
        o: source function from which arguments are taken from

    Returns:
        Function f with augumented docstring including arguments from both functions/objects
    """
    src_params = docstring_parser.parse_from_object(o).params
    #     logger.info(f"combine_params(): source:{_format_args(src_params)}")
    docs = docstring_parser.parse_from_object(f)
    #     logger.info(f"combine_params(): destination:{_format_args(docs.params)}")
    dst_params_names = [p.arg_name for p in docs.params]

    combined_params = docs.params + [
        x for x in src_params if not x.arg_name in dst_params_names
    ]
    #     logger.info(f"combine_params(): combined:{_format_args(combined_params)}")

    docs.meta = [
        x for x in docs.meta if not isinstance(x, docstring_parser.DocstringParam)
    ] + combined_params  # type: ignore

    f.__doc__ = docstring_parser.compose(
        docs, style=docstring_parser.DocstringStyle.GOOGLE
    )
    return f

# %% ../../nbs/998_Internal_Helpers.ipynb 10
def delegates_using_docstring(o: Union[Type, Callable[..., Any]]) -> Callable[[F], F]:
    def _delegates_using_docstring(f: F) -> F:
        def _combine_params(o: Union[Type, Callable[..., Any]]) -> Callable[[F], F]:
            def __combine_params(f: F, o: Union[Type, Callable[..., Any]] = o) -> F:
                return combine_params(f=f, o=o)

            return __combine_params

        @_combine_params(o)
        @delegates(o)  # type: ignore
        @wraps(f)
        def _f(*args: Any, **kwargs: Any) -> Any:
            return f(*args, **kwargs)

        return _f

    return _delegates_using_docstring

# %% ../../nbs/998_Internal_Helpers.ipynb 17
def use_parameters_of(
    o: Union[Type, Callable[..., Any]], **kwargs: Dict[str, Any]
) -> Dict[str, Any]:
    """Restrict parameters passwed as keyword arguments to parameters from the signature of ``o``

    Args:
        o: object or callable which signature is used for restricting keyword arguments
        kwargs: keyword arguments

    Returns:
        restricted keyword arguments

    """
    allowed_keys = set(signature(o).parameters.keys())
    return {k: v for k, v in kwargs.items() if k in allowed_keys}

# %% ../../nbs/998_Internal_Helpers.ipynb 19
def generate_app_src(out_path: Union[Path, str]) -> None:
    path = Path("099_Test_Service.ipynb")
    if not path.exists():
        path = Path("..") / "099_Test_Service.ipynb"
    if not path.exists():
        raise ValueError(f"Path '{path.resolve()}' does not exists.")

    with open(path, "r") as f:
        notebook = nbformat.reads(f.read(), nbformat.NO_CONVERT)
        exporter = PythonExporter()
        source, _ = exporter.from_notebook_node(notebook)

    with open(out_path, "w") as f:
        f.write(source)

# %% ../../nbs/998_Internal_Helpers.ipynb 21
@contextlib.contextmanager
def change_dir(d: str) -> Generator[None, None, None]:
    curdir = os.getcwd()
    os.chdir(d)
    try:
        yield
    finally:
        os.chdir(curdir)

# %% ../../nbs/998_Internal_Helpers.ipynb 23
class ImportFromStringError(Exception):
    pass


def _import_from_string(import_str: str) -> Any:
    """Imports library from string

    Note:
        copied from https://github.com/encode/uvicorn/blob/master/uvicorn/importer.py

    Args:
        import_str: input string in form 'main:app'

    """
    sys.path.append(".")

    if not isinstance(import_str, str):
        return import_str

    module_str, _, attrs_str = import_str.partition(":")
    if not module_str or not attrs_str:
        message = (
            'Import string "{import_str}" must be in format "<module>:<attribute>".'
        )
        typer.secho(f"{message}", err=True, fg=typer.colors.RED)
        raise ImportFromStringError(message.format(import_str=import_str))

    try:
        # nosemgrep: python.lang.security.audit.non-literal-import.non-literal-import
        module = importlib.import_module(module_str)
    except ImportError as exc:
        if exc.name != module_str:
            raise exc from None
        message = 'Could not import module "{module_str}".'
        raise ImportFromStringError(message.format(module_str=module_str))

    instance = module
    try:
        for attr_str in attrs_str.split("."):
            instance = getattr(instance, attr_str)
    except AttributeError:
        message = 'Attribute "{attrs_str}" not found in module "{module_str}".'
        raise ImportFromStringError(
            message.format(attrs_str=attrs_str, module_str=module_str)
        )

    return instance

# %% ../../nbs/998_Internal_Helpers.ipynb 25
def filter_using_signature(f: Callable, **kwargs: Dict[str, Any]) -> Dict[str, Any]:
    """todo: write docs"""
    param_names = list(signature(f).parameters.keys())
    return {k: v for k, v in kwargs.items() if k in param_names}

# %% ../../nbs/998_Internal_Helpers.ipynb 27
potential_jdk_path = list(Path(os.environ["HOME"] + "/.jdk").glob("jdk-11*"))


def check_java(*, potential_jdk_path: List[Path] = potential_jdk_path) -> bool:
    if potential_jdk_path != []:
        logger.info("Java is already installed.")
        if not shutil.which("java"):
            logger.info("But not exported to PATH, exporting...")
            os.environ["PATH"] = os.environ["PATH"] + f":{potential_jdk_path[0]}/bin"
        return True
    return False

# %% ../../nbs/998_Internal_Helpers.ipynb 28
def _install_java() -> None:
    """Checks if jdk-11 is installed on the machine and installs it if not
    Returns:
       None
    """
    if not check_java():
        logger.info("Installing Java...")
        logger.info(" - installing jdk...")
        jdk_bin_path = jdk.install("11")
        logger.info(f" - jdk path: {jdk_bin_path}")
        os.environ["PATH"] = os.environ["PATH"] + f":{jdk_bin_path}/bin"
        logger.info("Java installed.")

# %% ../../nbs/998_Internal_Helpers.ipynb 30
kafka_version = "3.3.2"
kafka_fname = f"kafka_2.13-{kafka_version}"
kafka_url = f"https://dlcdn.apache.org/kafka/{kafka_version}/{kafka_fname}.tgz"
local_path = Path(os.environ["HOME"]) / ".local"
tgz_path = local_path / f"{kafka_fname}.tgz"
kafka_path = local_path / f"{kafka_fname}"


def check_kafka(kafka_path: Path = kafka_path) -> bool:
    if (kafka_path / "bin").exists():
        logger.info("Kafka is installed.")
        if not shutil.which("kafka-server-start.sh"):
            logger.info("But not exported to PATH, exporting...")
            os.environ["PATH"] = os.environ["PATH"] + f":{kafka_path}/bin"
        return True
    return False

# %% ../../nbs/998_Internal_Helpers.ipynb 31
def _install_kafka(
    *,
    kafka_url: str = kafka_url,
    local_path: Path = local_path,
    tgz_path: Path = tgz_path,
) -> None:
    """Checks if kafka is installed on the machine and installs it if not
    Returns:
       None
    """
    if not check_kafka():
        logger.info("Installing Kafka...")
        local_path.mkdir(exist_ok=True, parents=True)
        response = requests.get(
            kafka_url,
            stream=True,
        )
        try:
            total = response.raw.length_remaining // 128
        except Exception:
            total = None

        with open(tgz_path, "wb") as f:
            for data in tqdm(response.iter_content(chunk_size=128), total=total):
                f.write(data)

        with tarfile.open(tgz_path) as tar:
            for tarinfo in tar:
                tar.extract(tarinfo, local_path)

        os.environ["PATH"] = os.environ["PATH"] + f":{kafka_path}/bin"
        logger.info(f"Kafka installed in {kafka_path}.")

# %% ../../nbs/998_Internal_Helpers.ipynb 33
def _install_testing_deps() -> None:
    _install_java()
    _install_kafka()
