Introduction
When building a larger system with custom classes, we will likely have different situations come up that wouldn’t be encompassed by existing exceptions. In this hands-on lab, we’ll create a few custom exception types that will fit into our employee management class hierarchy. To feel comfortable completing this lab, you’ll want to know how to create custom exception types (watch the “Creating Custom Exception Types” video from the Certified Associate in Python Programming Certification course).
Solution
To work through the lab, you can either log in via a terminal on your local machine and use a text editor in the terminal, or you can use VS Code in the browser. This lab guide will go through the steps using VS Code in the browser.
In order to use VS Code in the browser, navigate to the public IP address of the workstation server (provided on the lab page) on port 8080 (e.g., http://
Create MissingEmployeeError and DatabaseError in employee.py
-
In the menu at the top, click Terminal > New Terminal.
-
Run test_custom_exceptions.py:
python3.7 test_custom_exceptions.py
We’ll get a couple errors.
-
In the menu at the top, click File > Open.
-
Select employee.py.
-
Add the following to the top of the file:
class MissingEmployeeError(Exception):
pass
class DatabaseError(Exception):
pass
# Rest of file unchanged and omitted
- Run
test_custom_exceptions.py
:
python3.7 test_custom_exceptions.py
This time, we’ll get an error saying it expected a DatabaseError
but instead got FileNotFoundError
.
Raise DatabaseError Anywhere We Fail to Open the Database File
- Everywhere we use
open
in theEmployee
class, we need to catch these exceptions and instead raise aDatabaseError
. To do so, edit theclass Employee
section to match the following:
class Employee:
default_db_file = "employee_file.txt"
@classmethod
def get_all(cls, file_name=None):
results = []
if not file_name:
file_name = cls.default_db_file
try:
with open(file_name, "r") as f:
lines = [
line.strip("\n").split(",") + [index + 1]
for index, line in enumerate(f.readlines())
]
except (FileNotFoundError, PermissionError) as err:
raise DatabaseError(str(err))
for line in lines:
results.append(cls(*line))
return results
@classmethod
def get_at_line(cls, line_number, file_name=None):
if not file_name:
file_name = cls.default_db_file
try:
with open(file_name, "r") as f:
line = [
line.strip("\n").split(",") + [index + 1]
for index, line in enumerate(f.readlines())
][line_number - 1]
return cls(*line)
except (FileNotFoundError, PermissionError) as err:
raise DatabaseError(str(err))
def __init__(self, name, email_address, title, phone_number=None, identifier=None):
self.name = name
self.email_address = email_address
self.title = title
self.phone_number = phone_number
self.identifier = identifier
def email_signature(self, include_phone=False):
signature = f"{self.name} - {self.title}\n{self.email_address}"
if include_phone and self.phone_number:
signature += f" ({self.phone_number})"
return signature
def save(self, file_name=None):
if not file_name:
file_name = self.default_db_file
try:
with open(file_name, "r+") as f:
lines = f.readlines()
if self.identifier:
lines[self.identifier - 1] = self._database_line()
else:
lines.append(self._database_line())
f.seek(0)
f.writelines(lines)
except (FileNotFoundError, PermissionError) as err:
raise DatabaseError(str(err))
def _database_line(self):
return (
",".join(
[self.name, self.email_address, self.title, self.phone_number or ""]
)
+ "\n"
)
Raise MissingEmployeeError in get_at_line and save if IndexError Occurs
- At the bottom of the
get_at_line
section, add the following:
except IndexError:
raise MissingEmployeeError(f"no employee at line {line_number}")
At the bottom of the save section, add the following:
except IndexError:
raise MissingEmployeeError(f"no employee at line {self.identifier}")
For clarity's sake, this is what the class Employee section should now look like:
class Employee:
default_db_file = "employee_file.txt"
@classmethod
def get_all(cls, file_name=None):
results = []
if not file_name:
file_name = cls.default_db_file
try:
with open(file_name, "r") as f:
lines = [
line.strip("\n").split(",") + [index + 1]
for index, line in enumerate(f.readlines())
]
except (FileNotFoundError, PermissionError) as err:
raise DatabaseError(str(err))
for line in lines:
results.append(cls(*line))
return results
@classmethod
def get_at_line(cls, line_number, file_name=None):
if not file_name:
file_name = cls.default_db_file
try:
with open(file_name, "r") as f:
line = [
line.strip("\n").split(",") + [index + 1]
for index, line in enumerate(f.readlines())
][line_number - 1]
return cls(*line)
except (FileNotFoundError, PermissionError) as err:
raise DatabaseError(str(err))
except IndexError:
raise MissingEmployeeError(f"no employee at line {line_number}")
def __init__(self, name, email_address, title, phone_number=None, identifier=None):
self.name = name
self.email_address = email_address
self.title = title
self.phone_number = phone_number
self.identifier = identifier
def email_signature(self, include_phone=False):
signature = f"{self.name} - {self.title}\n{self.email_address}"
if include_phone and self.phone_number:
signature += f" ({self.phone_number})"
return signature
def save(self, file_name=None):
if not file_name:
file_name = self.default_db_file
try:
with open(file_name, "r+") as f:
lines = f.readlines()
if self.identifier:
lines[self.identifier - 1] = self._database_line()
else:
lines.append(self._database_line())
f.seek(0)
f.writelines(lines)
except (FileNotFoundError, PermissionError) as err:
raise DatabaseError(str(err))
except IndexError:
raise MissingEmployeeError(f"no employee at line {self.identifier}")
def _database_line(self):
return (
",".join(
[self.name, self.email_address, self.title, self.phone_number or ""]
)
+ "\n"
)
- Run the
test_custom_exceptions.py
script:
python3.7 test_custom_exceptions.py
We shouldn’t see any issues.