Error Handling ============= The USDA FDC Python Client provides a comprehensive error handling system to help you handle various error conditions gracefully. Exception Hierarchy ----------------- The library defines the following exception hierarchy: - ``FdcApiError``: Base exception for all API errors (HTTP 5xx and anything unmapped) - ``FdcAuthError``: Authentication failed — HTTP 401 or 403. FDC sits behind api.data.gov, which rejects an invalid key with **403**, so both are treated as an auth failure. - ``FdcRateLimitError``: API rate limit exceeded — HTTP 429 - ``FdcTimeoutError``: The API did not respond within the client timeout - ``FdcValidationError``: The API rejected the request — HTTP 400, typically a parameter outside the range FDC accepts (``page_size`` above 200, say) - ``FdcResourceNotFoundError``: Requested resource not found — HTTP 404. A food that does not exist is an ordinary outcome of a lookup, not a breakdown, so it is worth catching on its own. Every one of these is an ``FdcApiError``, so a broad ``except FdcApiError`` still catches the lot. Request Timeouts ---------------- Every request is issued with a timeout, so a server that accepts a connection but never answers cannot block the caller indefinitely. The default is 30 seconds, and it applies to both connect and read: .. code-block:: python from usda_fdc import FdcClient, FdcTimeoutError # Use the 30 second default, or set your own client = FdcClient(api_key="your_api_key_here", timeout=5.0) try: food = client.get_food(1750340) except FdcTimeoutError as e: print(f"FDC was too slow: {e}") ``FdcTimeoutError`` subclasses ``FdcApiError``, so existing ``except FdcApiError`` blocks keep catching timeouts. Catch it separately when you want to tell "slow" apart from "broken" — a timeout is usually worth retrying, a 400 is not. Basic Error Handling ----------------- Here's how to handle errors when using the client: .. code-block:: python from usda_fdc import FdcClient, FdcApiError, FdcAuthError, FdcRateLimitError client = FdcClient(api_key="your_api_key_here") try: food = client.get_food(1750340) except FdcAuthError: print("Authentication failed. Check your API key.") except FdcRateLimitError: print("Rate limit exceeded. Try again later.") except FdcApiError as e: print(f"API error: {e}") except Exception as e: print(f"Unexpected error: {e}") Handling Specific HTTP Status Codes -------------------------------- Every ``FdcApiError`` carries the HTTP status the API replied with, as ``status_code``. It is ``None`` for failures that never reached the API — a refused connection, a timeout — which is itself worth knowing: .. code-block:: python from usda_fdc import FdcClient, FdcApiError, FdcResourceNotFoundError client = FdcClient(api_key="your_api_key_here") try: food = client.get_food(1750340) except FdcResourceNotFoundError: print("No such food") # usually clearer than reading the status except FdcApiError as e: if e.status_code is None: print(f"Never reached the API: {e}") elif e.status_code >= 500: print("Server error. Try again later.") else: print(f"API error {e.status_code}: {e}") Retry Logic --------- For transient errors like rate limiting or server errors, you can implement retry logic: .. code-block:: python import time from usda_fdc import FdcClient, FdcApiError, FdcRateLimitError, FdcTimeoutError client = FdcClient(api_key="your_api_key_here") def get_food_with_retry(fdc_id, max_retries=3, retry_delay=5): retries = 0 while retries < max_retries: try: return client.get_food(fdc_id) except (FdcRateLimitError, FdcTimeoutError): # Both are "try again", not "you asked for the wrong thing" retries += 1 if retries < max_retries: print(f"Throttled or timed out. Retrying in {retry_delay} seconds...") time.sleep(retry_delay) retry_delay *= 2 # Exponential backoff else: raise except FdcApiError as e: if e.status_code is not None and e.status_code >= 500: retries += 1 if retries < max_retries: print(f"Server error. Retrying in {retry_delay} seconds...") time.sleep(retry_delay) retry_delay *= 2 # Exponential backoff else: raise else: # A 400 or a 404 will not fix itself raise # Use the retry function try: food = get_food_with_retry(1750340) except Exception as e: print(f"Failed after retries: {e}") Logging Errors ------------ It's a good practice to log errors for debugging: .. code-block:: python import logging from usda_fdc import FdcClient, FdcApiError # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger('usda_fdc') client = FdcClient(api_key="your_api_key_here") try: food = client.get_food(1750340) except FdcApiError as e: logger.error(f"API error when getting food {1750340}: {e}", exc_info=True) # Handle the error appropriately except Exception as e: logger.exception(f"Unexpected error when getting food {1750340}") # Handle the error appropriately