Final Mini Project: Building a Modular Contact Manager in Python
Objectives
By the end of this project, you will be able to:
- Define and call functions to manage contact records.
- Use
returnproperly to avoid unexpectedNone. - Document functions with docstrings.
- Work with function arguments (default, keyword,
*args,**kwargs). - Use lambda functions for sorting and filtering.
- Organise code into modules and a package.
- Follow PEP 8 style guidelines.
- Run your code as a CLI app (
python -m contact_manager).
Step 1 — Create Project Folder Structure
Make a new folder called contact_manager/. Inside it, create these files:
contact_manager/
__init__.py
contacts.py
utils.py
__main__.py
__init__.py→ marks the folder as a package.contacts.py→ core functions (add, get, update, delete).utils.py→ helper functions (list, find).__main__.py→ CLI entry point.
Step 2 — contacts.py (Core Module)
File: contact_manager/contacts.py
"""
Core contact management functions with CSV file handling.
"""
import csv
import os
CONTACTS_FILE = "contacts.csv"
def load_contacts():
contacts = {}
if os.path.exists(CONTACTS_FILE):
f = open(CONTACTS_FILE, mode="r", newline="", encoding="utf-8")
reader = csv.DictReader(f)
for row in reader:
contacts[row["name"]] = {"phone": row["phone"], "email": row.get("email")}
f.close()
return contacts
def save_contacts(contacts):
f = open(CONTACTS_FILE, mode="w", newline="", encoding="utf-8")
fieldnames = ["name", "phone", "email"]
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
for name, info in contacts.items():
writer.writerow({"name": name, "phone": info["phone"], "email": info.get("email", "")})
f.close()
def add_contact(name, phone, email=None):
contacts = load_contacts()
if name in contacts:
raise KeyError(f"Contact '{name}' already exists.")
contacts[name] = {"phone": phone, "email": email}
save_contacts(contacts)
return f"Contact '{name}' added successfully."
def get_contact(name) -> dict:
contacts = load_contacts()
try:
return contacts[name]
except KeyError:
raise KeyError(f"Contact '{name}' not found.")
def update_contact(name, **kwargs):
contacts = load_contacts()
if name not in contacts:
raise KeyError(f"Contact '{name}' not found.")
record = contacts[name]
if "phone" in kwargs:
record["phone"] = kwargs["phone"]
if "email" in kwargs:
record["email"] = kwargs["email"]
contacts[name] = record
save_contacts(contacts)
def delete_contact(name):
contacts = load_contacts()
try:
deleted = contacts.pop(name)
save_contacts(contacts)
return deleted
except KeyError:
raise KeyError(f"Contact '{name}' not found.")
Step 3 — utils.py (Helper Module)
File: contact_manager/utils.py
"""
Helper functions for querying and listing contacts using CSV file.
"""
from .contacts import load_contacts
def list_contacts(_unused, key="name"):
"""
List contact names or full records sorted by a given key.
"""
contacts_dict = load_contacts()
if key == "name":
return sorted(contacts_dict.keys())
return sorted(
contacts_dict.values(),
key=lambda record: record.get(key) or ""
)
def find_by_email(_unused, email, **kwargs):
"""
Find all contacts matching a given email address.
"""
contacts_dict = load_contacts()
return [
name
for name, record in contacts_dict.items()
if record.get("email") == email
]
Step 4 — init.py (Package Init)
File: contact_manager/__init__.py
"""
contact_manager package initialization.
"""
from .contacts import add_contact, get_contact, update_contact, delete_contact
Step 5 — main.py (CLI Entry Point)
File: contact_manager/__main__.py
"""
Command-line interface for contact_manager.
"""
import argparse
from .contacts import add_contact, get_contact, update_contact, delete_contact, load_contacts
from .utils import list_contacts, find_by_email
def _print_usage():
usage = """
Usage:
python -m contact_manager [command] [arguments]
Commands:
add [email] Add a new contact
get Get contact details
update [--phone PHONE] [--email EMAIL] Update phone and/or email
delete Delete a contact
list [key] List contacts (default key: 'name')
find-by-email Find contacts by email
"""
print(usage)
def main():
parser = argparse.ArgumentParser(description="Contact Manager CLI")
subparsers = parser.add_subparsers(dest="command")
# Add command
add_parser = subparsers.add_parser("add", help="Add a new contact")
add_parser.add_argument("name")
add_parser.add_argument("phone")
add_parser.add_argument("email", nargs="?")
# Get command
get_parser = subparsers.add_parser("get", help="Get contact details")
get_parser.add_argument("name")
# Update command
update_parser = subparsers.add_parser("update", help="Update contact")
update_parser.add_argument("name")
update_parser.add_argument("--phone")
update_parser.add_argument("--email")
# Delete command
delete_parser = subparsers.add_parser("delete", help="Delete a contact")
delete_parser.add_argument("name")
# List command
list_parser = subparsers.add_parser("list", help="List contacts")
list_parser.add_argument("key", nargs="?", default="name")
# Find-by-email command
find_parser = subparsers.add_parser("find-by-email", help="Find contacts by email")
find_parser.add_argument("email")
args = parser.parse_args()
try:
if args.command == "add":
print(add_contact(args.name, args.phone, args.email))
elif args.command == "get":
print(get_contact(args.name))
elif args.command == "update":
updates = {}
if args.phone:
updates["phone"] = args.phone
if args.email:
updates["email"] = args.email
update_contact(args.name, **updates)
print(f"Contact '{args.name}' updated.")
elif args.command == "delete":
deleted = delete_contact(args.name)
print(f"Deleted contact: {deleted}")
elif args.command == "list":
print(list_contacts(None, args.key))
elif args.command == "find-by-email":
print(find_by_email(None, args.email))
else:
_print_usage()
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
main()
Step 6 — Run the Project
From the parent folder (where contact_manager/ exists), run commands:
python -m contact_manager add Alice 12345 alice@mail.com
python -m contact_manager add Bob 55555 bob@mail.com
python -m contact_manager list
python -m contact_manager get Alice
python -m contact_manager update Alice --phone 98765
python -m contact_manager delete Bob
python -m contact_manager find-by-email alice@mail.com
