Source code for todays_paper_web.widgets.weather.core

from datetime import datetime, timedelta
from enum import Enum
from pathlib import Path, PurePath
from typing import Union

from PIL import Image
from pydantic import BaseModel, Field

from todays_paper_web.widgets import registerView
from todays_paper_web.widgets.core.image import ImageBaseView, ImageParams

BASE_PATH = Path(__file__).parent.resolve()


class WeatherType(Enum):
    Unknown = 0
    ClearSky = 1
    FewClouds = 2
    ScatteredClouds = 3
    BrokenClouds = 4
    ShowerRain = 9
    Rain = 10
    Thunderstorm = 11
    Snow = 13
    Mist = 50


class TimeOfDay(Enum):
    Day = "d"
    Night = "n"


class WeatherIcon(BaseModel):
    weather: WeatherType
    timeOfDay: TimeOfDay

    def id(self) -> str:
        return f"{self.weather.value:02d}{self.timeOfDay.value}"


class WeatherForecast(BaseModel):
    timestamp: datetime
    icon: WeatherIcon
    airTemperature: Union[float, None]
    precipitation: Union[float, None]
    relativeHumidity: Union[float, None]
    windGustSpeed: Union[float, None]
    windSpeed: Union[float, None]


class WeatherForecastModel:
    def __init__(self, forecasts: list[WeatherForecast]):
        self._forecasts = forecasts

    def nextHour(self, timestamp: datetime, hour: int) -> Union[WeatherForecast, None]:
        target = timestamp.replace(hour=hour)
        if target < timestamp:
            target += timedelta(days=1)

        best = None
        delta = timedelta.max
        for forecast in self._forecasts:
            newDelta = abs(forecast.timestamp - target)
            if newDelta < delta:
                best = forecast
                delta = newDelta
            if delta == timedelta():
                # No need to search longer, found an exact match
                break
        # Return the forecast adjusted to the correct timezone
        return best.copy(
            update={"timestamp": best.timestamp.astimezone(timestamp.tzinfo)}
        )


[docs] class WeatherIconParams(ImageParams): icon: str = Field( ..., description='The icon id, e.g. `"01d"` - see `WeatherIcon.id()` for how it\'s built.' ) theme: str = Field( "openweather", description="Which bundled icon set to use: `openweather` or `erikflower`." )
@registerView("weather.icon") class WeatherIconView(ImageBaseView): Params = WeatherIconParams def fetch_img(self): symbol_path = PurePath( BASE_PATH, "img", self.params.theme, self.params.icon ).with_suffix(".png") if Path(symbol_path).exists(): return Image.open(str(symbol_path)) return None