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