Compare commits
5 Commits
8047d07b86
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2112af7794 | |||
| b559d5660d | |||
| 3e05f6ff74 | |||
| 52400f888e | |||
| 97ca92b945 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -2,3 +2,7 @@ utils/__pycache__/__init__.cpython-313.pyc
|
|||||||
utils/__pycache__/geocode.cpython-313.pyc
|
utils/__pycache__/geocode.cpython-313.pyc
|
||||||
utils/__pycache__/weather.cpython-313.pyc
|
utils/__pycache__/weather.cpython-313.pyc
|
||||||
__pycache__/config.cpython-313.pyc
|
__pycache__/config.cpython-313.pyc
|
||||||
|
__pycache__/config.cpython-312.pyc
|
||||||
|
utils/__pycache__/geocode.cpython-312.pyc
|
||||||
|
utils/__pycache__/weather.cpython-312.pyc
|
||||||
|
utils/__pycache__/__init__.cpython-312.pyc
|
||||||
|
|||||||
@@ -18,5 +18,6 @@ COPY static/ ./static
|
|||||||
# Expose the Flask port
|
# Expose the Flask port
|
||||||
EXPOSE 5000
|
EXPOSE 5000
|
||||||
|
|
||||||
# Run the app with Gunicorn
|
# Run the app with Gunicorn, 4 workers, 2 threads each
|
||||||
CMD ["gunicorn", "-b", "0.0.0.0:5000", "app:app"]
|
CMD ["gunicorn", "-b", "0.0.0.0:5000", "app:app", "-w", "4", "--threads", "2"]
|
||||||
|
|
||||||
|
|||||||
23
app.py
23
app.py
@@ -1,10 +1,25 @@
|
|||||||
from flask import Flask, render_template, jsonify, request
|
from flask import Flask, render_template, jsonify, request
|
||||||
from utils.weather import get_today_snowfall
|
from utils.weather import get_today_snowfall_async
|
||||||
from utils.geocode import get_city_name
|
from utils.geocode import get_city_name_async
|
||||||
from config import MAX_SNOW_INCHES
|
from config import MAX_SNOW_INCHES
|
||||||
|
from flask_caching import Cache
|
||||||
|
import asyncio
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# Cache config: 10-minute default timeout
|
||||||
|
cache = Cache(app, config={"CACHE_TYPE": "SimpleCache", "CACHE_DEFAULT_TIMEOUT": 600})
|
||||||
|
|
||||||
|
@cache.memoize()
|
||||||
|
def get_snowfall_for_location(lat, lon):
|
||||||
|
# Run async functions in an event loop
|
||||||
|
return asyncio.run(fetch_snow_and_city(lat, lon))
|
||||||
|
|
||||||
|
async def fetch_snow_and_city(lat, lon):
|
||||||
|
inches, snowing, tz_name = await get_today_snowfall_async(lat, lon)
|
||||||
|
city_name = await get_city_name_async(lat, lon)
|
||||||
|
return inches, snowing, tz_name, city_name
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
return render_template("index.html")
|
return render_template("index.html")
|
||||||
@@ -16,8 +31,7 @@ def snowfall_api():
|
|||||||
if lat is None or lon is None:
|
if lat is None or lon is None:
|
||||||
return jsonify({"error": "Missing lat/lon"}), 400
|
return jsonify({"error": "Missing lat/lon"}), 400
|
||||||
|
|
||||||
inches, snowing, tz_name = get_today_snowfall(lat, lon)
|
inches, snowing, tz_name, city_name = get_snowfall_for_location(lat, lon)
|
||||||
city_name = get_city_name(lat, lon)
|
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import pytz
|
import pytz
|
||||||
@@ -34,6 +48,5 @@ def snowfall_api():
|
|||||||
"updated": datetime.now().isoformat()
|
"updated": datetime.now().isoformat()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(host="0.0.0.0", port=5000)
|
app.run(host="0.0.0.0", port=5000)
|
||||||
|
|||||||
@@ -3,3 +3,5 @@ requests
|
|||||||
gunicorn
|
gunicorn
|
||||||
pytz
|
pytz
|
||||||
timezonefinder
|
timezonefinder
|
||||||
|
Flask-Caching
|
||||||
|
httpx
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import requests
|
import httpx
|
||||||
|
|
||||||
def get_city_name(lat, lon):
|
async def get_city_name_async(lat, lon):
|
||||||
try:
|
async with httpx.AsyncClient(timeout=5) as client:
|
||||||
r = requests.get(
|
r = await client.get(
|
||||||
"https://nominatim.openstreetmap.org/reverse",
|
"https://nominatim.openstreetmap.org/reverse",
|
||||||
params={"lat": lat, "lon": lon, "format": "json", "zoom": 10, "addressdetails": 1},
|
params={"lat": lat, "lon": lon, "format": "json", "zoom": 10, "addressdetails": 1},
|
||||||
headers={"User-Agent": "AreWeBuriedApp/1.0"}
|
headers={"User-Agent": "AreWeBuriedApp/1.0"}
|
||||||
@@ -10,8 +10,4 @@ def get_city_name(lat, lon):
|
|||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
data = r.json()
|
data = r.json()
|
||||||
address = data.get("address", {})
|
address = data.get("address", {})
|
||||||
city = address.get("city") or address.get("town") or address.get("village") or address.get("county") or f"{lat:.2f},{lon:.2f}"
|
return address.get("city") or address.get("town") or address.get("village") or address.get("county") or f"{lat:.2f},{lon:.2f}"
|
||||||
return city
|
|
||||||
except Exception as e:
|
|
||||||
print("Reverse geocoding error:", e)
|
|
||||||
return f"{lat:.2f},{lon:.2f}"
|
|
||||||
|
|||||||
@@ -1,43 +1,33 @@
|
|||||||
import requests
|
import httpx
|
||||||
from datetime import datetime
|
|
||||||
import pytz
|
import pytz
|
||||||
|
from datetime import datetime
|
||||||
from timezonefinder import TimezoneFinder
|
from timezonefinder import TimezoneFinder
|
||||||
from config import OPEN_METEO_URL
|
from config import OPEN_METEO_URL
|
||||||
|
|
||||||
tf = TimezoneFinder()
|
tf = TimezoneFinder()
|
||||||
|
|
||||||
def get_today_snowfall(lat, lon):
|
async def get_today_snowfall_async(lat, lon):
|
||||||
try:
|
|
||||||
tz_name = tf.timezone_at(lat=lat, lng=lon) or "UTC"
|
tz_name = tf.timezone_at(lat=lat, lng=lon) or "UTC"
|
||||||
|
|
||||||
params = {
|
async with httpx.AsyncClient(timeout=5) as client:
|
||||||
"latitude": lat,
|
params = {"latitude": lat, "longitude": lon, "daily": "snowfall_sum", "timezone": tz_name}
|
||||||
"longitude": lon,
|
r = await client.get(OPEN_METEO_URL, params=params)
|
||||||
"hourly": "snowfall",
|
|
||||||
"timezone": tz_name
|
|
||||||
}
|
|
||||||
|
|
||||||
r = requests.get(OPEN_METEO_URL, params=params, timeout=5)
|
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
data = r.json()
|
data = r.json()
|
||||||
|
|
||||||
hourly = data.get("hourly", {})
|
daily = data.get("daily", {})
|
||||||
times = hourly.get("time", [])
|
days = daily.get("time", [])
|
||||||
snow_values = hourly.get("snowfall", [])
|
snow_values = daily.get("snowfall_sum", [])
|
||||||
|
|
||||||
today_str = datetime.now(pytz.timezone(tz_name)).date().isoformat()
|
today_str = datetime.now(pytz.timezone(tz_name)).date().isoformat()
|
||||||
|
|
||||||
snowfall_cm = 0.0
|
snowfall_cm = 0.0
|
||||||
snowing_now = False
|
snowing_now = False
|
||||||
for t, s in zip(times, snow_values):
|
for d, s in zip(days, snow_values):
|
||||||
if t.startswith(today_str):
|
if d.startswith(today_str):
|
||||||
snowfall_cm += s
|
snowfall_cm += s
|
||||||
if s > 0:
|
if s > 0:
|
||||||
snowing_now = True
|
snowing_now = True
|
||||||
|
|
||||||
inches = round(snowfall_cm / 2.54, 1)
|
inches = round(snowfall_cm / 2.54, 1)
|
||||||
return inches, snowing_now, tz_name
|
return inches, snowing_now, tz_name
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print("Error fetching snowfall:", e)
|
|
||||||
return 0.0, False, "UTC"
|
|
||||||
|
|||||||
Reference in New Issue
Block a user