# AUTOGENERATED! DO NOT EDIT! File to edit: notebooks/API_Helper.ipynb (unless otherwise specified).

__all__ = [
    "ensure_is_instance",
    "get_base_url",
    "post_data",
    "get_data",
    "delete_data",
    "add_ready_column",
    "generate_df",
    "get_values_from_item",
    "get_attributes_from_instances",
    "dict_to_df",
    "run_example",
    "add_example_to_doc",
]

# Cell

from typing import *

# Internal Cell

import os
import requests

import pandas as pd

from .constant import SERVER_URL, PROD_URL

# Cell


def ensure_is_instance(o: Any, cls: Type):
    """A function to check if the object argument is an instance of the class argument.

    Args:
        o: A python object for which the instance needs to be checked.
        cls: The expected instance of the object argument.

    Raises:
        A TypeError if the object is not an instance of the class type.
    """
    if not isinstance(o, cls):
        raise TypeError(
            f"The parameter must be a {cls} type, but got `{type(o).__name__}`"
        )


# Internal Cell


def _get_json(response: requests.Response) -> Any:
    """A function to validate the status of the response object.

    Args:
        response: The response object that encapsulates the server's response.

    Returns:
        A dictionary of the response body.

    Raises:
        ValueError: If the response code is not in range of 200 - 399.
    """
    if response:
        return response.json()
    else:
        raise ValueError(response.json()["detail"])


# Cell


def get_base_url(server: Optional[str]) -> str:
    """Return the base URL for the airt server.

    If the server value is `None`, retrive the value from the environment variable `AIRT_SERVER_URL`.
    If the variable is not set as well, then the default public server will be used.
    """

    return server if server is not None else os.environ.get(SERVER_URL, PROD_URL)


# Cell


def post_data(
    url: str,
    data: Optional[Dict[str, Any]],
    token: Optional[str],
) -> Dict[str, Any]:
    """A function to send a POST request.

    Args:
        url: The URL of the server to which the request needs to be sent.
        data: A Dictionary object to send in the body of the POST request.
        token: The unique auth token for the client, obtained via calling the `Client.get_token()` method.
            Set it to `None` in `Client.get_token()` to obtain the token.

    Returns:
        A dictionary that encapsulates the response body.

    Raises:
        ConnectionError: If the server is not reachable.
        ValueError: If the response code is not in range of 200 - 399.
    """
    if token is not None:
        headers = {"Authorization": f"Bearer {token}"}
        response = requests.post(url, json=data, headers=headers)
    else:
        response = requests.post(url, data=data)
    return _get_json(response)


# Cell


def get_data(url: str, token: Optional[str]) -> Any:
    """Send a GET request.

    Args:
        url: The URL of the server to which the request needs to be sent.
        token: The unique auth token for the client, obtained via calling the `Client.get_token()` method.

    Returns:
        A dictionary that encapsulates the response body.

    Raises:
        ConnectionError: If the server is not reachable.
        ValueError: If the response code is not in range of 200 - 399.
    """
    headers = {"Authorization": f"Bearer {token}"}
    response = requests.get(url, headers=headers)
    return _get_json(response)


# Cell


def delete_data(url: str, token: Optional[str]) -> Dict[str, Any]:
    """Send a DELETE request.

    Args:
        url: The URL of the server to which the request needs to be sent.
        token: The unique auth token for the client, obtained via calling the `Client.get_token()` method.

    Returns:
        A dictionary that encapsulates the response body.

    Raises:
        ConnectionError: If the server is not reachable.
        ValueError: If the response code is not in range of 200 - 399.
    """
    headers = {"Authorization": f"Bearer {token}"}
    response = requests.delete(url, headers=headers)
    return _get_json(response)


# Cell


def add_ready_column(df: pd.DataFrame) -> pd.DataFrame:
    """Add ready column to the DataFrame

    Args:
        df: A pandas DataFrame with completed_steps and total_steps columns

    Returns:
        A pandas DataFrame with ready column
    """

    df["ready"] = df["completed_steps"] == df["total_steps"]
    return df.drop(columns=["completed_steps", "total_steps"])


# Cell


def generate_df(
    items: Union[Dict[str, Any], List[Dict[str, Any]]], columns: list
) -> pd.DataFrame:
    """Generate a DataFrame based on the items length

    Args:
        items: A list encapsulating the response from an API endpoint that needs to be converted into a DataFrame.
        columns: A list of columns names to be included in the DataFrame.

    Returns:
        A DataFrame with a shape of (items, columns), if the length of the items is > 0, otherwise an empty DataFrame with only columns names.
    """

    if len(items) > 0:
        df = pd.DataFrame(items)[columns]
    else:
        df = pd.DataFrame({c: [] for c in columns})

    return df


# Cell


def get_values_from_item(items: list, value: Optional[str] = None) -> str:
    """Get **values** from items seperated by comma.

    Args:
        items: The item list from the response.
        value: The value to extract from each items.

    Returns:
        The **values** as string seperated by comma. If the tags list is empty, <none> will be returned.
    """

    if len(items) == 0:
        return "<none>"

    if value is None:
        return ", ".join(items)

    return ", ".join([str(i[value]) for i in items])


# Cell


def get_attributes_from_instances(
    ox: List[object], attributes: List[str]
) -> List[Dict[str, Any]]:
    """Extract the **attributes** from the instances.

    Args:
        ox: List of instances.
        attributes: Attributes to extract from the instances as a list

    Returns:
        A list encapsulating the attribute name and value pairs of each instance.
    """

    lists = [{i: getattr(o, i) for i in attributes} for o in ox]
    return lists


# Cell


def dict_to_df(d: Dict[str, Any]) -> pd.DataFrame:
    """Convert the dict into a pandas dataframe

    Args:
        d: Dict containing the data and dtypes

    Returns:
        The pandas dataframe constructed from the dict
    """
    data = d["data"]
    dtypes = d["dtypes"]

    df = pd.DataFrame(
        data=data["data"], index=data["index"], columns=data["columns"]
    ).rename_axis(data["index_names"])

    for k, v in dtypes.items():
        df[k] = df[k].astype(v)

    return df


# Internal Cell


def check_and_append_otp_query_param(relative_url: str, otp: Union[str, None]) -> str:
    """Append the otp query parameter to the relative url if its not None

    Args:
        relative_url: The relative url for the route
        otp: user otp

    Returns:
        The updated relative_url if otp is not None, else returns the original relative_url
    """
    if otp is not None:
        relative_url = (
            (relative_url + f"&otp={otp}")
            if len(relative_url.split("?")) > 1
            else (relative_url + f"?otp={otp}")
        )
    return relative_url


# Cell


def run_example(example: str, o: Optional[Dict[str, Any]] = None, **kwargs):
    """Runs the example string

    This function replaces the placeholders in the example and
    runs it.

    Args:
        example: Example with placeholders
        o: local namespaces object that exec() will use to run the example.
        kwargs: A dict containing actual values for the placeholders
    """
    if o is None:
        o = {}

    example_for_execution = example.format(**kwargs)

    # nosemgrep: python.lang.security.audit.exec-detected.exec-detected
    exec(example_for_execution, {}, o)  # nosec B102


# Cell


def add_example_to_doc(o: Any, example: str, **kwargs):
    """Add example to object's docmentation

    Args:
        o: Object to add example
        example: Example with placeholders
        kwargs: A dict containing actual values for the placeholders
    """
    kwargs_for_doc = {
        k: ("{fill in here}" if "curlybrac" not in k else v) for k, v in kwargs.items()
    }

    example_for_doc = example.format(**kwargs_for_doc)
    example_for_doc = f"For example:\n```python\n{example_for_doc}```".replace(
        "\n", "\n        "
    )
    o.__func__.__doc__ = o.__func__.__doc__ + example_for_doc
