thecodex.expert · The Codex Family of Knowledge
Tier 1 · Beginner · Python Project

Contact Book

Store, search, and list contacts — saved to a file so they persist. Your first taste of structured, searchable records.

🧠 Teaches how to think spoonfed, every age Last verified:

1 The Problem

We want a contact book: add people with a name and phone number, search for one by name, list them all, and save everything to a file. It teaches storing structured records (each contact has fields) and searching through them — the foundation of any database-backed app.

Where this shows up: address books, customer records (CRM), user accounts, product catalogues, any app with “records you can look up”. The leap from a plain list to records-with-fields is the leap toward real databases.

2 How to Think About It

Think about how to store a contact, before any code:

The plan — in plain English
1. Each contact is a small bundle of fields (name, phone) — a dictionary. → 2. Keep all contacts in a list. → 3. Add appends a new bundle; search looks through for a matching name; list shows them all. → 4. Save to a file so they persist.

Add

Search

List

Load contacts from file

Show menu

Choice?

Make name+phone dict, append

Find by name

Show all

Save to file

3 The Build — explained part by part

Here is the complete contact book. Each part is explained below.

Pythoncontacts.py
import json
import os

FILE = "contacts.json"

def load():
    if os.path.exists(FILE):
        with open(FILE) as f:
            return json.load(f)
    return []

def save(contacts):
    with open(FILE, "w") as f:
        json.dump(contacts, f)

contacts = load()

while True:
    print("\n1. Add  2. Search  3. List  4. Quit")
    choice = input("Choose: ")

    if choice == "1":
        name = input("Name: ")
        phone = input("Phone: ")
        contacts.append({"name": name, "phone": phone})
        save(contacts)
    elif choice == "2":
        term = input("Search name: ").lower()
        matches = [c for c in contacts if term in c["name"].lower()]
        for c in matches:
            print(f"{c['name']}: {c['phone']}")
    elif choice == "3":
        for c in contacts:
            print(f"{c['name']}: {c['phone']}")
    elif choice == "4":
        break
What each part does — in plain words
{"name": name, "phone": phone} — each contact is a dictionary: a bundle of labelled fields. This is the key upgrade from a plain list — each item now has structure.

contacts.append({...}) — add the new contact bundle to the list.

[c for c in contacts if term in c["name"].lower()] — the search: walk through every contact and keep the ones whose name contains the search term. Lowercasing both sides makes search case-insensitive.

c["name"], c["phone"] — pull a field out of a contact by its label.

load() / save() — the same JSON persistence as before, so contacts survive between runs.
Common mistakes — and how to avoid them
✗ Storing contacts as parallel lists (names[], phones[]) — they get out of sync.
✓ Keep each contact as one dictionary so its fields stay together.
✗ Searching with == instead of in — then partial names do not match.
✓ Use in for partial matches: term in name.
✗ Forgetting .lower() on both sides — case-sensitive search frustrates users.
✓ Lowercase the term and the name when comparing.

4 Test & Prove Each Part

We test adding and searching contacts directly, with known data.

Adding a contact stores its name and phone
Search finds a contact by partial name
Search is case-insensitive
Pythontest_contacts.py
def add(contacts, name, phone):
    contacts.append({"name": name, "phone": phone})
    return contacts


def search(contacts, term):
    term = term.lower()
    return [c for c in contacts if term in c["name"].lower()]


def test_add():
    """Adding stores name and phone."""
    result = add([], "Alice", "123")
    assert result == [{"name": "Alice", "phone": "123"}]


def test_search_partial():
    """Search finds by part of the name."""
    contacts = [{"name": "Alice", "phone": "1"}, {"name": "Bob", "phone": "2"}]
    assert len(search(contacts, "ali")) == 1


def test_search_case_insensitive():
    """Search ignores capitalisation."""
    contacts = [{"name": "Alice", "phone": "1"}]
    assert len(search(contacts, "ALICE")) == 1

Run with pytest -v. The add and search functions hold the logic, so tests check them with known contacts. The case-insensitive test proves search is forgiving.

5 The Interface

INPUTmenu + fieldschoice, then name/phone or search term
What it expects
Choose: 1
Name: Alice
Phone: 555-1234
OUTPUTcontactsmatching records, saved to file
What it returns
Alice: 555-1234

6 Run It & Automate It

Run it locally
python3 contacts.py
Add contacts, search, and list them — they are saved in contacts.json.

Jenkins runs the tests automatically — each line explained below.

What you should see when it works
Terminala real run
1. Add  2. Search  3. List  4. Quit
Choose: 1
Name: Alice
Phone: 555-1234

Choose: 2
Search name: ali
Alice: 555-1234
If it breaks — how to fix it
🚨 KeyError: 'name'.
A contact is missing the name field. Make sure every contact is added as {"name":..., "phone":...}.
🚨 Contacts vanish after quitting.
You did not save. Call save(contacts) after adding.
GroovyJenkinsfile
// Jenkinsfile — automated tests on every change.
pipeline {
    agent any
    stages {
        stage('Get the code') { steps { checkout scm } }       // download the code
        stage('Install') { steps { sh 'python3 -m venv venv && . venv/bin/activate && pip install pytest' } }  // test tool
        stage('Test') { steps { sh '. venv/bin/activate && pytest -v' } }   // run every test
    }
    post {
        success { echo 'All tests passed.' }
        failure { echo 'A test failed — look above.' }
    }
}
🎯 Try this next — make it yours
  1. Edit and delete. Add options to change or remove a contact. (Teaches: finding and updating records.)
  2. More fields. Add email and address. (Teaches: richer records.)
  3. Sort by name. List contacts alphabetically. (Teaches: sorting dictionaries.)
What you learned
You learned to store structured records (dictionaries with fields) in a list, search them with a list comprehension, and persist them to a file. This is the conceptual seed of every database. Related: json, Comprehensions.