Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 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
b4e916e
Merge pull request #1 from sakhileln/dotenv
sakhileln Nov 7, 2024
3c08a67
Add 'Installation' section on the README file.
sakhileln Nov 7, 2024
3bcbee9
Add 'Usage' section on the README file.
sakhileln Nov 7, 2024
d5e0565
Add 'Contributing' section on the README file.
sakhileln Nov 7, 2024
fa07f1b
Add emojis for Installation, Usage, Contributing sections.
sakhileln Nov 7, 2024
564a1d6
Add line after each section.
sakhileln Nov 7, 2024
b8e457c
Add .gitignore file and files not to be pushed to remote.
sakhileln Nov 11, 2024
16af401
Add requirements file.
sakhileln Nov 11, 2024
7ce446e
Add function that reads google spreasheet.
sakhileln Nov 11, 2024
eee0ca6
Add test case for reading spreasheet.
sakhileln Nov 11, 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
50 changes: 50 additions & 0 deletions Rain_alert/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,53 @@ 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
```
---

## 🛠️ 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.

---

## 👨‍💻 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.

---
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
}
3 changes: 3 additions & 0 deletions birthday_wisher/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.venv/
__pycache__/
.vscode/
44 changes: 44 additions & 0 deletions birthday_wisher/helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""
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


if __name__ == "__main__":
# Test cases
values = read_spreadsheet()
for row in values:
print(row)
Loading