feat: introduce WeatherDataCollector class for multi-source weather data retrieval, caching, and rate limiting.
This commit is contained in:
@@ -85,6 +85,12 @@ class WeatherDataCollector:
|
|||||||
os.getenv("OPEN_METEO_RATE_LIMIT_COOLDOWN_SEC", "900") # 默认 15 分钟
|
os.getenv("OPEN_METEO_RATE_LIMIT_COOLDOWN_SEC", "900") # 默认 15 分钟
|
||||||
)
|
)
|
||||||
self._open_meteo_rl_lock = threading.Lock()
|
self._open_meteo_rl_lock = threading.Lock()
|
||||||
|
# Open-Meteo burst control: avoid hammering API with many cities at once.
|
||||||
|
self._open_meteo_min_interval_sec: float = float(
|
||||||
|
os.getenv("OPEN_METEO_MIN_CALL_INTERVAL_SEC", "3")
|
||||||
|
)
|
||||||
|
self._open_meteo_last_call_ts: float = 0.0
|
||||||
|
self._open_meteo_call_lock = threading.Lock()
|
||||||
self.meteoblue_cache_ttl_sec = int(
|
self.meteoblue_cache_ttl_sec = int(
|
||||||
os.getenv("METEOBLUE_CACHE_TTL_SEC", "7200")
|
os.getenv("METEOBLUE_CACHE_TTL_SEC", "7200")
|
||||||
)
|
)
|
||||||
@@ -215,6 +221,22 @@ class WeatherDataCollector:
|
|||||||
logger.warning(f"磁盘缓存写入失败: {e}")
|
logger.warning(f"磁盘缓存写入失败: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def _wait_open_meteo_slot(self, endpoint: str) -> None:
|
||||||
|
"""Simple per-process rate gate for Open-Meteo endpoints."""
|
||||||
|
min_interval = self._open_meteo_min_interval_sec
|
||||||
|
if min_interval <= 0:
|
||||||
|
return
|
||||||
|
with self._open_meteo_call_lock:
|
||||||
|
now_ts = time.time()
|
||||||
|
wait_for = min_interval - (now_ts - self._open_meteo_last_call_ts)
|
||||||
|
if wait_for > 0:
|
||||||
|
logger.debug(
|
||||||
|
f"Open-Meteo {endpoint} 限流保护:sleep {wait_for:.2f}s (min_interval={min_interval:.2f}s)"
|
||||||
|
)
|
||||||
|
time.sleep(wait_for)
|
||||||
|
now_ts = time.time()
|
||||||
|
self._open_meteo_last_call_ts = now_ts
|
||||||
|
|
||||||
def fetch_from_openweather(self, city: str, country: str = None) -> Optional[Dict]:
|
def fetch_from_openweather(self, city: str, country: str = None) -> Optional[Dict]:
|
||||||
"""
|
"""
|
||||||
Fetch current weather and forecast from OpenWeatherMap
|
Fetch current weather and forecast from OpenWeatherMap
|
||||||
@@ -1123,6 +1145,7 @@ class WeatherDataCollector:
|
|||||||
else:
|
else:
|
||||||
params["temperature_unit"] = "celsius"
|
params["temperature_unit"] = "celsius"
|
||||||
|
|
||||||
|
self._wait_open_meteo_slot("forecast")
|
||||||
response = self.session.get(
|
response = self.session.get(
|
||||||
url,
|
url,
|
||||||
params=params,
|
params=params,
|
||||||
@@ -1276,6 +1299,7 @@ class WeatherDataCollector:
|
|||||||
else:
|
else:
|
||||||
params["temperature_unit"] = "celsius"
|
params["temperature_unit"] = "celsius"
|
||||||
|
|
||||||
|
self._wait_open_meteo_slot("ensemble")
|
||||||
response = self.session.get(
|
response = self.session.get(
|
||||||
url,
|
url,
|
||||||
params=params,
|
params=params,
|
||||||
@@ -1422,6 +1446,7 @@ class WeatherDataCollector:
|
|||||||
if use_fahrenheit:
|
if use_fahrenheit:
|
||||||
params["temperature_unit"] = "fahrenheit"
|
params["temperature_unit"] = "fahrenheit"
|
||||||
|
|
||||||
|
self._wait_open_meteo_slot("multi-model")
|
||||||
response = self.session.get(
|
response = self.session.get(
|
||||||
url,
|
url,
|
||||||
params=params,
|
params=params,
|
||||||
|
|||||||
Reference in New Issue
Block a user