Every piece of the pipeline assumes clean data. But real time-tracker exports have blank hours fields, text in the rate column, and clients with no data at all. What happens to rate * hours when hours is an empty string?
A TypeError. You can't multiply a float by an empty string. The whole script crashes.
try/except wraps the dangerous code. If it raises, the except block catches the error and returns a safe default. Three error types to catch: KeyError for missing dict fields, ValueError for bad string conversions, ZeroDivisionError for any division in downstream calculations:
def safe_compute_total(client: dict) -> float:
try:
rate = float(client["rate"])
hours = float(client["hours"])
return rate * hours
except (KeyError, ValueError, ZeroDivisionError):
return 0.0Why catch multiple error types in one tuple? I thought each except handled one type.
You can list multiple exception types in one tuple: except (KeyError, ValueError): catches either. It is equivalent to two separate except blocks, but more concise. Catch only what you expect — a bare except: catches everything including SystemExit and KeyboardInterrupt, which hides bugs.
So now I can run the full invoice pipeline on a messy export without it crashing halfway through. Bad rows return 0.0 and the run completes.
That is production-grade error handling. The invoice still prints for clean clients; broken rows silently return zero and get flagged:
def safe_compute_total(client: dict) -> float:
try:
rate = float(client["rate"])
hours = float(client["hours"])
total = rate * hours
print(f"Total for {client.get('name', 'unknown')}: ${total:.2f}")
return total
except (KeyError, ValueError, ZeroDivisionError):
return 0.0My invoice script no longer dies on a blank row from the time-tracker export.
The real question: should a zero return be silent? In a real pipeline you might log the bad row — name, which field was missing — so you can fix the export rather than silently ignoring the data.
try/except for Defensive Revenue CalculationThree errors can occur when computing rate * hours from an untrusted dict:
try:
return float(client['rate']) * float(client['hours'])
except (KeyError, ValueError, ZeroDivisionError):
return 0.0KeyError — rate or hours key is missing from the dictValueError — the value is not numeric (e.g. 'n/a' or an empty string)ZeroDivisionError — safety net; rate * hours does not divide, but float conversion edge cases can trigger itReturn 0.0 on error keeps the pipeline moving. A client with a missing rate contributes zero revenue rather than crashing the entire invoice report.
Every piece of the pipeline assumes clean data. But real time-tracker exports have blank hours fields, text in the rate column, and clients with no data at all. What happens to rate * hours when hours is an empty string?
A TypeError. You can't multiply a float by an empty string. The whole script crashes.
try/except wraps the dangerous code. If it raises, the except block catches the error and returns a safe default. Three error types to catch: KeyError for missing dict fields, ValueError for bad string conversions, ZeroDivisionError for any division in downstream calculations:
def safe_compute_total(client: dict) -> float:
try:
rate = float(client["rate"])
hours = float(client["hours"])
return rate * hours
except (KeyError, ValueError, ZeroDivisionError):
return 0.0Why catch multiple error types in one tuple? I thought each except handled one type.
You can list multiple exception types in one tuple: except (KeyError, ValueError): catches either. It is equivalent to two separate except blocks, but more concise. Catch only what you expect — a bare except: catches everything including SystemExit and KeyboardInterrupt, which hides bugs.
So now I can run the full invoice pipeline on a messy export without it crashing halfway through. Bad rows return 0.0 and the run completes.
That is production-grade error handling. The invoice still prints for clean clients; broken rows silently return zero and get flagged:
def safe_compute_total(client: dict) -> float:
try:
rate = float(client["rate"])
hours = float(client["hours"])
total = rate * hours
print(f"Total for {client.get('name', 'unknown')}: ${total:.2f}")
return total
except (KeyError, ValueError, ZeroDivisionError):
return 0.0My invoice script no longer dies on a blank row from the time-tracker export.
The real question: should a zero return be silent? In a real pipeline you might log the bad row — name, which field was missing — so you can fix the export rather than silently ignoring the data.
try/except for Defensive Revenue CalculationThree errors can occur when computing rate * hours from an untrusted dict:
try:
return float(client['rate']) * float(client['hours'])
except (KeyError, ValueError, ZeroDivisionError):
return 0.0KeyError — rate or hours key is missing from the dictValueError — the value is not numeric (e.g. 'n/a' or an empty string)ZeroDivisionError — safety net; rate * hours does not divide, but float conversion edge cases can trigger itReturn 0.0 on error keeps the pipeline moving. A client with a missing rate contributes zero revenue rather than crashing the entire invoice report.
Liv's time-tracker exports sometimes have blank hours fields or text in the rate column. Write `safe_compute_total(client)` that computes `rate * hours` from the client dict, returning the float total on success and `0.0` on any `KeyError`, `ValueError`, or `ZeroDivisionError`. Client dicts have `name`, `rate`, and `hours` keys but any may be missing or non-numeric.
Tap each step for scaffolded hints.
No blank-editor panic.