Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 46 additions & 24 deletions ai.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,56 @@
"""
# pylint: disable=C0301
import os
from openai import OpenAI
import logging
from openai import OpenAI, OpenAIError

def generate_ai_text(forecast):
"""
Method that generates AI text
"""
# 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"),
)

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
)

print(response.choices[0].message.content)
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=300,
top_p=1
)

if not response or not response.choices or not response.choices[0].message:
raise ValueError("Unexpect API response format")

ai_text = response.choices[0].message.content
return ai_text

except ValueError as err:
logging.error("ValueError: %s", err)
return "Daily Weather Forecast Is down Today"

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"
32 changes: 16 additions & 16 deletions bluesky.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand All @@ -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:
client.login(username, password)
except Exception as e:
raise RuntimeError(f"Login failed: {e}") from e

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)
post = client.send_post(post_text)
post_id = post.uri.split('/')[-1]
except Exception as e:
raise RuntimeError(f"Post creation failed: {e}") from e

url = f"https://bsky.app/profile/{username}/post/{post_id}"
return url
23 changes: 12 additions & 11 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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...")
bluesky.create_bluesky_post(bluesky_handle, bluesky_app_password, post_text)

print("Post Created Succesfully")

if __name__ == "__main__":
main()
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
requests
python-dotenv
pylint
openai
openai
atproto
61 changes: 1 addition & 60 deletions tests/bluesky_auth_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down Expand Up @@ -43,60 +41,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()
40 changes: 39 additions & 1 deletion tests/weather_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down Expand Up @@ -87,3 +87,41 @@ 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):
"""
Testing basic Input Functionality Using Diff Tests
"""
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
2 changes: 1 addition & 1 deletion weather.py
Original file line number Diff line number Diff line change
Expand Up @@ -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