From 6f3c5d8ccfbc94f4c67b55ce54d36f5d652a7b2e Mon Sep 17 00:00:00 2001 From: "Sakhile L. Ndlazi" Date: Wed, 6 Nov 2024 20:23:56 +0200 Subject: [PATCH 01/24] Add .gitignore file and files not to be pushed to remote. --- Rain_alert/.gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Rain_alert/.gitignore diff --git a/Rain_alert/.gitignore b/Rain_alert/.gitignore new file mode 100644 index 00000000..affcb4c3 --- /dev/null +++ b/Rain_alert/.gitignore @@ -0,0 +1,4 @@ +venv/ +__pycache__/ +.vscode/ +creds.py \ No newline at end of file From 004d9488b59490768f06eb73afd3a176834742df Mon Sep 17 00:00:00 2001 From: "Sakhile L. Ndlazi" Date: Wed, 6 Nov 2024 20:46:52 +0200 Subject: [PATCH 02/24] Make a simple request to the open weather API. --- Rain_alert/main.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Rain_alert/main.py b/Rain_alert/main.py index e69de29b..9eb2ae4c 100644 --- a/Rain_alert/main.py +++ b/Rain_alert/main.py @@ -0,0 +1,21 @@ +""" +A module to check rain forecast and arn the user through Twilio SMS +""" + +from creds import api_key +import requests + + +def get_weather_data(lat: float, lon: float) -> None: + """ + 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={part}&appid={api_key}" + + response = requests.get(URL) From 6f92feae940cf088475bbebc435416540d2abcb9 Mon Sep 17 00:00:00 2001 From: "Sakhile L. Ndlazi" Date: Wed, 6 Nov 2024 21:12:58 +0200 Subject: [PATCH 03/24] Add try-except block when making a request, and add test case. --- Rain_alert/main.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/Rain_alert/main.py b/Rain_alert/main.py index 9eb2ae4c..42e4666b 100644 --- a/Rain_alert/main.py +++ b/Rain_alert/main.py @@ -4,9 +4,10 @@ from creds import api_key import requests +from requests.exceptions import HTTPError -def get_weather_data(lat: float, lon: float) -> None: +def get_weather_data(lat: float, lon: float, exclude: str) -> dict: """ Get current weather from the given city's latitude and longitude. @@ -16,6 +17,27 @@ def get_weather_data(lat: float, lon: float) -> None: 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={part}&appid={api_key}" - response = requests.get(URL) + URL = f"https://api.openweathermap.org/data/3.0/onecall?lat={lat}&lon={lon}&exclude={exclude}&appid={api_key}" + test_url = "https://api.github.com" + #f"api.openweathermap.org/data/2.5/weather?q=London,uk&APPID=11169032316a0187407b8f2feec650e9" + + # Catch any errors that may arise trying to GET the Weather data + try: + response = requests.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 + + +if __name__ == "__main__": + # Test data + # Johannesburg (lat, lon) + lat = -26.204103 + lon = 28.047304 + exclude = "hourly" + print(get_weather_data(lat, lon, exclude)) From 8fc3b1b599eba42ae7c8dd8295d29943d4f29d07 Mon Sep 17 00:00:00 2001 From: "Sakhile L. Ndlazi" Date: Wed, 6 Nov 2024 21:18:32 +0200 Subject: [PATCH 04/24] Add requirements file. --- Rain_alert/requirements.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Rain_alert/requirements.txt diff --git a/Rain_alert/requirements.txt b/Rain_alert/requirements.txt new file mode 100644 index 00000000..9ee2289f --- /dev/null +++ b/Rain_alert/requirements.txt @@ -0,0 +1,5 @@ +certifi==2024.8.30 +charset-normalizer==3.4.0 +idna==3.10 +requests==2.32.3 +urllib3==2.2.3 From 33b5afed904275ff2a16b13cde09115bd1a43c13 Mon Sep 17 00:00:00 2001 From: "Sakhile L. Ndlazi" Date: Wed, 6 Nov 2024 21:24:10 +0200 Subject: [PATCH 05/24] Deactivate and remove exposed API Key. --- Rain_alert/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Rain_alert/main.py b/Rain_alert/main.py index 42e4666b..7f34bace 100644 --- a/Rain_alert/main.py +++ b/Rain_alert/main.py @@ -20,7 +20,8 @@ def get_weather_data(lat: float, lon: float, exclude: str) -> dict: URL = f"https://api.openweathermap.org/data/3.0/onecall?lat={lat}&lon={lon}&exclude={exclude}&appid={api_key}" test_url = "https://api.github.com" - #f"api.openweathermap.org/data/2.5/weather?q=London,uk&APPID=11169032316a0187407b8f2feec650e9" + + #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: From cd46b381b2644276c4d8e5009d423f2ccd481015 Mon Sep 17 00:00:00 2001 From: "Sakhile L. Ndlazi" Date: Wed, 6 Nov 2024 21:49:35 +0200 Subject: [PATCH 06/24] Retrieve user's current location and use that for getting current weather. --- Rain_alert/main.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/Rain_alert/main.py b/Rain_alert/main.py index 7f34bace..5f720384 100644 --- a/Rain_alert/main.py +++ b/Rain_alert/main.py @@ -20,7 +20,7 @@ def get_weather_data(lat: float, lon: float, exclude: str) -> dict: URL = f"https://api.openweathermap.org/data/3.0/onecall?lat={lat}&lon={lon}&exclude={exclude}&appid={api_key}" 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 @@ -35,10 +35,37 @@ def get_weather_data(lat: float, lon: float, exclude: str) -> dict: 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 = requests.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)) + + if __name__ == "__main__": # Test data # Johannesburg (lat, lon) lat = -26.204103 lon = 28.047304 exclude = "hourly" - print(get_weather_data(lat, lon, exclude)) + print(f"Hardcoded: ({lat}, {lon})") + print(f"Retrieval: {get_user_location()}") + # print(get_weather_data(lat, lon, exclude)) From f609ab3fdf48845a6bbf0698caa580b771dda3d3 Mon Sep 17 00:00:00 2001 From: "Sakhile L. Ndlazi" Date: Wed, 6 Nov 2024 21:51:33 +0200 Subject: [PATCH 07/24] Update requirements file with more packages. --- Rain_alert/requirements.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Rain_alert/requirements.txt b/Rain_alert/requirements.txt index 9ee2289f..5aacc145 100644 --- a/Rain_alert/requirements.txt +++ b/Rain_alert/requirements.txt @@ -1,5 +1,11 @@ certifi==2024.8.30 charset-normalizer==3.4.0 +click==8.1.7 +decorator==5.1.1 +future==1.0.0 +geocoder==1.38.1 idna==3.10 +ratelim==0.1.6 requests==2.32.3 +six==1.16.0 urllib3==2.2.3 From d429fea8924b908c6f90662f82227b49ab476417 Mon Sep 17 00:00:00 2001 From: Sakhile Ndlazi Date: Thu, 7 Nov 2024 08:43:47 +0200 Subject: [PATCH 08/24] Add helper module file and send_sms implementation. --- Rain_alert/helper.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Rain_alert/helper.py diff --git a/Rain_alert/helper.py b/Rain_alert/helper.py new file mode 100644 index 00000000..838dd6ad --- /dev/null +++ b/Rain_alert/helper.py @@ -0,0 +1,37 @@ +""" +A module for helper functions. +""" +from creds import ( + twilio_account_sid, + twilio_auth_token, + twilio_phone_number, + user_phone_number +) +from twilio.rest import Client + + +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(twilio_account_sid, twilio_auth_token) + + message = client.messages.create( + from_= twilio_phone_number, + body = message, + to = user_phone_number + ) + + return message.sid + + +if __name__ == "__main__": + # Test cases + message = "Hey, Sakhile, alien technology is here! Ayinabungozi lento." + print(send_sms(message)) \ No newline at end of file From a4ec8a470b5f27207a1c9bed91dceddf96ec5a92 Mon Sep 17 00:00:00 2001 From: Sakhile Ndlazi Date: Thu, 7 Nov 2024 08:47:02 +0200 Subject: [PATCH 09/24] Add sample secret configuration/environment variables. --- Rain_alert/.env.example | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Rain_alert/.env.example diff --git a/Rain_alert/.env.example b/Rain_alert/.env.example new file mode 100644 index 00000000..a092f2e0 --- /dev/null +++ b/Rain_alert/.env.example @@ -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 \ No newline at end of file From ded4cdba169e68c3f19b4b29cf240f4d607ba8f2 Mon Sep 17 00:00:00 2001 From: Sakhile Ndlazi Date: Thu, 7 Nov 2024 09:26:12 +0200 Subject: [PATCH 10/24] Move implementation to helper module. --- Rain_alert/helper.py | 57 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/Rain_alert/helper.py b/Rain_alert/helper.py index 838dd6ad..9951e8d4 100644 --- a/Rain_alert/helper.py +++ b/Rain_alert/helper.py @@ -1,15 +1,72 @@ """ A module for helper functions. """ + from creds import ( + api_key, twilio_account_sid, twilio_auth_token, twilio_phone_number, user_phone_number ) +import requests +from requests.exceptions import HTTPError from twilio.rest import Client +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={api_key}" + 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 = requests.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 = requests.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 send_sms(message: str) -> dict: """ Send SMS to verified number from predefined number. From b9342ffe30296c7b80b3bc8c5c47d8555babd9d4 Mon Sep 17 00:00:00 2001 From: Sakhile Ndlazi Date: Thu, 7 Nov 2024 09:28:25 +0200 Subject: [PATCH 11/24] Move implementation to helper module and add main method. --- Rain_alert/main.py | 59 ++++++++-------------------------------------- 1 file changed, 10 insertions(+), 49 deletions(-) diff --git a/Rain_alert/main.py b/Rain_alert/main.py index 5f720384..db6ba598 100644 --- a/Rain_alert/main.py +++ b/Rain_alert/main.py @@ -2,62 +2,23 @@ A module to check rain forecast and arn the user through Twilio SMS """ -from creds import api_key -import requests -from requests.exceptions import HTTPError +from helper import ( + get_user_location, + get_weather_data, + send_sms +) -def get_weather_data(lat: float, lon: float, exclude: str) -> dict: +def main() -> None: """ - Get current weather from the given city's latitude and longitude. + Main driver function for the program. - 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={api_key}" - 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 = requests.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: + Parameters None Return: - location (tuple): Location of user in latitude and longitude + None """ - - url = "https://ipinfo.io/" - try: - response = requests.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)) + pass if __name__ == "__main__": From c05ed8dc90268b9f7cb6c80f7d37a61815b3c45e Mon Sep 17 00:00:00 2001 From: Sakhile Ndlazi Date: Thu, 7 Nov 2024 09:35:19 +0200 Subject: [PATCH 12/24] Add test cases for other functions. --- Rain_alert/helper.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Rain_alert/helper.py b/Rain_alert/helper.py index 9951e8d4..a02b8d8d 100644 --- a/Rain_alert/helper.py +++ b/Rain_alert/helper.py @@ -90,5 +90,13 @@ def send_sms(message: str) -> dict: 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)) \ No newline at end of file From 3df03656fb2bc4412015744e370b62611cf58fe5 Mon Sep 17 00:00:00 2001 From: Sakhile Ndlazi Date: Thu, 7 Nov 2024 09:37:28 +0200 Subject: [PATCH 13/24] Implement the main driver function. --- Rain_alert/main.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Rain_alert/main.py b/Rain_alert/main.py index db6ba598..413a800d 100644 --- a/Rain_alert/main.py +++ b/Rain_alert/main.py @@ -18,15 +18,19 @@ def main() -> None: Return: None """ - pass + # 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 + mock_warning = "Rain in the next hour. We cannot sell ice cream today." + # Send the user a warning SMS + send_sms(mock_warning) if __name__ == "__main__": - # 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)) + # Start the program + main() From 7efe8bf1d21f90b08c91bf7e3bf287e61a150487 Mon Sep 17 00:00:00 2001 From: Sakhile Ndlazi Date: Thu, 7 Nov 2024 10:45:31 +0200 Subject: [PATCH 14/24] Process the weather information and give the user a warning if needed. --- Rain_alert/helper.py | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/Rain_alert/helper.py b/Rain_alert/helper.py index a02b8d8d..4b8b7330 100644 --- a/Rain_alert/helper.py +++ b/Rain_alert/helper.py @@ -11,6 +11,7 @@ ) import requests from requests.exceptions import HTTPError +from samples import wx_sample_response from twilio.rest import Client @@ -65,6 +66,42 @@ def get_user_location() -> tuple[str, str]: 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: @@ -98,5 +135,6 @@ def send_sms(message: str) -> dict: # 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)) \ No newline at end of file + # message = "Hey, Sakhile, alien technology is here! Ayinabungozi lento." + # print(send_sms(message)) + print(process_weather(wx_sample_response)) \ No newline at end of file From b64bcc2d3bae0056487bbf6495fcb47c489419a0 Mon Sep 17 00:00:00 2001 From: Sakhile Ndlazi Date: Thu, 7 Nov 2024 10:45:58 +0200 Subject: [PATCH 15/24] Add sample data structures. --- Rain_alert/samples.py | 55 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 Rain_alert/samples.py diff --git a/Rain_alert/samples.py b/Rain_alert/samples.py new file mode 100644 index 00000000..2b5e127e --- /dev/null +++ b/Rain_alert/samples.py @@ -0,0 +1,55 @@ +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" + } + ] +} \ No newline at end of file From bc4c1d0558dc330911a324c76b213efa24e6b4fe Mon Sep 17 00:00:00 2001 From: Sakhile Ndlazi Date: Thu, 7 Nov 2024 10:53:21 +0200 Subject: [PATCH 16/24] Update requirements file. --- Rain_alert/requirements.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Rain_alert/requirements.txt b/Rain_alert/requirements.txt index 5aacc145..b93584b2 100644 --- a/Rain_alert/requirements.txt +++ b/Rain_alert/requirements.txt @@ -1,11 +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 From 6e8ab04b7207ddb17d1ef33b14f2d96ddf7d65bb Mon Sep 17 00:00:00 2001 From: Sakhile Ndlazi Date: Thu, 7 Nov 2024 10:56:28 +0200 Subject: [PATCH 17/24] Add twilio sample response with unverified number. --- Rain_alert/samples.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Rain_alert/samples.py b/Rain_alert/samples.py index 2b5e127e..73cab7e7 100644 --- a/Rain_alert/samples.py +++ b/Rain_alert/samples.py @@ -1,3 +1,4 @@ +# Sample response from Openweather API wx_sample_response = { "lat": 52.2297, "lon": 21.0122, @@ -52,4 +53,13 @@ "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. +{ + "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 } \ No newline at end of file From b68071f459bd282232d2db7a6749a30d4dff5037 Mon Sep 17 00:00:00 2001 From: Sakhile Ndlazi Date: Thu, 7 Nov 2024 11:04:38 +0200 Subject: [PATCH 18/24] Add .env to files to be ignored. --- Rain_alert/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Rain_alert/.gitignore b/Rain_alert/.gitignore index affcb4c3..7c8c1516 100644 --- a/Rain_alert/.gitignore +++ b/Rain_alert/.gitignore @@ -1,4 +1,5 @@ venv/ __pycache__/ .vscode/ -creds.py \ No newline at end of file +creds.py +.env \ No newline at end of file From bc2e67ab12d5357dae058ed608f9a73622803df9 Mon Sep 17 00:00:00 2001 From: Sakhile Ndlazi Date: Thu, 7 Nov 2024 11:07:35 +0200 Subject: [PATCH 19/24] Use .env variables for sensitive values on API requests. --- Rain_alert/helper.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Rain_alert/helper.py b/Rain_alert/helper.py index 4b8b7330..4b668f4f 100644 --- a/Rain_alert/helper.py +++ b/Rain_alert/helper.py @@ -9,12 +9,15 @@ twilio_phone_number, user_phone_number ) +from dotenv import load_dotenv import requests 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. @@ -26,7 +29,7 @@ def get_weather_data(lat: float, lon: float, exclude: str) -> dict: 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={api_key}" + URL = f"https://api.openweathermap.org/data/3.0/onecall?lat={lat}&lon={lon}&exclude={exclude}&appid={OPENWEATHER_API_KEY}" test_url = "https://api.github.com" #f"api.openweathermap.org/data/2.5/weather?q=London,uk&APPID={api_key}" @@ -114,12 +117,12 @@ def send_sms(message: str) -> dict: response (dict): Response from the attempt to send the message """ - client = Client(twilio_account_sid, twilio_auth_token) + client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN) message = client.messages.create( - from_= twilio_phone_number, + from_= TWILIO_NUMBER, body = message, - to = user_phone_number + to = USER_NUMBER ) return message.sid From bd2bb2c440326e31a371ffc8b9c13328ab9de5ca Mon Sep 17 00:00:00 2001 From: Sakhile Ndlazi Date: Thu, 7 Nov 2024 11:20:43 +0200 Subject: [PATCH 20/24] Comment out the '400 - BAD REQUEST' line because of errrors. --- Rain_alert/samples.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Rain_alert/samples.py b/Rain_alert/samples.py index 73cab7e7..08709cea 100644 --- a/Rain_alert/samples.py +++ b/Rain_alert/samples.py @@ -56,8 +56,8 @@ } # Sample response from Twilio -400 - BAD REQUEST - The data given in the POST or PUT failed validation. Inspect the response body for details. -{ +# 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", From 0884f2e151f1f8b8cc4f10b09d25cd7d39358762 Mon Sep 17 00:00:00 2001 From: Sakhile Ndlazi Date: Thu, 7 Nov 2024 11:22:05 +0200 Subject: [PATCH 21/24] [Feature]: Use .env environment to for securing API keys. --- Rain_alert/helper.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/Rain_alert/helper.py b/Rain_alert/helper.py index 4b668f4f..445f790a 100644 --- a/Rain_alert/helper.py +++ b/Rain_alert/helper.py @@ -2,14 +2,8 @@ A module for helper functions. """ -from creds import ( - api_key, - twilio_account_sid, - twilio_auth_token, - twilio_phone_number, - user_phone_number -) from dotenv import load_dotenv +from os import getenv import requests from requests.exceptions import HTTPError from samples import wx_sample_response @@ -29,7 +23,7 @@ def get_weather_data(lat: float, lon: float, exclude: str) -> dict: 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={OPENWEATHER_API_KEY}" + 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}" @@ -117,12 +111,12 @@ def send_sms(message: str) -> dict: response (dict): Response from the attempt to send the message """ - client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN) + client = Client(str(getenv('TWILIO_ACCOUNT_SID')), str(getenv('TWILIO_AUTH_TOKEN'))) message = client.messages.create( - from_= TWILIO_NUMBER, + from_= str(getenv('TWILIO_NUMBER')), body = message, - to = USER_NUMBER + to = str(getenv('USER_NUMBER')) ) return message.sid @@ -138,6 +132,6 @@ def send_sms(message: str) -> dict: # 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)) \ No newline at end of file + message = "Hey, Sakhile, alien technology is here! Ayinabungozi lento." + print(send_sms(message)) + # print(process_weather(wx_sample_response)) \ No newline at end of file From e16a8d8f68bb71f866b33c7b0e279cd17480b4d4 Mon Sep 17 00:00:00 2001 From: Sakhile Ndlazi Date: Thu, 7 Nov 2024 11:23:49 +0200 Subject: [PATCH 22/24] Import get from requests only. --- Rain_alert/helper.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Rain_alert/helper.py b/Rain_alert/helper.py index 445f790a..834b042c 100644 --- a/Rain_alert/helper.py +++ b/Rain_alert/helper.py @@ -4,7 +4,7 @@ from dotenv import load_dotenv from os import getenv -import requests +from requests import get from requests.exceptions import HTTPError from samples import wx_sample_response from twilio.rest import Client @@ -30,7 +30,7 @@ def get_weather_data(lat: float, lon: float, exclude: str) -> dict: # Catch any errors that may arise trying to GET the Weather data try: - response = requests.get(URL) + response = get(URL) response.raise_for_status() except HTTPError as http_err: print(f"HTTP Error occured: {http_err}") @@ -52,7 +52,7 @@ def get_user_location() -> tuple[str, str]: url = "https://ipinfo.io/" try: - response = requests.get(url) + response = get(url) response.raise_for_status() except HTTPError as http_err: print(f"HTTP Error occured: {http_err}") From ced52d234333389d6a1dc71605857f5572fae1fe Mon Sep 17 00:00:00 2001 From: Sakhile Ndlazi Date: Thu, 7 Nov 2024 11:26:13 +0200 Subject: [PATCH 23/24] Test process_weather function. --- Rain_alert/helper.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Rain_alert/helper.py b/Rain_alert/helper.py index 834b042c..47b9db41 100644 --- a/Rain_alert/helper.py +++ b/Rain_alert/helper.py @@ -132,6 +132,6 @@ def send_sms(message: str) -> dict: # 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)) \ No newline at end of file + # message = "Hey, Sakhile, alien technology is here! Ayinabungozi lento." + # print(send_sms(message)) + print(process_weather(wx_sample_response)) \ No newline at end of file From ef50ccc57875f7183e698fcab7c1c0b4073333e1 Mon Sep 17 00:00:00 2001 From: Sakhile Ndlazi Date: Thu, 7 Nov 2024 11:27:08 +0200 Subject: [PATCH 24/24] Use process_message from the weather API to send user warning, --- Rain_alert/main.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Rain_alert/main.py b/Rain_alert/main.py index 413a800d..8eeeb45c 100644 --- a/Rain_alert/main.py +++ b/Rain_alert/main.py @@ -5,7 +5,8 @@ from helper import ( get_user_location, get_weather_data, - send_sms + send_sms, + process_weather ) @@ -26,9 +27,9 @@ def main() -> None: exclude = 'hourly' hourly_forcast = get_weather_data(lat, lon, exclude) # Process that data and formulate warning for the user - mock_warning = "Rain in the next hour. We cannot sell ice cream today." + warning = process_weather(hourly_forcast) # Send the user a warning SMS - send_sms(mock_warning) + send_sms(warning) if __name__ == "__main__":