CodingNic

Capstone Challenges

Challenge 1: Contact Book

Capstone Challenges 45 min read

Challenge 1: Contact Book

Objectives

By the end of this chapter, you should be able to:

  • Design a small program using more than one class working together
  • Persist a program’s data to disk between runs with JSON
  • Export structured data to CSV

💡 Why this matters: This is the first project in the course where nothing is handed to you piece by piece. You’ll decide how the classes fit together, not just fill in a single function. That’s the actual shape of most real programming work.

The Project

Build a Contact Book: a program that stores contacts (name, email, phone number), keeps them in memory as a collection of objects, and saves that collection to a file so it’s still there the next time the program runs.

Core Requirements

Contact

A class representing one person, with:

  • name, email, and phone attributes, set in __init__
  • A __str__ method so printing a contact shows something readable, like "Jordan Reyes <jordan@example.com>"
  • A to_dict() method that returns the contact as a plain dictionary. This is what makes saving it to JSON straightforward.

ContactBook

A class that manages a collection of Contact instances, with:

  • add_contact(contact): adds a new Contact to the collection
  • find_contact(name): returns the first contact whose name matches, or None if there isn’t one
  • remove_contact(name): removes a contact by name
  • all_contacts(): returns every contact currently stored

Persistence

  • save_to_file(filename): writes every contact in the book to a JSON file, using each contact’s to_dict() and json.dump(..., indent=2)
  • load_from_file(filename): reads a JSON file back into a ContactBook, rebuilding a Contact instance for each entry. If the file doesn’t exist yet, start with an empty book instead of raising an error.

CSV Export

  • export_to_csv(filename): writes every contact out to a CSV file with name, email, and phone columns, using csv.DictWriter

Suggested Structure

You don’t have to follow this exactly, but it’s a reasonable starting skeleton:

python
import json
import csv

class Contact:
    def __init__(self, name, email, phone):
        self.name = name
        self.email = email
        self.phone = phone

    def __str__(self):
        return f"{self.name} <{self.email}>"

    def to_dict(self):
        return {"name": self.name, "email": self.email, "phone": self.phone}


class ContactBook:
    def __init__(self):
        self.contacts = []

    def add_contact(self, contact):
        self.contacts.append(contact)

    def find_contact(self, name):
        for contact in self.contacts:
            if contact.name == name:
                return contact
        return None

    def remove_contact(self, name):
        self.contacts = [c for c in self.contacts if c.name != name]

    def all_contacts(self):
        return self.contacts

    def save_to_file(self, filename):
        with open(filename, "w") as f:
            json.dump([c.to_dict() for c in self.contacts], f, indent=2)

    def load_from_file(self, filename):
        try:
            with open(filename) as f:
                data = json.load(f)
        except FileNotFoundError:
            data = []
        self.contacts = [Contact(**entry) for entry in data]

    def export_to_csv(self, filename):
        with open(filename, "w") as f:
            writer = csv.DictWriter(f, fieldnames=["name", "email", "phone"])
            writer.writeheader()
            for contact in self.contacts:
                writer.writerow(contact.to_dict())

Fill in the pieces yourself rather than copying this directly. The value here is in designing the interactions between Contact and ContactBook, not in the exact syntax.

Example Session

Here’s what a short session looks like once Contact and ContactBook are working:

python
book = ContactBook()
book.add_contact(Contact("Priya", "priya@example.com", "555-0101"))
book.add_contact(Contact("Sam", "sam@example.com", "555-0102"))

print(book.find_contact("Priya"))
# Priya <priya@example.com>

book.save_to_file("contacts.json")
book.export_to_csv("contacts.csv")

contacts.json then contains:

json
[
  {"name": "Priya", "email": "priya@example.com", "phone": "555-0101"},
  {"name": "Sam", "email": "sam@example.com", "phone": "555-0102"}
]

And contacts.csv contains:

text
name,email,phone
Priya,priya@example.com,555-0101
Sam,sam@example.com,555-0102

Try It

  1. Build Contact and ContactBook, add a handful of contacts, and print each one using __str__.
  2. Save your contact book to a JSON file, then write a second small script that loads it back and confirms the contacts are all there.
  3. Export your contact book to a CSV file and open it in a spreadsheet program to confirm it looks right.
  4. Test find_contact and remove_contact against a name you know exists, and one you know doesn’t.

Recap

You’ve now designed and built a small multi-class program with real persistence: the same shape as most everyday scripts, just smaller. The next lesson builds on this same project with a set of optional extensions.

Next lesson: extend the contact book with custom exceptions, decorators, and generators.