From 1fb627110d86ba7dfed0524283e03d4533de7a16 Mon Sep 17 00:00:00 2001 From: shmyga Date: Sun, 28 Jul 2024 20:26:38 +0300 Subject: [PATCH] refactor(weather): add app_build method --- Dockerfile | 2 +- gismeteo/datehelp.py | 9 + pyproject.toml | 2 +- weather/api.py | 7 - weather/app.py | 31 +-- weather/main.py | 12 + weather/route/api.py | 9 +- weather/route/view/__init__.py | 54 ++-- weather/route/view/filters.py | 16 +- weather/route/view/static/style.css | 1 + weather/route/view/templates/index.html | 26 ++ weather/route/view/templates/weather.html | 284 +++++++++++----------- 12 files changed, 254 insertions(+), 199 deletions(-) create mode 100644 weather/main.py create mode 100644 weather/route/view/templates/index.html diff --git a/Dockerfile b/Dockerfile index da1fd7c..1ebfd12 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,4 +21,4 @@ COPY --from=builder /app ./ COPY gismeteo gismeteo/ COPY weather weather/ -CMD ["uvicorn", "weather.app:app", "--host", "0.0.0.0", "--port", "80"] +CMD ["uvicorn", "weather.main:app", "--host", "0.0.0.0", "--port", "80"] diff --git a/gismeteo/datehelp.py b/gismeteo/datehelp.py index b020b31..ed05d1b 100644 --- a/gismeteo/datehelp.py +++ b/gismeteo/datehelp.py @@ -1,9 +1,18 @@ import datetime +from enum import Enum import dateparser import dateparser.date_parser +class Day(str, Enum): + TODAY = "today" + TOMORROW = "tomorrow" + DAY_3 = "3-day" + DAY_4 = "4-day" + DAY_5 = "5-day" + + def parse(value: str) -> datetime.date: if value == "today" or value == "mock": return datetime.date.today() diff --git a/pyproject.toml b/pyproject.toml index 8ecef46..2ce537b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.poetry.scripts] -app = 'weather.app:run' +app = 'weather.main:run' [tool.pytest.ini_options] addopts = "-p no:warnings" diff --git a/weather/api.py b/weather/api.py index c292975..3101caf 100644 --- a/weather/api.py +++ b/weather/api.py @@ -6,10 +6,3 @@ from .model import WeatherResponse class WeatherApi: async def get_day(self, location_id: str, date: datetime.date) -> WeatherResponse: raise NotImplementedError - - -DEFAULT_API: WeatherApi = None - - -def get_api() -> WeatherApi: - return DEFAULT_API diff --git a/weather/app.py b/weather/app.py index 893c2df..0f9b1c8 100644 --- a/weather/app.py +++ b/weather/app.py @@ -1,27 +1,22 @@ import locale -from os import environ -import uvicorn from fastapi import FastAPI -from gismeteo.api import GismeteoApi +from weather.api import WeatherApi -from . import api as _api from .route import api, doc, view -_api.DEFAULT_API = GismeteoApi() -locale.setlocale(locale.LC_TIME, "ru_RU.UTF-8") +def build_app(weather_api: WeatherApi) -> FastAPI: + locale.setlocale(locale.LC_TIME, "ru_RU.UTF-8") -app = FastAPI( - title="Weather", - docs_url=None, - redoc_url=None, -) -doc.mount(app) -api.mount(app) -view.mount(app) - - -def run(): - uvicorn.run("weather.app:app", host="0.0.0.0", port=8000, reload="DEBUG" in environ) + app = FastAPI( + title="Weather", + docs_url=None, + redoc_url=None, + ) + app.state.weather_api = weather_api + doc.mount(app) + api.mount(app) + view.mount(app) + return app diff --git a/weather/main.py b/weather/main.py new file mode 100644 index 0000000..8902a72 --- /dev/null +++ b/weather/main.py @@ -0,0 +1,12 @@ +from os import environ + +import uvicorn + +from gismeteo.api import GismeteoApi +from weather.app import build_app + +app = build_app(GismeteoApi()) + + +def run(): + uvicorn.run("weather.main:app", host="0.0.0.0", port=8000, reload="DEBUG" in environ) diff --git a/weather/route/api.py b/weather/route/api.py index 355252d..e4b016b 100644 --- a/weather/route/api.py +++ b/weather/route/api.py @@ -1,12 +1,13 @@ import datetime -from fastapi import FastAPI +from fastapi import FastAPI, Request -from weather.api import get_api from weather.model import WeatherResponse def mount(app: FastAPI): @app.get("/api/weather/{location}/{date}") - async def get_weather(location: str, date: datetime.date) -> WeatherResponse: - return await get_api().get_day(location, date) + async def get_weather_api( + request: Request, location: str, date: datetime.date + ) -> WeatherResponse: + return await request.app.state.weather_api.get_day(location, date) diff --git a/weather/route/view/__init__.py b/weather/route/view/__init__.py index 9205f08..e28bc14 100644 --- a/weather/route/view/__init__.py +++ b/weather/route/view/__init__.py @@ -6,8 +6,10 @@ from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates +from gismeteo import datehelp +from gismeteo.location import LOCATION_BUNDLE from gismeteo.mock import MOCK_DATA -from weather.api import get_api +from weather.model import WeatherResponse from .filters import cloudness_icon, wind_direction_icon @@ -19,6 +21,30 @@ def mount(app: FastAPI): templates.env.filters["wind_direction_icon"] = wind_direction_icon templates.env.filters["cloudness_icon"] = cloudness_icon + def build_weather_response(request: Request, response: WeatherResponse): + return templates.TemplateResponse( + request=request, + name="weather.html", + context={ + "datetime": datetime, + "response": response, + }, + ) + + @app.get("/", response_class=RedirectResponse) + async def get_weather_root(): + return RedirectResponse("weather") + + @app.get("/weather", response_class=HTMLResponse) + async def get_weather_list(request: Request): + return templates.TemplateResponse( + request=request, + name="index.html", + context={ + "locations": LOCATION_BUNDLE._values, + }, + ) + @app.get("/weather/{location}", response_class=RedirectResponse) async def get_weather_default(location: str): return RedirectResponse(f"{location}/{datetime.date.today()}") @@ -26,23 +52,15 @@ def mount(app: FastAPI): @app.get("/weather/{location}/mock", response_class=HTMLResponse) async def get_weather_mock(request: Request): response = MOCK_DATA.response - return templates.TemplateResponse( - request=request, - name="weather.html", - context={ - "datetime": datetime, - "response": response, - }, - ) + return build_weather_response(request, response) + + # @app.get("/weather/{location}/{day}", response_class=HTMLResponse) + async def get_weather_day(request: Request, location: str, day: datehelp.Day): + date = datehelp.parse(day) + response = await request.app.state.weather_api.get_day(location, date) + return build_weather_response(request, response) @app.get("/weather/{location}/{date}", response_class=HTMLResponse) async def get_weather(request: Request, location: str, date: datetime.date): - response = await get_api().get_day(location, date) - return templates.TemplateResponse( - request=request, - name="weather.html", - context={ - "datetime": datetime, - "response": response, - }, - ) + response = await request.app.state.weather_api.get_day(location, date) + return build_weather_response(request, response) diff --git a/weather/route/view/filters.py b/weather/route/view/filters.py index 33cacb0..ebdd0f1 100644 --- a/weather/route/view/filters.py +++ b/weather/route/view/filters.py @@ -3,14 +3,14 @@ from weather.model import Cloudness, Precipitation, Sky, WindDirection def wind_direction_icon(wind_direction: WindDirection) -> str: return { - WindDirection.N: "🡫", - WindDirection.NO: "🡯", - WindDirection.O: "🡨", - WindDirection.SO: "🡬", - WindDirection.S: "🡡", - WindDirection.SW: "🡭", - WindDirection.W: "🡪", - WindDirection.NW: "🡦", + WindDirection.N: "⬇️", + WindDirection.NO: "↙️", + WindDirection.O: "⬅️", + WindDirection.SO: "↖️", + WindDirection.S: "⬆️", + WindDirection.SW: "↗️", + WindDirection.W: "➡️", + WindDirection.NW: "↘️", WindDirection.CALM: "", }.get(wind_direction, wind_direction) diff --git a/weather/route/view/static/style.css b/weather/route/view/static/style.css index 3b209c1..df88645 100644 --- a/weather/route/view/static/style.css +++ b/weather/route/view/static/style.css @@ -15,6 +15,7 @@ a.button { pointer-events: none; cursor: default; color: gray; + filter: grayscale(100%); } table { diff --git a/weather/route/view/templates/index.html b/weather/route/view/templates/index.html new file mode 100644 index 0000000..d38a068 --- /dev/null +++ b/weather/route/view/templates/index.html @@ -0,0 +1,26 @@ + + + + + + + + Погода + + + + + + + + + \ No newline at end of file diff --git a/weather/route/view/templates/weather.html b/weather/route/view/templates/weather.html index d1a3513..d524bbf 100644 --- a/weather/route/view/templates/weather.html +++ b/weather/route/view/templates/weather.html @@ -2,151 +2,151 @@ - - - - Погода | {{response.location}} | {{response.date.strftime('%a, %d %B %Y')}} - - + + + + Погода | {{response.location}} | {{response.date.strftime('%a, %d %B %Y')}} + + -

- 🡨 - {{response.location}} | {{response.date.strftime('%a, %d %B %Y')}} - 🡪 -

- - - - - {% for value in response.values %} - - {% endfor %} - - - - - - - {% for value in response.values %} - - {% endfor %} - - - - - - - {% for value in response.values %} - - {% endfor %} - - - - - - - {% for value in response.values %} - - {% endfor %} - - - - - - - {% for value in response.values %} - - {% endfor %} - - - - - - - {% for value in response.values %} - - {% endfor %} - - - - - - - {% for value in response.values %} - - {% endfor %} - - - - - - - {% for value in response.values %} - - {% endfor %} - - -
- {{value.date.strftime('%H:%M')}} -
- Облачность -
- {% for icon in value.sky | cloudness_icon %} -
{{icon}}
- {% endfor %} -
- Температура, °C -
- {{value.temperature}} -
- Направление ветра -
- {{value.wind_direction | wind_direction_icon}} -
- Скорость ветра, м/с -
- {{value.wind_speed}} - {% if value.wind_gust != value.wind_speed %} - - ({{value.wind_gust}}) - - {% endif %} -
- Осадки, мм -
- {{value.precipitation or ' '}} -
- Давление, мм рт. ст. -
- {{value.pressure}} -
- Влажность, % -
- {{value.humidity}} -
- +

+ ⬅️ + {{response.location}} | {{response.date.strftime('%a, %d %B %Y')}} + ➡️ +

+ + + + + {% for value in response.values %} + + {% endfor %} + + + + + + + {% for value in response.values %} + + {% endfor %} + + + + + + + {% for value in response.values %} + + {% endfor %} + + + + + + + {% for value in response.values %} + + {% endfor %} + + + + + + + {% for value in response.values %} + + {% endfor %} + + + + + + + {% for value in response.values %} + + {% endfor %} + + + + + + + {% for value in response.values %} + + {% endfor %} + + + + + + + {% for value in response.values %} + + {% endfor %} + + +
+ {{value.date.strftime('%H:%M')}} +
+ Облачность +
+ {% for icon in value.sky | cloudness_icon %} +
{{icon}}
+ {% endfor %} +
+ Температура, °C +
+ {{value.temperature}} +
+ Направление ветра +
+ {{value.wind_direction | wind_direction_icon}} +
+ Скорость ветра, м/с +
+ {{value.wind_speed}} + {% if value.wind_gust != value.wind_speed %} + + ({{value.wind_gust}}) + + {% endif %} +
+ Осадки, мм +
+ {{value.precipitation or ' '}} +
+ Давление, мм рт. ст. +
+ {{value.pressure}} +
+ Влажность, % +
+ {{value.humidity}} +
+ \ No newline at end of file