DEV Community

Thiruvengadam Sakthivel
Thiruvengadam Sakthivel

Posted on

#05 – Python File Handling & Exceptions

Welcome to Day 5! Today we shift from volatile, temporary in-memory variables to persistent storage and application durability. You will learn how to interact safely with your operating system's file system, read/write structured industry data patterns, handle real-world operational crashes gracefully, and keep execution timelines documented using professional logging architectures. 💾


1. File Handling & pathlib 📄

Python's pathlib module treats file paths as smart object structures instead of plain text strings. This avoids bugs caused by differing slash directions across operating systems (Windows uses \, while Mac/Linux use /).

  • Reading ("r"): Loads file contents into memory.
  • Writing ("w"): Erases any existing file contents and writes a fresh payload from scratch.
  • Appending ("a"): Targets the end of a file, adding fresh text without overwriting existing contents.

🌱 Easy Starter Example

from pathlib import Path

# Create a path reference pointing to a file in the current workspace directory
file_path = Path("notes.txt")

# Write text cleanly to a file space
file_path.write_text("Hello from Day 5!")

# Read data straight back into a string variable
content = file_path.read_text()
print(content)  # Output: Hello from Day 5!

Enter fullscreen mode Exit fullscreen mode

🏛️ Real-World Example: Multi-Platform System Telemetry Appender

from pathlib import Path
from datetime import datetime

def log_system_status(status_message: str) -> None:
    # Resolve home folder pathways seamlessly across Windows, Mac, or Linux systems
    target_dir = Path.home() / "app_workspace" / "telemetry"

    # Create the directory chain automatically if it doesn't exist yet
    target_dir.mkdir(parents=True, exist_ok=True)

    log_file = target_dir / "runtime_events.log"
    timestamp = datetime.now().isoformat()

    # Secure stream channel using Python's standard file-open context manager
    with open(log_file, mode="a", encoding="utf-8") as file:
        file.write(f"[{timestamp}] STATUS: {status_message}\n")

log_system_status("Core subsystem initialization sequence completed successfully.")

Enter fullscreen mode Exit fullscreen mode

2. Structured Data Formats (JSON & CSV) 📊

A. JSON (JavaScript Object Notation)

JSON is a text format used for web data exchanges and configuration states. It mirrors Python dictionaries and lists.

🌱 Easy Starter Example

import json

user_profile = {"username": "alice", "active": True, "roles": ["admin", "dev"]}

# Convert a Python dictionary to a structured JSON data string
json_string = json.dumps(user_profile)
print(json_string)  # Output: {"username": "alice", "active": true, ...}

# Parse a JSON data string straight back into a standard Python dictionary
parsed_dict = json.loads(json_string)

Enter fullscreen mode Exit fullscreen mode

🏛️ Real-World Example: Storing and Modifying Local Configuration Files

import json
from pathlib import Path

config_path = Path("settings.json")

# Write configuration updates safely into a localized file storage space
default_settings = {"theme": "dark", "port": 8080, "cache_enabled": True}
with open(config_path, mode="w", encoding="utf-8") as file:
    json.dump(default_settings, file, indent=4)

# Read, modify, and update configuration maps securely
if config_path.exists():
    with open(config_path, mode="r", encoding="utf-8") as file:
        current_config = json.load(file)

    # Modify parameter configurations
    current_config["theme"] = "dim-nordic-blue"

    with open(config_path, mode="w", encoding="utf-8") as file:
        json.dump(current_config, file, indent=4)

Enter fullscreen mode Exit fullscreen mode

B. CSV (Comma-Separated Values)

CSV files store tabular spreadsheet data in plain text lines.

🌱 Easy Starter Example

import csv

# Writing records cleanly into rows
with open("basic.csv", mode="w", newline="", encoding="utf-8") as file:
    writer = csv.writer(file)
    writer.writerow(["Name", "Score"])
    writer.writerow(["Bob", "92"])

# Reading records straight back from rows
with open("basic.csv", mode="r", encoding="utf-8") as file:
    reader = csv.reader(file)
    for row in reader:
        print(row)  # Output: ['Name', 'Score'], ['Bob', '92']

Enter fullscreen mode Exit fullscreen mode

🏛️ Real-World Example: Product Inventory Report Processing Engine

import csv
from pathlib import Path

inventory_file = Path("warehouse_inventory.csv")

# Generate a cleanly tracked CSV document mapping item column cells out explicitly
products = [
    {"sku": "PROD-001", "name": "Mechanical Keyboard", "quantity": "45"},
    {"sku": "PROD-002", "name": "Ergonomic Mouse", "quantity": "120"}
]

with open(inventory_file, mode="w", newline="", encoding="utf-8") as file:
    # Use DictWriter to map dictionaries directly to structured CSV columns
    fieldnames = ["sku", "name", "quantity"]
    writer = csv.DictWriter(file, fieldnames=fieldnames)

    writer.writeheader()
    writer.writerows(products)

Enter fullscreen mode Exit fullscreen mode

3. Exception Handling 🛠️

Exceptions are errors detected during application execution. Exception Handling keeps your program running smoothly when something goes wrong, instead of crashing unexpectedly.

  • try: Wraps code blocks that might trigger an unexpected error.
  • except: Code block that catches and handles specific runtime errors.
  • else: Runs only if the code inside the try block executes without any errors.
  • finally: Runs always, regardless of whether an error occurred or not. Ideal for cleanup tasks like closing database connections.
  • raise: Forces a specific error to occur intentionally.

🌱 Easy Starter Example

try:
    number = int(input("Enter a number to divide 10 by: "))
    result = 10 / number
except ZeroDivisionError:
    print("Error: You cannot divide numbers by zero.")
except ValueError:
    print("Error: Invalid character input. You must input digits.")
else:
    print(f"Calculation success! Answer is: {result}")
finally:
    print("Execution loop complete.")

Enter fullscreen mode Exit fullscreen mode

🏛️ Real-World Example: Robust External API Data File Extraction Pipeline

from pathlib import Path

def fetch_transaction_payload(filename: str) -> str:
    target_file = Path(filename)

    try:
        # Code block might trigger a FileNotFoundError if missing
        with open(target_file, mode="r", encoding="utf-8") as file:
            data = file.read()
            if not data.strip():
                # Manually raise an explicit error flag if data payload is completely blank
                raise ValueError("Target file payload context space is completely empty.")

    except FileNotFoundError as error:
        return f"Operational Failure: Target file reference '{filename}' missing on disk. Details: {error}"
    except ValueError as validation_error:
        return f"Data Integrity Failure: {validation_error}"
    else:
        print("File parsed from local disks without encountering structural blocks.")
        return data
    finally:
        print("System resource locks released successfully.")

print(fetch_transaction_payload("missing_ledger.dat"))

Enter fullscreen mode Exit fullscreen mode

4. Custom Exceptions 🚨

Standard exceptions (like ValueError) are often too broad. Creating Custom Exceptions lets you name specific errors after real-world issues in your business logic, making bugs much easier to trace.

🌱 Easy Starter Example

# Custom exception classes inherit directly from the base Exception class
class AgeRestrictionError(Exception):
    pass

def verify_voter_age(age: int):
    if age < 18:
        raise AgeRestrictionError("User is under the required voting age threshold.")
    print("Access granted.")

try:
    verify_voter_age(16)
except AgeRestrictionError as error:
    print(f"Registration Denied: {error}")

Enter fullscreen mode Exit fullscreen mode

🏛️ Real-World Example: Digital Wallet Transaction Guardrail

class InsufficientFundsError(Exception):
    """Raised when an account attempts to withdraw more than their active balance."""
    def __init__(self, requested: float, available: float):
        super().__init__(f"Attempted to withdraw ${requested}, but only ${available} is available.")
        self.requested = requested
        self.available = available

class BankAccount:
    def __init__(self, owner: str, balance: float):
        self.owner = owner
        self.balance = balance

    def withdraw(self, amount: float) -> float:
        if amount > self.balance:
            # Trigger custom exception, passing runtime values into the error context
            raise InsufficientFundsError(amount, self.balance)
        self.balance -= amount
        return self.balance

# Execution verification handling loops
user_account = BankAccount("Sarah Jenkins", 250.00)
try:
    user_account.withdraw(400.00)
except InsufficientFundsError as error:
    print(f"[TRANSACTION BLOCKED] Overdraft prevention active: {error}")

Enter fullscreen mode Exit fullscreen mode

5. Logging 🪵

Using print() to track errors is an anti-pattern; outputs disappear when the terminal closes. The logging module provides a permanent tracker that categorizes events by severity and writes them to disk.

Log Levels

Level Severity Code Value Core Functional Destination Purpose
DEBUG 10 Detailed diagnostic information for local debugging.
INFO 20 Confirms the application is working as expected.
WARNING 30 Indicates something unexpected happened, but the app can still run.
ERROR 40 A serious issue that caused a specific operation or function to fail.
CRITICAL 50 A fatal error that threatens to crash the entire application instantly.

🌱 Easy Starter Example

import logging

# Simple logger configuration setup out of the box
logging.basicConfig(level=logging.INFO)

logging.info("System process successfully started.")
logging.warning("Disk space approaching filled capacity limits.")
logging.error("Database connection dropped.")

Enter fullscreen mode Exit fullscreen mode

🏛️ Real-World Example: Enterprise Application Logging Architecture

import logging
from pathlib import Path

def initialize_production_logger():
    log_dir = Path("logs")
    log_dir.mkdir(exist_ok=True)

    # Configure production logger to write messages to a file with timestamps
    logging.basicConfig(
        filename=log_dir / "production_runtime.log",
        filemode="a",
        format="%(asctime)s - [%(levelname)s] - %(name)s - %(message)s",
        level=logging.WARNING # Directs the application to log only WARNING level issues or worse
    )

initialize_production_logger()
logger = logging.getLogger("PaymentGateway")

def process_credit_card():
    try:
        # Simulating external microservice outage breakdown error states
        raise ConnectionRefusedError("Payment processor API endpoint timeout.")
    except ConnectionRefusedError as error:
        # Logs the detailed stack trace to your file automatically
        logger.error("Failed to process transaction securely.", exc_info=True)

process_credit_card()

Enter fullscreen mode Exit fullscreen mode

6. Practice Challenge: Data Processing Engine 🏋️

Let’s combine everything from today into a single, comprehensive exercise. We will write a data ingestion script that reads a user dataset from a file, validates the information using custom exceptions, and logs everything to a localized metrics file.

Step 1: Set Up Your Project Files

Using your terminal skills from Day 4, create a workspace with a fresh script file:

uv init day_5_challenge && cd day_5_challenge
touch data_processor.py

Enter fullscreen mode Exit fullscreen mode

Step 2: Implement the Code Base

Open data_processor.py and implement the production data parsing pipeline:

# data_processor.py
import json
import csv
import logging
from pathlib import Path

# 1. Configure the system logging tracking layout
logging.basicConfig(
    filename=Path("system_execution.log"),
    filemode="w",
    format="%(asctime)s - [%(levelname)s] - %(message)s",
    level=logging.INFO
)
logger = logging.getLogger("DataProcessor")

# 2. Define custom domain error exception patterns
class InvalidUserDataError(Exception):
    """Raised when user data records fail business rule validation criteria."""
    pass

# 3. Implement parsing pipeline logic
def validate_and_transform_records(raw_csv_path: str, output_json_path: str) -> None:
    source_file = Path(raw_csv_path)
    destination_file = Path(output_json_path)
    processed_records = []

    logger.info("Initializing raw transaction CSV import pipeline.")

    try:
        with open(source_file, mode="r", encoding="utf-8") as file:
            reader = csv.DictReader(file)

            for row_index, row in enumerate(reader, start=1):
                try:
                    username = row.get("username", "").strip()
                    email = row.get("email", "").strip()
                    age_str = row.get("age", "").strip()

                    # Core Validation Rules: Check for missing data fields
                    if not username or not email or not age_str:
                        raise InvalidUserDataError(f"Row {row_index}: Missing critical data fields.")

                    age = int(age_str)
                    if age <= 0 or age > 120:
                        raise InvalidUserDataError(f"Row {row_index}: Age context constraint anomaly ({age}).")

                    # Data Transformation step
                    processed_records.append({
                        "user_id": username.lower(),
                        "contact": email,
                        "years_active": age,
                        "verified_status": True if age >= 18 else False
                    })
                    logger.info(f"Successfully processed user record: '{username}'.")

                except InvalidUserDataError as data_error:
                    logger.warning(f"Skipping row registry error: {data_error}")
                except ValueError:
                    logger.warning(f"Skipping row {row_index}: Failed parsing age string component to numerical digit.")

    except FileNotFoundError:
        logger.critical(f"Pipeline processing halted. Source input file missing: '{raw_csv_path}'")
        print("Fatal Error: Source database file not found. Check system execution logs.")
        return

    # Write successfully processed data records out to JSON format storage
    try:
        with open(destination_file, mode="w", encoding="utf-8") as out_file:
            json.dump(processed_records, out_file, indent=4)
        logger.info(f"Ingestion successful! Saved {len(processed_records)} records to '{output_json_path}'.")
        print(f"Success! Processed data exported cleanly to {output_json_path}")
    except Exception as general_error:
        logger.error(f"Failed exporting JSON package data: {general_error}")

# 4. Local Execution Test Suite Harness Setup
if __name__ == "__main__":
    # Generate dummy source data for testing the validation loops
    raw_data_path = "source_users.csv"
    with open(raw_data_path, mode="w", newline="", encoding="utf-8") as test_csv:
        writer = csv.writer(test_csv)
        writer.writerow(["username", "email", "age"])
        writer.writerow(["s_rogers", "cap@avengers.org", "102"])  # Valid Row
        writer.writerow(["t_stark", "", "48"])                  # Invalid Row: Missing Email
        writer.writerow(["p_parker", "spidey@dailybugle.com", "-5"]) # Invalid Row: Bad Age
        writer.writerow(["b_banner", "hulk@smash.com", "51"])    # Valid Row

    # Run the data pipeline processing execution
    validate_and_transform_records(raw_data_path, "cleansed_output.json")

Enter fullscreen mode Exit fullscreen mode

Step 3: Run and Verify Your Outputs

Execute your processing engine script inside your terminal space:

uv run data_processor.py

Enter fullscreen mode Exit fullscreen mode

Now, check the generated outputs. Your cleansed_output.json file will contain only the verified rows:

[
    {
        "user_id": "s_rogers",
        "contact": "cap@avengers.org",
        "years_active": 102,
        "verified_status": true
    },
    {
        "user_id": "b_banner",
        "contact": "hulk@smash.com",
        "years_active": 51,
        "verified_status": true
    }
]

Enter fullscreen mode Exit fullscreen mode

Open system_execution.log to review how the engine tracked errors behind the scenes without crashing:

2026-07-17 23:59:00,124 - [INFO] - Initializing raw transaction CSV import pipeline.
2026-07-17 23:59:00,125 - [INFO] - Successfully processed user record: 's_rogers'.
2026-07-17 23:59:00,125 - [WARNING] - Skipping row registry error: Row 3: Missing critical data fields.
2026-07-17 23:59:00,126 - [WARNING] - Skipping row registry error: Row 4: Age context constraint anomaly (-5).
2026-07-17 23:59:00,126 - [INFO] - Successfully processed user record: 'b_banner'.
2026-07-17 23:59:00,127 - [INFO] - Ingestion successful! Saved 2 records to 'cleansed_output.json'.

Enter fullscreen mode Exit fullscreen mode

Top comments (0)