Python REST API Client for a Fictional Weather Service
This challenge requires you to build a Python client that interacts with a simulated RESTful API. You'll be responsible for fetching data, handling different HTTP methods, and processing responses, which are fundamental skills for integrating with web services.
Problem Description
Your task is to create a Python class that acts as a client for a fictional "WeatherData" REST API. This API provides endpoints for retrieving current weather conditions and historical data for various locations. The client should be able to make GET requests to these endpoints, handle potential errors gracefully, and return the data in a usable format.
Key Requirements:
WeatherDataClientClass: Create a Python class namedWeatherDataClient.- Initialization: The constructor (
__init__) should accept thebase_urlof the API as an argument. get_current_weather(city)Method:- This method should take a
cityname (string) as input. - It should construct the appropriate URL for the current weather endpoint (e.g.,
/weather/current?city=<city>). - It should perform an HTTP GET request to this URL.
- If the request is successful (status code 200), it should return the JSON response as a Python dictionary.
- If the city is not found (status code 404), it should raise a custom
CityNotFoundErrorexception. - For any other HTTP error (e.g., 500 Internal Server Error), it should raise a generic
APIErrorexception.
- This method should take a
get_historical_weather(city, date)Method:- This method should take a
cityname (string) and adatestring (in 'YYYY-MM-DD' format) as input. - It should construct the URL for the historical weather endpoint (e.g.,
/weather/historical?city=<city>&date=<date>). - It should perform an HTTP GET request to this URL.
- If the request is successful (status code 200), it should return the JSON response as a Python dictionary.
- If the data for the specified date is not found or the city is not found (status code 404), it should raise a custom
DataNotFoundErrorexception. - For any other HTTP error, it should raise a generic
APIErrorexception.
- This method should take a
- Custom Exceptions: Define the following custom exceptions:
CityNotFoundError(Exception)DataNotFoundError(Exception)APIError(Exception)
- HTTP Library: You are free to use any standard Python HTTP library (e.g.,
requests).
Expected Behavior:
The client should behave like a robust interface to the WeatherData API, providing clear and actionable exceptions for common API interaction failures.
Edge Cases to Consider:
- Invalid API base URL.
- Network connectivity issues.
- API returning non-JSON responses.
- Case sensitivity of city names (assume case-insensitive for this challenge unless specified otherwise).
Examples
Example 1: Fetching Current Weather
# Assume the API is running at 'http://localhost:5000'
# And it returns the following for '/weather/current?city=London':
# {"city": "London", "temperature": 15.5, "conditions": "Cloudy"}
client = WeatherDataClient("http://localhost:5000")
try:
weather_data = client.get_current_weather("London")
print(weather_data)
except (CityNotFoundError, APIError) as e:
print(f"Error: {e}")
# Expected Output:
# {'city': 'London', 'temperature': 15.5, 'conditions': 'Cloudy'}
Example 2: Fetching Historical Weather
# Assume the API is running at 'http://localhost:5000'
# And it returns the following for '/weather/historical?city=Paris&date=2023-10-26':
# {"city": "Paris", "date": "2023-10-26", "avg_temperature": 12.0, "precipitation": 2.5}
client = WeatherDataClient("http://localhost:5000")
try:
historical_data = client.get_historical_weather("Paris", "2023-10-26")
print(historical_data)
except (DataNotFoundError, APIError) as e:
print(f"Error: {e}")
# Expected Output:
# {'city': 'Paris', 'date': '2023-10-26', 'avg_temperature': 12.0, 'precipitation': 2.5}
Example 3: Handling Not Found Errors
# Assume the API is running at 'http://localhost:5000'
# And '/weather/current?city=UnknownCity' returns a 404 status code.
client = WeatherDataClient("http://localhost:5000")
try:
client.get_current_weather("UnknownCity")
except CityNotFoundError as e:
print(f"Caught expected error: {e}")
except APIError as e:
print(f"Caught unexpected API error: {e}")
# Expected Output:
# Caught expected error: City 'UnknownCity' not found.
# Assume '/weather/historical?city=Berlin&date=9999-01-01' returns a 404 status code.
client = WeatherDataClient("http://localhost:5000")
try:
client.get_historical_weather("Berlin", "9999-01-01")
except DataNotFoundError as e:
print(f"Caught expected error: {e}")
except APIError as e:
print(f"Caught unexpected API error: {e}")
# Expected Output:
# Caught expected error: No weather data found for city 'Berlin' on date '9999-01-01'.
Constraints
- The
base_urlwill be a valid-looking URL string (e.g.,http://example.comorhttp://localhost:8080). - City names and dates will be provided as strings.
- The client should handle responses with a content type that is not JSON gracefully (though for this specific challenge, we assume the API will return valid JSON or an appropriate error response).
- The solution should be efficient and not introduce significant overhead for typical API calls.
Notes
- You'll need to install the
requestslibrary if you choose to use it:pip install requests. - You can simulate the API for testing purposes using a simple web framework like Flask or FastAPI, or by using mock objects.
- Pay close attention to how you handle exceptions. The goal is to make your client easy to use and debug.
- Consider the structure of your URLs carefully based on common REST API design patterns.
- The provided examples assume a specific API response structure. Your client should be able to parse standard JSON.