import datetime import json import logging from os import environ from typing import Any from bs4 import BeautifulSoup from fastapi import HTTPException, status from gallery.sketch.source import ApiSource from gallery.sketch.weather.api import WeatherApi from gallery.sketch.weather.model import Location, WeatherResponse, WeatherValue from gallery.sketch.weather.util import merge_weather_values from .parser import DAYS_PARSER, LOCATION_PARSER, ONE_DAY_PARSER, ROW_PARSERS logger = logging.getLogger("gismeteo") def format_date(date: datetime.date) -> str: today = datetime.date.today() days = (date - today).days if days == 0: return "today" elif days == 1: return "tomorrow" else: return f"{days + 1}-day" class GismeteoApi(WeatherApi): PROVIDER = "gismeteo" def _build_source(self) -> ApiSource: return ApiSource( "https://www.gismeteo.ru", cookies={"xauth": auth} if (auth := environ.get("GISMETEO_AUTH")) else None, ) def _parse_oneday(self, date: datetime.date, data: str) -> WeatherResponse: result: list[dict[str, Any]] = [] soup = BeautifulSoup(data, features="html.parser") location = LOCATION_PARSER.parse_location(data) widget = ONE_DAY_PARSER.parse_widget(soup) for parser in ROW_PARSERS: for index, value in enumerate(parser.parse_row(widget)): while len(result) < index + 1: result.append({}) result[index][parser.KEY] = value values = [WeatherValue(**item) for item in result] # group values by 3 hours if len(values) > 8: n = 3 values = [ merge_weather_values(values[i].date, values[i : i + n], "average") for i in range(0, len(values), n) ] return WeatherResponse( location=location or "n/a", date=date, period="day", values=values, ) def _parse_manydays(self, data: str) -> WeatherResponse: result: list[dict[str, Any]] = [] soup = BeautifulSoup(data, features="html.parser") location = LOCATION_PARSER.parse_location(data) widget = DAYS_PARSER.parse_widget(soup) for parser in ROW_PARSERS: for index, value in enumerate(parser.parse_row(widget)): while len(result) < index + 1: result.append({}) result[index][parser.KEY] = value values = [WeatherValue(**item) for item in result] return WeatherResponse( location=location or "n/a", date=datetime.date.today(), period="days", values=values, ) async def find_locations(self, query: str) -> list[Location]: geo = "ru" latitude = 52.968498 longitude = 36.0695 data = json.loads( await self.source.request( f"mq/city/q/?q={query}&geo={geo}&latitude={latitude}&longitude={longitude}&limit=10" ) ) result = [] for item in data["data"]: result.append( Location( id=f"{item['slug']}-{item['id']}", name=item["translations"]["kk"]["city"]["name"], provider=self.provider, lat=item["coordinates"]["latitude"], lon=item["coordinates"]["longitude"], country=item["translations"]["kk"]["country"]["name"], country_code=item["country"]["code"].lower(), district=( item["translations"]["kk"]["district"]["name"] if item["translations"]["kk"]["district"] else "" ), subdistrict=( item["translations"]["kk"]["subdistrict"]["name"] if "subdistrict" in item["translations"]["kk"] else "" ), ) ) return result async def get_day(self, location_id: str, date: datetime.date) -> WeatherResponse: max_date = datetime.date.today() + datetime.timedelta(days=9) if date > max_date: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail={"max_date": max_date.strftime("%Y-%m-%d")} ) data = await self.source.request(f"weather-{location_id}/{format_date(date)}") return self._parse_oneday(date, data) async def get_days(self, location_id: str, days: int) -> WeatherResponse: data = await self.source.request(f"weather-{location_id}/{days}-days") return self._parse_manydays(data)