feat(openweather): add location name to forecast response

This commit is contained in:
2026-07-01 16:40:16 +03:00
parent 29fe06462e
commit 081aa8d17f
4 changed files with 26 additions and 5 deletions

View File

@@ -12,6 +12,7 @@ services:
build: . build: .
environment: environment:
- REDIS_HOST=redis - REDIS_HOST=redis
- OPENWEATHER_KEY=$OPENWEATHER_KEY
- DEBUG=1 - DEBUG=1
ports: ports:
- 8000:80 - 8000:80

View File

@@ -12,6 +12,7 @@ services:
image: ${DOCKER_ROOT}/gallery image: ${DOCKER_ROOT}/gallery
environment: environment:
- REDIS_HOST=redis - REDIS_HOST=redis
- OPENWEATHER_KEY=$OPENWEATHER_KEY
depends_on: depends_on:
- redis - redis
ports: ports:

View File

@@ -10,7 +10,9 @@ from gallery.sketch.weather.model import Location, WeatherResponse, WeatherValue
from gallery.sketch.weather.util import merge_weather_values from gallery.sketch.weather.util import merge_weather_values
from gallery.util import TimeUnit 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 from .parser import FORECAST_ITEM_PARSER
logger = logging.getLogger("openweather") logger = logging.getLogger("openweather")
@@ -24,6 +26,14 @@ class OpenWeatherApi(WeatherApi):
def _parse_location(cls, location_id: str) -> tuple[float, float]: def _parse_location(cls, location_id: str) -> tuple[float, float]:
return tuple(map(float, location_id.split(":", maxsplit=2))) 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( @cached(
key_builder=lambda fun, self, location_id: f"api.weather.{self.provider}.source.{location_id}.forecast", key_builder=lambda fun, self, location_id: f"api.weather.{self.provider}.source.{location_id}.forecast",
alias="redis", alias="redis",
@@ -33,7 +43,7 @@ class OpenWeatherApi(WeatherApi):
return await self.SOURCE.get_forecast(*self._parse_location(location_id)) return await self.SOURCE.get_forecast(*self._parse_location(location_id))
async def find_locations(self, query: str) -> list[Location]: async def find_locations(self, query: str) -> list[Location]:
result = await self.SOURCE.get_locations(query) result = await self.SOURCE.find_locations(query)
return [ return [
Location( Location(
id=f"{item.lat}:{item.lon}", 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: 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) data: Forecast = await self._get_location_forecast(location_id)
values = [] values = []
for item in data.list: for item in data.list:
@@ -57,13 +68,14 @@ class OpenWeatherApi(WeatherApi):
if value.date.date() == date: if value.date.date() == date:
values.append(value) values.append(value)
return WeatherResponse( return WeatherResponse(
location=location_id, location=location.name,
date=date, date=date,
period="day", period="day",
values=values, values=values,
) )
async def get_days(self, location_id: str, days: int) -> WeatherResponse: 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) data: Forecast = await self._get_location_forecast(location_id)
values_by_date: dict[datetime.datetime, list[WeatherValue]] = defaultdict(list) values_by_date: dict[datetime.datetime, list[WeatherValue]] = defaultdict(list)
for item in data.list: for item in data.list:
@@ -72,7 +84,7 @@ class OpenWeatherApi(WeatherApi):
values_by_date[item_date].append(value) values_by_date[item_date].append(value)
values = [merge_weather_values(date, values) for date, values in values_by_date.items()] values = [merge_weather_values(date, values) for date, values in values_by_date.items()]
return WeatherResponse( return WeatherResponse(
location=location_id, location=location.name,
date=datetime.date.today(), date=datetime.date.today(),
period="days", period="days",
values=list(sorted(values, key=lambda item: item.date)), values=list(sorted(values, key=lambda item: item.date)),

View File

@@ -89,8 +89,15 @@ class OpenWeather:
response_data = json.loads(response) response_data = json.loads(response)
return Forecast.model_validate(response_data) 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}" endpoint = f"geo/1.0/direct?q={query}&limit={limit}&appid={self._api_key}"
response = await self._source.request(endpoint) response = await self._source.request(endpoint)
response_data = json.loads(response) response_data = json.loads(response)
return [Location.model_validate(item) for item in response_data] 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])