refactor(easel): remove TagUtil
This commit is contained in:
@@ -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)]
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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 ...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,
|
||||
}
|
||||
|
||||
@@ -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,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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" %}
|
||||
{% 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 %}
|
||||
<app-link href="/weather"
|
||||
@@ -9,18 +12,18 @@
|
||||
{% block content %}
|
||||
<h4>
|
||||
{% if response.period == 'day' %}
|
||||
<a class="icon-link {{'disabled' if response.date == datetime.date.today() else ''}}"
|
||||
href="../tag/{{tag_util.create_tag('day', response.date, -1)}}">
|
||||
<a class="icon-link {{'disabled' if date == datetime.date.today() else ''}}"
|
||||
href="{{build_url(request, path=['..', date - datetime.timedelta(days=1)])}}">
|
||||
<i class="bi bi-arrow-left-square"></i>
|
||||
</a>
|
||||
<a class="icon-link"
|
||||
href="../tag/days-10">
|
||||
href="{{build_url(request, path=['..', '..', 'days', '10'])}}">
|
||||
<i class="bi bi-arrow-up-square"></i>
|
||||
</a>
|
||||
<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>
|
||||
<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>
|
||||
</a>
|
||||
{% endif %}
|
||||
@@ -47,7 +50,7 @@
|
||||
{% if response.period == 'days' %}
|
||||
<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 ''}}">
|
||||
<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)}}
|
||||
</a>
|
||||
</span>
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user