feat(easel): implement multiple weather providers
This commit is contained in:
@@ -50,7 +50,13 @@ def build_weather_response(request: AppRequest, response: WeatherResponse):
|
||||
router = APIRouter(prefix="/weather")
|
||||
|
||||
|
||||
@router.get("/", response_class=HTMLResponse)
|
||||
@router.get("/", response_class=RedirectResponse)
|
||||
async def get_weather(request: AppRequest):
|
||||
default_provider = request.app.state.api.get_api_providers(WeatherApi.TYPE)[0]
|
||||
return RedirectResponse(default_provider)
|
||||
|
||||
|
||||
@router.get("/{provider}", response_class=HTMLResponse)
|
||||
async def get_weather_index(request: AppRequest, weather_api: WeatherApiDepends, query: str | None = None):
|
||||
locations = (await weather_api.find_locations(query)) if query else []
|
||||
return templates.TemplateResponse(
|
||||
@@ -62,12 +68,12 @@ async def get_weather_index(request: AppRequest, weather_api: WeatherApiDepends,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{location}", response_class=RedirectResponse)
|
||||
@router.get("/{provider}/{location}", response_class=RedirectResponse)
|
||||
async def get_weather_default(location: str):
|
||||
return RedirectResponse(f"{location}/tag/today")
|
||||
|
||||
|
||||
@router.get("/{location}/day/{date}", response_class=HTMLResponse)
|
||||
@router.get("/{provider}/{location}/day/{date}", response_class=HTMLResponse)
|
||||
async def get_weather_day(
|
||||
request: AppRequest,
|
||||
weather_api: WeatherApiDepends,
|
||||
@@ -78,13 +84,13 @@ async def get_weather_day(
|
||||
return build_weather_response(request, response)
|
||||
|
||||
|
||||
@router.get("/{location}/days/{days}", response_class=HTMLResponse)
|
||||
@router.get("/{provider}/{location}/days/{days}", response_class=HTMLResponse)
|
||||
async def get_weather_days(request: AppRequest, weather_api: WeatherApiDepends, location: str, days: int):
|
||||
response = await weather_api.get_days(location, days)
|
||||
return build_weather_response(request, response)
|
||||
|
||||
|
||||
@router.get("/{location}/tag/{tag}", response_class=HTMLResponse)
|
||||
@router.get("/{provider}/{location}/tag/{tag}", response_class=HTMLResponse)
|
||||
async def get_weather_tag(request: AppRequest, weather_api: WeatherApiDepends, location: str, tag: str):
|
||||
tag_value = TagUtil.parse_tag(tag)
|
||||
if tag_value.type == TagType.DAY:
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<ul id="locations"
|
||||
class="list-group mb-5">
|
||||
{% for location in locations %}
|
||||
<a href="{{location.id}}"
|
||||
<a href="{{ url_for('get_weather_default', provider=request.path_params.provider, location=location.id) }}"
|
||||
class="list-group-item list-group-item-action px-4"
|
||||
onclick="saveLocation({id:'{{location.id}}', name:'{{location.name}}'});">
|
||||
<span class="fi fi-{{location.country_code}} me-1"></span>
|
||||
@@ -50,13 +50,20 @@
|
||||
</ul>
|
||||
<script>
|
||||
(function () {
|
||||
const getStorageKey = () => {
|
||||
const provider = window.location.pathname.split('/').pop();
|
||||
return `locations:${provider}`;
|
||||
}
|
||||
|
||||
document.loadLocations = () => {
|
||||
const locations = JSON.parse(window.localStorage.getItem('locations') || '{}');
|
||||
const provider = window.location.pathname.split('/').pop();
|
||||
console.log('!', provider);
|
||||
const locations = JSON.parse(window.localStorage.getItem(getStorageKey()) || '{}');
|
||||
const container = document.querySelector('#locations');
|
||||
container.innerHTML = '';
|
||||
for (const [id, name] of Object.entries(locations)) {
|
||||
const element = document.createElement('a');
|
||||
element.href = `${id}`;
|
||||
element.href = `${window.location.pathname}/${id}`;
|
||||
element.className = 'list-group-item list-group-item-action px-4 d-flex justify-content-between align-items-start';
|
||||
element.innerHTML = `
|
||||
<span class="text-primary me-auto">${name}</span>
|
||||
@@ -67,15 +74,15 @@
|
||||
}
|
||||
|
||||
document.saveLocation = (location) => {
|
||||
const locations = JSON.parse(window.localStorage.getItem('locations') || '{}');
|
||||
const locations = JSON.parse(window.localStorage.getItem(getStorageKey()) || '{}');
|
||||
locations[location.id] = location.name;
|
||||
window.localStorage.setItem('locations', JSON.stringify(locations));
|
||||
window.localStorage.setItem(getStorageKey(), JSON.stringify(locations));
|
||||
}
|
||||
|
||||
document.removeLocation = (id) => {
|
||||
const locations = JSON.parse(window.localStorage.getItem('locations') || '{}');
|
||||
const locations = JSON.parse(window.localStorage.getItem(getStorageKey()) || '{}');
|
||||
delete locations[id];
|
||||
window.localStorage.setItem('locations', JSON.stringify(locations));
|
||||
window.localStorage.setItem(getStorageKey(), JSON.stringify(locations));
|
||||
document.loadLocations();
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,9 @@ class GismeteoApi(WeatherApi):
|
||||
lon=item["coordinates"]["longitude"],
|
||||
country=item["translations"]["kk"]["country"]["name"],
|
||||
country_code=item["country"]["code"].lower(),
|
||||
district=item["translations"]["kk"]["district"]["name"],
|
||||
district=(
|
||||
item["translations"]["kk"]["district"]["name"] if item["translations"]["kk"]["district"] else ""
|
||||
),
|
||||
subdistrict=(
|
||||
item["translations"]["kk"]["subdistrict"]["name"]
|
||||
if "subdistrict" in item["translations"]["kk"]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import datetime
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from os import environ
|
||||
|
||||
from aiocache import cached
|
||||
|
||||
@@ -17,7 +18,7 @@ logger = logging.getLogger("openweather")
|
||||
|
||||
class OpenWeatherApi(WeatherApi):
|
||||
PROVIDER = "openweather"
|
||||
SOURCE = OpenWeather("517a6bccceaa1c48127f6199ec3fb7cf")
|
||||
SOURCE = OpenWeather(environ["OPENWEATHER_KEY"])
|
||||
|
||||
@classmethod
|
||||
def _parse_location(cls, location_id: str) -> tuple[float, float]:
|
||||
@@ -32,7 +33,20 @@ class OpenWeatherApi(WeatherApi):
|
||||
return await self.SOURCE.get_forecast(*self._parse_location(location_id))
|
||||
|
||||
async def find_locations(self, query: str) -> list[Location]:
|
||||
raise NotImplementedError
|
||||
result = await self.SOURCE.get_locations(query)
|
||||
return [
|
||||
Location(
|
||||
id=f"{item.lat}:{item.lon}",
|
||||
name=item.name,
|
||||
lat=item.lat,
|
||||
lon=item.lon,
|
||||
country=item.country,
|
||||
country_code=item.country.lower(),
|
||||
district=item.state,
|
||||
subdistrict="",
|
||||
)
|
||||
for item in result
|
||||
]
|
||||
|
||||
async def get_day(self, location_id: str, date: datetime.date) -> WeatherResponse:
|
||||
data: Forecast = await self._get_location_forecast(location_id)
|
||||
|
||||
@@ -67,6 +67,15 @@ class Forecast(Model):
|
||||
list: list[ForecastItem]
|
||||
|
||||
|
||||
class Location(Model):
|
||||
name: str
|
||||
local_names: dict[str, str]
|
||||
lat: float
|
||||
lon: float
|
||||
country: str
|
||||
state: str
|
||||
|
||||
|
||||
class OpenWeather:
|
||||
BASE_URL = "https://api.openweathermap.org"
|
||||
|
||||
@@ -78,4 +87,10 @@ class OpenWeather:
|
||||
endpoint = f"data/2.5/forecast?lat={lat}&lon={lon}&appid={self._api_key}&units=metric"
|
||||
response = await self._source.request(endpoint)
|
||||
response_data = json.loads(response)
|
||||
return Forecast(**response_data)
|
||||
return Forecast.model_validate(response_data)
|
||||
|
||||
async def get_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]
|
||||
|
||||
@@ -20,7 +20,7 @@ class ApiBundle:
|
||||
if isinstance(value, api_type):
|
||||
if provider is None or provider == value.provider:
|
||||
return value
|
||||
raise ValueError(api_type)
|
||||
raise ValueError(api_type, provider)
|
||||
|
||||
def get_weather(self, provider: str | None = None) -> WeatherApi:
|
||||
return self.get_api(WeatherApi, provider)
|
||||
|
||||
Reference in New Issue
Block a user