• Our Office
  • 429 A-Block DHA EME Lahore

 

Day 30 of Python-30: Mastering error handling, JSON operations, and achieving 100% test coverage


📖 Table of Contents

  1. Project Overview
  2. The Learning Journey
  3. Architecture & Design
  4. Core Features
  5. Exception Handling Deep Dive
  6. Testing Strategy
  7. Development Workflow
  8. Data Flow & State Management
  9. 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:

  1. Initial Idea → GUI Design
  2. Basic Save Feature → JSON Storage
  3. Encountered Crashes & Errors
  4. 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_string
  • validate_password_data
  • save_password_data
  • search_password_data
  • load_all_passwords

Data Storage Layer:

  • data.json

Testing Layer:

  • Unit Tests (test_password_manager.py)

Component Interaction

Flow:

  1. User → GUI (main.py): Enter Website & Password
  2. GUI → Logic (password_manager.py): Call save_password_data()
  3. Logic: Validate data with validate_password_data()
  4. Logic → File (data.json): Read existing data or create new file
  5. Logic → File: Write updated data
  6. Logic → GUI: Return success/error message
  7. 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:

  1. Password Generation
    • Random Characters
    • Letters + Numbers + Symbols
    • Auto Copy to Clipboard
    • Configurable Length
  2. Save Passwords
    • Input Validation
    • JSON Storage
    • Update Existing
    • Error Handling
  3. Search Passwords
    • Search by Website
    • Show Email & Password
    • Handle Not Found
  4. Data Management
    • Auto Create File
    • Handle Corruption
    • Persistent Storage

1. Password Generation Flow

Process:

  1. User Clicks Generate Button
  2. Call generate_password_string()
  3. Select 8-10 Random Letters
  4. Select 2-4 Random Symbols
  5. Select 2-4 Random Numbers
  6. Combine All Characters
  7. Shuffle Randomly
  8. Join into String
  9. Display in Entry Field
  10. 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:

  1. User Clicks Add Button
  2. Get Input Data (Website, Email, Password)
  3. Validate Data
    • If empty fields → Show Error Dialog
    • If valid → Continue
  4. Try to Read JSON File
    • FileNotFoundError → Create New File
    • JSONDecodeError → Create New File
    • Success → Update Existing Data
  5. Write Data to JSON
  6. Finally Block: Clear Input Fields
  7. 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:

  1. User Clicks Search Button
  2. Check if Website Field is Empty
    • If empty → Show Error
  3. Try to Open JSON File
    • FileNotFoundError → Show “No Data File”
    • JSONDecodeError → Show “Corrupted File”
    • Success → Continue
  4. 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:

  1. Try Block – Execute risky code
  2. 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
  3. Else Block – Executes only if no exception
  4. 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:

  1. Unit Tests – 26 Tests, 100% Coverage ✅
    • test_password_manager.py
    • Test Classes:
      • TestPasswordGeneration
      • TestPasswordValidation
      • TestPasswordSaving
      • TestPasswordSearching
      • TestLoadAllPasswords
  2. Integration Tests – Future Enhancement
  3. 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:

  1. Developer runs: pytest --cov
  2. pytest starts coverage.py tracking
  3. pytest executes test suite
  4. 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
  5. All tests complete
  6. pytest generates reports (terminal + HTML)
  7. 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:

  1. Happy Path Tests
    • Valid inputs work correctly
    • File operations succeed
    • Data persists correctly
  2. Edge Cases
    • Empty inputs
    • Whitespace only
    • Special characters
    • Very long inputs
  3. Error Conditions
    • File not found
    • Corrupted JSON
    • Invalid data types
    • Missing keys
  4. 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):

  1. Arrange – Setup test data
    • Create fixtures
    • Mock dependencies
    • Prepare test environment
  2. Act – Call function
    • Trigger action
    • Execute code under test
  3. 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:

  1. Requirements → Define what to build
  2. Design → Plan the implementation
  3. Implement → Write the code
  4. Test → Run tests
    • ❌ Tests fail? → Debug → Back to Implement
    • ✅ Tests pass? → Check coverage
  5. Coverage Check
    • Coverage < 100%? → Add more tests → Back to Test
    • Coverage = 100%? → Check if refactoring needed
  6. Refactor (if needed)
    • Refactor code → Back to Test
    • No refactoring needed → Documentation
  7. Documentation → Write docs
  8. 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:

  1. Developer → git push → GitHub Repository
  2. CI Pipeline Triggered
  3. Install Dependencies (pip install -r requirements.txt)
  4. Run Linters
    • ❌ Linting failed? → Notify Developer → Fix Issues
    • ✅ Linting passed? → Continue
  5. Run Tests (pytest)
    • ❌ Tests failed? → Notify Developer → Fix Issues
    • ✅ Tests passed? → Continue
  6. Generate Coverage (pytest --cov)
    • ❌ Coverage < 80%? → Notify Developer → Add Tests
    • ✅ Coverage ≥ 80%? → Continue
  7. Build Application
    • ❌ Build failed? → Notify Developer → Fix Issues
    • ✅ Build success? → Continue
  8. 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:

  1. User → Enters password in Application
  2. Application → Validates input
  3. Application → Reads existing data from File (data.json)
  4. File → Loads data to Memory (Python dictionary)
  5. Memory → Updates dictionary with new password
  6. Memory → Writes updated dictionary back to Disk
  7. File → Confirms save
  8. 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:

  1. [Start] → AppStart
  2. AppStart → Ready (GUI Loaded)
  3. 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:

  1. Exception Handling: Don’t just catch errors; implement **recovery strategies** (e.g., reset corrupted JSON).
  2. Code Separation: Decouple **Business Logic** from **UI** for testability.
  3. Testing: Aim for **100% coverage** to test every line, including all exception paths.
  4. JSON Management: Use try/except blocks on json.load() to handle file access and corruption gracefully.
  5. **The else Block:** Use the else block in try/except statements for code that should *only* run if the try block succeeds (e.g., file update).

Best Practices for Production Code

  1. **Fail Gracefully:** Never let the application crash. Log the error and show a user-friendly message.
  2. **Automated Testing:** Use pytest and coverage.py to ensure quality and prevent regressions.
  3. **Use the with statement:** Use with open(...) to ensure files are automatically closed, even if an exception occurs (simplifying the finally block).
  4. **TDD Approach:** Write the test *before* the exception-handling logic to ensure the logic actually handles the error case correctly.
  5. **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.

 

Leave A Comment

All fields marked with an asterisk (*) are required