By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.
Error handling in Python for Data Science is not just about preventing crashes—it’s about building resilient pipelines that fail gracefully, log useful diagnostics, and recover automatically when things go wrong.
You’re building a daily ETL job that: 1. Pulls sales data from an API.2. Cleans it with pandas.3. Writes results to a database.
Without error handling:- If the API returns a 500 error, your script crashes, and the database never gets updated.- If a column name changes in the CSV, pd.read_csv() fails, and you lose a day’s worth of data.- If the database connection drops mid-write, you corrupt the table.
pd.read_csv()
With error handling:- The script retries failed API calls.- It logs missing columns and skips bad rows instead of crashing.- It rolls back database transactions if something fails.
try-except
FileNotFoundError
except:
KeyboardInterrupt
MemoryError
else
try
finally
raise
raise ValueError("Invalid date format")
APIRateLimitError
InvalidDataError
with
logging.exception()
print()
tenacity
retry
pandas
requests
sqlalchemy
pip install pandas requests sqlalchemy
We’ll build a script that: 1. Fetches JSON data from an API.2. Cleans it with pandas.3. Writes it to a database.4. Handles errors gracefully (retries, logs, and skips bad data).
import logging logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s", handlers=[logging.FileHandler("etl.log"), logging.StreamHandler()] )
Why?- Logs go to both a file (etl.log) and the console.- Helps debug failures in production.
etl.log
import requests from requests.exceptions import RequestException from tenacity import retry, stop_after_attempt, wait_exponential @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10)) def fetch_data(url): try: response = requests.get(url, timeout=10) response.raise_for_status() # Raises HTTPError for bad responses return response.json() except RequestException as e: logging.error(f"API request failed: {e}") raise # Re-raise to trigger retry
Key Features:- Retries 3 times with exponential backoff (4s, 8s, 16s).- Logs errors before retrying.- Raises RequestException if all retries fail.
RequestException
import pandas as pd def clean_data(raw_data): try: df = pd.DataFrame(raw_data) # Example: Drop rows with missing 'price' column df = df.dropna(subset=["price"]) return df except (KeyError, ValueError) as e: logging.error(f"Data cleaning failed: {e}") raise # Re-raise to handle upstream
Why?- Skips rows with missing critical fields (price).- Logs errors but doesn’t crash the pipeline.
price
from sqlalchemy import create_engine, exc def write_to_db(df, table_name): engine = create_engine("sqlite:///sales.db") try: with engine.begin() as conn: # Auto-rollback on failure df.to_sql(table_name, conn, if_exists="append", index=False) except exc.SQLAlchemyError as e: logging.error(f"Database write failed: {e}") raise
Key Features:- Uses engine.begin() for automatic transaction rollback.- Logs database errors for debugging.
engine.begin()
def run_etl(api_url, table_name): try: logging.info("Starting ETL pipeline") raw_data = fetch_data(api_url) cleaned_data = clean_data(raw_data) write_to_db(cleaned_data, table_name) logging.info("ETL completed successfully") except Exception as e: logging.exception("ETL failed") # Logs full stack trace raise # Re-raise to notify monitoring systems finally: logging.info("ETL run finished (success or failure)") # Example usage run_etl("https://api.example.com/sales", "daily_sales")
Expected Output (Success):
2023-10-01 12:00:00 - INFO - Starting ETL pipeline 2023-10-01 12:00:01 - INFO - ETL completed successfully 2023-10-01 12:00:01 - INFO - ETL run finished (success or failure)
Expected Output (Failure):
2023-10-01 12:00:00 - INFO - Starting ETL pipeline 2023-10-01 12:00:01 - ERROR - API request failed: 500 Server Error 2023-10-01 12:00:05 - ERROR - API request failed: 500 Server Error 2023-10-01 12:00:13 - ERROR - API request failed: 500 Server Error 2023-10-01 12:00:13 - ERROR - ETL failed Traceback (most recent call last): File "etl.py", line 42, in run_etl raw_data = fetch_data(api_url) File "/usr/local/lib/python3.9/site-packages/tenacity/__init__.py", line 324, in wrapped_f return self(f, *args, kw) File "/usr/local/lib/python3.9/site-packages/tenacity/__init__.py", line 404, in __call__ do = self.iter(retry_state=retry_state) File "/usr/local/lib/python3.9/site-packages/tenacity/__init__.py", line 350, in iter raise retry_exc.reraise() File "/usr/local/lib/python3.9/site-packages/tenacity/__init__.py", line 193, in reraise raise self.last_attempt.result() File "/usr/local/lib/python3.9/concurrent/futures/_base.py", line 438, in result return self.__get_result() File "/usr/local/lib/python3.9/concurrent/futures/_base.py", line 390, in __get_result raise self._exception requests.exceptions.HTTPError: 500 Server Error: Internal Server Error for url: https://api.example.com/sales 2023-10-01 12:00:13 - INFO - ETL run finished (success or failure)
except Exception:
SystemExit
Answer: Always use except Exception: (or specific exceptions) unless you have a good reason.
"When should you use else in a try-except block?"
Answer: Use it for code that should run on success (e.g., committing a transaction).
"What’s the purpose of finally?"
Answer: Use it for cleanup (closing files, database connections).
"How do you create a custom exception?" python class InvalidDataError(Exception): pass
python class InvalidDataError(Exception): pass
Exception
raise InvalidDataError("message")
try-finally
Use both together for robust error handling + cleanup.
Logging vs. Printing:
Write a function that: 1. Reads a CSV file.2. Skips rows with missing values in the price column.3. Logs errors for bad rows.4. Returns a cleaned DataFrame.
Solution:
import pandas as pd import logging def read_clean_csv(file_path): try: df = pd.read_csv(file_path) initial_rows = len(df) df = df.dropna(subset=["price"]) skipped_rows = initial_rows - len(df) if skipped_rows > 0: logging.warning(f"Skipped {skipped_rows} rows with missing 'price'") return df except FileNotFoundError: logging.error(f"File not found: {file_path}") raise except Exception as e: logging.exception(f"Failed to read CSV: {e}") raise # Example usage df = read_clean_csv("sales.csv")
Why It Works:- Skips bad rows instead of crashing.- Logs warnings for skipped rows.- Raises exceptions for unrecoverable errors (e.g., missing file).
try: ... except ValueError: ...
try: ... except: ... else: ...
try: ... finally: ...
raise ValueError("Invalid data")
class MyError(Exception): pass
logging.exception("Error")
@retry(stop=stop_after_attempt(3))
with open("file.txt") as f:
except: pass
logging
Join 4M+ learners. Unlock unlimited quizzes, wrong-answer tracking, flashcards + reminders, study guides, and 1-on-1 challenges.