Skip to content

ERROR HANDLING - Robust file operations with error handling

Python
#!/usr/bin/env python3
"""
ERROR HANDLING - Robust file operations with error handling
Demonstrates handling various file-related errors
"""

import os
import tempfile
import errno

print("=" * 60)
print("ERROR HANDLING - Robust File Operations")
print("=" * 60)

temp_dir = tempfile.gettempdir()

# Example 1: FileNotFoundError
print("\n1. Handling FileNotFoundError")
print("-" * 40)
nonexistent = os.path.join(temp_dir, "does_not_exist.txt")

try:
    with open(nonexistent, 'r') as f:
        content = f.read()
except FileNotFoundError:
    print(f"  Error: File not found: {nonexistent}")
    print("  Creating file instead...")
    with open(nonexistent, 'w') as f:
        f.write("Now it exists\n")
    print("  File created")

os.remove(nonexistent)

# Example 2: PermissionError
print("\n2. Handling PermissionError")
print("-" * 40)
readonly_file = os.path.join(temp_dir, "readonly.txt")

# Create and make read-only
with open(readonly_file, 'w') as f:
    f.write("Read-only content\n")

os.chmod(readonly_file, 0o444)  # Read-only

try:
    with open(readonly_file, 'w') as f:
        f.write("Try to write\n")
except PermissionError:
    print(f"  Error: Permission denied")
    print("  File is read-only")

# Restore permissions and remove
os.chmod(readonly_file, 0o644)
os.remove(readonly_file)

# Example 3: IsADirectoryError
print("\n3. Handling IsADirectoryError")
print("-" * 40)
test_dir = os.path.join(temp_dir, "test_directory")
os.makedirs(test_dir, exist_ok=True)

try:
    with open(test_dir, 'r') as f:
        content = f.read()
except IsADirectoryError:
    print(f"  Error: {test_dir} is a directory, not a file")
    print("  Listing directory instead:")
    print(f"    Contents: {os.listdir(test_dir)}")

os.rmdir(test_dir)

# Example 4: UnicodeDecodeError
print("\n4. Handling UnicodeDecodeError")
print("-" * 40)
binary_file = os.path.join(temp_dir, "binary.dat")

# Write binary data
with open(binary_file, 'wb') as f:
    f.write(bytes([0xFF, 0xFE, 0xFD, 0xFC]))

try:
    with open(binary_file, 'r', encoding='ascii') as f:
        content = f.read()
except UnicodeDecodeError as e:
    print(f"  Error: Cannot decode as ASCII")
    print(f"  Position: {e.start}-{e.end}")
    print("  Reading as binary instead:")
    with open(binary_file, 'rb') as f:
        data = f.read()
        print(f"    Binary data: {list(data)}")

os.remove(binary_file)

# Example 5: IOError / OSError
print("\n5. Handling IOError / OSError")
print("-" * 40)
test_file = os.path.join(temp_dir, "iotest.txt")

try:
    with open(test_file, 'w') as f:
        f.write("Test data\n")

    # Try to write to closed file
    with open(test_file, 'r') as f:
        f.write("This will fail")

except (IOError, OSError) as e:
    print(f"  Error: {e}")
    print("  Cannot write to file opened in read mode")

os.remove(test_file)

# Example 6: ValueError (invalid mode)
print("\n6. Handling ValueError (Invalid Mode)")
print("-" * 40)
test_file = os.path.join(temp_dir, "modetest.txt")

try:
    with open(test_file, 'z') as f:  # Invalid mode
        pass
except ValueError as e:
    print(f"  Error: {e}")
    print("  Valid modes: 'r', 'w', 'a', 'x', 'b', 't', '+'")

# Example 7: FileExistsError (exclusive creation)
print("\n7. Handling FileExistsError (Mode 'x')")
print("-" * 40)
existing_file = os.path.join(temp_dir, "existing.txt")

# Create file
with open(existing_file, 'w') as f:
    f.write("Already exists\n")

try:
    # Try to create exclusively
    with open(existing_file, 'x') as f:
        f.write("New content\n")
except FileExistsError:
    print(f"  Error: File already exists")
    print("  Using append mode instead:")
    with open(existing_file, 'a') as f:
        f.write("Appended content\n")

os.remove(existing_file)

# Example 8: Comprehensive error handling
print("\n8. Comprehensive Error Handling")
print("-" * 40)

def safe_read_file(filepath):
    """Safely read file with comprehensive error handling"""
    try:
        with open(filepath, 'r') as f:
            return f.read()
    except FileNotFoundError:
        return f"ERROR: File not found: {filepath}"
    except PermissionError:
        return f"ERROR: Permission denied: {filepath}"
    except IsADirectoryError:
        return f"ERROR: Is a directory: {filepath}"
    except UnicodeDecodeError:
        return f"ERROR: Cannot decode file: {filepath}"
    except Exception as e:
        return f"ERROR: Unexpected error: {e}"

# Test with various scenarios
test_file = os.path.join(temp_dir, "test.txt")
with open(test_file, 'w') as f:
    f.write("Success!\n")

result = safe_read_file(test_file)
print(f"  Valid file: {result.strip()}")

result = safe_read_file("/nonexistent/path/file.txt")
print(f"  {result}")

os.remove(test_file)

# Example 9: Using errno for specific errors
print("\n9. Using errno for Specific Error Codes")
print("-" * 40)

def handle_file_error(filepath):
    """Handle errors using errno codes"""
    try:
        with open(filepath, 'r') as f:
            return f.read()
    except OSError as e:
        if e.errno == errno.ENOENT:
            print(f"  Error code {errno.ENOENT}: File not found")
        elif e.errno == errno.EACCES:
            print(f"  Error code {errno.EACCES}: Permission denied")
        else:
            print(f"  Error code {e.errno}: {e}")

handle_file_error("/nonexistent.txt")

# Example 10: Cleanup with finally
print("\n10. Cleanup with Finally Block")
print("-" * 40)
test_file = os.path.join(temp_dir, "finally_test.txt")

f = None
try:
    f = open(test_file, 'w')
    f.write("Data\n")
    print("  File written successfully")
except Exception as e:
    print(f"  Error: {e}")
finally:
    if f is not None:
        f.close()
        print("  File closed in finally block")

os.remove(test_file)

# Example 11: Context manager is safer
print("\n11. Why Context Managers Are Better")
print("-" * 40)

# Without context manager - risky
try:
    f = open(os.path.join(temp_dir, "risk.txt"), 'w')
    f.write("Data\n")
    # If error occurs here, file won't close
    # 1 / 0  # Simulate error
    f.close()
except Exception as e:
    print(f"  Error without context manager: {e}")

# With context manager - safe
try:
    with open(os.path.join(temp_dir, "safe.txt"), 'w') as f:
        f.write("Data\n")
        # File closes automatically even on error
        # 1 / 0  # Simulate error
except Exception as e:
    print(f"  Error with context manager: {e}")

print("  Context manager ensures file is closed")

# Cleanup
for f in [os.path.join(temp_dir, "risk.txt"),
          os.path.join(temp_dir, "safe.txt")]:
    if os.path.exists(f):
        os.remove(f)

# Example 12: Error recovery strategies
print("\n12. Error Recovery Strategies")
print("-" * 40)

def read_with_fallback(primary, fallback):
    """Try primary file, fall back to secondary"""
    try:
        with open(primary, 'r') as f:
            print(f"  Reading from primary: {primary}")
            return f.read()
    except FileNotFoundError:
        print(f"  Primary not found, trying fallback")
        try:
            with open(fallback, 'r') as f:
                print(f"  Reading from fallback: {fallback}")
                return f.read()
        except FileNotFoundError:
            print(f"  Both files not found")
            return None

# Create fallback file
fallback_file = os.path.join(temp_dir, "fallback.txt")
with open(fallback_file, 'w') as f:
    f.write("Fallback data\n")

result = read_with_fallback(
    os.path.join(temp_dir, "primary.txt"),
    fallback_file
)

os.remove(fallback_file)

print("\n" + "=" * 60)
print("Key Points:")
print("  - Always use try/except for file operations")
print("  - Handle specific exceptions (FileNotFoundError, etc.)")
print("  - Context managers ensure cleanup")
print("  - Provide fallback strategies")
print("  - Use errno for detailed error codes")
print("=" * 60)