From 1d36354191d8d8f743bba6a1b00d4940d3a21bb7 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sun, 2 Feb 2025 23:44:29 -0500 Subject: [PATCH 1/3] Huge Commit Made changes to include error testing within ai.py Created tand updated tests Created Method to post onto bluesky social included atproto within dependencies Removed methods from bluesky.py because create_session() was no longer needed --- ai.py | 62 +++++++++++++++++++++++++------------- bluesky.py | 32 ++++++++++---------- main.py | 23 +++++++------- requirements.txt | 3 +- tests/bluesky_auth_test.py | 57 ----------------------------------- tests/weather_test.py | 37 ++++++++++++++++++++++- weather.py | 2 +- 7 files changed, 108 insertions(+), 108 deletions(-) diff --git a/ai.py b/ai.py index 66b97a0..900be87 100644 --- a/ai.py +++ b/ai.py @@ -4,6 +4,7 @@ """ # pylint: disable=C0301 import os +import logging from openai import OpenAI def generate_ai_text(forecast): @@ -12,26 +13,45 @@ def generate_ai_text(forecast): """ # To authenticate with the model you will need to generate a personal access token (PAT) in your GitHub settings. # Create your PAT token by following instructions here: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens - client = OpenAI( - base_url="https://models.inference.ai.azure.com", - api_key=os.getenv("OPENAI_API_KEY"), - ) + try: + api_key=os.getenv("OPENAI_API_KEY") + if not api_key: + raise ValueError("Missing API Key") + + client = OpenAI( + base_url="https://models.inference.ai.azure.com", + api_key=api_key, + ) - response = client.chat.completions.create( - messages=[ - { - "role": "system", - "content": "You are a young meteorologist explaining today's forecast in Syracuse NY in the morning(Say 'Good Morning Syracuse!'). Act like you're making a social media post. Make sure to Consildate the days into one description (don't do morning and night seperately). Make sure it's under 300 characters", - }, - { - "role": "user", - "content": forecast, - } - ], - model="gpt-4o", - temperature=1, - max_tokens=4096, - top_p=1 - ) + response = client.chat.completions.create( + messages=[ + { + "role": "system", + "content": "You are a young meteorologist explaining today's forecast in Syracuse NY in the morning(Say 'Good Morning Syracuse!'). Act like you're making a social media post. Make sure to Consildate the days into one description (don't do morning and night seperately). Make sure it's under 300 characters", + }, + { + "role": "user", + "content": forecast, + } + ], + model="gpt-4o", + temperature=1, + max_tokens=300, + top_p=1 + ) + + if not response or not response.choices or not response.choices[0].message: + raise ValueError("Unexpect API response format") + + aiText = response.choices[0].message.content + return aiText + + except ValueError as ve: + logging.error(f"ValueError: {ve}") + return "Daily Weather Forecast Is down Today" + + except Exception as e: + logging.error(f"Unexpected error: {e}") + return "Daily Weather Forecast Is down Today" + - print(response.choices[0].message.content) diff --git a/bluesky.py b/bluesky.py index 8d461ff..8e5301f 100644 --- a/bluesky.py +++ b/bluesky.py @@ -5,8 +5,8 @@ import os import sys -import requests from dotenv import load_dotenv +from atproto import Client # Load enviornment variables from the .env file def load_bluesky_credentials(): @@ -23,22 +23,22 @@ def load_bluesky_credentials(): print(".env does not exist.") sys.exit(1) -def create_bluesky_session(username: str, password: str): +def create_bluesky_post(username: str, password: str, post_text: str): """ - Authenticate BlueSky credentials and create a session to recieve access token. - - returns: - - Access token (accessJwt) for authentication. + Creates post on BlueSky and returns its post link (URL) """ - url = "https://bsky.social/xrpc/com.atproto.server.createSession" - payload = {"identifier": username, "password": password} + client = Client() try: - response = requests.post(url, json=payload, timeout=10) - response.raise_for_status() - session = response.json() - return session["accessJwt"] - except requests.exceptions.RequestException as err: - print("Error during authentication:", err) - print("Response:", response.text if "response" in locals() else "No response") - sys.exit(1) + client.login(username, password) + except Exception as e: + raise RuntimeError(f"Login failed: {e}") + + try: + post = client.send_post(post_text) + post_id = post.uri.split('/')[-1] + except Exception as e: + raise RuntimeError(f"Post creation failed: {e}") + + url = f"https://bsky.app/profile/{username}/post/{post_id}" + return url diff --git a/main.py b/main.py index be5dbf8..e01f8b5 100644 --- a/main.py +++ b/main.py @@ -25,21 +25,22 @@ def main(): """ This runs the main module of BlueSkyTracker """ - print("Loading Credentials...") - bluesky_handle, bluesky_app_password = bluesky.load_bluesky_credentials() - - print("Athenticating...") - access_token = bluesky.create_bluesky_session(bluesky_handle, bluesky_app_password) - print("Authentication successful.") - - print("Fetching Forecast") + print("Fetching Forecast...") forecast = weather.fetch_weather_forecast() - print("Formatting Weather Data") + print("Formatting Weather Data...") forecast = weather.format_weather_data(forecast) - print("Generating AI Text") - ai.generate_ai_text(forecast) + print("Generating AI Text...") + post_text = ai.generate_ai_text(forecast) + + print("Loading BlueSky Credentials...") + bluesky_handle, bluesky_app_password = bluesky.load_bluesky_credentials() + + print("Creating Post...") + post_link = bluesky.create_bluesky_post(bluesky_handle, bluesky_app_password, post_text) + + print(f"Post Link: {post_link}") if __name__ == "__main__": main() diff --git a/requirements.txt b/requirements.txt index 329150c..83cce53 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ requests python-dotenv pylint -openai \ No newline at end of file +openai +atproto \ No newline at end of file diff --git a/tests/bluesky_auth_test.py b/tests/bluesky_auth_test.py index abcbaa6..66b0852 100644 --- a/tests/bluesky_auth_test.py +++ b/tests/bluesky_auth_test.py @@ -43,60 +43,3 @@ def test_env_exist_with_valid_credentials(self, mock_load_dotenv, mock_getenv): """ credentials = load_bluesky_credentials() self.assertTrue(all(credentials)) - - - -class TestCreateBlueSkySession(unittest.TestCase): - """ - Testing the create_bluesky_session() method - """ - def setUp(self): - self.username = "ValidUsername" - self.password = "ValidPassword" - - @patch("bluesky.requests.post") - def test_successful_authentication(self, mock_post): - """" - Test if authentication was successful - """ - mock_response = Mock() - mock_response.json.return_value = {"accessJwt": "mocked_jwt_token"} - mock_response.status_code = 200 - mock_post.return_value = mock_response - - result = create_bluesky_session(username = self.username, password = self.password) - self.assertEqual(result, "mocked_jwt_token") - - @patch("bluesky.requests.post") - def test_unsuccesful_authentication(self, mock_post): - """ - Test if authentication was not successful - - Password or User was incorrect - """ - mock_response = Mock() - mock_response.status_code = 401 - mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("401 Client Error: Unauthorized") - mock_post.return_value = mock_response - - with self.assertRaises(SystemExit): - create_bluesky_session(username = self.username, password = self.password) - - @patch("bluesky.requests.post") - def test_invalid_request_error(self, mock_post): - """ - Test if authentication was not successful - - Bad Request Status Code 400 - """ - mock_response = Mock() - mock_response.status_code = 400 - mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("400 Client Error: Bad Request") - mock_post.return_value = mock_response - - with self.assertRaises(SystemExit): - create_bluesky_session(self.username, self.password) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/weather_test.py b/tests/weather_test.py index 7b2304e..8949855 100644 --- a/tests/weather_test.py +++ b/tests/weather_test.py @@ -6,7 +6,7 @@ import unittest from unittest.mock import patch, Mock import requests -from weather import fetch_weather_forecast +from weather import fetch_weather_forecast, format_weather_data class TestFetchWeatherForecast(unittest.TestCase): """ @@ -87,3 +87,38 @@ def test_api_500_error(self, mock_get): result = fetch_weather_forecast() self.assertIsNone(result) + +class TestFormatWeatherData(unittest.TestCase): + """ + Testing the format_weather_data method + """ + def test_basic_input(self): + sample_data = { + "day_1": [ + { + "name": "Monday", + "temperature": 75, + "temperatureUnit": "F", + "probabilityOfPrecipitation": 20, + "windSpeed": "10 mph", + "windDirection": "S", + "detailedForecast": "Sunny with some clouds" + }, + { + "name": "Monday Night", + "temperature": 55, + "temperatureUnit": "F", + "probabilityOfPrecipitation": 40, + "windSpeed": "5 mph", + "windDirection": "SW", + "detailedForecast": "Clear Skies" + } + ] + } + + result = format_weather_data(sample_data) + assert "Monday & Monday Night:" in result + assert "Temperature: 75F" in result + assert "Probability Of Precipitation: 40" in result + assert "Wind Speed: 5 mph, Direction SW" in result + assert "Detailed Forecast: Clear Skies" in result \ No newline at end of file diff --git a/weather.py b/weather.py index 7d2ff7a..dadc726 100644 --- a/weather.py +++ b/weather.py @@ -59,5 +59,5 @@ def format_weather_data(weather_data): f"Wind Speed: {period['windSpeed']}, Direction {period['windDirection']}\n" f"Detailed Forecast: {period['detailedForecast']}\n\n" ) - print(weather_input) + return weather_input From 07dd7faa5d590e2358f4b4b30360816e4766ac38 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sun, 2 Feb 2025 23:57:09 -0500 Subject: [PATCH 2/3] Fixed Pylint Requirments --- ai.py | 22 ++++++++++++---------- bluesky.py | 6 +++--- tests/bluesky_auth_test.py | 4 +--- tests/weather_test.py | 5 ++++- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/ai.py b/ai.py index 900be87..ce1c18e 100644 --- a/ai.py +++ b/ai.py @@ -5,7 +5,7 @@ # pylint: disable=C0301 import os import logging -from openai import OpenAI +from openai import OpenAI, OpenAIError def generate_ai_text(forecast): """ @@ -17,7 +17,7 @@ def generate_ai_text(forecast): api_key=os.getenv("OPENAI_API_KEY") if not api_key: raise ValueError("Missing API Key") - + client = OpenAI( base_url="https://models.inference.ai.azure.com", api_key=api_key, @@ -43,15 +43,17 @@ def generate_ai_text(forecast): if not response or not response.choices or not response.choices[0].message: raise ValueError("Unexpect API response format") - aiText = response.choices[0].message.content - return aiText + ai_text = response.choices[0].message.content + return ai_text - except ValueError as ve: - logging.error(f"ValueError: {ve}") + except ValueError as err: + logging.error("ValueError: %s", err) return "Daily Weather Forecast Is down Today" - - except Exception as e: - logging.error(f"Unexpected error: {e}") + + except OpenAIError as err: + logging.error("OpenAIError: %s", err) return "Daily Weather Forecast Is down Today" - + except TimeoutError as err: + logging.error("Request Time Out: %s", err) + return "Daily Weather Forecast Is down Today" diff --git a/bluesky.py b/bluesky.py index 8e5301f..effbe5c 100644 --- a/bluesky.py +++ b/bluesky.py @@ -32,13 +32,13 @@ def create_bluesky_post(username: str, password: str, post_text: str): try: client.login(username, password) except Exception as e: - raise RuntimeError(f"Login failed: {e}") - + raise RuntimeError(f"Login failed: {e}") from e + try: post = client.send_post(post_text) post_id = post.uri.split('/')[-1] except Exception as e: - raise RuntimeError(f"Post creation failed: {e}") + raise RuntimeError(f"Post creation failed: {e}") from e url = f"https://bsky.app/profile/{username}/post/{post_id}" return url diff --git a/tests/bluesky_auth_test.py b/tests/bluesky_auth_test.py index 66b0852..25dba08 100644 --- a/tests/bluesky_auth_test.py +++ b/tests/bluesky_auth_test.py @@ -6,10 +6,8 @@ # pylint: disable=E0401 # pylint: disable=W0613 import unittest -from unittest.mock import patch, Mock -import requests +from unittest.mock import patch from bluesky import load_bluesky_credentials -from bluesky import create_bluesky_session class TestLoadCredentials(unittest.TestCase): """ diff --git a/tests/weather_test.py b/tests/weather_test.py index 8949855..acb316f 100644 --- a/tests/weather_test.py +++ b/tests/weather_test.py @@ -93,6 +93,9 @@ class TestFormatWeatherData(unittest.TestCase): Testing the format_weather_data method """ def test_basic_input(self): + """ + Testing basic Input Functionality Using Diff Tests + """ sample_data = { "day_1": [ { @@ -121,4 +124,4 @@ def test_basic_input(self): assert "Temperature: 75F" in result assert "Probability Of Precipitation: 40" in result assert "Wind Speed: 5 mph, Direction SW" in result - assert "Detailed Forecast: Clear Skies" in result \ No newline at end of file + assert "Detailed Forecast: Clear Skies" in result From 716f4bd85ce756f78b6e9c8bc4c7e744e65b79a9 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 3 Feb 2025 00:03:44 -0500 Subject: [PATCH 3/3] Removed Post_Link Printing This was not needed I can log into the account myself and see it --- main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index e01f8b5..410edc8 100644 --- a/main.py +++ b/main.py @@ -38,9 +38,9 @@ def main(): bluesky_handle, bluesky_app_password = bluesky.load_bluesky_credentials() print("Creating Post...") - post_link = bluesky.create_bluesky_post(bluesky_handle, bluesky_app_password, post_text) + bluesky.create_bluesky_post(bluesky_handle, bluesky_app_password, post_text) - print(f"Post Link: {post_link}") + print("Post Created Succesfully") if __name__ == "__main__": main()