April's CSV Exercise Reflection

by April Martin

02 Nov 2022

Round 1

I had a mixed level of success during my first attempt at editing the csv code. Something that ended up causing issues was that I did not complete the tasks in order. Instead, I approached the tasks that I felt I could accomplish and as a result it complicated other tasks. I will explain my approach or thought process for each task during Round 1, then I will try starting over with the code we edited together in class to see if I can apply the functioning bits of code from Round 1.

Feature: Put the interface in a while loop so that the user can do multiple things.

I’m so glad that Elliott clarified that interface was a general term here and not interface.py because I spent a while looking through the module for a place to add a loop. I ended up skipping this step while noting: I’m not 100% sure what the goal here is but I think we want to be able to enter multiple menu prompt numbers in one input attempt. I went to interface.py, found get_menu_choice(). But I’m not sure where in the function a while loop would be helpful? During Class: Elliott added while True: on main.py which applies to everything after the user makes a choice.

Feature: Create a new main menu option so that user can exit the program

Added "5" : "Exit program" to menu_dict Added elif choice_str == “5”: above print(“Goodbye”) Then I asked myself: Does it really exit though? Must be in loop to use break or exit. During Class: Indeed, it did need to be in a loop, which was covered in the first task that I had skipped. Elliott added break in class. So choice 5 code now reads:

  elif choice_str == "5":
    print("Goodbye")
    break

Refactor: Make selecting of columns_to_print a function, and call it

On main.py I set the column related chunk of code as column_choice() function and tabbed it over for proper alignment. Then I called it in the response to choice_str == “3”.

# Let user choose which columns to print, or print them all
def column_choice():
# Headers of the CSV are the keys of any row dictionary
# Get the keys from the first row
  headers = row_dicts_list[0].keys()
  columns_to_print = []
  if interface.input_is_yes(f"File '{filename}' has {len(headers)} columns. Select which columns to print?\n", default = "y"):
    for header in headers:
      selected = interface.input_is_yes(f"Do you want {header} in simplified print view?", default = 'y')
      if selected:
        columns_to_print.append(header)
  else:
    print("All columns will be printed")
    columns_to_print = headers

During class we made changes to this code, the result is much cleaner (see code below). We pulled the header definition out of the column_choice() function as well. This change allows code side the function to access the definition.

# Let user choose which columns to print, or print them all
def column_choice():
  columns= []
  for header in headers:
    selected = interface.input_is_yes(f"Do you want {header} in simplified print view?", default = 'y')
    if selected:
      columns.append(header)
      
  return columns

columns_to_print = headers

Feature: Allow user to re-select which columns get printed

I felt like I did not completely understand when this would be executed? at anytime? was it a segway back to the Main Menu? I did not attempt this.

Test: Ensure program uses new selected columns

An after-step to above. I would just test this by running the code, choosing one column, then entering a new number and seeing if I got an error, wrong information, or the correct result.

Refactor: Make creating the row_dicts_list into a function, and call it

I skipped to: Use the selected filename to create a new row_dicts_list and that portion is discussed later.

Feature: Use get_csv_filename to let the user select a new file

I commented out the defaulted filename and wrote a user input request for the filename then named it new_filename_str so that the file remains accessible by exsiting code which uses the name new_filename_str. (see code below)

#filename = "ischools-clean.csv"
csv_name = input("Enter the file name you would like to open:")
new_filename_str = str(csv_name)

I specified how to handle an invalid user input which will reroute to get_csv_filename() in interface.py. The user would then be presented with a menu of available csv files:

# if csv_name is invalid produce list of options
if csv_name != "ischools-clean.csv" and "ischools-messy.csv":
  interface.get_csv_filename()
# if file is valid continue
else:
  pass

This change caused problems, the program reverted to the user input as a string even after interface.get_csv_filename() was executed which returns a new new_filename_str.

Feature: Use the selected filename to create a new row_dicts_list

I created a function called row_dicts_list() and applied it to the existing code that opened a file, read it, and produced a row list. I thought that if I changed the object to be opened, to the new term new_filename_str that this would create the desired function. I was very wrong and still don’t know exactly what needs to happen. The row_dicts_list() code produces this error: with open(new_filename_str) as file: NameError: name 'new_filename_str' is not defined

#turn row_dicts_list into a function
def row_dicts_list():
  #Get a list of dictionaries from CSV file
  with open(new_filename_str) as file:
    reader = csv.DictReader(file)
    row_dicts_list = list(reader)
  return row_dicts_list

If I create a function to get the user input, can I return new_filename_str to use later? answer: it doesn’t make a impact on the errors.

Test: Ensure user is able to select ischools-messy.csv (Note: program will not work in the same way; that’s fine)

When my code was less edited/broken, I was able to open both files through user input (a temporary success)

Round 1 End Point (it’s busted)

Round 2

I have been tinkering with the code as I have been writing this reflection, no big break throughs, however, I have been learning through by eliminating the unsuccessful routes. There is a lot of what-if moments that generally end in an error, and then I look into why the error occured. Because there are differences bewteen the Trinket above, and my starting point today, here is Round 2 starting point Trinket.

I’m just constantly running into issues around defning row_dicts_list and new_filename_str I’m going to try to create a row_dicts_list starting with a copy of Elliott’s code from class and see if I have better luck since this version will be less processed. Update: No progress. There is something about defining and calling functions that I am not doing correctly. I am getting the same errors even without using user input for file choice.

def get_row_dicts_list():
  with open(filename) as file:
    reader = csv.DictReader(file)
    row_dicts_list = list(reader)
  return row_dicts_list
  
get_row_dicts_list()

I thought the above code says, open the file, read it, create a list from that reading, call it row_dicts_list, and then remember what that list is so I can use it later. I even tried confirming that row_dicts_list is a list by setting it equal to [] at the beginning of the function, nothing changed. I got this error: Traceback (most recent call last): File "/tmp/sessions/e283df6c23eae167/main.py", line 49, in <module> headers = row_dicts_list[0].keys() NameError: name 'row_dicts_list' is not defined. Did you mean: 'get_row_dicts_list'?

I’ll have to be done for today because I’ve spent hours on this with no actual progress.

(next day)

Ok it’s my last oppurtunity to give this a shot.
After playing around a bit, I feel like I just don’t know how to take invalid user input, navigate to interface.py and then return the new file name to main.py. I bet I’m over thinking it but that’s where I am getting stuck.\nI’m going to comment out the user input part for now.
After looking at the formatting of the columns section we edited in class, I am going to try to match the last line with row_dicts_list = get_row_dicts_list()
While testing each menu option, I noticed I had not asked the columns to print as a response to Menu choice 3, I tried adding it but it’s just printing the headers. I just copy pasted (sue me.) everything from the get_row_list() and changed the term row out for column. I’m running into the same issue in main.py where I struggle to use information from interface.py in main.py.

Round 2 code

April Martin is a MSIS student focusing on physical and digital preservation in libraries, archives, and museums, at the UT Austin iSchool. Find April Martin on Twitter, Github, and on the web.