Day 30 of Python-30: Mastering error handling, JSON operations, and achieving 100% test coverage
📖 Table of Contents
- Project Overview
- The Learning Journey
- Architecture & Design
- Core Features
- Exception Handling Deep Dive
- Testing Strategy
- Development Workflow
- Data Flow & State Management
- Key Learnings & Best Practices
Project Overview
This project started as a simple password manager GUI application but evolved into a comprehensive study of exception handling, JSON operations, test-driven development, and software architecture best practices.
🎯 Project Goals
- Exception Handling: FileNotFoundError, JSONDecodeError, KeyError, Input Validation
- JSON Operations: Create Files, Read Data, Update Records, Error Recovery
- Testing: Unit Tests, 100% Coverage, Edge Cases, Error Scenarios
- Architecture: Business Logic, UI Separation, Testable Code, Clean Design
📊 Project Statistics
- Lines of Code: ~400+ lines
- Test Cases: 26 comprehensive tests
- Code Coverage: 100% ✅
- Files Created: 8 files
- Technologies: Python, Tkinter, pytest, coverage.py
The Learning Journey
Phase 1: Basic Implementation
Started with a simple GUI application that could save passwords to a JSON file.
Progress Flow:
- Initial Idea → GUI Design
- Basic Save Feature → JSON Storage
- Encountered Crashes & Errors
- Realized Need for Better Error Handling
Phase 2: Exception Handling Mastery
Learned to handle errors gracefully instead of letting the app crash.
Error Handling Flow:
- User Action → Try Operation
- On Success: Execute Else Block → Finally: Cleanup
- On FileNotFoundError: Create New File → Finally: Cleanup
- On JSONDecodeError: Reset Corrupted File → Finally: Cleanup
- On KeyError: Handle Missing Data → Finally: Cleanup
- On ValueError: Validate Input → Finally: Cleanup
Phase 3: Code Refactoring & Separation
Separated business logic from UI code to make it testable.
Before Refactoring:
main.py– GUI + Logic mixed together
After Refactoring:
main.py– GUI Only (imports password_manager.py)password_manager.py– Business Logic (tested by test_password_manager.py)test_password_manager.py– Unit Tests
Phase 4: Comprehensive Testing
Achieved 100% code coverage with systematic testing.
Test Distribution (26 Tests):
- Password Generation: 7 tests
- Password Validation: 5 tests
- Password Saving: 6 tests
- Password Searching: 5 tests
- Load All Passwords: 3 tests
Architecture & Design
System Architecture
User Interface Layer:
- Tkinter GUI (
main.py)
Business Logic Layer:
generate_password_stringvalidate_password_datasave_password_datasearch_password_dataload_all_passwords
Data Storage Layer:
data.json
Testing Layer:
- Unit Tests (
test_password_manager.py)
Component Interaction
Flow:
- User → GUI (
main.py): Enter Website & Password - GUI → Logic (
password_manager.py): Callsave_password_data() - Logic: Validate data with
validate_password_data() - Logic → File (
data.json): Read existing data or create new file - Logic → File: Write updated data
- Logic → GUI: Return success/error message
- GUI → User: Display message and clear fields
Example Interaction:
# In main.py (GUI)
def save():
website = website_entry.get()
email = email_entry.get()
password = password_entry.get()
# Call business logic
success, message = save_password_data(website, email, password)
if success:
messagebox.showinfo(title="Success", message=message)
# Clear fields
website_entry.delete(0, END)
password_entry.delete(0, END)
else:
messagebox.showerror(title="Error", message=message)
Core Features
Feature Map
Password Manager Core Features:
- Password Generation
- Random Characters
- Letters + Numbers + Symbols
- Auto Copy to Clipboard
- Configurable Length
- Save Passwords
- Input Validation
- JSON Storage
- Update Existing
- Error Handling
- Search Passwords
- Search by Website
- Show Email & Password
- Handle Not Found
- Data Management
- Auto Create File
- Handle Corruption
- Persistent Storage
1. Password Generation Flow
Process:
- User Clicks Generate Button
- Call
generate_password_string() - Select 8-10 Random Letters
- Select 2-4 Random Symbols
- Select 2-4 Random Numbers
- Combine All Characters
- Shuffle Randomly
- Join into String
- Display in Entry Field
- Copy to Clipboard
Implementation:
import random
import string
def generate_password_string():
"""Generate a random password with letters, numbers, and symbols."""
# Define character sets
letters = list(string.ascii_letters)
numbers = list(string.digits)
symbols = ['!', '#', '$', '%', '&', '(', ')', '*', '+']
# Randomly select characters
password_letters = random.choices(letters, k=random.randint(8, 10))
password_symbols = random.choices(symbols, k=random.randint(2, 4))
password_numbers = random.choices(numbers, k=random.randint(2, 4))
# Combine and shuffle
password_list = password_letters + password_symbols + password_numbers
random.shuffle(password_list)
# Convert to string
password = "".join(password_list)
return password
# Usage in GUI
def generate_password():
password = generate_password_string()
password_entry.delete(0, END)
password_entry.insert(0, password)
pyperclip.copy(password) # Copy to clipboard
2. Save Password Flow
Process:
- User Clicks Add Button
- Get Input Data (Website, Email, Password)
- Validate Data
- If empty fields → Show Error Dialog
- If valid → Continue
- Try to Read JSON File
- FileNotFoundError → Create New File
- JSONDecodeError → Create New File
- Success → Update Existing Data
- Write Data to JSON
- Finally Block: Clear Input Fields
- Show Success Message
Implementation:
import json
def validate_password_data(website, email, password):
"""Validate that all fields are filled."""
if len(website) == 0 or len(email) == 0 or len(password) == 0:
return False, "Please don't leave any fields empty!"
return True, "Valid"
def save_password_data(website, email, password, filepath="data.json"):
"""Save password data to JSON file with error handling."""
# Validate input
is_valid, message = validate_password_data(website, email, password)
if not is_valid:
return False, message
# Prepare new data
new_data = {
website: {
"email": email,
"password": password
}
}
try:
# Try to read existing data
with open(filepath, "r") as data_file:
data = json.load(data_file)
except FileNotFoundError:
# File doesn't exist, create new one
with open(filepath, "w") as data_file:
json.dump(new_data, data_file, indent=4)
except json.JSONDecodeError:
# File is corrupted, overwrite with new data
with open(filepath, "w") as data_file:
json.dump(new_data, data_file, indent=4)
else:
# Update existing data
data.update(new_data)
with open(filepath, "w") as data_file:
json.dump(data, data_file, indent=4)
return True, f"Password for {website} saved successfully!"
3. Search Password Flow
Process:
- User Clicks Search Button
- Check if Website Field is Empty
- If empty → Show Error
- Try to Open JSON File
- FileNotFoundError → Show “No Data File”
- JSONDecodeError → Show “Corrupted File”
- Success → Continue
- Search for Website in Data
- Found → Show Email & Password
- Not Found → Show “No Data Found”
Implementation:
def search_password_data(website, filepath="data.json"):
"""Search for password data by website name."""
# Validate input
if len(website) == 0:
return False, "Please enter a website name!", None, None
try:
# Try to open and read data file
with open(filepath, "r") as data_file:
data = json.load(data_file)
except FileNotFoundError:
return False, "No data file found.", None, None
except json.JSONDecodeError:
return False, "Data file is corrupted.", None, None
else:
# Search for website
if website in data:
email = data[website]["email"]
password = data[website]["password"]
return True, f"Found credentials for {website}", email, password
else:
return False, f"No details for {website} exist.", None, None
# Usage in GUI
def find_password():
website = website_entry.get()
success, message, email, password = search_password_data(website)
if success:
messagebox.showinfo(
title=website,
message=f"Email: {email}\nPassword: {password}"
)
else:
messagebox.showerror(title="Error", message=message)
Exception Handling Deep Dive
Exception Hierarchy in Project
Exception Types Handled:
BaseException
└── Exception
├── FileNotFoundError → data.json missing
├── json.JSONDecodeError → Corrupted JSON file
├── KeyError → Website not in data
├── ValueError → Empty input fields
└── IndexError → (handled in edge cases)
Real Example:
# Example of handling multiple exceptions
try:
with open("data.json", "r") as file:
data = json.load(file)
email = data[website]["email"] # Could raise KeyError
except FileNotFoundError:
print("❌ File doesn't exist - creating new one")
data = {}
except json.JSONDecodeError:
print("❌ File is corrupted - resetting data")
data = {}
except KeyError:
print("❌ Website not found in data")
else:
print("✅ Successfully loaded data")
finally:
print("🔄 Cleanup operations complete")
Try/Except/Else/Finally Flow
Execution Order:
- Try Block – Execute risky code
- Decision Point:
- No Exception → Go to Else Block
- FileNotFoundError → Go to Except Block 1
- JSONDecodeError → Go to Except Block 2
- Other Exception → Go to Except Block N
- Else Block – Executes only if no exception
- Finally Block – Always executes (cleanup)
Complete Example:
def save_with_error_handling(data, filepath="data.json"):
"""Demonstrates try/except/else/finally flow."""
file = None
try:
print("🔄 Attempting to save data...")
file = open(filepath, "w")
json.dump(data, file, indent=4)
except PermissionError:
print("❌ Permission denied - cannot write to file")
return False
except Exception as e:
print(f"❌ Unexpected error: {e}")
return False
else:
# Only runs if no exception occurred
print("✅ Data saved successfully!")
return True
finally:
# Always runs, even if return was called
if file:
file.close()
print("🔄 Cleanup complete - file closed")
Real-World Exception Handling Example
Scenario: User tries to save password, but file operations may fail.
def robust_save_password(website, email, password):
"""Production-ready save with comprehensive error handling."""
# Step 1: Validate inputs
if not all([website, email, password]):
return False, "❌ All fields are required"
# Step 2: Prepare data
new_entry = {website: {"email": email, "password": password}}
try:
# Step 3: Try to read existing data
with open("data.json", "r") as file:
data = json.load(file)
except FileNotFoundError:
# Strategy: Create new file
print("📁 No existing file - creating new database")
data = {}
except json.JSONDecodeError:
# Strategy: File corrupted - backup and reset
print("⚠️ Corrupted file detected - creating backup")
try:
os.rename("data.json", "data.json.backup")
except:
pass
data = {}
except PermissionError:
return False, "❌ Permission denied - check file permissions"
else:
# Step 4: Only runs if file read successfully
print("✅ Existing data loaded successfully")
# Step 5: Update data
data.update(new_entry)
try:
# Step 6: Write updated data
with open("data.json", "w") as file:
json.dump(data, file, indent=4)
except Exception as e:
return False, f"❌ Failed to save: {str(e)}"
else:
return True, f"✅ Password for {website} saved!"
finally:
# Step 7: Always log the operation
print(f"📝 Operation completed for {website}")
Error Recovery Strategies
Error Type → Recovery Strategy:
| Error | Strategy | Action | Result |
|---|---|---|---|
| FileNotFoundError | Create File | Create data.json with empty dict {} |
Application continues |
| JSONDecodeError | Reset File | Backup old file, write valid JSON | Data recovered |
| KeyError | Return Default | Return None or empty dict |
Graceful failure |
| ValueError | Show Warning | Display error message to user | App keeps running |
Implementation Example:
def get_password_with_recovery(website):
"""Get password with automatic error recovery."""
try:
with open("data.json", "r") as file:
data = json.load(file)
return data[website]["password"]
except FileNotFoundError:
# Recovery: Create new file
print("Creating new data file...")
with open("data.json", "w") as file:
json.dump({}, file)
return None
except json.JSONDecodeError:
# Recovery: Reset corrupted file
print("Fixing corrupted file...")
import shutil
shutil.copy("data.json", "data.json.corrupt")
with open("data.json", "w") as file:
json.dump({}, file)
return None
except KeyError:
# Recovery: Return default
print(f"No password found for {website}")
return None
except Exception as e:
# Recovery: Log and continue
print(f"Unexpected error: {e}")
return None
Testing Strategy
Testing Pyramid
Testing Levels:
- Unit Tests – 26 Tests, 100% Coverage ✅
test_password_manager.py- Test Classes:
TestPasswordGenerationTestPasswordValidationTestPasswordSavingTestPasswordSearchingTestLoadAllPasswords
- Integration Tests – Future Enhancement
- GUI Tests – Manual Testing
Example Test Structure:
import unittest
import os
import json
from password_manager import (
generate_password_string,
validate_password_data,
save_password_data,
search_password_data
)
class TestPasswordGeneration(unittest.TestCase):
"""Test password generation functionality."""
def test_password_length(self):
"""Test that generated password has correct length range."""
password = generate_password_string()
self.assertGreaterEqual(len(password), 12) # Min: 8 letters + 2 symbols + 2 numbers
self.assertLessEqual(len(password), 18) # Max: 10 letters + 4 symbols + 4 numbers
def test_password_contains_letters(self):
"""Test that password contains letters."""
password = generate_password_string()
self.assertTrue(any(c.isalpha() for c in password))
def test_password_contains_numbers(self):
"""Test that password contains numbers."""
password = generate_password_string()
self.assertTrue(any(c.isdigit() for c in password))
class TestPasswordSaving(unittest.TestCase):
"""Test password saving functionality."""
def setUp(self):
"""Run before each test - create test file."""
self.test_file = "test_data.json"
def tearDown(self):
"""Run after each test - cleanup."""
if os.path.exists(self.test_file):
os.remove(self.test_file)
def test_save_to_new_file(self):
"""Test saving password creates new file."""
success, msg = save_password_data(
"google.com",
"test@email.com",
"pass123",
self.test_file
)
self.assertTrue(success)
self.assertTrue(os.path.exists(self.test_file))
# Verify content
with open(self.test_file, "r") as file:
data = json.load(file)
self.assertIn("google.com", data)
self.assertEqual(data["google.com"]["email"], "test@email.com")
Test Coverage Breakdown
Coverage Analysis – password_manager.py (52 Statements):
| Function | Tests | Status |
|---|---|---|
generate_password_string |
7 Tests | ✅ 100% |
validate_password_data |
5 Tests | ✅ 100% |
save_password_data |
6 Tests | ✅ 100% |
search_password_data |
5 Tests | ✅ 100% |
load_all_passwords |
3 Tests | ✅ 100% |
Overall Coverage: 100% ✅
Running Coverage Report:
# Run tests with coverage
pytest test_password_manager.py --cov=password_manager --cov-report=html
# Output:
# ============================= test session starts ==============================
# collected 26 items
#
# test_password_manager.py::TestPasswordGeneration::test_password_length PASSED
# test_password_manager.py::TestPasswordGeneration::test_contains_letters PASSED
# ... (24 more tests)
#
# ---------- coverage: platform win32, python 3.13.0 -----------
# Name Stmts Miss Cover
# ------------------------------------------
# password_manager.py 52 0 100%
# ------------------------------------------
# TOTAL 52 0 100%
Test Execution Flow
Step-by-Step Process:
- Developer runs:
pytest --cov - pytest starts coverage.py tracking
- pytest executes test suite
- For each test:
- Tests call functions in
password_manager.py - Coverage records which lines are executed
- Function returns result
- Test asserts the result is correct
- Tests call functions in
- All tests complete
- pytest generates reports (terminal + HTML)
- Developer reviews coverage results
Example Test Execution:
# Run a single test with verbose output
def test_password_generation_workflow():
"""Integration test showing full workflow."""
# Step 1: Generate password
password = generate_password_string()
print(f"Generated: {password}")
assert len(password) >= 12
# Step 2: Validate data
is_valid, msg = validate_password_data("test.com", "user@email.com", password)
print(f"Validation: {msg}")
assert is_valid
# Step 3: Save password
success, msg = save_password_data("test.com", "user@email.com", password, "test.json")
print(f"Save: {msg}")
assert success
# Step 4: Search for password
success, msg, email, saved_password = search_password_data("test.com", "test.json")
print(f"Search: Found {email}")
assert success
assert saved_password == password
# Cleanup
os.remove("test.json")
print("✅ All steps passed!")
Test Categories
26 Tests organized into 4 categories:
- Happy Path Tests
- Valid inputs work correctly
- File operations succeed
- Data persists correctly
- Edge Cases
- Empty inputs
- Whitespace only
- Special characters
- Very long inputs
- Error Conditions
- File not found
- Corrupted JSON
- Invalid data types
- Missing keys
- Boundary Testing
- Min/max password length
- Empty JSON file
- Single entry
- Multiple entries
Example Tests:
class TestEdgeCases(unittest.TestCase):
"""Test edge cases and boundary conditions."""
def test_empty_website_name(self):
"""Test validation with empty website."""
is_valid, msg = validate_password_data("", "email@test.com", "pass123")
self.assertFalse(is_valid)
self.assertIn("empty", msg.lower())
def test_special_characters_in_website(self):
"""Test websites with special characters."""
success, msg = save_password_data(
"test-site_123.com",
"user@email.com",
"password",
"test.json"
)
self.assertTrue(success)
def test_corrupted_json_recovery(self):
"""Test recovery from corrupted JSON file."""
# Create corrupted JSON file
with open("test.json", "w") as f:
f.write("{invalid json content")
# Should recover and create new file
success, msg = save_password_data(
"test.com",
"user@email.com",
"pass123",
"test.json"
)
self.assertTrue(success)
Testing Best Practices Applied
The AAA Pattern (Arrange-Act-Assert):
- Arrange – Setup test data
- Create fixtures
- Mock dependencies
- Prepare test environment
- Act – Call function
- Trigger action
- Execute code under test
- Assert – Verify results
- Check return values
- Verify side effects
- Cleanup resources
Implementation Example:
def test_save_and_retrieve_password(self):
"""Complete AAA pattern example."""
# ===== ARRANGE =====
# Setup: Create test file and test data
test_file = "test_data.json"
website = "example.com"
email = "test@email.com"
password = "SecurePass123!"
# Ensure clean state
if os.path.exists(test_file):
os.remove(test_file)
# ===== ACT =====
# Execute: Save the password
save_success, save_msg = save_password_data(
website, email, password, test_file
)
# Execute: Retrieve the password
search_success, search_msg, found_email, found_password = search_password_data(
website, test_file
)
# ===== ASSERT =====
# Verify: Check all results
self.assertTrue(save_success, "Save should succeed")
self.assertTrue(search_success, "Search should succeed")
self.assertEqual(found_email, email, "Email should match")
self.assertEqual(found_password, password, "Password should match")
self.assertTrue(os.path.exists(test_file), "File should exist")
# Verify: Check file contents
with open(test_file, "r") as f:
data = json.load(f)
self.assertIn(website, data)
# ===== CLEANUP =====
os.remove(test_file)
Test Result Flow:
- ✅ Test Passes → Green → Move to next test
- ❌ Test Fails → Red → Debug & Fix → Re-run test
Development Workflo
Iterative Development Process
Development Cycle:
- Requirements → Define what to build
- Design → Plan the implementation
- Implement → Write the code
- Test → Run tests
- ❌ Tests fail? → Debug → Back to Implement
- ✅ Tests pass? → Check coverage
- Coverage Check
- Coverage < 100%? → Add more tests → Back to Test
- Coverage = 100%? → Check if refactoring needed
- Refactor (if needed)
- Refactor code → Back to Test
- No refactoring needed → Documentation
- Documentation → Write docs
- Review → Quality check
- Quality issues? → Improve → Back to Implement
- Quality good? → Deploy/Complete ✅
Git Workflow
Branch Strategy:
# Main branch development
main
├── Initial commit
├── Add basic GUI
│
├── feature/password-gen (branch)
│ ├── Implement password generation
│ ├── Add clipboard copy
│ └── Merge to main ✅
│
├── feature/save-load (branch)
│ ├── Add save functionality
│ ├── Implement JSON storage
│ └── Merge to main ✅
│
├── refactor/separate-logic (branch)
│ ├── Extract business logic
│ ├── Create password_manager.py
│ └── Merge to main ✅
│
├── feature/testing (branch)
│ ├── Add unit tests
│ ├── Achieve 100% coverage
│ ├── Generate HTML reports
│ └── Merge to main ✅
│
├── Add documentation
└── Final polish
Example Git Commands:
# Create feature branch
git checkout -b feature/password-gen
# Make changes and commit
git add password_manager.py
git commit -m "Implement password generation"
# Merge back to main
git checkout main
git merge feature/password-gen
# Delete feature branch
git branch -d feature/password-gen
Continuous Integration Flow
CI/CD Pipeline Steps:
- Developer →
git push→ GitHub Repository - CI Pipeline Triggered
- Install Dependencies (
pip install -r requirements.txt) - Run Linters
- ❌ Linting failed? → Notify Developer → Fix Issues
- ✅ Linting passed? → Continue
- Run Tests (
pytest)- ❌ Tests failed? → Notify Developer → Fix Issues
- ✅ Tests passed? → Continue
- Generate Coverage (
pytest --cov)- ❌ Coverage < 80%? → Notify Developer → Add Tests
- ✅ Coverage ≥ 80%? → Continue
- Build Application
- ❌ Build failed? → Notify Developer → Fix Issues
- ✅ Build success? → Continue
- Deploy/Publish → Success! 🎉
Example CI Configuration (.github/workflows/test.yml):
name: Test and Coverage
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.13'
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Run tests with coverage
run: |
pytest --cov=password_manager --cov-report=term
- name: Check coverage threshold
run: |
pytest --cov=password_manager --cov-fail-under=80
Data Flow & State Management
JSON Data Structure
File Structure (data.json):
{
"google.com": {
"email": "user@email.com",
"password": "abc123XYZ!"
},
"facebook.com": {
"email": "user@email.com",
"password": "secure456DEF#"
},
"twitter.com": {
"email": "user@email.com",
"password": "strong789GHI$"
}
}
Code to Read/Write:
import json
# Writing data
data = {
"google.com": {
"email": "user@email.com",
"password": "abc123XYZ!"
}
}
with open("data.json", "w") as file:
json.dump(data, file, indent=4)
# Reading data
with open("data.json", "r") as file:
data = json.load(file)
# Accessing specific website
google_password = data["google.com"]["password"]
print(f"Google password: {google_password}")
# Adding new entry
data["amazon.com"] = {
"email": "user@email.com",
"password": "newPass999!"
}
with open("data.json", "w") as file:
json.dump(data, file, indent=4)
Data Persistence Flow
Process:
- User → Enters password in Application
- Application → Validates input
- Application → Reads existing data from File (
data.json) - File → Loads data to Memory (Python dictionary)
- Memory → Updates dictionary with new password
- Memory → Writes updated dictionary back to Disk
- File → Confirms save
- Application → Shows success message to User
Note: Data is stored in JSON file → Persistent storage that survives app restarts
Implementation:
def persist_password_workflow(website, email, password):
"""Complete data persistence workflow."""
# Step 1: Validate input
print("1. Validating input...")
if not all([website, email, password]):
return False, "Invalid input"
# Step 2: Read existing data from file to memory
print("2. Reading existing data...")
try:
with open("data.json", "r") as file:
data = json.load(file) # Load to memory (dict)
except (FileNotFoundError, json.JSONDecodeError):
data = {} # Start with empty dict
# Step 3: Update dictionary in memory
print("3. Updating data in memory...")
data[website] = {
"email": email,
"password": password
}
# Step 4: Write back to disk (persist)
print("4. Writing to disk...")
with open("data.json", "w") as file:
json.dump(data, file, indent=4)
# Step 5: Confirm save
print("5. ✅ Data persisted successfully!")
return True, f"Password for {website} saved"
# Data survives app restart!
# Next time app starts, it reads from data.json
State Transitions
Application States:
- [Start] → AppStart
- AppStart → Ready (GUI Loaded)
- Ready State – Main idle state, waiting for user action
From Ready State:
- Generate Password Path:
- Ready → GeneratingPassword (Click Generate)
- GeneratingPassword → Ready (Password Generated)
- Add Password Path:
- Ready → ValidatingInput (Click Add)
- ValidatingInput → SavingData (Valid) OR Ready (Invalid)
- SavingData → CheckingFile (Validate Complete)
- CheckingFile → ReadingJSON (File Exists) OR CreatingFile (File Missing)
- ReadingJSON → UpdatingData (Parse Success) OR CreatingFile (Parse Failed)
- UpdatingData → WritingJSON (Data Updated)
- CreatingFile → WritingJSON (File Created)
- WritingJSON → Ready (Save Complete)
- Search Password Path:
- Ready → Searching (Click Search)
- Searching → Ready (Display Result)
- Close App:
- Ready → [End] (Close App)
State Machine Code Example:
class PasswordManagerState:
"""Simple state machine for password manager."""
def __init__(self):
self.state = "READY"
def generate_password(self):
"""Handle password generation."""
print(f"State: {self.state} → GENERATING")
self.state = "GENERATING"
# Generate password
password = generate_password_string()
print(f"State: {self.state} → READY")
self.state = "READY"
return password
def save_password(self, website, email, password):
"""Handle password saving with state transitions."""
print(f"State: {self.state} → VALIDATING")
self.state = "VALIDATING"
# Validate
if not validate_password_data(website, email, password):
self.state = "READY"
return False
print(f"State: {self.state} → SAVING")
self.state = "SAVING"
# Save
success = save_password_data(website, email, password)
print(f"State: {self.state} → READY")
self.state = "READY"
return success
Key Learnings & Best Practices
Core Concepts Mastered
Lessons Learned:
- Exception Handling: Don’t just catch errors; implement **recovery strategies** (e.g., reset corrupted JSON).
- Code Separation: Decouple **Business Logic** from **UI** for testability.
- Testing: Aim for **100% coverage** to test every line, including all exception paths.
- JSON Management: Use
try/exceptblocks onjson.load()to handle file access and corruption gracefully. - **The
elseBlock:** Use theelseblock intry/exceptstatements for code that should *only* run if thetryblock succeeds (e.g., file update).
Best Practices for Production Code
- **Fail Gracefully:** Never let the application crash. Log the error and show a user-friendly message.
- **Automated Testing:** Use
pytestandcoverage.pyto ensure quality and prevent regressions. - **Use the
withstatement:** Usewith open(...)to ensure files are automatically closed, even if an exception occurs (simplifying thefinallyblock). - **TDD Approach:** Write the test *before* the exception-handling logic to ensure the logic actually handles the error case correctly.
- **Logging vs. Printing:** In production, use a proper logging system instead of
print()for debugging and error tracking.
This project was a fantastic exercise in moving beyond basic scripting to building a robust, production-ready application. **Error handling and testing** are the foundations of reliable software.
—
The complete source code is available on GitHub.