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).
Learning Objectives
Successfully complete this lab by achieving the following learning objectives:
- Create `MissingEmployeeError` and `DatabaseError` in `employee.py`
Our custom errors don’t need to have any implementation, so we’re going to create them at the top of our
employee.py
file to inherit fromException
withpass
as the class body.~/employee.py
class MissingEmployeeError(Exception): pass class DatabaseError(Exception): pass # Rest of file unchanged and omitted
Now we’ll need to use these exception types in the proper areas of the
Employee
class.- Raise `DatabaseError` Anywhere We Fail to Open the Database File
Our
DatabaseError
occurs whenever we can’t open and read the database. This can happen in two situations:- A
FileNotFoundError
because the file doesn’t exist yet - A
PermissionError
because we’re not allowed to access the file
Everywhere we use
open
in theEmployee
class, we need to catch these exceptions and instead raise aDatabaseError
.~/employee.py
# custom errors omitted 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" )
Now all the areas where our class interacts with the database file will raise the proper error if accessing the file doesn’t go according to plan.
- A
- Raise `MissingEmployeeError` in `get_at_line` and `save` if `IndexError` Occurs
Within
get_at_line
andsave
, we need to handle the potentialIndexError
that will be raised if we try to access a line that doesn’t exist. This indicates the employee we were searching for wasn’t found, so we want to raise theMissingEmployeeError
.~/employee.py
# custom errors omitted 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" )
Now if we run the
test_custom_exceptions.py
script, we shouldn’t see any issues:python3.7 test_custom_exceptions.py