Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6f3c5d8
Add .gitignore file and files not to be pushed to remote.
sakhileln Nov 6, 2024
004d948
Make a simple request to the open weather API.
sakhileln Nov 6, 2024
6f92fea
Add try-except block when making a request, and add test case.
sakhileln Nov 6, 2024
8fc3b1b
Add requirements file.
sakhileln Nov 6, 2024
33b5afe
Deactivate and remove exposed API Key.
sakhileln Nov 6, 2024
cd46b38
Retrieve user's current location and use that for getting current wea…
sakhileln Nov 6, 2024
f609ab3
Update requirements file with more packages.
sakhileln Nov 6, 2024
d429fea
Add helper module file and send_sms implementation.
sakhileln Nov 7, 2024
a4ec8a4
Add sample secret configuration/environment variables.
sakhileln Nov 7, 2024
ded4cdb
Move implementation to helper module.
sakhileln Nov 7, 2024
b9342ff
Move implementation to helper module and add main method.
sakhileln Nov 7, 2024
c05ed8d
Add test cases for other functions.
sakhileln Nov 7, 2024
3df0365
Implement the main driver function.
sakhileln Nov 7, 2024
7efe8bf
Process the weather information and give the user a warning if needed.
sakhileln Nov 7, 2024
b64bcc2
Add sample data structures.
sakhileln Nov 7, 2024
bc4c1d0
Update requirements file.
sakhileln Nov 7, 2024
6e8ab04
Add twilio sample response with unverified number.
sakhileln Nov 7, 2024
b68071f
Add .env to files to be ignored.
sakhileln Nov 7, 2024
bc2e67a
Use .env variables for sensitive values on API requests.
sakhileln Nov 7, 2024
bd2bb2c
Comment out the '400 - BAD REQUEST' line because of errrors.
sakhileln Nov 7, 2024
0884f2e
[Feature]: Use .env environment to for securing API keys.
sakhileln Nov 7, 2024
e16a8d8
Import get from requests only.
sakhileln Nov 7, 2024
ced52d2
Test process_weather function.
sakhileln Nov 7, 2024
ef50ccc
Use process_message from the weather API to send user warning,
sakhileln Nov 7, 2024
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
5 changes: 5 additions & 0 deletions Rain_alert/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
OPENWEATHER_API_KEY=Open-Weather-API
TWILIO_ACCOUNT_SID=Your-Account-SID
TWILIO_AUTH_TOKEN=Your-Twilio-Auth-Token
TWILIO_NUMBER=Your-Twilio-Phone-Number
USER_NUMBER=User-Number
5 changes: 5 additions & 0 deletions Rain_alert/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
venv/
__pycache__/
.vscode/
creds.py
.env
137 changes: 137 additions & 0 deletions Rain_alert/helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
"""
A module for helper functions.
"""

from dotenv import load_dotenv
from os import getenv
from requests import get
from requests.exceptions import HTTPError
from samples import wx_sample_response
from twilio.rest import Client


load_dotenv() # Take environment variables from .env

def get_weather_data(lat: float, lon: float, exclude: str) -> dict:
"""
Get current weather from the given city's latitude and longitude.

Parameters:
lat (float): Angular distance of place north/south of equater
lonv(float): Angular distance of place east/west of greenwich meridian
Return:
weather_data (dict): All weather information about the city
"""

URL = f"https://api.openweathermap.org/data/3.0/onecall?lat={lat}&lon={lon}&exclude={exclude}&appid={getenv('OPENWEATHER_API')}"
test_url = "https://api.github.com"

#f"api.openweathermap.org/data/2.5/weather?q=London,uk&APPID={api_key}"

# Catch any errors that may arise trying to GET the Weather data
try:
response = get(URL)
response.raise_for_status()
except HTTPError as http_err:
print(f"HTTP Error occured: {http_err}")
except Exception as e:
print(f"Other error occured: {e}")
else:
return response.json() # If request was a success


def get_user_location() -> tuple[str, str]:
"""
Get current user location and return coordinates (lat, lon)

Parameters:
None
Return:
location (tuple): Location of user in latitude and longitude
"""

url = "https://ipinfo.io/"
try:
response = get(url)
response.raise_for_status()
except HTTPError as http_err:
print(f"HTTP Error occured: {http_err}")
except Exception as e:
print(f"Other error occured: {e}")
else:
json_format = response.json() # Make response into json for referencing
location_str = tuple(json_format['loc'].split(",")) # Get lat, long, make them a tuple
# Convert the str location to a float and give it back
return tuple(map(float, location_str))


def process_weather(weather_data: dict) -> str:
"""
Process weather from given location, give rain warning if needed

Parameters:
weather_data (dict): Weather information from a given location
Return:
weather_forecast (str): Warning to user if there is rain
"""
warning_words = [
"rain",
"drizzle",
"precipitation",
"thunderstorms",
"showers",
"hail",
"sleet",
"snow",
"snowflake"
]

# Get weather data from the dictionary at 'data' key element 0
dict_wx_data = weather_data['data'][0]
# Get dictionary with weather data we are interested on
weather_dict = dict_wx_data['weather'][0]
# Issue the warning
for item in weather_dict:
for warning in warning_words:
if type(weather_dict[item]) == int: # Skip integeer items
continue
else:
if warning in weather_dict[item].lower():
return "Rain is expected in you area (Time Expected: 1 hour), get to shelter."
return "Clear blue sky, go to the swimming pool"


def send_sms(message: str) -> dict:
"""
Send SMS to verified number from predefined number.

Parameters:
message (str): Message we want to send to the user
Return:
response (dict): Response from the attempt to send the message
"""

client = Client(str(getenv('TWILIO_ACCOUNT_SID')), str(getenv('TWILIO_AUTH_TOKEN')))

message = client.messages.create(
from_= str(getenv('TWILIO_NUMBER')),
body = message,
to = str(getenv('USER_NUMBER'))
)

return message.sid


if __name__ == "__main__":
# Test cases
# Test data
# Johannesburg (lat, lon)
# lat = -26.204103
# lon = 28.047304
# exclude = "hourly"
# print(f"Hardcoded: ({lat}, {lon})")
# print(f"Retrieval: {get_user_location()}")
# print(get_weather_data(lat, lon, exclude))
# message = "Hey, Sakhile, alien technology is here! Ayinabungozi lento."
# print(send_sms(message))
print(process_weather(wx_sample_response))
37 changes: 37 additions & 0 deletions Rain_alert/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
A module to check rain forecast and arn the user through Twilio SMS
"""

from helper import (
get_user_location,
get_weather_data,
send_sms,
process_weather
)


def main() -> None:
"""
Main driver function for the program.

Parameters
None
Return:
None
"""
# Get user location
location = get_user_location()
# Uunpack tuple to latitude and longitude
lat, lon = location
# Get hourly weather data
exclude = 'hourly'
hourly_forcast = get_weather_data(lat, lon, exclude)
# Process that data and formulate warning for the user
warning = process_weather(hourly_forcast)
# Send the user a warning SMS
send_sms(warning)


if __name__ == "__main__":
# Start the program
main()
25 changes: 25 additions & 0 deletions Rain_alert/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
aiohappyeyeballs==2.4.3
aiohttp==3.10.10
aiohttp-retry==2.9.1
aiosignal==1.3.1
async-timeout==4.0.3
attrs==24.2.0
certifi==2024.8.30
charset-normalizer==3.4.0
click==8.1.7
decorator==5.1.1
frozenlist==1.5.0
future==1.0.0
geocoder==1.38.1
idna==3.10
multidict==6.1.0
propcache==0.2.0
PyJWT==2.9.0
python-dotenv==1.0.1
ratelim==0.1.6
requests==2.32.3
six==1.16.0
twilio==9.3.6
typing_extensions==4.12.2
urllib3==2.2.3
yarl==1.17.1
65 changes: 65 additions & 0 deletions Rain_alert/samples.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Sample response from Openweather API
wx_sample_response = {
"lat": 52.2297,
"lon": 21.0122,
"timezone": "Sakhile/Mars",
"timezone_offset": 3600,
"data": [
{
"dt": 1645888976,
"sunrise": 1645853361,
"sunset": 1645891727,
"temp": 279.13,
"feels_like": 276.44,
"pressure": 1029,
"humidity": 64,
"dew_point": 272.88,
"uvi": 0.06,
"clouds": 0,
"visibility": 10000,
"wind_speed": 3.6,
"wind_deg": 340,
"weather": [
{
"id": 800,
"main": "Clear",
"description": "clear sky",
"icon": "01d"
}
]
}
]
}

sample_dict_wx_data = {
"dt": 1645888976,
"sunrise": 1645853361,
"sunset": 1645891727,
"temp": 279.13,
"feels_like": 276.44,
"pressure": 1029,
"humidity": 64,
"dew_point": 272.88,
"uvi": 0.06,
"clouds": 0,
"visibility": 10000,
"wind_speed": 3.6,
"wind_deg": 340,
"weather": [
{
"id": 800,
"main": "Clear",
"description": "clear sky",
"icon": "01d"
}
]
}

# Sample response from Twilio
# 400 - BAD REQUEST - The data given in the POST or PUT failed validation. Inspect the response body for details.
res = {
"code": 21608,
"message": "The number +27082581XXXX is unverified. Trial accounts cannot send messages to unverified numbers; verify +27082581XXXX at twilio.com/user/account/phone-numbers/verified, or purchase a Twilio number to send messages to unverified numbers",
"more_info": "https://www.twilio.com/docs/errors/21608",
"status": 400
}