From fa0edca52259b5372e7b188d07a108f253b25046 Mon Sep 17 00:00:00 2001 From: shmyga Date: Tue, 8 Sep 2026 23:22:05 +0300 Subject: [PATCH] refactor(easel): remove TagUtil --- gallery/easel/depends/date.py | 25 +++++ gallery/easel/route/api/schedule.py | 6 +- gallery/easel/route/api/weather.py | 6 +- gallery/easel/route/view/common/utils/tag.py | 59 ---------- .../easel/route/view/common/utils/template.py | 3 - gallery/easel/route/view/depends/tag.py | 18 --- gallery/easel/route/view/schedule/route.py | 18 ++- gallery/easel/route/view/weather/__init__.py | 103 +----------------- gallery/easel/route/view/weather/route.py | 81 ++++++++++++++ .../route/view/weather/templates/weather.html | 15 ++- gallery/painting/rp5/api.py | 2 + 11 files changed, 131 insertions(+), 205 deletions(-) create mode 100644 gallery/easel/depends/date.py delete mode 100644 gallery/easel/route/view/common/utils/tag.py delete mode 100644 gallery/easel/route/view/depends/tag.py create mode 100644 gallery/easel/route/view/weather/route.py diff --git a/gallery/easel/depends/date.py b/gallery/easel/depends/date.py new file mode 100644 index 0000000..d05e6de --- /dev/null +++ b/gallery/easel/depends/date.py @@ -0,0 +1,25 @@ +import datetime +from typing import Annotated + +from fastapi import Depends, HTTPException, status + + +def date_parser(date: str) -> datetime.date: + if date == "today": + return datetime.date.today() + elif date == "tomorrow": + return datetime.date.today() + datetime.timedelta(days=1) + elif date.startswith("day-"): + days = int(date.split("-")[-1]) + return datetime.date.today() + datetime.timedelta(days=days) + else: + try: + return datetime.date.strptime(date, "%Y-%m-%d") + except ValueError as exc: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail={"date": f"{exc}"}, + ) from exc + + +DateDepends = Annotated[datetime.date, Depends(date_parser)] diff --git a/gallery/easel/route/api/schedule.py b/gallery/easel/route/api/schedule.py index a0dc8b2..93f3af9 100644 --- a/gallery/easel/route/api/schedule.py +++ b/gallery/easel/route/api/schedule.py @@ -1,5 +1,3 @@ -import datetime - from fastapi import APIRouter from gallery.easel.core import AppRequest @@ -7,6 +5,8 @@ from gallery.easel.depends.schedule import ScheduleApiDepends from gallery.sketch.schedule.api import ScheduleApi from gallery.sketch.schedule.model import Channel, Schedule +from ...depends.date import DateDepends + router = APIRouter(prefix="/schedule", tags=["Schedule"]) @@ -22,6 +22,6 @@ async def find_api_schedule_channels(schedule_api: ScheduleApiDepends, query: st @router.get("/{provider}/{channel}/{date}") async def get_api_schedule_channel_schedule( - schedule_api: ScheduleApiDepends, channel: str, date: datetime.date + schedule_api: ScheduleApiDepends, channel: str, date: DateDepends ) -> Schedule: return await schedule_api.get_schedule(channel, date) diff --git a/gallery/easel/route/api/weather.py b/gallery/easel/route/api/weather.py index b00095b..d050180 100644 --- a/gallery/easel/route/api/weather.py +++ b/gallery/easel/route/api/weather.py @@ -1,5 +1,3 @@ -import datetime - from fastapi import APIRouter from gallery.easel.core import AppRequest @@ -7,6 +5,8 @@ from gallery.easel.depends.weather import WeatherApiDepends from gallery.sketch.weather.api import WeatherApi from gallery.sketch.weather.model import Location, WeatherResponse +from ...depends.date import DateDepends + router = APIRouter(prefix="/weather", tags=["Weather"]) @@ -21,7 +21,7 @@ async def find_api_weather_locations(weather_api: WeatherApiDepends, query: str) @router.get("/{provider}/{location}/day/{date}") -async def get_api_weather_day(weather_api: WeatherApiDepends, location: str, date: datetime.date) -> WeatherResponse: +async def get_api_weather_day(weather_api: WeatherApiDepends, location: str, date: DateDepends) -> WeatherResponse: return await weather_api.get_day(location, date) diff --git a/gallery/easel/route/view/common/utils/tag.py b/gallery/easel/route/view/common/utils/tag.py deleted file mode 100644 index 1de7d95..0000000 --- a/gallery/easel/route/view/common/utils/tag.py +++ /dev/null @@ -1,59 +0,0 @@ -import datetime -from enum import Enum -from typing import NamedTuple - - -class TagType(str, Enum): - DAY = "day" - DAYS = "days" - - -class Tag(NamedTuple): - type: TagType - date: datetime.date - days: int = 1 - - def __str__(self) -> str: - if self.type == TagType.DAY: - today = datetime.date.today() - day = (self.date - today).days - return f"day-{day}" - elif self.type == TagType.DAYS: - return f"days-{self.days}" - else: - raise ValueError(self.type) - - -class TagUtil: - @classmethod - def parse_tag(cls, tag: str) -> Tag: - if tag == "today": - return Tag(TagType.DAY, datetime.date.today()) - elif tag == "tomorrow": - return Tag(TagType.DAY, datetime.date.today() + datetime.timedelta(days=1)) - elif tag.startswith("day-"): - days = int(tag.split("-")[-1]) - return Tag( - TagType.DAY, - datetime.date.today() + datetime.timedelta(days=days), - ) - elif tag.startswith("days-"): - days = int(tag.split("-")[-1]) - return Tag(TagType.DAYS, datetime.date.today(), days) - else: - date = datetime.date.strptime(tag, "%Y-%m-%d") - return cls.create_tag(TagType.DAY, date) - - @classmethod - def create_tag( - cls, - tag_type: TagType, - date: datetime.date, - day: int = 0, - days: int = 10, - ) -> Tag: - if tag_type == TagType.DAY: - return Tag(tag_type, date + datetime.timedelta(days=day)) - if tag_type == TagType.DAYS: - return Tag(tag_type, date, days) - raise ValueError(tag_type) diff --git a/gallery/easel/route/view/common/utils/template.py b/gallery/easel/route/view/common/utils/template.py index 35eeda9..d8ea894 100644 --- a/gallery/easel/route/view/common/utils/template.py +++ b/gallery/easel/route/view/common/utils/template.py @@ -11,7 +11,6 @@ from gallery.easel.core import AppRequest from gallery.version import __version__ from ...translation import _ -from .tag import TagUtil ContextProcessor = typing.Callable[[AppRequest], dict[str, typing.Any]] @@ -32,7 +31,6 @@ def base_context_processor(request: Request) -> dict: def build_url( request: Request, *, path: list[str] | None = None, up: int | None = None, query: dict | None = None ) -> str: - print("!", path) parts = urlsplit(str(request.url)) if up: path = [".."] * up @@ -66,7 +64,6 @@ def build_templates( "version": __version__, "format_date": format_date, "datetime": datetime, - "tag_util": TagUtil, "DATE_FORMAT": "E, d MMMM Y", "build_url": build_url, } diff --git a/gallery/easel/route/view/depends/tag.py b/gallery/easel/route/view/depends/tag.py deleted file mode 100644 index e5e7c8f..0000000 --- a/gallery/easel/route/view/depends/tag.py +++ /dev/null @@ -1,18 +0,0 @@ -import datetime -from typing import Annotated - -from fastapi import Depends - -from ..common.utils.tag import Tag, TagType, TagUtil - - -def _resolve_tag(date: datetime.date | None = None, tag: str | None = None) -> Tag: - if tag: - result = TagUtil.parse_tag(tag) - else: - date = date or datetime.date.today() - result = TagUtil.create_tag(TagType.DAY, date) - return result - - -TagDepends = Annotated[Tag, Depends(_resolve_tag)] diff --git a/gallery/easel/route/view/schedule/route.py b/gallery/easel/route/view/schedule/route.py index 0452ace..27f8ba3 100644 --- a/gallery/easel/route/view/schedule/route.py +++ b/gallery/easel/route/view/schedule/route.py @@ -7,14 +7,13 @@ from fastapi.responses import HTMLResponse, RedirectResponse from gallery.easel.core import AppRequest from gallery.easel.depends.api import api_resolver +from gallery.easel.depends.date import DateDepends from gallery.easel.depends.schedule import ScheduleApiDepends from gallery.sketch.bundle import ApiBundle from gallery.sketch.schedule.api import ScheduleApi from gallery.sketch.schedule.model import Schedule -from ..common.utils.tag import TagType from ..common.utils.template import build_templates -from ..depends.tag import TagDepends from .bundle import GROUP_MAP from .filters import timedelta_format @@ -61,10 +60,10 @@ async def get_group_default(group_id: str): return RedirectResponse(f"{parse.quote(group_id)}/today") -@router.get("/group/{group_id}/{tag}", response_class=HTMLResponse) -async def get_group(request: AppRequest, group_id: str, tag: TagDepends): +@router.get("/group/{group_id}/{date}", response_class=HTMLResponse) +async def get_group(request: AppRequest, group_id: str, date: DateDepends): channels = GROUP_MAP.get(group_id, [tuple(item.split(":")) for item in group_id.split(",")]) - responses = await _get_group_schedule(request.app.state.api, channels, tag.date) + responses = await _get_group_schedule(request.app.state.api, channels, date) return templates.TemplateResponse( request=request, name="group.html", @@ -77,12 +76,9 @@ async def get_channel_default(channel: str): return RedirectResponse(f"{channel}/today") -@router.get("/{provider}/{channel}/{tag}", response_class=HTMLResponse) -async def get_channel_tag(request: AppRequest, schedule_api: ScheduleApiDepends, channel: str, tag: TagDepends): - if tag.type == TagType.DAY: - response = await schedule_api.get_schedule(channel, tag.date) - else: - raise ValueError(tag) +@router.get("/{provider}/{channel}/{date}", response_class=HTMLResponse) +async def get_channel(request: AppRequest, schedule_api: ScheduleApiDepends, channel: str, date: DateDepends): + response = await schedule_api.get_schedule(channel, date) return templates.TemplateResponse( request=request, name="channel.html", diff --git a/gallery/easel/route/view/weather/__init__.py b/gallery/easel/route/view/weather/__init__.py index e35235d..2b6f557 100644 --- a/gallery/easel/route/view/weather/__init__.py +++ b/gallery/easel/route/view/weather/__init__.py @@ -1,102 +1 @@ -import datetime -from pathlib import Path - -from fastapi import APIRouter -from fastapi.responses import HTMLResponse, RedirectResponse - -from gallery.easel.core import AppRequest -from gallery.easel.depends.api import api_resolver -from gallery.easel.depends.weather import WeatherApiDepends -from gallery.sketch.weather.api import WeatherApi -from gallery.sketch.weather.model import WeatherResponse - -from ..common.utils.tag import TagType, TagUtil -from ..common.utils.template import build_templates -from .filters import cloudness_icon, weather_icon_svg, wind_direction_icon - - -def context_procesor(request: AppRequest) -> dict: - return { - "providers": request.app.state.api.get_api_providers(WeatherApi), - } - - -templates = build_templates( - Path(__file__).parent / "templates", - { - "wind_direction_icon": wind_direction_icon, - "cloudness_icon": cloudness_icon, - "weather_icon_svg": weather_icon_svg, - }, - context_procesor, -) - - -def build_weather_response(request: AppRequest, response: WeatherResponse): - return templates.TemplateResponse( - request=request, - name="weather.html", - context={ - "response": response, - }, - ) - - -router = APIRouter(prefix="/weather") - - -@router.get("/", response_class=HTMLResponse) -async def get_weather_index(request: AppRequest, provider: str | None = None, query: str | None = None): - if query and provider: - weather_api = api_resolver(WeatherApi)(request, provider) - locations = await weather_api.find_locations(query) - else: - locations = [] - return templates.TemplateResponse( - request=request, - name="index.html", - context={ - "locations": locations, - }, - ) - - -@router.get("/{provider}/{location}", response_class=RedirectResponse) -async def get_weather(location: str): - return RedirectResponse(f"{location}/tag/today") - - -@router.get("/{provider}/{location}/day/{date}", response_class=HTMLResponse) -async def get_weather_day( - request: AppRequest, - weather_api: WeatherApiDepends, - location: str, - date: datetime.date, -): - response = await weather_api.get_day(location, date) - return build_weather_response(request, response) - - -@router.get("/{provider}/{location}/days/{days}", response_class=HTMLResponse) -async def get_weather_days(request: AppRequest, weather_api: WeatherApiDepends, location: str, days: int): - response = await weather_api.get_days(location, days) - return build_weather_response(request, response) - - -@router.get("/{provider}/{location}/tag/{tag}", response_class=HTMLResponse) -async def get_weather_tag(request: AppRequest, weather_api: WeatherApiDepends, location: str, tag: str): - tag_value = TagUtil.parse_tag(tag) - if tag_value.type == TagType.DAY: - response = await weather_api.get_day(location, tag_value.date) - elif tag_value.type == TagType.DAYS: - response = await weather_api.get_days(location, tag_value.days) - else: - raise ValueError(tag) - return build_weather_response(request, response) - - -@router.get("/zmiyevka-184640/tag/{tag}", response_class=RedirectResponse) -async def get_zmiyevka_weather_compat(request: AppRequest): - path_parts = request.url.path.split("/") - path_parts.insert(2, "gismeteo") - return RedirectResponse("/".join(path_parts)) +from .route import router diff --git a/gallery/easel/route/view/weather/route.py b/gallery/easel/route/view/weather/route.py new file mode 100644 index 0000000..0990be8 --- /dev/null +++ b/gallery/easel/route/view/weather/route.py @@ -0,0 +1,81 @@ +from pathlib import Path +from typing import Literal + +from fastapi import APIRouter +from fastapi.responses import HTMLResponse, RedirectResponse + +from gallery.easel.core import AppRequest +from gallery.easel.depends.api import api_resolver +from gallery.easel.depends.date import DateDepends +from gallery.easel.depends.weather import WeatherApiDepends +from gallery.sketch.weather.api import WeatherApi +from gallery.sketch.weather.model import WeatherResponse + +from ..common.utils.template import build_templates +from .filters import cloudness_icon, weather_icon_svg, wind_direction_icon + + +def context_procesor(request: AppRequest) -> dict: + return { + "providers": request.app.state.api.get_api_providers(WeatherApi), + } + + +templates = build_templates( + Path(__file__).parent / "templates", + { + "wind_direction_icon": wind_direction_icon, + "cloudness_icon": cloudness_icon, + "weather_icon_svg": weather_icon_svg, + }, + context_procesor, +) + + +def build_weather_response(request: AppRequest, response: WeatherResponse): + return templates.TemplateResponse( + request=request, + name="weather.html", + context={"response": response}, + ) + + +router = APIRouter(prefix="/weather") + + +@router.get("/", response_class=HTMLResponse) +async def get_weather_index(request: AppRequest, provider: str | None = None, query: str | None = None): + if query and provider: + weather_api = api_resolver(WeatherApi)(request, provider) + locations = await weather_api.find_locations(query) + else: + locations = [] + return templates.TemplateResponse( + request=request, + name="index.html", + context={"locations": locations}, + ) + + +@router.get("/{provider}/{location}", response_class=RedirectResponse) +async def get_weather(location: str): + return RedirectResponse(f"{location}/day/today") + + +@router.get("/{provider}/{location}/days/{days}", response_class=HTMLResponse) +async def get_weather_days(request: AppRequest, weather_api: WeatherApiDepends, location: str, days: int): + response = await weather_api.get_days(location, days) + return build_weather_response(request, response) + + +# TODO: tag mode route deprecated, used in tizen app +@router.get("/{provider}/{location}/{mode:str}/{date}", response_class=HTMLResponse) +async def get_weather_day( + request: AppRequest, + weather_api: WeatherApiDepends, + location: str, + date: DateDepends, + mode: Literal["day", "tag"], # pylint:disable=unused-argument +): + response = await weather_api.get_day(location, date) + return build_weather_response(request, response) diff --git a/gallery/easel/route/view/weather/templates/weather.html b/gallery/easel/route/view/weather/templates/weather.html index 57f9e31..b7d8db5 100644 --- a/gallery/easel/route/view/weather/templates/weather.html +++ b/gallery/easel/route/view/weather/templates/weather.html @@ -1,5 +1,8 @@ {% extends "base.html" %} -{% block title %}{{_("Weather")}} | {{response.location}} | {{response.date.strftime('%a, %d %B %Y')}}{% endblock %} +{% set date = response.date %} + +{% block title %}{{_("Weather")}} | {{response.location}} | {{format_date(response.date, DATE_FORMAT, +locale=request.state.language)}}{% endblock %} {% block header %} {% if response.period == 'day' %} - + + href="{{build_url(request, path=['..', '..', 'days', '10'])}}"> {{provider}} {{response.location}} | {{format_date(response.date, DATE_FORMAT, locale=request.state.language)}} + href="{{build_url(request, path=['..', date + datetime.timedelta(days=1)])}}"> {% endif %} @@ -47,7 +50,7 @@ {% if response.period == 'days' %} - + {{format_date(value.date, 'E d', locale=request.state.language)}} diff --git a/gallery/painting/rp5/api.py b/gallery/painting/rp5/api.py index 1403445..8bcf809 100644 --- a/gallery/painting/rp5/api.py +++ b/gallery/painting/rp5/api.py @@ -71,6 +71,8 @@ class RP5WeatherApi(WeatherApi): for response in forecast: if response.date == date: return response + if date == datetime.date.today(): + return WeatherResponse(location=location_id, date=date, period="day", values=[]) raise ValueError(f"Unsupported date: {date}") async def get_days(self, location_id: str, days: int) -> WeatherResponse: