diff --git a/Dockerfile b/Dockerfile index aa74792..da1fd7c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,7 @@ RUN apt update && \ dpkg-reconfigure --frontend=noninteractive locales ENV LANG=ru_RU.UTF-8 ENV LC_ALL=ru_RU.UTF-8 +ENV TZ="Europe/Moscow" COPY --from=builder /app ./ COPY gismeteo gismeteo/ COPY weather weather/ diff --git a/gismeteo/location.py b/gismeteo/location.py index ad77c70..f314160 100644 --- a/gismeteo/location.py +++ b/gismeteo/location.py @@ -10,7 +10,7 @@ class LocationValue(NamedTuple): @classmethod def parse(cls, source: str) -> "LocationValue": - location, name = source.split("-") + name, location = source.split("-") return cls(int(location), name) diff --git a/gismeteo/parser.py b/gismeteo/parser.py index 1622d7d..9e44133 100644 --- a/gismeteo/parser.py +++ b/gismeteo/parser.py @@ -5,6 +5,8 @@ from typing import Dict, Iterable, List, Optional import dateparser from bs4 import Tag +from weather.model import Cloudness, Precipitation, Sky, WindDirection + from .core import BaseWidgetParser, RowParser ONE_DAY_PARSER = BaseWidgetParser(".widget.widget-oneday .widget-items") @@ -40,12 +42,45 @@ class DateParser(RowParser[datetime.datetime]): yield time -class CloudnessParser(RowParser[str]): - KEY = "cloudness" +class SkyParser(RowParser[Sky]): + KEY = "sky" - def parse_row(self, tag: Tag) -> Iterable[str]: + CLOUDNESS_MAP: Dict[str, Cloudness] = { + "ясно": Cloudness.CLEAR, + "малооблачно": Cloudness.PARTLY_CLOUDY, + "облачно": Cloudness.CLOUDY, + "пасмурно": Cloudness.MAINLY_CLOUDY, + } + + PRECIPITATION_MAP: Dict[str, Precipitation] = { + "без осадков": Precipitation.NO, + "небольшой дождь": Precipitation.SMALL_RAIN, + "дождь": Precipitation.RAIN, + "ливень": Precipitation.SHOWER, + } + + def parse_row(self, tag: Tag) -> Iterable[Sky]: for item in tag.select(".widget-row[data-row=icon-tooltip] > .row-item"): - yield item.attrs["data-tooltip"] + sky_str = item.attrs["data-tooltip"] + values = {item.strip().lower() for item in sky_str.split(",")} + cloudness = Cloudness.CLEAR + precipitation = Precipitation.NO + thunder = "гроза" in values + fog = "дымка" in values + for k, v in self.CLOUDNESS_MAP.items(): + if k in values: + cloudness = v + break + for k, v in self.PRECIPITATION_MAP.items(): + if k in values: + precipitation = v + break + yield Sky( + cloudness=cloudness, + precipitation=precipitation, + thunder=thunder, + fog=fog, + ) class TemperatureParser(RowParser[int]): @@ -77,14 +112,27 @@ class WindGustParser(RowParser[int]): yield int(value.attrs["value"]) if value else 0 -class WindDirectionParser(RowParser[str]): +class WindDirectionParser(RowParser[WindDirection]): KEY = "wind_direction" - def parse_row(self, tag: Tag) -> Iterable[str]: + WIND_DIRECTION_MAP: Dict[str, WindDirection] = { + "штиль": WindDirection.CALM, + "с": WindDirection.N, + "св": WindDirection.NO, + "в": WindDirection.O, + "юв": WindDirection.SO, + "ю": WindDirection.S, + "юз": WindDirection.SW, + "з": WindDirection.W, + "сз": WindDirection.NW, + } + + def parse_row(self, tag: Tag) -> Iterable[WindDirection]: for item in tag.select( ".widget-row[data-row=wind-direction] > .row-item > .direction" ): - yield item.text + wind_direction_str = item.text.lower() + yield self.WIND_DIRECTION_MAP[wind_direction_str] class WindPrecipitationParser(RowParser[float]): @@ -117,7 +165,7 @@ class HumidityParser(RowParser[int]): ROW_PARSERS: List[RowParser] = [ DateParser(), - CloudnessParser(), + SkyParser(), TemperatureParser(), WindSpeedParser(), WindGustParser(), diff --git a/weather/model.py b/weather/model.py index 4476be4..ababc40 100644 --- a/weather/model.py +++ b/weather/model.py @@ -1,21 +1,60 @@ import datetime +from enum import Enum from pydantic import BaseModel -class WeatherValue(BaseModel): +class Model(BaseModel): + class Config: + use_enum_values = True + + +class Cloudness(str, Enum): + CLEAR = "clear" + PARTLY_CLOUDY = "party_cloudy" + CLOUDY = "cloudy" + MAINLY_CLOUDY = "mainly_cloudy" + + +class Precipitation(str, Enum): + NO = "no" + SMALL_RAIN = "small_rain" + RAIN = "rain" + SHOWER = "shower" + + +class Sky(Model): + cloudness: Cloudness + precipitation: Precipitation + thunder: bool + fog: bool + + +class WindDirection(str, Enum): + CALM = "calm" + N = "N" + NO = "NO" + O = "O" + SO = "SO" + S = "S" + SW = "SW" + W = "W" + NW = "NW" + + +class WeatherValue(Model): date: datetime.datetime - cloudness: str + sky: Sky temperature: int wind_speed: int wind_gust: int - wind_direction: str + wind_direction: WindDirection precipitation: float pressure: int humidity: int -class WeatherResponse(BaseModel): +class WeatherResponse(Model): location: str date: datetime.date period: str diff --git a/weather/route/view/filters.py b/weather/route/view/filters.py index cc01ae5..33cacb0 100644 --- a/weather/route/view/filters.py +++ b/weather/route/view/filters.py @@ -1,38 +1,39 @@ -def wind_direction_icon(wind_direction: str) -> str: +from weather.model import Cloudness, Precipitation, Sky, WindDirection + + +def wind_direction_icon(wind_direction: WindDirection) -> str: return { - "С": "🡫", - "СВ": "🡯", - "В": "🡨", - "ЮВ": "🡬", - "Ю": "🡡", - "ЮЗ": "🡭", - "З": "🡪", - "СЗ": "🡦", - "штиль": "", + WindDirection.N: "🡫", + WindDirection.NO: "🡯", + WindDirection.O: "🡨", + WindDirection.SO: "🡬", + WindDirection.S: "🡡", + WindDirection.SW: "🡭", + WindDirection.W: "🡪", + WindDirection.NW: "🡦", + WindDirection.CALM: "", }.get(wind_direction, wind_direction) -def cloudness_icon(cloudness: str) -> list[str]: - icons = [] - values = {item.strip().lower() for item in cloudness.split(",")} - if "дымка" in values: +def cloudness_icon(sky: Sky) -> list[str]: + main_icon = "" + if sky.thunder: + if sky.cloudness == Cloudness.CLEAR: + main_icon = "🌩️" + if sky.precipitation == Precipitation.NO: + main_icon = "⚡" + else: + main_icon = "⛈️" + elif sky.precipitation == Precipitation.NO: + main_icon = { + Cloudness.CLEAR: "☀️", + Cloudness.PARTLY_CLOUDY: "🌤️", + Cloudness.CLOUDY: "⛅", + Cloudness.MAINLY_CLOUDY: "☁️", + }[sky.cloudness] + else: + main_icon = "🌧️" + icons = [main_icon] + if sky.fog: icons.append("🌫️") - values.remove("дымка") - icons = [ - { - frozenset(["ясно"]): "☀️", - frozenset(["малооблачно", "без осадков"]): "🌤️", - frozenset(["облачно", "без осадков"]): "⛅", - frozenset(["малооблачно", "небольшой дождь"]): "🌦️", - frozenset(["пасмурно", "без осадков"]): "☁️", - frozenset(["облачно", "небольшой дождь"]): "🌧️", - frozenset(["облачно", "дождь"]): "🌧️", - frozenset(["облачно", "небольшой дождь", "гроза"]): "⛈️", - frozenset(["малооблачно", "дождь"]): "🌦️", - frozenset(["пасмурно", "небольшой дождь"]): "🌧️", - frozenset(["облачно", "дождь", "гроза"]): "⛈️", - frozenset(["пасмурно", "дождь"]): "🌧️", - frozenset(["пасмурно", "дождь", "гроза"]): "⛈️", - }.get(frozenset(values), "|".join(values)) - ] + icons return icons diff --git a/weather/route/view/templates/weather.html b/weather/route/view/templates/weather.html index 558e215..d1a3513 100644 --- a/weather/route/view/templates/weather.html +++ b/weather/route/view/templates/weather.html @@ -44,7 +44,7 @@ {% for value in response.values %} - {% for icon in value.cloudness | cloudness_icon %} + {% for icon in value.sky | cloudness_icon %}
{{icon}}
{% endfor %} @@ -76,7 +76,6 @@ {% for value in response.values %} {{value.wind_direction | wind_direction_icon}} - {{value.wind_direction}} {% endfor %}