feat(weather): add openweather api
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
from typing import TypeVar
|
||||
|
||||
|
||||
class Api:
|
||||
PROVIDER: str
|
||||
|
||||
@property
|
||||
def provider(self) -> str:
|
||||
return self.PROVIDER
|
||||
|
||||
|
||||
API = TypeVar("API", bound=Api)
|
||||
|
||||
30
gallery/sketch/bundle.py
Normal file
30
gallery/sketch/bundle.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from typing import Type
|
||||
|
||||
from .api import API, Api
|
||||
from .schedule.api import ScheduleApi
|
||||
from .weather.api import WeatherApi
|
||||
|
||||
|
||||
class ApiBundle(list[Api]):
|
||||
def __init__(self, values: list[Api]) -> None:
|
||||
super().__init__(values)
|
||||
|
||||
def get_api_by_provider(self, provider: str) -> Api:
|
||||
for value in self:
|
||||
if value.PROVIDER == provider:
|
||||
return value
|
||||
raise ValueError(provider)
|
||||
|
||||
def get_api_by_type(self, api_type: Type[API]) -> API:
|
||||
for value in self:
|
||||
if isinstance(value, api_type):
|
||||
return value
|
||||
raise ValueError(api_type)
|
||||
|
||||
@property
|
||||
def weather(self) -> WeatherApi:
|
||||
return self.get_api_by_type(WeatherApi)
|
||||
|
||||
@property
|
||||
def schedule(self) -> ScheduleApi:
|
||||
return self.get_api_by_type(ScheduleApi)
|
||||
@@ -1,10 +1,8 @@
|
||||
from typing import Generic, TypeVar
|
||||
from typing import Generic
|
||||
|
||||
from gallery.util import TimeUnit
|
||||
|
||||
from .api import Api
|
||||
|
||||
API = TypeVar("API", bound=Api)
|
||||
from .api import API, Api
|
||||
|
||||
|
||||
class CachedApi(Api, Generic[API]):
|
||||
|
||||
@@ -7,5 +7,8 @@ class CatalogBundle(Generic[T]):
|
||||
def __init__(self, items: list[T]) -> None:
|
||||
self._items_by_id = {item.id: item for item in items}
|
||||
|
||||
def get_item(self, item_id: str) -> T:
|
||||
return self._items_by_id[item_id]
|
||||
|
||||
def select_items(self, ids: list[str]) -> list[T]:
|
||||
return [self._items_by_id[id_] for id_ in ids]
|
||||
|
||||
@@ -6,9 +6,12 @@ class MockData:
|
||||
def __init__(self, data_dir) -> None:
|
||||
self._data_dir = data_dir
|
||||
|
||||
def get_text(self, key: str) -> str:
|
||||
return (self._data_dir / f"{key}").read_text()
|
||||
|
||||
def get_html(self, key: str) -> str:
|
||||
return (self._data_dir / f"{key}.html").read_text()
|
||||
return self.get_text(f"{key}.html")
|
||||
|
||||
def get_json(self, key: str) -> dict:
|
||||
data = json.loads((self._data_dir / f"{key}.json").read_text())
|
||||
data = json.loads(self.get_text(f"{key}.json"))
|
||||
return data
|
||||
|
||||
@@ -15,7 +15,17 @@ class LocationId(str, Enum):
|
||||
|
||||
BUNDLE = CatalogBundle(
|
||||
[
|
||||
Location(id=LocationId.OREL, name="Орёл"),
|
||||
Location(id=LocationId.ZMIYEVKA, name="Змиёвка"),
|
||||
Location(
|
||||
id=LocationId.OREL,
|
||||
name="Орёл",
|
||||
lat=52.9687747,
|
||||
lon=36.0694937,
|
||||
),
|
||||
Location(
|
||||
id=LocationId.ZMIYEVKA,
|
||||
name="Змиёвка",
|
||||
lat=52.672192,
|
||||
lon=36.380112,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -12,6 +12,8 @@ class Model(BaseModel):
|
||||
class Location(Model):
|
||||
id: str
|
||||
name: str
|
||||
lat: float
|
||||
lon: float
|
||||
|
||||
|
||||
class Cloudness(str, Enum):
|
||||
@@ -38,22 +40,69 @@ class Sky(Model):
|
||||
class WindDirection(str, Enum):
|
||||
CALM = "calm"
|
||||
N = "N"
|
||||
NO = "NO"
|
||||
O = "O"
|
||||
SO = "SO"
|
||||
NE = "NE"
|
||||
E = "E"
|
||||
SE = "SE"
|
||||
S = "S"
|
||||
SW = "SW"
|
||||
W = "W"
|
||||
NW = "NW"
|
||||
|
||||
|
||||
class WindDirectionDeg(float):
|
||||
@property
|
||||
def direction(self) -> WindDirection:
|
||||
return self.to_direction()
|
||||
|
||||
@property
|
||||
def value(self) -> float:
|
||||
return self
|
||||
|
||||
# pylint:disable=too-many-return-statements
|
||||
def to_direction(self) -> WindDirection:
|
||||
if self > 337.5 or self <= 22.25:
|
||||
return WindDirection.N
|
||||
elif self <= 67.5:
|
||||
return WindDirection.NE
|
||||
elif self <= 112.5:
|
||||
return WindDirection.E
|
||||
elif self <= 157.5:
|
||||
return WindDirection.SE
|
||||
elif self <= 202.5:
|
||||
return WindDirection.S
|
||||
elif self <= 247.5:
|
||||
return WindDirection.SW
|
||||
elif self <= 292.5:
|
||||
return WindDirection.W
|
||||
elif self <= 337.5:
|
||||
return WindDirection.NW
|
||||
else:
|
||||
return WindDirection.CALM
|
||||
|
||||
@classmethod
|
||||
def from_direction(cls, direction: WindDirection) -> "WindDirectionDeg":
|
||||
return cls(
|
||||
{
|
||||
WindDirection.CALM: -1,
|
||||
WindDirection.N: 0,
|
||||
WindDirection.NE: 45,
|
||||
WindDirection.E: 90,
|
||||
WindDirection.SE: 135,
|
||||
WindDirection.S: 180,
|
||||
WindDirection.SW: 225,
|
||||
WindDirection.W: 270,
|
||||
WindDirection.NW: 315,
|
||||
}[direction]
|
||||
)
|
||||
|
||||
|
||||
class WeatherValue(Model):
|
||||
date: datetime.datetime
|
||||
sky: Sky
|
||||
temperature: list[int]
|
||||
wind_speed: int
|
||||
wind_gust: int
|
||||
wind_direction: WindDirection
|
||||
wind_direction: float
|
||||
precipitation: float
|
||||
pressure: list[int]
|
||||
humidity: int
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import datetime
|
||||
import statistics
|
||||
|
||||
from .model import Cloudness, Precipitation, Sky, WeatherValue, WindDirection
|
||||
from .model import Cloudness, Precipitation, Sky, WeatherValue, WindDirectionDeg
|
||||
|
||||
|
||||
def build_weather_value(date: datetime.datetime) -> WeatherValue:
|
||||
@@ -15,8 +16,49 @@ def build_weather_value(date: datetime.datetime) -> WeatherValue:
|
||||
temperature=[],
|
||||
wind_speed=0,
|
||||
wind_gust=0,
|
||||
wind_direction=WindDirection.CALM,
|
||||
wind_direction=WindDirectionDeg(-1),
|
||||
precipitation=0,
|
||||
pressure=[],
|
||||
humidity=0,
|
||||
)
|
||||
|
||||
|
||||
def merge_weather_values(
|
||||
date: datetime.datetime, values: list[WeatherValue]
|
||||
) -> WeatherValue:
|
||||
result = build_weather_value(date)
|
||||
temperatures = []
|
||||
pressures = []
|
||||
humidities = []
|
||||
wind_speeds = []
|
||||
wind_gusts = []
|
||||
wind_directions = []
|
||||
cloudnesses = []
|
||||
precipitations = []
|
||||
precipitation = 0
|
||||
for value in values:
|
||||
temperatures += value.temperature
|
||||
pressures += value.pressure
|
||||
humidities.append(value.humidity)
|
||||
wind_speeds.append(value.wind_speed)
|
||||
wind_gusts.append(value.wind_gust)
|
||||
wind_directions.append(value.wind_direction)
|
||||
cloudnesses.append(value.sky.cloudness)
|
||||
precipitations.append(value.sky.precipitation)
|
||||
precipitation += value.precipitation
|
||||
result.temperature = [max(temperatures), min(temperatures)]
|
||||
result.pressure = [max(pressures), min(pressures)]
|
||||
result.humidity = round(statistics.mean(humidities))
|
||||
result.wind_speed = round(statistics.mean(wind_speeds))
|
||||
result.wind_gust = round(statistics.mean(wind_gusts))
|
||||
result.wind_direction = statistics.mean(wind_directions)
|
||||
# TODO: merge cloudnesses
|
||||
for item in cloudnesses:
|
||||
if item != Cloudness.CLEAR:
|
||||
result.sky.cloudness = item
|
||||
# TODO: merge precipitations
|
||||
for item in precipitations:
|
||||
if item != Precipitation.NO:
|
||||
result.sky.precipitation = item
|
||||
result.precipitation = precipitation
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user