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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] 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/33] [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/33] 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/33] 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/33] 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__": From 3c08a6705a578a7553f0d97f6e464e8285e23ce0 Mon Sep 17 00:00:00 2001 From: Sakhile Ndlazi Date: Thu, 7 Nov 2024 11:52:45 +0200 Subject: [PATCH 25/33] Add 'Installation' section on the README file. --- Rain_alert/README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Rain_alert/README.md b/Rain_alert/README.md index 047fe46a..1ecbd594 100644 --- a/Rain_alert/README.md +++ b/Rain_alert/README.md @@ -33,3 +33,22 @@ Before you begin, ensure you have the following installed: 4. **OpenWeather API Key** – [Generate your key here](https://openweathermap.org/api) --- + +## Installation +To install the program and run the program +- Go to the shell + ```shell + $ git clone https://github.com/sakhileln/Code-Crunch-Marathon.git + ``` +- Change directory to the project + ```shell + $ cd Code-Crunch-Marathon/Rain_alert/ + ``` +- Install the required packages + ```shell + $ pip install -r requirements.txt + ``` +- Run the project + ```shell + $ python3 main.py + ``` From 3bcbee934823c2e748ccad96b4b0a5f422da6dc1 Mon Sep 17 00:00:00 2001 From: Sakhile Ndlazi Date: Thu, 7 Nov 2024 12:00:23 +0200 Subject: [PATCH 26/33] Add 'Usage' section on the README file. --- Rain_alert/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Rain_alert/README.md b/Rain_alert/README.md index 1ecbd594..c5ad5f42 100644 --- a/Rain_alert/README.md +++ b/Rain_alert/README.md @@ -52,3 +52,11 @@ To install the program and run the program ```shell $ python3 main.py ``` +## Usage +If you are interested to know if there is expected rain in your vicinity. +1. Create a .env file at the root of the project. +2. Get your OpenWeather API Key and Twilio Authorasation Token and Account SID, and a Twilio Number. +3. Get the number you like to receive the SMS forecast on. +4. Add the items mentioned above to .env file you just created. +5. Use .env.sample file as a guide for adding items on .env file +6. Run the project and you will receive a SMS message with the forecast. \ No newline at end of file From d5e05651e60359fe09a28ae9ca6f48ed5fbd7463 Mon Sep 17 00:00:00 2001 From: Sakhile Ndlazi Date: Thu, 7 Nov 2024 12:05:18 +0200 Subject: [PATCH 27/33] Add 'Contributing' section on the README file. --- Rain_alert/README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Rain_alert/README.md b/Rain_alert/README.md index c5ad5f42..21eaeb1f 100644 --- a/Rain_alert/README.md +++ b/Rain_alert/README.md @@ -59,4 +59,21 @@ If you are interested to know if there is expected rain in your vicinity. 3. Get the number you like to receive the SMS forecast on. 4. Add the items mentioned above to .env file you just created. 5. Use .env.sample file as a guide for adding items on .env file -6. Run the project and you will receive a SMS message with the forecast. \ No newline at end of file +6. Run the project and you will receive a SMS message with the forecast. + +## Contributing +Contributions are welcome! If you have suggestions for improvements or new features, please fork the repository and create a pull request. +1. Fork the repository +2. Create your feature branch: + ```bash + git checkout -b feature/YourFeature + ``` +3. Commit your changes: + ```bash + git commit -m "Add some feature" + ``` +4. Push to the branch: + ```bash + git push origin feature/YourFeature + ``` +5. Open a pull request. \ No newline at end of file From fa07f1bfffe6c3070bda00fbb1d5f03536e47c05 Mon Sep 17 00:00:00 2001 From: Sakhile Ndlazi Date: Thu, 7 Nov 2024 12:11:38 +0200 Subject: [PATCH 28/33] Add emojis for Installation, Usage, Contributing sections. --- Rain_alert/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Rain_alert/README.md b/Rain_alert/README.md index 21eaeb1f..425ce32f 100644 --- a/Rain_alert/README.md +++ b/Rain_alert/README.md @@ -34,7 +34,7 @@ Before you begin, ensure you have the following installed: --- -## Installation +## 📥 Installation To install the program and run the program - Go to the shell ```shell @@ -52,7 +52,7 @@ To install the program and run the program ```shell $ python3 main.py ``` -## Usage +## 🛠️ Usage If you are interested to know if there is expected rain in your vicinity. 1. Create a .env file at the root of the project. 2. Get your OpenWeather API Key and Twilio Authorasation Token and Account SID, and a Twilio Number. @@ -61,7 +61,7 @@ If you are interested to know if there is expected rain in your vicinity. 5. Use .env.sample file as a guide for adding items on .env file 6. Run the project and you will receive a SMS message with the forecast. -## Contributing +## 👨‍💻 Contributing Contributions are welcome! If you have suggestions for improvements or new features, please fork the repository and create a pull request. 1. Fork the repository 2. Create your feature branch: From 564a1d60946312822c0ef0cfb7a57a1afb522321 Mon Sep 17 00:00:00 2001 From: Sakhile Ndlazi Date: Thu, 7 Nov 2024 12:13:45 +0200 Subject: [PATCH 29/33] Add line after each section. --- Rain_alert/README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Rain_alert/README.md b/Rain_alert/README.md index 425ce32f..256cc1bf 100644 --- a/Rain_alert/README.md +++ b/Rain_alert/README.md @@ -52,6 +52,8 @@ To install the program and run the program ```shell $ python3 main.py ``` +--- + ## 🛠️ Usage If you are interested to know if there is expected rain in your vicinity. 1. Create a .env file at the root of the project. @@ -61,6 +63,8 @@ If you are interested to know if there is expected rain in your vicinity. 5. Use .env.sample file as a guide for adding items on .env file 6. Run the project and you will receive a SMS message with the forecast. +--- + ## 👨‍💻 Contributing Contributions are welcome! If you have suggestions for improvements or new features, please fork the repository and create a pull request. 1. Fork the repository @@ -76,4 +80,6 @@ Contributions are welcome! If you have suggestions for improvements or new featu ```bash git push origin feature/YourFeature ``` -5. Open a pull request. \ No newline at end of file +5. Open a pull request. + +--- \ No newline at end of file From b8e457ccb7e863625e4a1f30fe8b7ceee7884997 Mon Sep 17 00:00:00 2001 From: "Sakhile L. Ndlazi" Date: Mon, 11 Nov 2024 03:43:37 +0200 Subject: [PATCH 30/33] Add .gitignore file and files not to be pushed to remote. --- birthday_wisher/.gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 birthday_wisher/.gitignore diff --git a/birthday_wisher/.gitignore b/birthday_wisher/.gitignore new file mode 100644 index 00000000..76317214 --- /dev/null +++ b/birthday_wisher/.gitignore @@ -0,0 +1,3 @@ +.venv/ +__pycache__/ +.vscode/ From 16af4014653f07d87112b6a09bc1e06fccdebe4d Mon Sep 17 00:00:00 2001 From: "Sakhile L. Ndlazi" Date: Mon, 11 Nov 2024 03:46:10 +0200 Subject: [PATCH 31/33] Add requirements file. --- birthday_wisher/requirements.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 birthday_wisher/requirements.txt diff --git a/birthday_wisher/requirements.txt b/birthday_wisher/requirements.txt new file mode 100644 index 00000000..22d19de4 --- /dev/null +++ b/birthday_wisher/requirements.txt @@ -0,0 +1,18 @@ +cachetools==5.5.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +google-auth==2.36.0 +google-auth-oauthlib==1.2.1 +gspread==6.1.4 +httplib2==0.22.0 +idna==3.10 +oauth2client==4.1.3 +oauthlib==3.2.2 +pyasn1==0.6.1 +pyasn1_modules==0.4.1 +pyparsing==3.2.0 +requests==2.32.3 +requests-oauthlib==2.0.0 +rsa==4.9 +six==1.16.0 +urllib3==2.2.3 From 7ce446ec637fa84c2f809232ec2d11678e4b4d51 Mon Sep 17 00:00:00 2001 From: "Sakhile L. Ndlazi" Date: Mon, 11 Nov 2024 04:04:44 +0200 Subject: [PATCH 32/33] Add function that reads google spreasheet. --- birthday_wisher/helper.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 birthday_wisher/helper.py diff --git a/birthday_wisher/helper.py b/birthday_wisher/helper.py new file mode 100644 index 00000000..ea178cb8 --- /dev/null +++ b/birthday_wisher/helper.py @@ -0,0 +1,37 @@ +""" +A helper module for helper funcions. +""" + +import gspread +from oauth2client.service_account import ServiceAccountCredentials + + +def read_spreadsheet() -> list[list]: + """ + Authenticat and read a Google Sheet using gspread + + Parameters: + None + Return: + values (list): Values in a spreadsheet + """ + # Define the scope for the Sheets API and Google Drive API + SCOPES = [ + "https://www.googleapis.com/auth/spreadsheets", + "https://www.googleapis.com/auth/drive.file", + ] + # Use the credentials file from Google Cloud Console + credentials = ServiceAccountCredentials.from_json_keyfile_name( + "credentials.json", SCOPES + ) + + # Authenticate using credentials and create client + gc = gspread.authorize(credentials) + spreadsheet = gc.open("Birthday Spreadsheet") + + worksheet = spreadsheet.get_worksheet(0) # Get first sheet + + # Read values from sheet + values = worksheet.get_all_values() + + return values From eee0ca68d6e1e2ca724697aadf5c0aeb0d79635b Mon Sep 17 00:00:00 2001 From: "Sakhile L. Ndlazi" Date: Mon, 11 Nov 2024 04:07:13 +0200 Subject: [PATCH 33/33] Add test case for reading spreasheet. --- birthday_wisher/helper.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/birthday_wisher/helper.py b/birthday_wisher/helper.py index ea178cb8..bc224866 100644 --- a/birthday_wisher/helper.py +++ b/birthday_wisher/helper.py @@ -35,3 +35,10 @@ def read_spreadsheet() -> list[list]: values = worksheet.get_all_values() return values + + +if __name__ == "__main__": + # Test cases + values = read_spreadsheet() + for row in values: + print(row)