feat(yandexweather): add yandexweather api

This commit is contained in:
2026-08-28 10:01:49 +03:00
parent a92b63b855
commit 50c4221b8d
33 changed files with 506 additions and 74 deletions
+16 -1
View File
@@ -1,10 +1,19 @@
from typing import TypeVar
from .source import ApiSource
class Api:
S = TypeVar("S", bound=ApiSource)
class Api[S = ApiSource]:
TYPE: str
PROVIDER: str
_source: S | None = None
def _build_source(self) -> S:
raise NotImplementedError
@property
def type(self) -> str:
return self.TYPE
@@ -13,5 +22,11 @@ class Api:
def provider(self) -> str:
return self.PROVIDER
@property
def source(self) -> S:
if self._source is None:
self._source = self._build_source()
return self._source
API = TypeVar("API", bound=Api)
+4
View File
@@ -1,5 +1,6 @@
from typing import Generic, NamedTuple
from gallery.sketch.source import ApiSource
from gallery.util import TimeUnit
from .api import API, Api
@@ -19,6 +20,9 @@ class CachedApi(Api, Generic[API]):
def __init__(self, api: API):
self._api = api
def _build_source(self) -> ApiSource:
raise RuntimeError("Source not used in CachedApi")
@property
def provider(self) -> str:
return self._api.provider
+2 -1
View File
@@ -1,10 +1,11 @@
import datetime
from ..api import Api
from ..source import ApiSource
from .model import Channel, Schedule
class ScheduleApi(Api):
class ScheduleApi[S = ApiSource](Api[S]):
TYPE = "schedule"
async def find_channels(self, query: str) -> list[Channel]:
+25 -6
View File
@@ -1,9 +1,15 @@
import logging
from typing import Literal
import aiohttp
from playwright.async_api import async_playwright
logger = logging.getLogger("source")
RequestMode = Literal["base", "browser"]
DEFAULT_REQUEST_MODE: RequestMode = "base"
class ApiSource:
DEFAULT_USER_AGENT = (
@@ -25,12 +31,7 @@ class ApiSource:
self._cookies = cookies
self._headers = headers
async def request(self, endpoint: str) -> str:
if endpoint.startswith("https:"):
url = endpoint
else:
url = f"{self._base_url}/{endpoint}"
logger.info(url)
async def _base_request(self, url: str) -> str:
headers = {"User-Agent": self._user_agent, **(self._headers or {})}
async with aiohttp.ClientSession(
headers=headers,
@@ -39,3 +40,21 @@ class ApiSource:
) as session:
async with session.request("GET", url, timeout=self._timeout) as response:
return await response.text()
async def _browser_request(self, url: str) -> str:
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
await page.goto(url, wait_until="networkidle")
content = await page.content()
await browser.close()
return content
async def request(self, endpoint: str, /, mode: RequestMode = DEFAULT_REQUEST_MODE) -> str:
if endpoint.startswith("https:"):
url = endpoint
else:
url = f"{self._base_url}/{endpoint}"
logger.info(url)
method = getattr(self, f"_{mode}_request")
return await method(url)
+2 -1
View File
@@ -1,10 +1,11 @@
import datetime
from ..api import Api
from ..source import ApiSource
from .model import Location, WeatherResponse
class WeatherApi(Api):
class WeatherApi[S = ApiSource](Api[S]):
TYPE = "weather"
async def find_locations(self, query: str) -> list[Location]:
+24
View File
@@ -0,0 +1,24 @@
from gallery.sketch.weather.model import WindDirection, WindDirectionDeg
class ParseError(Exception):
pass
WIND_DIRECTION_MAP: dict[str, WindDirection] = {
"—": WindDirection.CALM,
"": WindDirection.CALM,
"штиль": WindDirection.CALM,
"с": WindDirection.N,
"св": WindDirection.NE,
"в": WindDirection.E,
"юв": WindDirection.SE,
"ю": WindDirection.S,
"юз": WindDirection.SW,
"з": WindDirection.W,
"сз": WindDirection.NW,
}
def parse_wind_direction(value: str) -> WindDirectionDeg:
return WindDirectionDeg.from_direction(WIND_DIRECTION_MAP[value.lower()])
+1 -1
View File
@@ -19,7 +19,7 @@ def build_weather_value(date: datetime.datetime) -> WeatherValue:
wind_direction=WindDirectionDeg(-1),
precipitation=0,
pressure=[],
humidity=0,
humidity=-1,
)