refactor(easel): remove TagUtil

This commit is contained in:
2026-09-08 23:22:05 +03:00
parent f76f33b501
commit fa0edca522
11 changed files with 131 additions and 205 deletions
+25
View File
@@ -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)]
+3 -3
View File
@@ -1,5 +1,3 @@
import datetime
from fastapi import APIRouter from fastapi import APIRouter
from gallery.easel.core import AppRequest 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.api import ScheduleApi
from gallery.sketch.schedule.model import Channel, Schedule from gallery.sketch.schedule.model import Channel, Schedule
from ...depends.date import DateDepends
router = APIRouter(prefix="/schedule", tags=["Schedule"]) 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}") @router.get("/{provider}/{channel}/{date}")
async def get_api_schedule_channel_schedule( async def get_api_schedule_channel_schedule(
schedule_api: ScheduleApiDepends, channel: str, date: datetime.date schedule_api: ScheduleApiDepends, channel: str, date: DateDepends
) -> Schedule: ) -> Schedule:
return await schedule_api.get_schedule(channel, date) return await schedule_api.get_schedule(channel, date)
+3 -3
View File
@@ -1,5 +1,3 @@
import datetime
from fastapi import APIRouter from fastapi import APIRouter
from gallery.easel.core import AppRequest 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.api import WeatherApi
from gallery.sketch.weather.model import Location, WeatherResponse from gallery.sketch.weather.model import Location, WeatherResponse
from ...depends.date import DateDepends
router = APIRouter(prefix="/weather", tags=["Weather"]) 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}") @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) return await weather_api.get_day(location, date)
@@ -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)
@@ -11,7 +11,6 @@ from gallery.easel.core import AppRequest
from gallery.version import __version__ from gallery.version import __version__
from ...translation import _ from ...translation import _
from .tag import TagUtil
ContextProcessor = typing.Callable[[AppRequest], dict[str, typing.Any]] ContextProcessor = typing.Callable[[AppRequest], dict[str, typing.Any]]
@@ -32,7 +31,6 @@ def base_context_processor(request: Request) -> dict:
def build_url( def build_url(
request: Request, *, path: list[str] | None = None, up: int | None = None, query: dict | None = None request: Request, *, path: list[str] | None = None, up: int | None = None, query: dict | None = None
) -> str: ) -> str:
print("!", path)
parts = urlsplit(str(request.url)) parts = urlsplit(str(request.url))
if up: if up:
path = [".."] * up path = [".."] * up
@@ -66,7 +64,6 @@ def build_templates(
"version": __version__, "version": __version__,
"format_date": format_date, "format_date": format_date,
"datetime": datetime, "datetime": datetime,
"tag_util": TagUtil,
"DATE_FORMAT": "E, d MMMM Y", "DATE_FORMAT": "E, d MMMM Y",
"build_url": build_url, "build_url": build_url,
} }
-18
View File
@@ -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)]
+7 -11
View File
@@ -7,14 +7,13 @@ from fastapi.responses import HTMLResponse, RedirectResponse
from gallery.easel.core import AppRequest from gallery.easel.core import AppRequest
from gallery.easel.depends.api import api_resolver from gallery.easel.depends.api import api_resolver
from gallery.easel.depends.date import DateDepends
from gallery.easel.depends.schedule import ScheduleApiDepends from gallery.easel.depends.schedule import ScheduleApiDepends
from gallery.sketch.bundle import ApiBundle from gallery.sketch.bundle import ApiBundle
from gallery.sketch.schedule.api import ScheduleApi from gallery.sketch.schedule.api import ScheduleApi
from gallery.sketch.schedule.model import Schedule from gallery.sketch.schedule.model import Schedule
from ..common.utils.tag import TagType
from ..common.utils.template import build_templates from ..common.utils.template import build_templates
from ..depends.tag import TagDepends
from .bundle import GROUP_MAP from .bundle import GROUP_MAP
from .filters import timedelta_format 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") return RedirectResponse(f"{parse.quote(group_id)}/today")
@router.get("/group/{group_id}/{tag}", response_class=HTMLResponse) @router.get("/group/{group_id}/{date}", response_class=HTMLResponse)
async def get_group(request: AppRequest, group_id: str, tag: TagDepends): 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(",")]) 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( return templates.TemplateResponse(
request=request, request=request,
name="group.html", name="group.html",
@@ -77,12 +76,9 @@ async def get_channel_default(channel: str):
return RedirectResponse(f"{channel}/today") return RedirectResponse(f"{channel}/today")
@router.get("/{provider}/{channel}/{tag}", response_class=HTMLResponse) @router.get("/{provider}/{channel}/{date}", response_class=HTMLResponse)
async def get_channel_tag(request: AppRequest, schedule_api: ScheduleApiDepends, channel: str, tag: TagDepends): async def get_channel(request: AppRequest, schedule_api: ScheduleApiDepends, channel: str, date: DateDepends):
if tag.type == TagType.DAY: response = await schedule_api.get_schedule(channel, date)
response = await schedule_api.get_schedule(channel, tag.date)
else:
raise ValueError(tag)
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, request=request,
name="channel.html", name="channel.html",
+1 -102
View File
@@ -1,102 +1 @@
import datetime from .route import router
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))
+81
View File
@@ -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)
@@ -1,5 +1,8 @@
{% extends "base.html" %} {% 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 %} {% block header %}
<app-link href="/weather" <app-link href="/weather"
@@ -9,18 +12,18 @@
{% block content %} {% block content %}
<h4> <h4>
{% if response.period == 'day' %} {% if response.period == 'day' %}
<a class="icon-link {{'disabled' if response.date == datetime.date.today() else ''}}" <a class="icon-link {{'disabled' if date == datetime.date.today() else ''}}"
href="../tag/{{tag_util.create_tag('day', response.date, -1)}}"> href="{{build_url(request, path=['..', date - datetime.timedelta(days=1)])}}">
<i class="bi bi-arrow-left-square"></i> <i class="bi bi-arrow-left-square"></i>
</a> </a>
<a class="icon-link" <a class="icon-link"
href="../tag/days-10"> href="{{build_url(request, path=['..', '..', 'days', '10'])}}">
<i class="bi bi-arrow-up-square"></i> <i class="bi bi-arrow-up-square"></i>
</a> </a>
<span class="badge rounded-pill text-bg-secondary fs-6">{{provider}}</span> <span class="badge rounded-pill text-bg-secondary fs-6">{{provider}}</span>
<span>{{response.location}} | {{format_date(response.date, DATE_FORMAT, locale=request.state.language)}}</span> <span>{{response.location}} | {{format_date(response.date, DATE_FORMAT, locale=request.state.language)}}</span>
<a class="icon-link" <a class="icon-link"
href="../tag/{{tag_util.create_tag('day', response.date, 1)}}"> href="{{build_url(request, path=['..', date + datetime.timedelta(days=1)])}}">
<i class="bi bi-arrow-right-square"></i> <i class="bi bi-arrow-right-square"></i>
</a> </a>
{% endif %} {% endif %}
@@ -47,7 +50,7 @@
{% if response.period == 'days' %} {% if response.period == 'days' %}
<td class="date {{'now' if value.date.date() == datetime.date.today() else ''}}"> <td class="date {{'now' if value.date.date() == datetime.date.today() else ''}}">
<span class="value {{'text-danger' if value.date.weekday() in [5,6] else ''}}"> <span class="value {{'text-danger' if value.date.weekday() in [5,6] else ''}}">
<a href="../tag/{{tag_util.create_tag('day', value.date.date())}}"> <a href="{{build_url(request, path=['..', '..', 'day', value.date.date()])}}">
{{format_date(value.date, 'E d', locale=request.state.language)}} {{format_date(value.date, 'E d', locale=request.state.language)}}
</a> </a>
</span> </span>
+2
View File
@@ -71,6 +71,8 @@ class RP5WeatherApi(WeatherApi):
for response in forecast: for response in forecast:
if response.date == date: if response.date == date:
return response return response
if date == datetime.date.today():
return WeatherResponse(location=location_id, date=date, period="day", values=[])
raise ValueError(f"Unsupported date: {date}") raise ValueError(f"Unsupported date: {date}")
async def get_days(self, location_id: str, days: int) -> WeatherResponse: async def get_days(self, location_id: str, days: int) -> WeatherResponse: