From 081aa8d17f18fe7576e2ecd5169010e4679f7b41 Mon Sep 17 00:00:00 2001 From: shmyga Date: Wed, 1 Jul 2026 16:40:16 +0300 Subject: [PATCH] feat(openweather): add location name to forecast response --- docker-compose-develop.yaml | 1 + docker-compose.yaml | 1 + gallery/painting/openweather/api.py | 20 ++++++++++++++++---- gallery/painting/openweather/openweather.py | 9 ++++++++- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/docker-compose-develop.yaml b/docker-compose-develop.yaml index e978fe0..6742296 100644 --- a/docker-compose-develop.yaml +++ b/docker-compose-develop.yaml @@ -12,6 +12,7 @@ services: build: . environment: - REDIS_HOST=redis + - OPENWEATHER_KEY=$OPENWEATHER_KEY - DEBUG=1 ports: - 8000:80 diff --git a/docker-compose.yaml b/docker-compose.yaml index d0afd58..22a98b7 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -12,6 +12,7 @@ services: image: ${DOCKER_ROOT}/gallery environment: - REDIS_HOST=redis + - OPENWEATHER_KEY=$OPENWEATHER_KEY depends_on: - redis ports: diff --git a/gallery/painting/openweather/api.py b/gallery/painting/openweather/api.py index 48c9f39..0dee4a9 100644 --- a/gallery/painting/openweather/api.py +++ b/gallery/painting/openweather/api.py @@ -10,7 +10,9 @@ from gallery.sketch.weather.model import Location, WeatherResponse, WeatherValue from gallery.sketch.weather.util import merge_weather_values from gallery.util import TimeUnit -from .openweather import Forecast, OpenWeather +from .openweather import Forecast +from .openweather import Location as OpenWeatherLocation +from .openweather import OpenWeather from .parser import FORECAST_ITEM_PARSER logger = logging.getLogger("openweather") @@ -24,6 +26,14 @@ class OpenWeatherApi(WeatherApi): def _parse_location(cls, location_id: str) -> tuple[float, float]: return tuple(map(float, location_id.split(":", maxsplit=2))) + @cached( + key_builder=lambda fun, self, location_id: f"api.weather.{self.provider}.source.{location_id}.location", + alias="redis", + ttl=TimeUnit.DAY, + ) + async def _get_location(self, location_id: str) -> OpenWeatherLocation: + return await self.SOURCE.get_location(*self._parse_location(location_id)) + @cached( key_builder=lambda fun, self, location_id: f"api.weather.{self.provider}.source.{location_id}.forecast", alias="redis", @@ -33,7 +43,7 @@ class OpenWeatherApi(WeatherApi): return await self.SOURCE.get_forecast(*self._parse_location(location_id)) async def find_locations(self, query: str) -> list[Location]: - result = await self.SOURCE.get_locations(query) + result = await self.SOURCE.find_locations(query) return [ Location( id=f"{item.lat}:{item.lon}", @@ -50,6 +60,7 @@ class OpenWeatherApi(WeatherApi): ] async def get_day(self, location_id: str, date: datetime.date) -> WeatherResponse: + location: OpenWeatherLocation = await self._get_location(location_id) data: Forecast = await self._get_location_forecast(location_id) values = [] for item in data.list: @@ -57,13 +68,14 @@ class OpenWeatherApi(WeatherApi): if value.date.date() == date: values.append(value) return WeatherResponse( - location=location_id, + location=location.name, date=date, period="day", values=values, ) async def get_days(self, location_id: str, days: int) -> WeatherResponse: + location: OpenWeatherLocation = await self._get_location(location_id) data: Forecast = await self._get_location_forecast(location_id) values_by_date: dict[datetime.datetime, list[WeatherValue]] = defaultdict(list) for item in data.list: @@ -72,7 +84,7 @@ class OpenWeatherApi(WeatherApi): values_by_date[item_date].append(value) values = [merge_weather_values(date, values) for date, values in values_by_date.items()] return WeatherResponse( - location=location_id, + location=location.name, date=datetime.date.today(), period="days", values=list(sorted(values, key=lambda item: item.date)), diff --git a/gallery/painting/openweather/openweather.py b/gallery/painting/openweather/openweather.py index 3f53807..d49fc9c 100644 --- a/gallery/painting/openweather/openweather.py +++ b/gallery/painting/openweather/openweather.py @@ -89,8 +89,15 @@ class OpenWeather: response_data = json.loads(response) return Forecast.model_validate(response_data) - async def get_locations(self, query: str, limit: int = 5) -> list[Location]: + async def find_locations(self, query: str, limit: int = 5) -> list[Location]: endpoint = f"geo/1.0/direct?q={query}&limit={limit}&appid={self._api_key}" response = await self._source.request(endpoint) response_data = json.loads(response) return [Location.model_validate(item) for item in response_data] + + async def get_location(self, lat: float, lon: float) -> Location: + limit = 1 + endpoint = f"geo/1.0/reverse?lat={lat}&lon={lon}&limit={limit}&appid={self._api_key}" + response = await self._source.request(endpoint) + response_data = json.loads(response) + return Location.model_validate(response_data[0])