# 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.
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.