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:

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:

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:

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:

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:

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