feat(yandexweather): add yandexweather api
This commit is contained in:
+16
-1
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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()])
|
||||
@@ -19,7 +19,7 @@ def build_weather_value(date: datetime.datetime) -> WeatherValue:
|
||||
wind_direction=WindDirectionDeg(-1),
|
||||
precipitation=0,
|
||||
pressure=[],
|
||||
humidity=0,
|
||||
humidity=-1,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user