-
Notifications
You must be signed in to change notification settings - Fork 0
Add weather-app module for fetching city temperatures #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
omar-coderabbitai
wants to merge
5
commits into
main
Choose a base branch
from
og-test-feature
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
e55477a
Create weather-app.py
omar-coderabbitai b5287db
Update weather-app.py
omar-coderabbitai b27abc1
Update weather-app.py
omar-coderabbitai 0fc090c
Update weather-app.py
omar-coderabbitai 707ef30
Implement unit tests for weather app functions
omar-coderabbitai File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,310 @@ | ||
| import requests | ||
|
|
||
| def get_temperature(city_name, api_key): | ||
| """ | ||
| Fetches the current temperature in Fahrenheit for a given U.S. city | ||
| using the OpenWeatherMap API. | ||
|
|
||
| Parameters: | ||
| city_name (str): The name of the U.S. city. | ||
| api_key (str): Your OpenWeatherMap API key. | ||
|
|
||
| Returns: | ||
| float: Current temperature in Fahrenheit if successful. | ||
| None: If an error occurs or the data cannot be retrieved. | ||
| """ | ||
| if not api_key or not isinstance(api_key, str): | ||
| print("Error: Invalid or missing API key.") | ||
| return None | ||
|
|
||
| base_url = "https://api.openweathermap.org/data/2.5/weather" | ||
| params = { | ||
| 'q': f"{city_name},US", | ||
| 'appid': api_key, | ||
| 'units': 'imperial' | ||
| } | ||
|
|
||
| try: | ||
| response = requests.get(base_url, params=params, timeout=10) | ||
| response.raise_for_status() | ||
| data = response.json() | ||
|
|
||
| if 'main' in data and 'temp' in data['main']: | ||
| return data['main']['temp'] | ||
| else: | ||
| print("Error: Unexpected response structure.") | ||
| return None | ||
| except requests.exceptions.HTTPError as http_err: | ||
| print(f"HTTP error occurred: {http_err}") | ||
| except requests.exceptions.ConnectionError: | ||
| print("Error: Network connection error.") | ||
| except requests.exceptions.Timeout: | ||
| print("Error: The request timed out.") | ||
| except requests.exceptions.RequestException as err: | ||
| print(f"Error: An unexpected error occurred: {err}") | ||
| except ValueError: | ||
| print("Error: Failed to parse JSON response.") | ||
|
|
||
| return None | ||
|
|
||
| def main(): | ||
| import os | ||
| api_key = os.getenv('OPENWEATHER_API_KEY') | ||
| if not api_key: | ||
| print("Error: Please set the OPENWEATHER_API_KEY environment variable") | ||
| return | ||
|
|
||
| city = input("Enter a major U.S. city: ").strip() | ||
| if not city: | ||
| print("Error: City name cannot be empty") | ||
| return | ||
|
|
||
| temp = get_temperature(city, api_key) | ||
| if temp is not None: | ||
| print(f"The current temperature in {city.title()} is {temp:.1f}°F.") | ||
| else: | ||
| print("Sorry, couldn't find weather data for that city.") | ||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
|
|
||
|
|
||
| import unittest | ||
| from unittest.mock import patch, Mock, MagicMock | ||
| import requests | ||
| from weather-app import get_temperature, main | ||
|
|
||
|
|
||
| class TestGetTemperature(unittest.TestCase): | ||
| """Test cases for the get_temperature function.""" | ||
|
|
||
| @patch('weather-app.requests.get') | ||
| def test_successful_temperature_fetch(self, mock_get): | ||
| """Test successful temperature retrieval.""" | ||
| # Mock successful API response | ||
| mock_response = Mock() | ||
| mock_response.json.return_value = { | ||
| 'main': {'temp': 72.5} | ||
| } | ||
| mock_response.raise_for_status = Mock() | ||
| mock_get.return_value = mock_response | ||
|
|
||
| result = get_temperature("New York", "valid_api_key") | ||
|
|
||
| self.assertEqual(result, 72.5) | ||
| mock_get.assert_called_once_with( | ||
| "https://api.openweathermap.org/data/2.5/weather", | ||
| params={'q': 'New York,US', 'appid': 'valid_api_key', 'units': 'imperial'}, | ||
| timeout=10 | ||
| ) | ||
|
|
||
| @patch('weather-app.requests.get') | ||
| @patch('builtins.print') | ||
| def test_invalid_api_key_none(self, mock_print, mock_get): | ||
| """Test with None API key.""" | ||
| result = get_temperature("Chicago", None) | ||
|
|
||
| self.assertIsNone(result) | ||
| mock_print.assert_called_once_with("Error: Invalid or missing API key.") | ||
| mock_get.assert_not_called() | ||
|
|
||
| @patch('weather-app.requests.get') | ||
| @patch('builtins.print') | ||
| def test_invalid_api_key_empty_string(self, mock_print, mock_get): | ||
| """Test with empty string API key.""" | ||
| result = get_temperature("Chicago", "") | ||
|
|
||
| self.assertIsNone(result) | ||
| mock_print.assert_called_once_with("Error: Invalid or missing API key.") | ||
| mock_get.assert_not_called() | ||
|
|
||
| @patch('weather-app.requests.get') | ||
| @patch('builtins.print') | ||
| def test_invalid_api_key_non_string(self, mock_print, mock_get): | ||
| """Test with non-string API key.""" | ||
| result = get_temperature("Chicago", 12345) | ||
|
|
||
| self.assertIsNone(result) | ||
| mock_print.assert_called_once_with("Error: Invalid or missing API key.") | ||
| mock_get.assert_not_called() | ||
|
|
||
| @patch('weather-app.requests.get') | ||
| @patch('builtins.print') | ||
| def test_http_error_401_unauthorized(self, mock_print, mock_get): | ||
| """Test HTTP 401 Unauthorized error.""" | ||
| mock_response = Mock() | ||
| mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("401 Unauthorized") | ||
| mock_get.return_value = mock_response | ||
|
|
||
| result = get_temperature("Boston", "invalid_key") | ||
|
|
||
| self.assertIsNone(result) | ||
| mock_print.assert_called_once() | ||
| self.assertIn("HTTP error occurred", str(mock_print.call_args)) | ||
|
|
||
| @patch('weather-app.requests.get') | ||
| @patch('builtins.print') | ||
| def test_http_error_404_not_found(self, mock_print, mock_get): | ||
| """Test HTTP 404 Not Found error.""" | ||
| mock_response = Mock() | ||
| mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("404 Not Found") | ||
| mock_get.return_value = mock_response | ||
|
|
||
| result = get_temperature("InvalidCity", "valid_key") | ||
|
|
||
| self.assertIsNone(result) | ||
| mock_print.assert_called_once() | ||
| self.assertIn("HTTP error occurred", str(mock_print.call_args)) | ||
|
|
||
| @patch('weather-app.requests.get') | ||
| @patch('builtins.print') | ||
| def test_connection_error(self, mock_print, mock_get): | ||
| """Test network connection error.""" | ||
| mock_get.side_effect = requests.exceptions.ConnectionError("Network unreachable") | ||
|
|
||
| result = get_temperature("Seattle", "valid_key") | ||
|
|
||
| self.assertIsNone(result) | ||
| mock_print.assert_called_once_with("Error: Network connection error.") | ||
|
|
||
| @patch('weather-app.requests.get') | ||
| @patch('builtins.print') | ||
| def test_timeout_error(self, mock_print, mock_get): | ||
| """Test request timeout.""" | ||
| mock_get.side_effect = requests.exceptions.Timeout("Request timed out") | ||
|
|
||
| result = get_temperature("Miami", "valid_key") | ||
|
|
||
| self.assertIsNone(result) | ||
| mock_print.assert_called_once_with("Error: The request timed out.") | ||
|
|
||
| @patch('weather-app.requests.get') | ||
| @patch('builtins.print') | ||
| def test_generic_request_exception(self, mock_print, mock_get): | ||
| """Test generic request exception.""" | ||
| mock_get.side_effect = requests.exceptions.RequestException("Unknown error") | ||
|
|
||
| result = get_temperature("Phoenix", "valid_key") | ||
|
|
||
| self.assertIsNone(result) | ||
| mock_print.assert_called_once() | ||
| self.assertIn("An unexpected error occurred", str(mock_print.call_args)) | ||
|
|
||
| @patch('weather-app.requests.get') | ||
| @patch('builtins.print') | ||
| def test_json_parsing_error(self, mock_print, mock_get): | ||
| """Test JSON parsing error.""" | ||
| mock_response = Mock() | ||
| mock_response.raise_for_status = Mock() | ||
| mock_response.json.side_effect = ValueError("Invalid JSON") | ||
| mock_get.return_value = mock_response | ||
|
|
||
| result = get_temperature("Denver", "valid_key") | ||
|
|
||
| self.assertIsNone(result) | ||
| mock_print.assert_called_once_with("Error: Failed to parse JSON response.") | ||
|
|
||
| @patch('weather-app.requests.get') | ||
| @patch('builtins.print') | ||
| def test_unexpected_response_structure_missing_main(self, mock_print, mock_get): | ||
| """Test response with missing 'main' key.""" | ||
| mock_response = Mock() | ||
| mock_response.json.return_value = {'weather': [{'description': 'clear sky'}]} | ||
| mock_response.raise_for_status = Mock() | ||
| mock_get.return_value = mock_response | ||
|
|
||
| result = get_temperature("Atlanta", "valid_key") | ||
|
|
||
| self.assertIsNone(result) | ||
| mock_print.assert_called_once_with("Error: Unexpected response structure.") | ||
|
|
||
| @patch('weather-app.requests.get') | ||
| @patch('builtins.print') | ||
| def test_unexpected_response_structure_missing_temp(self, mock_print, mock_get): | ||
| """Test response with missing 'temp' key.""" | ||
| mock_response = Mock() | ||
| mock_response.json.return_value = {'main': {'humidity': 65}} | ||
| mock_response.raise_for_status = Mock() | ||
| mock_get.return_value = mock_response | ||
|
|
||
| result = get_temperature("Portland", "valid_key") | ||
|
|
||
| self.assertIsNone(result) | ||
| mock_print.assert_called_once_with("Error: Unexpected response structure.") | ||
|
|
||
|
|
||
| class TestMain(unittest.TestCase): | ||
| """Test cases for the main function.""" | ||
|
|
||
| @patch('weather-app.get_temperature') | ||
| @patch('builtins.input') | ||
| @patch('weather-app.os.getenv') | ||
| @patch('builtins.print') | ||
| def test_successful_flow(self, mock_print, mock_getenv, mock_input, mock_get_temp): | ||
| """Test successful execution with valid inputs.""" | ||
| mock_getenv.return_value = "test_api_key" | ||
| mock_input.return_value = " San Francisco " | ||
| mock_get_temp.return_value = 68.3 | ||
|
|
||
| main() | ||
|
|
||
| mock_getenv.assert_called_once_with('OPENWEATHER_API_KEY') | ||
| mock_input.assert_called_once_with("Enter a major U.S. city: ") | ||
| mock_get_temp.assert_called_once_with("San Francisco", "test_api_key") | ||
| mock_print.assert_called_once_with("The current temperature in San Francisco is 68.3°F.") | ||
|
|
||
| @patch('weather-app.os.getenv') | ||
| @patch('builtins.print') | ||
| def test_missing_api_key_env_var(self, mock_print, mock_getenv): | ||
| """Test when OPENWEATHER_API_KEY environment variable is not set.""" | ||
| mock_getenv.return_value = None | ||
|
|
||
| main() | ||
|
|
||
| mock_getenv.assert_called_once_with('OPENWEATHER_API_KEY') | ||
| mock_print.assert_called_once_with("Error: Please set the OPENWEATHER_API_KEY environment variable") | ||
|
|
||
| @patch('builtins.input') | ||
| @patch('weather-app.os.getenv') | ||
| @patch('builtins.print') | ||
| def test_empty_city_name(self, mock_print, mock_getenv, mock_input): | ||
| """Test when user enters empty city name.""" | ||
| mock_getenv.return_value = "test_api_key" | ||
| mock_input.return_value = " " | ||
|
|
||
| main() | ||
|
|
||
| mock_print.assert_called_once_with("Error: City name cannot be empty") | ||
|
|
||
| @patch('weather-app.get_temperature') | ||
| @patch('builtins.input') | ||
| @patch('weather-app.os.getenv') | ||
| @patch('builtins.print') | ||
| def test_get_temperature_returns_none(self, mock_print, mock_getenv, mock_input, mock_get_temp): | ||
| """Test when get_temperature fails and returns None.""" | ||
| mock_getenv.return_value = "test_api_key" | ||
| mock_input.return_value = "InvalidCity" | ||
| mock_get_temp.return_value = None | ||
|
|
||
| main() | ||
|
|
||
| mock_get_temp.assert_called_once_with("InvalidCity", "test_api_key") | ||
| mock_print.assert_called_once_with("Sorry, couldn't find weather data for that city.") | ||
|
|
||
| @patch('weather-app.get_temperature') | ||
| @patch('builtins.input') | ||
| @patch('weather-app.os.getenv') | ||
| @patch('builtins.print') | ||
| def test_temperature_formatting(self, mock_print, mock_getenv, mock_input, mock_get_temp): | ||
| """Test temperature output formatting with various values.""" | ||
| mock_getenv.return_value = "test_api_key" | ||
| mock_input.return_value = "los angeles" | ||
| mock_get_temp.return_value = 75.678 | ||
|
|
||
| main() | ||
|
|
||
| mock_print.assert_called_once_with("The current temperature in Los Angeles is 75.7°F.") | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| unittest.main() | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CRITICAL: Module name contains hyphen causing import syntax errors.
The filename
weather-app.pycannot be imported in Python because hyphens are invalid in module names. Line 75 (from weather-app import) and all@patch('weather-app.*')decorators throughout the tests will fail with syntax errors.Rename the file to use an underscore:
Then update line 75:
And update all patch decorators (lines 81, 101, 111, 121, 131, 145, 159, 170, 181, 193, 207, 221, 239, 256, 267, 279, 294):
🧰 Tools
🪛 Ruff (0.14.3)
75-75: Expected 'import', found '-'
(invalid-syntax)
75-75: Simple statements must be separated by newlines or semicolons
(invalid-syntax)
🤖 Prompt for AI Agents