Spaces:
Running
Running
Upload 8 files
Browse files- Dockerfile +20 -0
- app.py +70 -0
- docker-compose.yml +14 -0
- requirements.txt +5 -0
- static/css/style.css +125 -0
- static/js/script.js +96 -0
- templates/index.html +44 -0
- weather_api.py +54 -0
Dockerfile
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use an official Python runtime as a parent image
|
| 2 |
+
FROM python:3.9-slim
|
| 3 |
+
|
| 4 |
+
# Set the working directory
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# Copy the current directory contents into the container at /app
|
| 8 |
+
COPY . /app
|
| 9 |
+
|
| 10 |
+
# Install any needed packages specified in requirements.txt
|
| 11 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 12 |
+
|
| 13 |
+
# Make port 7860 available to the world outside this container
|
| 14 |
+
EXPOSE 7860
|
| 15 |
+
|
| 16 |
+
# Define environment variable
|
| 17 |
+
ENV NAME WeatherDashboard
|
| 18 |
+
|
| 19 |
+
# Run app.py when the container launches
|
| 20 |
+
CMD ["python", "app.py"]
|
app.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask import Flask, render_template, request, jsonify
|
| 2 |
+
from weather_api import get_weather_data, save_weather_data, get_cached_weather_data
|
| 3 |
+
from flask_mail import Mail, Message
|
| 4 |
+
|
| 5 |
+
app = Flask(__name__)
|
| 6 |
+
|
| 7 |
+
app.config['MAIL_SERVER'] = 'smtp.gmail.com'
|
| 8 |
+
app.config['MAIL_PORT'] = 587
|
| 9 |
+
app.config['MAIL_USE_TLS'] = True
|
| 10 |
+
app.config['MAIL_USERNAME'] = '[email protected]'
|
| 11 |
+
app.config['MAIL_PASSWORD'] = 'your_password'
|
| 12 |
+
|
| 13 |
+
mail = Mail(app)
|
| 14 |
+
|
| 15 |
+
@app.route('/')
|
| 16 |
+
def index():
|
| 17 |
+
return render_template('index.html')
|
| 18 |
+
|
| 19 |
+
@app.route('/weather', methods=['GET'])
|
| 20 |
+
def weather():
|
| 21 |
+
city = request.args.get('city')
|
| 22 |
+
if city:
|
| 23 |
+
# Check cache first
|
| 24 |
+
cached_data = get_cached_weather_data(city)
|
| 25 |
+
if cached_data:
|
| 26 |
+
return jsonify(cached_data)
|
| 27 |
+
|
| 28 |
+
# If not in cache, fetch from API
|
| 29 |
+
data = get_weather_data(city = city)
|
| 30 |
+
if data:
|
| 31 |
+
save_weather_data(city, data)
|
| 32 |
+
return jsonify(data)
|
| 33 |
+
else:
|
| 34 |
+
return jsonify({'error': 'City not found'}), 404
|
| 35 |
+
else:
|
| 36 |
+
return jsonify({'error': 'No city provided'}), 400
|
| 37 |
+
|
| 38 |
+
@app.route('/weatherlocation', methods=['GET'])
|
| 39 |
+
def weatherlocation():
|
| 40 |
+
lat = request.args.get('lat')
|
| 41 |
+
lon = request.args.get('lon')
|
| 42 |
+
if lat and lon:
|
| 43 |
+
# Check cache first
|
| 44 |
+
cached_data = get_cached_weather_data(lat+lon)
|
| 45 |
+
if cached_data:
|
| 46 |
+
return jsonify(cached_data)
|
| 47 |
+
|
| 48 |
+
# If not in cache, fetch from API
|
| 49 |
+
data = get_weather_data(lat = lat, lon = lon)
|
| 50 |
+
if data:
|
| 51 |
+
save_weather_data(lat+lon, data)
|
| 52 |
+
return jsonify(data)
|
| 53 |
+
else:
|
| 54 |
+
return jsonify({'error': 'City not found'}), 404
|
| 55 |
+
else:
|
| 56 |
+
return jsonify({'error': 'No city provided'}), 400
|
| 57 |
+
|
| 58 |
+
@app.route('/subscribe', methods=['POST'])
|
| 59 |
+
def subscribe():
|
| 60 |
+
email = request.json.get('email')
|
| 61 |
+
if email:
|
| 62 |
+
msg = Message('Weather Subscription', sender='[email protected]', recipients=[email])
|
| 63 |
+
msg.body = 'Thank you for subscribing to daily weather updates!'
|
| 64 |
+
mail.send(msg)
|
| 65 |
+
return jsonify({'message': 'Subscription successful, please check your email to confirm.'})
|
| 66 |
+
else:
|
| 67 |
+
return jsonify({'error': 'No email provided'}), 400
|
| 68 |
+
|
| 69 |
+
if __name__ == '__main__':
|
| 70 |
+
app.run(debug=True, host='0.0.0.0', port=7860)
|
docker-compose.yml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
web:
|
| 5 |
+
build: .
|
| 6 |
+
ports:
|
| 7 |
+
- "7860:7860"
|
| 8 |
+
environment:
|
| 9 |
+
- FLASK_ENV=development
|
| 10 |
+
- MAIL_SERVER=smtp.example.com
|
| 11 |
+
- MAIL_PORT=587
|
| 12 |
+
- MAIL_USE_TLS=true
|
| 13 | |
| 14 |
+
- MAIL_PASSWORD=your_password
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Flask
|
| 2 |
+
requests
|
| 3 |
+
Flask-Mail
|
| 4 |
+
beautifulsoup4
|
| 5 |
+
Werkzeug
|
static/css/style.css
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
html, body {
|
| 2 |
+
margin: 0;
|
| 3 |
+
padding: 0;
|
| 4 |
+
width: 100%;
|
| 5 |
+
height: 100%;
|
| 6 |
+
font-family: Arial, sans-serif;
|
| 7 |
+
background-color: #e0f7fa;
|
| 8 |
+
color: #333;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
.container {
|
| 12 |
+
display: flex;
|
| 13 |
+
flex-direction: column;
|
| 14 |
+
height: 100%;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
header {
|
| 18 |
+
text-align: center;
|
| 19 |
+
background-color: #42a5f5;
|
| 20 |
+
color: white;
|
| 21 |
+
padding: 10px;
|
| 22 |
+
border-radius: 5px;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
main {
|
| 26 |
+
flex: 1;
|
| 27 |
+
display: flex;
|
| 28 |
+
flex-direction: column;
|
| 29 |
+
justify-content: space-between;
|
| 30 |
+
padding: 20px;
|
| 31 |
+
overflow-y: auto;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
.content {
|
| 35 |
+
display: flex;
|
| 36 |
+
justify-content: space-between;
|
| 37 |
+
align-items: flex-start;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.search-section, .subscribe-section {
|
| 41 |
+
text-align: center;
|
| 42 |
+
margin: 20px 0;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
.search-section {
|
| 46 |
+
flex: 0 0 30%;
|
| 47 |
+
display: flex;
|
| 48 |
+
flex-direction: column;
|
| 49 |
+
align-items: center;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
.search-section input, .subscribe-section input {
|
| 53 |
+
padding: 10px;
|
| 54 |
+
width: 80%;
|
| 55 |
+
margin: 5px 0;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
.search-section button, .subscribe-section button {
|
| 59 |
+
padding: 10px 20px;
|
| 60 |
+
margin: 5px;
|
| 61 |
+
background-color: #42a5f5;
|
| 62 |
+
color: white;
|
| 63 |
+
border: none;
|
| 64 |
+
border-radius: 5px;
|
| 65 |
+
cursor: pointer;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
.search-section button#location-button {
|
| 69 |
+
background-color: #757575;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
.weather-section {
|
| 73 |
+
flex: 1;
|
| 74 |
+
display: flex;
|
| 75 |
+
flex-direction: column;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
.current-weather, .forecast {
|
| 79 |
+
background-color: #bbdefb;
|
| 80 |
+
padding: 20px;
|
| 81 |
+
margin: 10px 0;
|
| 82 |
+
border-radius: 5px;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
.current-weather {
|
| 86 |
+
display: flex;
|
| 87 |
+
justify-content: space-between;
|
| 88 |
+
align-items: center;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
.current-weather .weather-details {
|
| 92 |
+
flex: 1;
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
.current-weather .weather-details p {
|
| 96 |
+
margin: 5px 0;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
.weather-icon {
|
| 100 |
+
text-align: center;
|
| 101 |
+
flex: 0 0 400px;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
.weather-icon img {
|
| 105 |
+
width: 50px;
|
| 106 |
+
height: 50px;
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
.forecast {
|
| 110 |
+
display: flex;
|
| 111 |
+
justify-content: space-between;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
.forecast-day {
|
| 115 |
+
background-color: #eceff1;
|
| 116 |
+
padding: 10px;
|
| 117 |
+
border-radius: 5px;
|
| 118 |
+
text-align: center;
|
| 119 |
+
width: 23%;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
.forecast-day img {
|
| 123 |
+
width: 40px;
|
| 124 |
+
height: 40px;
|
| 125 |
+
}
|
static/js/script.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
document.getElementById('search-button').addEventListener('click', function() {
|
| 2 |
+
let city = document.getElementById('city-input').value;
|
| 3 |
+
fetchWeatherData(city);
|
| 4 |
+
});
|
| 5 |
+
|
| 6 |
+
document.getElementById('location-button').addEventListener('click', function() {
|
| 7 |
+
if (navigator.geolocation) {
|
| 8 |
+
navigator.geolocation.getCurrentPosition(position => {
|
| 9 |
+
let lat = position.coords.latitude;
|
| 10 |
+
let lon = position.coords.longitude;
|
| 11 |
+
fetchWeatherDataByLocation(lat, lon);
|
| 12 |
+
});
|
| 13 |
+
} else {
|
| 14 |
+
alert('Geolocation is not supported by this browser.');
|
| 15 |
+
}
|
| 16 |
+
});
|
| 17 |
+
|
| 18 |
+
function fetchWeatherData(city) {
|
| 19 |
+
fetch(`/weather?city=${city}`)
|
| 20 |
+
.then(response => response.json())
|
| 21 |
+
.then(data => {
|
| 22 |
+
if (data.error) {
|
| 23 |
+
alert(data.error);
|
| 24 |
+
} else {
|
| 25 |
+
displayWeatherData(data);
|
| 26 |
+
}
|
| 27 |
+
})
|
| 28 |
+
.catch(error => console.error('Error:', error));
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
function fetchWeatherDataByLocation(lat, lon) {
|
| 32 |
+
fetch(`/weatherlocation?lat=${lat}&lon=${lon}`)
|
| 33 |
+
.then(response => response.json())
|
| 34 |
+
.then(data => {
|
| 35 |
+
if (data.error) {
|
| 36 |
+
alert(data.error);
|
| 37 |
+
} else {
|
| 38 |
+
displayWeatherData(data);
|
| 39 |
+
}
|
| 40 |
+
})
|
| 41 |
+
.catch(error => console.error('Error:', error));
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
function displayWeatherData(data) {
|
| 45 |
+
let currentWeather = document.getElementById('current-weather');
|
| 46 |
+
currentWeather.innerHTML = `
|
| 47 |
+
<div class="weather-details">
|
| 48 |
+
<h2>${data.location.name} (${data.current.last_updated})</h2>
|
| 49 |
+
<p>Temperature: ${data.current.temp_c}°C</p>
|
| 50 |
+
<p>Wind: ${data.current.wind_kph} KPH</p>
|
| 51 |
+
<p>Humidity: ${data.current.humidity}%</p>
|
| 52 |
+
</div>
|
| 53 |
+
<div class="weather-icon">
|
| 54 |
+
<img src="${data.current.condition.icon}" alt="${data.current.condition.text}">
|
| 55 |
+
<p>${data.current.condition.text}</p>
|
| 56 |
+
</div>
|
| 57 |
+
`;
|
| 58 |
+
|
| 59 |
+
let forecast = document.getElementById('forecast');
|
| 60 |
+
forecast.innerHTML = '';
|
| 61 |
+
|
| 62 |
+
let lastFourDays = data.forecast.forecastday.slice(-4);
|
| 63 |
+
lastFourDays.forEach(day => {
|
| 64 |
+
forecast.innerHTML += `
|
| 65 |
+
<div class="forecast-day">
|
| 66 |
+
<h3>${day.date}</h3>
|
| 67 |
+
<img src="${day.day.condition.icon}" alt="${day.day.condition.text}">
|
| 68 |
+
<p>Temp: ${day.day.avgtemp_c}°C</p>
|
| 69 |
+
<p>Wind: ${day.day.maxwind_kph} KPH</p>
|
| 70 |
+
<p>Humidity: ${day.day.avghumidity}%</p>
|
| 71 |
+
</div>
|
| 72 |
+
`;
|
| 73 |
+
});
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
document.getElementById('subscribe-form').addEventListener('submit', function(event) {
|
| 78 |
+
event.preventDefault();
|
| 79 |
+
let email = document.getElementById('email-input').value;
|
| 80 |
+
fetch('/subscribe', {
|
| 81 |
+
method: 'POST',
|
| 82 |
+
headers: {
|
| 83 |
+
'Content-Type': 'application/json',
|
| 84 |
+
},
|
| 85 |
+
body: JSON.stringify({ email: email })
|
| 86 |
+
})
|
| 87 |
+
.then(response => response.json())
|
| 88 |
+
.then(data => {
|
| 89 |
+
if (data.error) {
|
| 90 |
+
alert(data.error);
|
| 91 |
+
} else {
|
| 92 |
+
alert(data.message);
|
| 93 |
+
}
|
| 94 |
+
})
|
| 95 |
+
.catch(error => console.error('Error:', error));
|
| 96 |
+
});
|
templates/index.html
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Weather Dashboard</title>
|
| 7 |
+
<link rel="stylesheet" href="/static/css/style.css">
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<div class="container">
|
| 11 |
+
<header>
|
| 12 |
+
<h1>Weather Dashboard</h1>
|
| 13 |
+
</header>
|
| 14 |
+
<main>
|
| 15 |
+
<div class="content">
|
| 16 |
+
<section class="search-section">
|
| 17 |
+
<input type="text" id="city-input" placeholder="E.g., New York, London, Tokyo">
|
| 18 |
+
<button id="search-button">Search</button>
|
| 19 |
+
<p>or</p>
|
| 20 |
+
<button id="location-button">Use Current Location</button>
|
| 21 |
+
</section>
|
| 22 |
+
<section class="weather-section">
|
| 23 |
+
<div class="current-weather" id="current-weather">
|
| 24 |
+
<!-- Current weather data will be displayed here -->
|
| 25 |
+
</div>
|
| 26 |
+
<h2>4-Day Forecast</h2>
|
| 27 |
+
<div class="forecast" id="forecast">
|
| 28 |
+
<!-- <h2>4-Day Forecast</h2> -->
|
| 29 |
+
<!-- Forecast data will be displayed here -->
|
| 30 |
+
</div>
|
| 31 |
+
</section>
|
| 32 |
+
</div>
|
| 33 |
+
<section class="subscribe-section">
|
| 34 |
+
<h2>Subscribe to Daily Weather Updates</h2>
|
| 35 |
+
<form id="subscribe-form">
|
| 36 |
+
<input type="email" id="email-input" placeholder="Enter your email">
|
| 37 |
+
<button type="submit">Subscribe</button>
|
| 38 |
+
</form>
|
| 39 |
+
</section>
|
| 40 |
+
</main>
|
| 41 |
+
</div>
|
| 42 |
+
<script src="/static/js/script.js"></script>
|
| 43 |
+
</body>
|
| 44 |
+
</html>
|
weather_api.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
from datetime import datetime, timedelta
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def is_time_difference_greater_than_one_hour(time1, time2):
|
| 6 |
+
datetime1 = datetime.strptime(time1, "%Y-%m-%d %H:%M:%S")
|
| 7 |
+
datetime2 = datetime.strptime(time2, "%Y-%m-%d %H:%M:%S")
|
| 8 |
+
|
| 9 |
+
difference = abs(datetime1 - datetime2)
|
| 10 |
+
|
| 11 |
+
return difference >= timedelta(hours=1)
|
| 12 |
+
|
| 13 |
+
API_KEY = '0eab9492e3024ce4969105357241507'
|
| 14 |
+
weather_cache = {}
|
| 15 |
+
date_check = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 16 |
+
|
| 17 |
+
def get_weather_data(city = "",lat = "",lon = ""):
|
| 18 |
+
if city !="":
|
| 19 |
+
url = f'http://api.weatherapi.com/v1/forecast.json?key={API_KEY}&q={city}&days=5'
|
| 20 |
+
response = requests.get(url)
|
| 21 |
+
if response.status_code == 200:
|
| 22 |
+
return response.json()
|
| 23 |
+
else:
|
| 24 |
+
return None
|
| 25 |
+
elif lat != "" and lon != "":
|
| 26 |
+
url = f'http://api.weatherapi.com/v1/forecast.json?key={API_KEY}&q={lat},{lon}&days=5'
|
| 27 |
+
response = requests.get(url)
|
| 28 |
+
if response.status_code == 200:
|
| 29 |
+
return response.json()
|
| 30 |
+
else:
|
| 31 |
+
return None
|
| 32 |
+
return None
|
| 33 |
+
|
| 34 |
+
def save_weather_data(city, data):
|
| 35 |
+
global weather_cache
|
| 36 |
+
timestamp = datetime.now()
|
| 37 |
+
weather_cache[city] = {
|
| 38 |
+
'data': data,
|
| 39 |
+
'timestamp': timestamp
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
def get_cached_weather_data(city):
|
| 43 |
+
global weather_cache, date_check
|
| 44 |
+
#Remove all weather_cache when more than 50 caches or There's a one-hour time difference.
|
| 45 |
+
if len(weather_cache) >= 50 or is_time_difference_greater_than_one_hour(date_check,datetime.now().strftime("%Y-%m-%d %H:%M:%S")):
|
| 46 |
+
weather_cache = {}
|
| 47 |
+
date_check = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 48 |
+
|
| 49 |
+
cached_data = weather_cache.get(city)
|
| 50 |
+
if cached_data:
|
| 51 |
+
# Check if the cached data is from today
|
| 52 |
+
if cached_data['timestamp'].date() == datetime.now().date():
|
| 53 |
+
return cached_data['data']
|
| 54 |
+
return None
|