# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs-dev/0_01_ipython.ipynb.

# %% ../nbs-dev/0_01_ipython.ipynb 2
from __future__ import annotations
from fastcore.test import test_fail
from functools import wraps
from IPython.core.getipython import get_ipython
from IPython.display import display, Markdown, display_markdown
from pathlib import Path
from typing import Any, Callable, Optional
from .core import validate_path, validate_type, IsLocalMachine

import configparser
import numpy as np
import os
import pandas as pd
import subprocess
import warnings

# %% auto 0
__all__ = ['run_cli', 'nb_setup', 'cloud_install_project_code', 'display_mds', 'display_dfs', 'pandas_nrows_ncols',
           'df_all_cols_and_rows', 'display_full_df']

# %% ../nbs-dev/0_01_ipython.ipynb 5
def run_cli(cmd:str = 'ls -l'   # command to execute in the cli
           ):
    """Runs a cli command from jupyter notebook and print the shell output message
    
    Uses subprocess.run with passed command to run the cli command"""
    p = subprocess.run(cmd, stdout=subprocess.PIPE, shell=True)
    print(str(p.stdout, 'utf-8'))

# %% ../nbs-dev/0_01_ipython.ipynb 8
def nb_setup(autoreload:bool = True,   # True to set autoreload in this notebook
             paths:list(Path) = None   # Paths to add to the path environment variable
            ):
    """Use in first cell of notebook to set autoreload, and paths"""
#   Add paths. Default is 'src' if it exists
    if paths is None:
        p = Path('../src').resolve().absolute()
        if p.is_dir():
            paths = [str(p)]
        else:
            paths=[]
    if paths:
        for p in paths:
            sys.path.insert(1, str(p))
        print(f"Added following paths: {','.join(paths)}")

#   Setup auto reload
    if autoreload:
        ipshell = get_ipython()
        ipshell.run_line_magic('load_ext',  'autoreload')
        ipshell.run_line_magic('autoreload', '2')
        print('Set autoreload mode')

# %% ../nbs-dev/0_01_ipython.ipynb 12
def cloud_install_project_code(
    package_name:str # project package name, e.g. metagentools or git+https://github.com/repo.git@main
):
    """When nb is running in the cloud, pip install the project code package"""
    
    # test whether it runs on colab
    try:
        from google.colab import drive
        RUN_LOCALLY = False
        print('The notebook is running on colab')

    except ModuleNotFoundError:
        # not running on colab, testing is it runs on on a local machine
        RUN_LOCALLY = IsLocalMachine().is_local()
        
        if RUN_LOCALLY:
            print('The notebook is running locally, will not automatically install project code')
        else:
            print('The notebook is running on a cloud VM or the machine was not registered as local')

    if not RUN_LOCALLY:
        print(f'Installing project code {package_name}')
        cmd = f"pip install -U {package_name}"
        run_cli(cmd)
        print((f"{package_name} is installed."))
        
    return RUN_LOCALLY

# %% ../nbs-dev/0_01_ipython.ipynb 16
def display_mds(
    *strings:str|tuple[str] # any number of strings with text in markdown format
):
    """Display one or several strings formatted in markdown format"""
    for string in strings:
        display_markdown(Markdown(data=string))

# %% ../nbs-dev/0_01_ipython.ipynb 20
def display_dfs(*dfs:pd.DataFrame       # any number of Pandas DataFrames
               ):
    """Display one or several `pd.DataFrame` in a single cell output"""
    for df in dfs:
        display(df)

# %% ../nbs-dev/0_01_ipython.ipynb 23
class pandas_nrows_ncols:
    """Context manager set max number of rows and cols to apply to any output within the context"""
    def __init__(
        self, 
        nrows:int|None=None, # max number of rows to show; show all rows if `None`
        ncols:int|None=None, # max number of columns to show; show all columns if `None`
    ):
        self.nrows = nrows
        self.ncols = ncols
    
    def __enter__(self):
        self.max_rows = pd.options.display.max_rows
        self.max_cols = pd.options.display.max_columns
        pd.options.display.max_rows = self.nrows
        pd.options.display.max_columns = self.ncols
        return self.max_rows, self.max_cols

    def __exit__(self, exc_type, exc_value, exc_tb):
        pd.options.display.max_rows = self.max_rows
        pd.options.display.max_columns = self.max_cols

# %% ../nbs-dev/0_01_ipython.ipynb 36
def df_all_cols_and_rows(
    f:Callable,   # function to apply the decorator ti
)-> Callable:     # decorated function
    """decorator function forcing all rows and columns of `DataFrames` to be displayed in the wrapped function"""
    
    msg = 'This decorator is deprecated. Will be removed soon. Use context manager `pandas_nrows_ncols` instead.'
    warnings.warn(msg, category=DeprecationWarning)
    
    @wraps(f)
    def wrapper(*args, **kwargs):
        max_rows = pd.options.display.max_rows
        max_cols = pd.options.display.max_columns
        pd.options.display.max_rows = None
        pd.options.display.max_columns = None
        f(*args, **kwargs)
        pd.options.display.max_rows = max_rows
        pd.options.display.max_columns = max_cols
    
    return wrapper

# %% ../nbs-dev/0_01_ipython.ipynb 40
def display_full_df(
    df:pd.DataFrame|pd.Series,  # `DataFrame` or `Series` to display
):
    """Display a pandas `DataFrame` or `Series` showing all rows and columns"""
    if validate_type(df, pd.DataFrame, raise_error=False) or validate_type(df, pd.Series, raise_error=False):
        with pandas_nrows_ncols():
            display(df)
    else:
        raise TypeError(f"df must me a pandas `DataFrame` or `Series`, not a {type(df)}")
