Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 68 additions & 2 deletions Beginner/14_address_book.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,107 @@
import json
import os
import re

CONTACTS_FILE = "contacts.json"

# Matches names that already end in a numbered suffix, e.g. "Rani (2)".
NUMBERED_NAME = re.compile(r"^(.*?)\s*\((\d+)\)$")


def load_contacts():
if os.path.exists(CONTACTS_FILE):
with open(CONTACTS_FILE, "r") as f:
return json.load(f)
return {}


def save_contacts(contacts):
with open(CONTACTS_FILE, "w") as f:
json.dump(contacts, f, indent=4)


def next_available_name(contacts, name):
"""Return the first free numbered name, e.g. 'Rani (2)', 'Rani (3)', ..."""
base = name
index = 2

match = NUMBERED_NAME.match(name)
if match:
base = match.group(1)
index = max(2, int(match.group(2)) + 1)

while f"{base} ({index})" in contacts:
index += 1
return f"{base} ({index})"


def resolve_name(contacts, name):
"""Handle duplicate names without silently overwriting a contact.

Returns the name to save the new contact under, or None if the user
cancels. Existing contacts are only replaced after explicit confirmation.
"""
if name not in contacts:
return name

existing = contacts[name]
print(f"A contact named '{name}' already exists.")
print(f" Phone: {existing['phone']}, Email: {existing['email']}")
suggested = next_available_name(contacts, name)

while True:
print("What would you like to do?")
print("1. Overwrite the existing contact")
print(f"2. Keep both (save the new contact as '{suggested}')")
print("3. Cancel")
choice = input("Enter your choice: ")

if choice == "1":
return name
elif choice == "2":
return suggested
elif choice == "3":
return None
else:
print("Invalid choice. Please try again.")


def add_contact(contacts):
name = input("Enter name: ")
name = input("Enter name: ").strip()
if not name:
print("Name cannot be empty.")
return

name = resolve_name(contacts, name)
if name is None:
print("Contact not added.")
return

phone = input("Enter phone number: ")
email = input("Enter email address: ")
contacts[name] = {"phone": phone, "email": email}
save_contacts(contacts)
print(f"Contact '{name}' added.")


def view_contacts(contacts):
if not contacts:
print("No contacts found.")
return
for name, info in contacts.items():
print(f"Name: {name}, Phone: {info['phone']}, Email: {info['email']}")


def delete_contact(contacts):
name = input("Enter the name of the contact to delete: ")
name = input("Enter the name of the contact to delete: ").strip()
if name in contacts:
del contacts[name]
save_contacts(contacts)
print(f"Contact '{name}' deleted.")
else:
print("Contact not found.")


def main():
contacts = load_contacts()
while True:
Expand All @@ -58,5 +123,6 @@ def main():
else:
print("Invalid choice. Please try again.")


if __name__ == "__main__":
main()
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ This repository is designed to help Python learners at all levels, starting with
- Temperature Converter validates the input against absolute zero for **each unit** (−273.15°C or −459.67°F) and reports invalid values.
- Roman Numeral Converter rejects noncanonical numerals (such as `IIII` and `IC`) and values outside 1–3999.
- JSON Reader now uses a Python 3.6-compatible type annotation instead of Python 3.10's `|` union syntax.
- Address Book no longer silently overwrites contacts that share a name. It now asks whether to overwrite, keep both (saving the new contact as `Name (2)`, `Name (3)`, …), or cancel.

</details>

Expand Down Expand Up @@ -592,6 +593,7 @@ These projects are ideal for those new to Python. Each project includes a descri
1. Create functions to add, view, and delete contacts.
2. Store contacts in a file (e.g., JSON).
3. Create a main loop to interact with the user.
4. Check for an existing contact with the same name before saving.
- **Tips:**

<details><summary>Tip 1:</summary>
Expand All @@ -604,6 +606,13 @@ These projects are ideal for those new to Python. Each project includes a descri
Use the `json` module to save and load contacts from a file.

</details>
<details><summary>Tip 3:</summary>

Never overwrite a contact silently. Check whether the name already exists and ask the user what to do: overwrite it, cancel, or keep both by saving the new contact under an incremented name like `Rani (2)`, `Rani (3)`, and so on.

Thanks to [@SVarnavi06](https://github.com/SVarnavi06) for pointing out the silent overwrite bug in [issue #18](https://github.com/Infinitode/Python-Projects/issues/18)!

</details>

### 15. Pomodoro Timer

Expand Down