pythonpandastypeerrornonetypekeyboardinterrupt

How can I fix this specific instance of a TypeError in python


# importing necessary libraries & my own library of functions called dependancies:
from tkinter import N
import Dependancies
import pandas as pd
import time
from pathlib import Path
import os
#________________________________________________________________________________


def sourceFile_path():
    """
    A function that asks the user to provide a valid data-source-file path as well as a valid worksheet name.
    """
    # preparing the file-path object
    data_source_file_path = Path(str(input("\nplease provide the filepath of data file\n")))
    
    # Asking the user to specify the name of the sheet inside the excel file that contains the data.
    sheet_name = str(input("\nplease provide the name of the sheet containing the wanted data\n"))
    

    # The return of the function in the form of a tuple
    return data_source_file_path, sheet_name
#________________________________________________________________________________

# Defining a repeating code block that frequently is seen the need for.
# It simply offers the user the choice, either to quit the program or start all over again due to a critically need step for a correct output to be produced from the program.
def parsing_startOver_quit(reciprocate=False):
    """
     offers the user the choice, either to quit the program of star all over again due to a critically need step for a correct output to be produced from the program.
    """
    if not reciprocate:
        if Dependancies.choice():
            data_parser()
        else:
            quit()
    elif reciprocate:
        if Dependancies.choice(reciprocate=reciprocate):
            data_parser()
        else:
            quit()
#________________________________________________________________________________

def data_parser():
    """
    - The function that reaps the data out of an excel file then sorts and segregates that data into separate pandas dataframs according to certain parameters.
    - It outputs/returns several dataframes that'll afterwards be held in variables to be passed on to the `data_exporter()` function as arguments to it's input parameters.
    """
    # Declaring 2 variables to hold the tuple output of the user input function
    # In order to pass them on to the appropriate kwargs of the read_excel() function
    file_path, sheet = sourceFile_path()

    # Now Loading 4 separate dataframes from the excel sheet containing the data.
    # Each of which using a different set of columns.
    # Then drop any duplicate rows for dataframes that need to hold unique data, Such as customers, products and distributors.
    # Such step is done in a single go.
    # By appending the .drop_duplicates() function at the end of the read_excel function
    try:
        """
        TODO: - Add a please wait effect from the Dependancies module.
        """
        products_df = pd.read_excel(
            io= file_path,
            sheet_name=sheet,
            header=0,
            usecols=["Product Group","Product_Code","Product_Name"],    # specifying what columns to parse from the dataset
            #nrows=0     # Setting this parameter to specify the number or rows to parse from the dataset, if 0 then only the header is retrieved
        ).drop_duplicates(subset = 'Product_Name', keep = 'first')

        customers_df = pd.read_excel(
            io= file_path,
            sheet_name=sheet,
            header=0,
            usecols=['CUSTOMER_CODE', 'CUSTOMER_Name', 'Street', 'DISTRICT', 'GOVERNARATE',
            'CUST_TYPE', 'Pharco_Cust_Type', 'REGION_CODE', 'REGION_NAME',
            'TERRITORY_CODE', 'TERRITORY_NAME', 'BRICK_CODE_148', 'BRICK_NAME_148',
            'Is_Ceiling', 'Allocation_Region_Code', 'Allocation_Region_Name',
            'Allocation_TERRITORY_CODE', 'Allocation_TERRITORY_Name',
            'Allocation_Brick_Code', 'Allocation_Brick_Name'],
            #nrows=0     # Setting this parameter to specify the number or rows to parse from the dataset, if 0 then only the header is retrieved
        ).drop_duplicates(subset="CUSTOMER_CODE",keep='first')

        distributors_df = pd.read_excel(
            io= file_path,
            sheet_name=sheet,
            header=0,
            usecols=['Dist_Name']
        ).drop_duplicates(keep='first')

        sales_df = pd.read_excel(
            io= file_path,
            sheet_name=sheet,
            header=0,
            usecols=['CUSTOMER_CODE', 'Product_Code', 'Dist_Name', 'Is_Ceiling', 'QTY',
                    'Actual_Val', 'Pharco_Val', 'Source', 'Quarter', 'Month_Name', 'Target_QTY',
                    'Target_VAL']
        )
    except FileNotFoundError:
        print(f"\nCaught a FileNotFoundError\n[Errno 2] No such file or directory: {file_path}\n")
        parsing_startOver_quit()
    except ValueError:
        print(f"\nCaught a ValueError.\nWorksheet named {sheet} not found")
        parsing_startOver_quit()
    except PermissionError:
        print(f"\nCaught a PermissionError\n[Errno 13] Permission denied: {file_path}\nThis error arises usually as a result of not specifying a filename and extension in the filepath string.\nOr may be due to random play & not providing neither a path nor filename\nPlease take care of these.")
        parsing_startOver_quit()
    except KeyboardInterrupt:
        print("\nData parsing operation in progress.\nUpon successful completion, it'd be loaded into 4 dataframes.\nAre you sure you want to prematurely terminate?\ny = Terminate program\nn = Start over")
        parsing_startOver_quit(reciprocate=True)
        """
        FIXME: Try to fix the type error that arises after one time keyboard interrupt.
        """
    except:
        print("\nEncountered an unexpected error of some sort")
        parsing_startOver_quit()
    else:

        if not (customers_df.empty and products_df.empty and distributors_df.empty and sales_df.empty): # the dataframe ain't empty & happens to contain data based on the df.emnpty method
            print(f"\nDataFrames loaded successfully from:\n{file_path}")
            return  customers_df, products_df, distributors_df, sales_df
            time.sleep(1.67)
        else:
            print("\nSome or all of the dataframes happen to be empty\nIf you wish you could go back and load data from a nonempty excel file")
            parsing_startOver_quit()
#____________________________________________________________________________________

def data_exporter(customers_df, products_df, distributors_df, sales_df):
    
    # Prompt for the user to provide a valid path for the program to store the output file
    output_path = Path(str(input("\nplease provide a valid path to save the output file\n")))
    
    try:
        with pd.ExcelWriter(path=Path(output_path).joinpath("amriya_sales_sorter.xlsx"), engine='openpyxl') as output_file_path:
            products_df.to_excel(
                excel_writer = output_file_path,
                index = False,  # bool, default True. Write row names (index).
                header = ['Product_Group', 'Product_Code', 'Product_Name'],  # Write out the column names. If a list of string is given it is assumed to be aliases for the column names.
                sheet_name= "Products"
            )
            customers_df.to_excel(
                excel_writer = output_file_path,
                index = False,  # bool, default True. Write row names (index).
                sheet_name= "Customers"
            )
            distributors_df.to_excel(
                excel_writer = output_file_path,
                index = False,  # bool, default True. Write row names (index).
                sheet_name= "Distriubuters"
            )
            sales_df.to_excel(
                excel_writer = output_file_path,
                index = False,  # bool, default True. Write row names (index).
                sheet_name= "Total_sales"
            )
    except FileNotFoundError:
        print(f"\nFileNotFoundError: [Errno 2] No such file or directory\n")
        if Dependancies.choice():
            data_exporter(customers_df, products_df, distributors_df, sales_df)
        else:
            quit()
    else:
        print(f"\nFile: 'sales_sorter.xlsx' successfully exported to:\n{output_path}\n")
#________________________________________________________________________________

# The driver/executer program
if __name__ == '__main__':
    customers_df, products_df, distributors_df, sales_df = data_parser()
    data_exporter(customers_df, products_df, distributors_df, sales_df)
    print("The current working directory is:\n",Path(os.getcwd()))


I konw I has been asked as well as answered, but could someone help me figure out how to fix this: In brief the program ask the user to provide a valid path containing an excel file, reads that file using pd.read_excel() and picks out some columns and gorups them in several dataframes. That's accomplished using the function data_parser which returns 4 dataframes. In that data_parser() function I did add a KeyboardInterrupt handler to catch a keyboard Ctrl+C interrupt just in case, but unfortunately if this happens that function doesn't return those dataframes despite having a return statement.

And thus gives me this error:

Traceback (most recent call last): 
  File "g:/path/sales_analyzer_1-0/test_anlyzer.py", line 179, in
 <module>
     customers_df, products_df, distributors_df, sales_df = data_parser()
TypeError: cannot unpack non-iterable NoneType object

Whereas if I let the program flow normally without keyboard interrupts it works correctly.

My exact question is what stops the data_parser() function from returning the 4 dataframes -inspite of the clear return statement- when I use the Ctrl+C interrupt? Note that I have the KeyboardInterrupt handled in a try except block.


Solution

  • In parsing_startOver_quit, return the result of data_parser()

    def parsing_startOver_quit(reciprocate=False):
        """
         offers the user the choice, either to quit the program of star all over again due to a critically need step for a correct output to be produced from the program.
        """
        if not reciprocate:
            if Dependancies.choice():
                return data_parser()  # << here
            else:
                quit()
        elif reciprocate:
            if Dependancies.choice(reciprocate=reciprocate):
                return data_parser()  # << and here
            else:
                quit()
    

    Then, everywhere you call parsing_startOver_quit() within data_parser, return the value of those calls too.

    Currently you're not returning the results, so it just implicitly returns None when it exits the final else in data_parser, causing your unpacking error.