Saturday, June 7, 2025

5 Error service patterns in Python (except for the test)

Share

Photo by the author Canva

When it comes to servicing errors, the first thing we usually learn is to operate test blocks. But is that really enough because our code database becomes more elaborate? I don’t believe that. Relying only on rehearsals can lead to repetitive, cluttered and complex to maintain code.

In this article I will go to you 5 Advanced but practical error service patterns This can make your code more reliable and easier to debug. Each pattern has an example, so you can clearly see where and why it makes sense. Let’s start.

1. Aggregation of error for batch processing

When processing many elements (e.g. in a loop) you can continue processing, even if some elements fail, report all errors at the end. This pattern, called Aggregation of errorsavoids stopping the first failure. This pattern is perfect for checking the correctness of the form, data import scenarios or any situation in which you want to provide comprehensive feedback about all problems, and not stop at the first error.

Example: Processing of the user record list. Continue even if some failure.

def process_user_record(record, record_number):
    if not record.get("email"):
        raise ValueError(f"Record #{record_number} failed: Missing email in record {record}")
    
    # Simulate processing
    print(f"Processed user #{record_number}: {record['email']}")

def process_users(records):
    errors = []
    for index, record in enumerate(records, start=1):  
        try:
            process_user_record(record, index)
        except ValueError as e:
            errors.append(str(e))
    return errors

users = [
    {"email": "qasim@example.com"},
    {"email": ""},
    {"email": "zeenat@example.com"},
    {"email": ""}
]

errors = process_users(users)

if errors:
    print("nProcessing completed with errors:")
    for error in errors:
        print(f"- {error}")
else:
    print("All records processed successfully")

This code loopes through user records and processes each of them individually. If the record lacks E -Mail in the record, it raises Valueerror, which is caught and stored on the error list. The process lasts for all records, and all failures are reported at the end without stopping the whole batch in this way:

Output:
Processed user #1: qasim@example.com
Processed user #3: zeenat@example.com

Processing completed with errors:
- Record #2 failed: Missing email in record {'email': ''}
- Record #4 failed: Missing email in record {'email': ''}

2. Context for resource management

When working with resources such as files, database connections or network sockets, you need to make sure that they are properly open and closed, even if an error occurs. Context managers, using the instructions, support it automatically, reducing the chance of resource leaks compared to manual test blocks. This pattern is particularly helpful in We/O operations or in contacts with external systems.

Example: Suppose you read the CSV file and want to make sure it is properly closed, even if the file processing fails.

import csv

def read_csv_data(file_path):
    try:
        with open(file_path, 'r') as file:
            print(f"Inside 'with': file.closed = {file.closed}")  # Should be False
            reader = csv.reader(file)
            for row in reader:
                if len(row) < 2:
                    raise ValueError("Invalid row format")
                print(row)
        print(f"After 'with': file.closed = {file.closed}")  # Should be True
        
    except FileNotFoundError:
        print(f"Error: File {file_path} not found")
        print(f"In except block: file is closed? {file.closed}")

    except ValueError as e:
        print(f"Error: {e}")
        print(f"In except block: file is closed? {file.closed}")

# Create test file
with open("data.csv", "w", newline="") as f:
    writer = csv.writer(f)
    writer.writerows([["Name", "Age"], ["Sarwar", "30"], ["Babar"], ["Jamil", "25"]])

# Run
read_csv_data("data.csv")

This code uses instructions (context manager) to securely open and read the file. If any government has less than 2 values, it raises ValueerrorBut the file is still closed automatically. . file.closed The inspections confirm the file status both inside and after the block - even in the case of an error. Start the above code to observe this behavior:

Output:
Inside 'with': file.closed = False
['Name', 'Age']
['Sarwar', '30']
Error: Invalid row format
In except block: file is closed? True

3. Packaging of exceptions for contextual errors

Sometimes an exception to a lower level function does not ensure a sufficient context about what went wrong in the wider application. Packaging of exception (or connecting) allows you to catch an exception, add the context and recruit a novel exception that includes the original. This is especially useful in sandwich applications (e.g. API or service).

Example: Suppose you download user data from the database and want to provide context when the database error occurs.

class DatabaseAccessError(Exception):
    """Raised when database operations fail."""
    pass

def fetch_user(user_id):
    try:
        # Simulate database query
        raise ConnectionError("Failed to connect to database")
    except ConnectionError as e:
        raise DatabaseAccessError(f"Failed to fetch user {user_id}") from e

try:
    fetch_user(123)
except DatabaseAccessError as e:
    print(f"Error: {e}")
    print(f"Caused by: {e.__cause__}")

. Connectionerror He is caught and wrapped in Databaseaccesserror with an additional context about the user identifier. The syntax combines the original exception, so the full error chain is available for debugation. The output may look like this:

Output:
Error: Failed to fetch user 123
Caused by: Failed to connect to database

4. Retry logic for transient failures

Some errors, such as network time limit or unavailability of transient services, are transient and can resolve again. Using the pattern of re -coping with grace without the extermination of the code with the assist of manual loops. Automates recovery after transient failures.

Example: Let's velvety the flaky call of the API, which sometimes fails due to simulated network errors. The code below tries to connect the API many times with a delayed delay between trials. If the connection succeeds, it immediately returns the result. If all again goes again, it raises an exception that should be handled by the caller.

import random
import time

def flaky_api_call():
    # Simulate 50% chance of failure (like timeout or server error)
    if random.random() < 0.5:
        raise ConnectionError("Simulated network failure")
    return {"status": "success", "data": [1, 2, 3]}

def fetch_data_with_retry(retries=4, delay=2):
    attempt = 0
    while attempt < retries:
        try:
            result = flaky_api_call()
            print("API call succeeded:", result)
            return result
        except ConnectionError as e:
            attempt += 1
            print(f"Attempt {attempt} failed: {e}. Retrying in {delay} seconds...")
            time.sleep(delay)
    raise ConnectionError(f"All {retries} attempts failed.")

try:
    fetch_data_with_retry()
except ConnectionError as e:
    print("Final failure:", e)
Output:
Attempt 1 failed: Simulated network failure. Retrying in 2 seconds...
API call succeeded: {'status': 'success', 'data': [1, 2, 3]}

As you can see, the first attempt failed due to a simulated network error (which happens randomly in 50% of cases). The logic again waited for 2 seconds, and then successfully completed the API connection at the next attempt.

5. Non -standard exception classes for specific errors for the domain

Instead of relying on general exceptions such as Valueerror Or RuntimeerrorYou can create non -standard exception classes to present specific errors in the application domain. This makes error handling more semantic and easier to maintain.

Example: Let's assume that the payment processing system in which different types of payment failure require specific service.

class PaymentError(Exception):
    """Base class for payment-related exceptions."""
    pass

class InsufficientFundsError(PaymentError):
    """Raised when the account has insufficient funds."""
    pass

class InvalidCardError(PaymentError):
    """Raised when the card details are invalid."""
    pass

def process_payment(amount, card_details):
    try:
        if amount > 1000:
            raise InsufficientFundsError("Not enough funds for this transaction")
        if not card_details.get("valid"):
            raise InvalidCardError("Invalid card details provided")
        print("Payment processed successfully")
    except InsufficientFundsError as e:
        print(f"Payment failed: {e}")
        # Notify user to top up account
    except InvalidCardError as e:
        print(f"Payment failed: {e}")
        # Prompt user to re-enter card details
    except Exception as e:
        print(f"Unexpected error: {e}")
        # Log for debugging

process_payment(1500, {"valid": False})

Non -standard exceptions (Insfitfit Fundserror, Invalidcarderror) Inherit from the basic payment class, enabling a different problem of payment problems, while attracting unexpected mistakes with the assist of a general block of exceptions. For example in Call process_payment (1500, {"correct": false})The first check starts because the amount (1500) exceeds 1000, so it increases the insufficient sedserror. This exception is caught in the appropriate block, printing:

Output:
Payment failed: Not enough funds for this transaction

Application

That's all. In this article, we examined 5 practical error service patterns:

  1. Aggregation of errors: Process all elements, collect errors and report them together
  2. Contextual manager: Safely manage resources such as files with blocks
  3. Packaging of exceptions: Add the context, catching and collecting exceptions again
  4. Logic again again Automatically re -transitional errors, such as network failures
  5. Custom exceptions: Create specific error classes for clearer service

Try these patterns in the next project. With a little practice you will find a code easier to maintain and operate errors much more effective.

Canwal Mehreen Kanwal is a machine learning engineer and a technical writer with a deep passion for data learning and AI intersection with medicine. He is the co -author of the ebook "maximizing performance from chatgpt". As a Google 2022 generation scholar for APAC, it tells diversity and academic perfection. It is also recognized as a variety of terradate at Tech Scholar, Mitacs Globalink Research Scholar and Harvard Wecode Scholar. Kanwalwal is a heated supporter of changes, after establishing FemCodes to strengthen women in the STEM fields.

Latest Posts

More News