-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAssignment07
More file actions
346 lines (277 loc) · 11.8 KB
/
Assignment07
File metadata and controls
346 lines (277 loc) · 11.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# ------------------------------------------------------------------------------------------ #
# Title: Assignment07
# Desc: This assignment demonstrates using data classes
# with structured error handling
# Change Log: (Who, When, What)
# RRoot,1/1/2030,Created Script
# Julianne Tillmann, 11/26/2024, Added set of data classes
# ------------------------------------------------------------------------------------------ #
import json
# Define the Data Constants
MENU: str = '''
---- Course Registration Program ----
Select from the following menu:
1. Register a Student for a Course.
2. Show current data.
3. Save data to a file.
4. Exit the program.
-----------------------------------------
'''
FILE_NAME: str = "Enrollments.json"
# Define the Data Variables
students: list = [] # a table of student data
menu_choice: str # Hold the choice made by the user.
# TODO Create a Person Class
# TODO Add first_name and last_name properties to the constructor (Done)
# TODO Create a getter and setter for the first_name property (Done)
# TODO Create a getter and setter for the last_name property (Done)
# TODO Override the __str__() method to return Person data (Done)
# TODO Create a Student class the inherits from the Person class (Done)
# TODO call to the Person constructor and pass it the first_name and last_name data (Done)
# TODO add a assignment to the course_name property using the course_name parameter (Done)
# TODO add the getter for course_name (Done)
# TODO add the setter for course_name (Done)
# TODO Override the __str__() method to return the Student data (Done)
# Data classes --------------------------------------#
class Person:
"""
A base class repreenting a person, used to augment student data.
ChangeLog: (Who, When, What)
Julianne Tillmann, 11/26/2024, Created Class
Removed @staticmethod, as it is not needed with self
"""
# __init__ is used to initialize an object's attributes
# self is the conventional name in Python to refer to the current instance
def __init__(self, first_name: str, last_name: str):
self.first_name = first_name
self.last_name = last_name
@property
def first_name(self):
return self._first_name
@first_name.setter
def first_name(self, value: str):
if not value.isalpha():
raise ValueError("First name must contain only alphanumeric characters.")
self._first_name = value
@property
def last_name(self):
return self._last_name
@last_name.setter
def last_name(self, value: str):
if not value.isalpha():
raise ValueError("Last name must contain only alphanumeric characters.")
self._last_name = value
def __str__(self):
return f"{self.first_name} {self.last_name}"
class Student(Person):
"""
Represents a student enrolled in a course
ChangeLog: (Who, When, What)
Julianne Tillmann, 11/26/2024, Created Class
Removed @staticmethod as it is no needed with self
"""
def __init__(self, first_name: str, last_name: str, course_name: str):
super().__init__(first_name, last_name)
self.course_name = course_name
@property
def course_name(self):
return self._course_name
@course_name.setter
def course_name(self, value: str):
if not value:
raise ValueError("Course name cannot be empty.")
self._course_name = value
def __str__(self):
return f"{super().__str__()} is enrolled in {self.course_name}."
# Processing --------------------------------------- #
class FileProcessor:
"""
A collection of processing layer functions that work with Json files
ChangeLog: (Who, When, What)
RRoot,1.1.2030,Created Class
"""
@staticmethod
def read_data_from_file(file_name: str, student_data: list):
""" This function reads data from a json file and loads it into a list of dictionary rows
ChangeLog: (Who, When, What)
RRoot,1.1.2030,Created function
Julianne Tillmann, 11/26/2024, added file not found and json errors and creating new file
:param file_name: string data with name of file to read from
:param student_data: list of dictionary rows to be filled with file data
:return: list
"""
file = None
try:
with open(file_name, "r") as file:
raw_data = json.load(file)
for item in raw_data:
try:
student = Student(
first_name=item["FirstName"],
last_name=item["LastName"],
course_name=item["CourseName"]
)
student_data.append(student)
except KeyError as e:
print(f"Error: Missing key {e} in JSON record: {item}")
print(f"Data loaded successfully from {file_name}.")
except FileNotFoundError:
print(f"{file_name} was not found. A new file will be created.")
except json.JSONDecodeError:
print(f"Error decoding JSON data in {file_name}.")
except Exception as e:
IO.output_error_messages(message="Error: There was a problem with reading the file.", error=e)
finally:
if file is not None and not file.closed:
file.close()
return student_data
@staticmethod
def write_data_to_file(file_name: str, student_data: list):
""" This function writes data to a json file with data from a list of dictionary rows
ChangeLog: (Who, When, What)
RRoot,1.1.2030,Created function
:param file_name: string data with name of file to write to
:param student_data: list of dictionary rows to be writen to the file
:return: None
"""
try:
with open(file_name, "w") as file:
json.dump(
[
{
"FirstName": student.first_name,
"LastName": student.last_name,
"CourseName": student.course_name,
}
for student in student_data
],
file,
)
print(f"Data saved successful to {file_name}.")
except Exception as e:
message = "Error: There was a problem with writing to the file.\n"
message += "Please check that the file is not open by another program."
IO.output_error_messages(message=message,error=e)
finally:
if file.closed == False:
file.close()
# Presentation --------------------------------------- #
class IO:
"""
A collection of presentation layer functions that manage user input and output
ChangeLog: (Who, When, What)
RRoot,1.1.2030,Created Class
RRoot,1.2.2030,Added menu output and input functions
RRoot,1.3.2030,Added a function to display the data
RRoot,1.4.2030,Added a function to display custom error messages
"""
@staticmethod
def output_error_messages(message: str, error: Exception = None):
""" This function displays a custom error messages to the user
ChangeLog: (Who, When, What)
RRoot,1.3.2030,Created function
:param message: string with message data to display
:param error: Exception object with technical message to display
:return: None
"""
print(message, end="\n\n")
if error is not None:
print("-- Technical Error Message -- ")
print(error, error.__doc__, type(error), sep='\n')
@staticmethod
def output_menu(menu: str):
""" This function displays the menu of choices to the user
ChangeLog: (Who, When, What)
RRoot,1.1.2030,Created function
:return: None
"""
print() # Adding extra space to make it look nicer.
print(menu)
print() # Adding extra space to make it look nicer.
@staticmethod
def input_menu_choice() -> str:
""" This function gets a menu choice from the user
ChangeLog: (Who, When, What)
RRoot,1.1.2030,Created function
:return: string with the users choice
"""
choice = "0"
try:
choice = input("Enter your menu choice number: ")
if choice not in ("1","2","3","4"): # Note these are strings
raise Exception("Please, choose only 1, 2, 3, or 4")
except Exception as e:
IO.output_error_messages(e.__str__()) # Not passing e to avoid the technical message
return choice
@staticmethod
def output_student_and_course_names(student_data: list):
""" This function displays the student and course names to the user
ChangeLog: (Who, When, What)
RRoot,1.1.2030,Created function
:param student_data: list of dictionary rows to be displayed
:return: None
"""
print("-" * 50)
print("\nCurrent Enrollments:")
if student_data:
for student in student_data:
try:
print(f'Student {student.first_name} {student.last_name}'
f' is enrolled in {student.course_name}')
except AttributeError as e:
print(f"Error: Missing {e} in student record {student}.")
else:
print("No data available.")
print("-" * 50)
@staticmethod
def input_student_data(student_data: list):
""" This function gets the student's first name and last name, with a course name from the user
ChangeLog: (Who, When, What)
RRoot,1.1.2030,Created function
:param student_data: list of dictionary rows to be filled with input data
:return: list
"""
try:
student_first_name = input("Enter the student's first name: ")
if not student_first_name.isalpha():
raise ValueError("The last name should not contain numbers.")
student_last_name = input("Enter the student's last name: ")
if not student_last_name.isalpha():
raise ValueError("The last name should not contain numbers.")
course_name = input("Please enter the name of the course: ")
student = Student(first_name=student_first_name, last_name=student_last_name, course_name=course_name)
student_data.append(student)
print()
print(f"You have registered {student_first_name} {student_last_name} for {course_name}.")
except ValueError as e:
IO.output_error_messages(message="One of the values was the correct type of data!", error=e)
except Exception as e:
IO.output_error_messages(message="Error: There was a problem with your entered data.", error=e)
return student_data
# Start of main body
# When the program starts, read the file data into a list of lists (table)
# Extract the data from the file
students = FileProcessor.read_data_from_file(file_name=FILE_NAME, student_data=students)
# Present and Process the data
while (True):
# Present the menu of choices
IO.output_menu(menu=MENU)
menu_choice = IO.input_menu_choice()
# Input user data
if menu_choice == "1": # This will not work if it is an integer!
students = IO.input_student_data(student_data=students)
continue
# Present the current data
elif menu_choice == "2":
IO.output_student_and_course_names(students)
continue
# Save the data to a file
elif menu_choice == "3":
FileProcessor.write_data_to_file(file_name=FILE_NAME, student_data=students)
continue
# Stop the loop
elif menu_choice == "4":
break # out of the loop
else:
print("Please only choose option 1, 2, or 3")
print("Program Ended")