· 4 years ago · Aug 23, 2021, 06:16 AM
1# ------------------------------------------#
2# Title: CDInventory.py
3# Desc: Working with Exception Handling, pickling, and shelves.
4# Change Log: (Who, When, What)
5# Charles Hodges(hodges11@uw.edu), 2021-Aug-22, Created File
6# ------------------------------------------#
7
8import pickle
9
10# Variables
11dic_row = {} # dictionary data row
12lst_input_options = ['l', 'a', 'i', 'd', 's', 'x']
13lst_tbl = [] # list of lists to hold data
14obj_file = None # file object
15
16# Strings
17str_cancelling_reload = (
18 'Canceling...\n'
19 'Inventory data NOT reloaded.\n'
20 'Press [ENTER] to continue to the menu.\n'
21 )
22str_cd_removed = 'The CD was removed.'
23str_choice = '' # User input
24str_confirm_reload = (
25 'Type \'yes\' to continue and reload from the file. '
26 'Otherwise, the reload will be canceled. --> '
27 )
28str_file_name = 'CDInventory.dat' # The data storage file
29str_footer = '======================================'
30str_general_error = '!General Error!'
31str_header = '\n======= The Current Inventory: ======='
32str_inventory_not_saved = (
33 'The inventory was NOT saved to file. Press [ENTER] to return to the menu.'
34 )
35str_menu = (
36 '\n'
37 'MENU\n\n'
38 '[l] Load Inventory from file\n'
39 '[a] Add CD\n'
40 '[i] Display Current Inventory\n'
41 '[d] Delete CD from Inventory\n'
42 '[s] Save Inventory to file\n'
43 '[x] Exit\n'
44 )
45str_not_find_cd = 'Could not find this CD!'
46str_reloading = 'reloading...'
47str_save_inventory = (
48 "Save this inventory to file? Must type 'yes' to confirm: "
49 )
50str_sub_header = 'ID\tCD Title \t(by: Artist)\n'
51str_what_artist = 'What is the Artist\'s name? '
52str_what_id = 'Enter ID: '
53str_what_title = 'What is the CD\'s title? '
54str_which_delete = 'Which CD would you like to delete? Please use ID: '
55str_which_operation = (
56 'Which operation would you like to perform?'
57 '[l, a, i, d, s or x]: '
58 )
59str_warning = (
60 'WARNING: If you continue, all unsaved data will be lost and the '
61 'Inventory will be re-loaded from the file.'
62 )
63str_whole_num_error_msg = "Please only enter whole numbers."
64
65
66# -- PROCESSING -- #
67class DataProcessor:
68 """Processing the data in the table, before file interaction"""
69
70 @staticmethod
71 def add_cd(int_id_input, str_title_input,
72 str_artist_input, dic_row, lst_tbl):
73 """Function to manage data ingestion from User input of CD info.
74
75 Accepts the User input of new CD information, and creates a dictionary
76 object, which is appended to the list table which makes up the
77 Inventory.
78
79 Args:
80 str_id_input (int): ID number of CD
81 str_title_input (string): Title of CD
82 str_artist_input (string): Name of CD's artist
83 dic_row(dictionary): dictionary to store CD data
84 lst_tbl(list): List of lists to hold data
85
86 Returns:
87 None.
88 """
89 dic_row = {
90 'ID': int_id_input,
91 'Title': str_title_input,
92 'Artist': str_artist_input
93 }
94 lst_tbl.append(dic_row)
95 IO.show_inventory(lst_tbl)
96
97 @staticmethod
98 def delete_cd(int_id_del, lst_tbl):
99 """Function to delete a CD from the Inventory.
100
101 When the User selects a CD to delete, by ID, that CD is deleted from
102 the Inventory.
103
104 Args:
105 int_id_del(int) = ID value in an int, for identifying each CD
106 lst_tbl(list): List of lists to hold data
107
108 Returns:
109 None.
110 """
111 # Search thru table and delete CD
112 int_row_nr = -1
113 bln_cd_removed = False
114 for row in lst_tbl:
115 int_row_nr += 1
116 if row['ID'] == int_id_del:
117 del lst_tbl[int_row_nr]
118 bln_cd_removed = True
119 break
120 if bln_cd_removed:
121 print(str_cd_removed)
122 else:
123 print(str_not_find_cd)
124 # Display Inventory to user again
125 IO.show_inventory(lst_tbl)
126
127
128class FileProcessor:
129 """Processing the data to and from text file"""
130
131 @staticmethod
132 def read_file(str_file_name, obj_file, lst_tbl, dic_row):
133 """Function to manage data ingestion from file to a list of
134 dictionaries.
135
136 Reads the data from file identified by file_name into a 2D table
137 (list of dicts) table one line in the file represents one dictionary
138 row in table.
139
140 Args:
141 str_file_name(string): File name from which the data will be read
142 obj_file(defaults to None): file object
143 lst_tbl(list): List of lists to hold data
144 dic_row(dictionary): dictionary data row
145
146 Returns:
147 lst_tbl(list): List of lists to hold data
148 """
149 # Clears existing data
150 lst_tbl.clear()
151
152 # Loads data from file
153 obj_file = open(str_file_name, 'rb')
154 try:
155 lst_tbl = pickle.load(obj_file)
156 except EOFError:
157 pass
158 obj_file.close()
159 return lst_tbl
160
161 @staticmethod
162 def save_file(str_yes_no, str_file_name, obj_file, lst_tbl):
163 """Function to save a file.
164
165 After the User either confirms or declines saving this function
166 processes the answer and either completes the objective to save, or
167 returns the User back to the menu.
168
169 Args:
170 str_yes_no(string): User's response to save confirmation as a y/n
171 str_file_name(string): File name where the data will be saved
172 obj_file(defaults to None): file object
173 lst_tbl(list): List of lists to hold data
174
175 Returns:
176 None.
177 """
178 # Process choice
179 if str_yes_no == 'yes':
180 # Save data
181 obj_file = open(str_file_name, 'wb')
182 pickle.dump(lst_tbl, obj_file)
183 obj_file.close()
184 else:
185 input(str_inventory_not_saved)
186
187 @staticmethod
188 def create_file(str_file_name, obj_file):
189 """Function to create a binary file.
190
191 Args:
192 str_file_name(string): File name where the data will be saved
193 obj_file(defaults to None): file object
194
195 Returns:
196 None.
197 """
198 # Create file
199 obj_file = open(str_file_name, 'ab')
200 obj_file.close()
201
202
203# -- PRESENTATION (Input/Output) -- #
204
205class IO:
206 """Handling Input / Output"""
207
208 @staticmethod
209 def print_menu():
210 """Displays a menu of choices to the user
211
212 Args:
213 None.
214
215 Returns:
216 None.
217 """
218 print(str_menu)
219
220 @staticmethod
221 def menu_choice():
222 """Gets user input for menu selection
223
224 Args:
225 None.
226
227 Returns:
228 choice (string): a lower case string of the users input out of the
229 choices: l, a, i, d, s or x
230 """
231 choice = ' '
232 while choice not in lst_input_options:
233 choice = input(str_which_operation).lower().strip()
234 print() # Add extra line for layout
235 return choice
236
237 @staticmethod
238 def show_inventory(lst_tbl):
239 """Displays current inventory table
240
241 Args:
242 lst_tbl(list): List of lists to hold data
243
244 Returns:
245 None.
246
247 """
248 print(str_header)
249 print(str_sub_header)
250 for row in lst_tbl:
251 print('{}\t{} \t\t(by:{})'.format(*row.values()))
252 print(str_footer)
253
254 @staticmethod
255 def input_cd_info():
256 """Requests and receives CD information from the User.
257
258 Args:
259 None.
260
261 Returns:
262 int_id_input(int): ID Number
263 str_title_input(string): CD Title
264 str_artist_input(string): Artist Name
265 """
266 while True:
267 try:
268 int_id_input = int(input(str_what_id).strip())
269 break
270 except ValueError:
271 print(str_whole_num_error_msg)
272 str_title_input = input(str_what_title).strip()
273 str_artist_input = input(str_what_artist).strip()
274 return int_id_input, str_title_input, str_artist_input
275
276 @staticmethod
277 def ask_to_save(str_file_name, obj_file):
278 """Function to ask a User if they really want to save a file.
279
280 This function accepts the User's y/n response, and passes it to
281 a function in the FileProcessor Class, to evaluate the answer,
282 and complete the User's stated objective.'
283
284 Args:
285 str_file_name(string): File name where the data will be saved
286
287 Returns:
288 None.
289 """
290 # Display current inventory and ask user for confirmation to save
291 IO.show_inventory(lst_tbl)
292 str_yes_no = input(str_save_inventory).strip().lower()
293 FileProcessor.save_file(str_yes_no, str_file_name, obj_file, lst_tbl)
294
295 @staticmethod
296 def ask_to_delete():
297 """Function to identify a CD to delete from the Inventory.
298
299 User selects a CD to delete, by ID, that CD will be deleted from
300 the Inventory.
301
302 Args:
303 None.
304
305 Returns:
306 None.
307 """
308 # Display Inventory to user
309 IO.show_inventory(lst_tbl)
310 # Ask user which ID to remove
311 while True:
312 try:
313 int_id_del = int(input(str_which_delete).strip())
314 break
315 except ValueError:
316 print(str_whole_num_error_msg)
317 DataProcessor.delete_cd(int_id_del, lst_tbl)
318
319 @staticmethod
320 def ask_load_file(obj_file, lst_tbl, dic_row):
321 """Function to confirm loading from the file, with the User.
322
323 Handles the verification that the User wants to load the Inventory
324 from a file, which will delete the surrent unsaved Inventory.
325
326 Args:
327 obj_file(defaults to None): file object
328 lst_tbl(list): List of lists to hold data
329 dic_row(dictionary): dictionary data row
330
331 Returns:
332 None.
333 """
334 print(str_warning)
335 str_yes_no = input(str_confirm_reload)
336 if str_yes_no.lower() == 'yes':
337 print(str_reloading)
338 lst_tbl = FileProcessor.read_file(
339 str_file_name, obj_file, lst_tbl, dic_row)
340 IO.show_inventory(lst_tbl)
341 else:
342 input(str_cancelling_reload)
343
344
345# When program starts, read in the currently saved Inventory, if it exists.
346# Otherwise, create the inventory file.
347try:
348 lst_tbl = FileProcessor.read_file(
349 str_file_name, obj_file, lst_tbl, dic_row)
350except FileNotFoundError:
351 FileProcessor.create_file(str_file_name, obj_file)
352
353# Start main loop
354while True:
355 # Display Menu to user, and get choice
356 IO.print_menu()
357 str_choice = IO.menu_choice()
358
359 # Exit
360 if str_choice == 'x':
361 break
362
363 # Load Inventory.
364 if str_choice == 'l':
365 IO.ask_load_file(obj_file, lst_tbl, dic_row)
366 continue # start loop back at top.
367
368 # Add a CD.
369 elif str_choice == 'a':
370 # Ask user for new ID, CD Title and Artist,
371 int_id_input, str_title_input, str_artist_input = IO.input_cd_info()
372 # Add CD information to the Inventory
373 DataProcessor.add_cd(
374 int_id_input,
375 str_title_input,
376 str_artist_input,
377 dic_row,
378 lst_tbl
379 )
380 continue # start loop back at top.
381
382 # Display current inventory.
383 elif str_choice == 'i':
384 IO.show_inventory(lst_tbl)
385 continue # start loop back at top.
386
387 # Delete a CD.
388 elif str_choice == 'd':
389 IO.ask_to_delete()
390 continue # start loop back at top.
391
392 # Save inventory to file.
393 elif str_choice == 's':
394 IO.ask_to_save(str_file_name, obj_file)
395 continue # start loop back at top.
396
397 # A catch-all, which should not be possible, as user choice gets
398 # vetted in IO, but to be safe.
399 else:
400 print(str_general_error)
401