refactor(weather): add app_build method

This commit is contained in:
2024-07-28 20:26:38 +03:00
parent 48a6cce569
commit 1fb627110d
12 changed files with 254 additions and 199 deletions

View File

@@ -21,4 +21,4 @@ COPY --from=builder /app ./
COPY gismeteo gismeteo/ COPY gismeteo gismeteo/
COPY weather weather/ 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"]

View File

@@ -1,9 +1,18 @@
import datetime import datetime
from enum import Enum
import dateparser import dateparser
import dateparser.date_parser 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: def parse(value: str) -> datetime.date:
if value == "today" or value == "mock": if value == "today" or value == "mock":
return datetime.date.today() return datetime.date.today()

View File

@@ -31,7 +31,7 @@ requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts] [tool.poetry.scripts]
app = 'weather.app:run' app = 'weather.main:run'
[tool.pytest.ini_options] [tool.pytest.ini_options]
addopts = "-p no:warnings" addopts = "-p no:warnings"

View File

@@ -6,10 +6,3 @@ from .model import WeatherResponse
class WeatherApi: class WeatherApi:
async def get_day(self, location_id: str, date: datetime.date) -> WeatherResponse: async def get_day(self, location_id: str, date: datetime.date) -> WeatherResponse:
raise NotImplementedError raise NotImplementedError
DEFAULT_API: WeatherApi = None
def get_api() -> WeatherApi:
return DEFAULT_API

View File

@@ -1,27 +1,22 @@
import locale import locale
from os import environ
import uvicorn
from fastapi import FastAPI 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 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( app = FastAPI(
title="Weather", title="Weather",
docs_url=None, docs_url=None,
redoc_url=None, redoc_url=None,
) )
doc.mount(app) app.state.weather_api = weather_api
api.mount(app) doc.mount(app)
view.mount(app) api.mount(app)
view.mount(app)
return app
def run():
uvicorn.run("weather.app:app", host="0.0.0.0", port=8000, reload="DEBUG" in environ)

12
weather/main.py Normal file
View File

@@ -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)

View File

@@ -1,12 +1,13 @@
import datetime import datetime
from fastapi import FastAPI from fastapi import FastAPI, Request
from weather.api import get_api
from weather.model import WeatherResponse from weather.model import WeatherResponse
def mount(app: FastAPI): def mount(app: FastAPI):
@app.get("/api/weather/{location}/{date}") @app.get("/api/weather/{location}/{date}")
async def get_weather(location: str, date: datetime.date) -> WeatherResponse: async def get_weather_api(
return await get_api().get_day(location, date) request: Request, location: str, date: datetime.date
) -> WeatherResponse:
return await request.app.state.weather_api.get_day(location, date)

View File

@@ -6,8 +6,10 @@ from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from gismeteo import datehelp
from gismeteo.location import LOCATION_BUNDLE
from gismeteo.mock import MOCK_DATA 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 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["wind_direction_icon"] = wind_direction_icon
templates.env.filters["cloudness_icon"] = cloudness_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) @app.get("/weather/{location}", response_class=RedirectResponse)
async def get_weather_default(location: str): async def get_weather_default(location: str):
return RedirectResponse(f"{location}/{datetime.date.today()}") return RedirectResponse(f"{location}/{datetime.date.today()}")
@@ -26,23 +52,15 @@ def mount(app: FastAPI):
@app.get("/weather/{location}/mock", response_class=HTMLResponse) @app.get("/weather/{location}/mock", response_class=HTMLResponse)
async def get_weather_mock(request: Request): async def get_weather_mock(request: Request):
response = MOCK_DATA.response response = MOCK_DATA.response
return templates.TemplateResponse( return build_weather_response(request, response)
request=request,
name="weather.html", # @app.get("/weather/{location}/{day}", response_class=HTMLResponse)
context={ async def get_weather_day(request: Request, location: str, day: datehelp.Day):
"datetime": datetime, date = datehelp.parse(day)
"response": response, 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) @app.get("/weather/{location}/{date}", response_class=HTMLResponse)
async def get_weather(request: Request, location: str, date: datetime.date): async def get_weather(request: Request, location: str, date: datetime.date):
response = await get_api().get_day(location, date) response = await request.app.state.weather_api.get_day(location, date)
return templates.TemplateResponse( return build_weather_response(request, response)
request=request,
name="weather.html",
context={
"datetime": datetime,
"response": response,
},
)

View File

@@ -3,14 +3,14 @@ from weather.model import Cloudness, Precipitation, Sky, WindDirection
def wind_direction_icon(wind_direction: WindDirection) -> str: def wind_direction_icon(wind_direction: WindDirection) -> str:
return { return {
WindDirection.N: "🡫", WindDirection.N: "⬇️",
WindDirection.NO: "🡯", WindDirection.NO: "↙️",
WindDirection.O: "🡨", WindDirection.O: "⬅️",
WindDirection.SO: "🡬", WindDirection.SO: "↖️",
WindDirection.S: "🡡", WindDirection.S: "⬆️",
WindDirection.SW: "🡭", WindDirection.SW: "↗️",
WindDirection.W: "🡪", WindDirection.W: "➡️",
WindDirection.NW: "🡦", WindDirection.NW: "↘️",
WindDirection.CALM: "", WindDirection.CALM: "",
}.get(wind_direction, wind_direction) }.get(wind_direction, wind_direction)

View File

@@ -15,6 +15,7 @@ a.button {
pointer-events: none; pointer-events: none;
cursor: default; cursor: default;
color: gray; color: gray;
filter: grayscale(100%);
} }
table { table {

View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible"
content="ie=edge">
<title>Погода</title>
<link rel="stylesheet"
href="/static/style.css">
<link rel="icon"
href="/static/favicon.ico"
type="image/x-icon">
</head>
<body>
<ul>
{% for location in locations %}
<li><a href="weather/{{location.name}}-{{location.location_id}}">{{location.name}}</a></li>
{% endfor %}
</ul>
</body>
</html>

View File

@@ -2,151 +2,151 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" <meta name="viewport"
content="width=device-width, initial-scale=1.0"> content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" <meta http-equiv="X-UA-Compatible"
content="ie=edge"> content="ie=edge">
<title>Погода | {{response.location}} | {{response.date.strftime('%a, %d %B %Y')}}</title> <title>Погода | {{response.location}} | {{response.date.strftime('%a, %d %B %Y')}}</title>
<link rel="stylesheet" <link rel="stylesheet"
href="/static/style.css"> href="/static/style.css">
<link rel="icon" <link rel="icon"
href="/static/favicon.ico" href="/static/favicon.ico"
type="image/x-icon"> type="image/x-icon">
</head> </head>
<body> <body>
<h3> <h3>
<a class="button {{'disabled' if response.date == datetime.date.today() else ''}}" <a class="button {{'disabled' if response.date == datetime.date.today() else ''}}"
href="{{response.date - datetime.timedelta(days=1)}}">🡨</a> href="{{response.date - datetime.timedelta(days=1)}}">⬅️</a>
<span>{{response.location}} | {{response.date.strftime('%a, %d %B %Y')}}</span> <span>{{response.location}} | {{response.date.strftime('%a, %d %B %Y')}}</span>
<a class="button" <a class="button"
href="{{response.date + datetime.timedelta(days=1)}}">🡪</a> href="{{response.date + datetime.timedelta(days=1)}}">➡️</a>
</h3> </h3>
<table> <table>
<tbody> <tbody>
<!-- date --> <!-- date -->
<tr> <tr>
{% for value in response.values %} {% for value in response.values %}
<td <td
class="date {{'now' if value.date < datetime.datetime.now() and value.date + datetime.timedelta(hours=3) > datetime.datetime.now() else ''}}"> class="date {{'now' if value.date < datetime.datetime.now() and value.date + datetime.timedelta(hours=3) > datetime.datetime.now() else ''}}">
<span class="value">{{value.date.strftime('%H:%M')}}</span> <span class="value">{{value.date.strftime('%H:%M')}}</span>
</td> </td>
{% endfor %} {% endfor %}
</tr> </tr>
<!-- cloudness --> <!-- cloudness -->
<tr> <tr>
<td colspan="{{response.values | length}}" <td colspan="{{response.values | length}}"
class="header"> class="header">
Облачность Облачность
</td> </td>
</tr> </tr>
<tr> <tr>
{% for value in response.values %} {% for value in response.values %}
<td class="cloudness"> <td class="cloudness">
{% for icon in value.sky | cloudness_icon %} {% for icon in value.sky | cloudness_icon %}
<div class="icon">{{icon}}</div> <div class="icon">{{icon}}</div>
{% endfor %} {% endfor %}
</td> </td>
{% endfor %} {% endfor %}
</tr> </tr>
<!-- temperature --> <!-- temperature -->
<tr> <tr>
<td colspan="{{response.values | length}}" <td colspan="{{response.values | length}}"
class="header"> class="header">
Температура, °C Температура, °C
</td> </td>
</tr> </tr>
<tr> <tr>
{% for value in response.values %} {% for value in response.values %}
<td class="temperature {{'positive' if value.temperature > 0 else 'negative'}}" <td class="temperature {{'positive' if value.temperature > 0 else 'negative'}}"
style="background-color: rgba(255, 128, 128, {{(value.temperature - 10) * 0.015}});"> style="background-color: rgba(255, 128, 128, {{(value.temperature - 10) * 0.015}});">
<span class="value">{{value.temperature}}</span> <span class="value">{{value.temperature}}</span>
</td> </td>
{% endfor %} {% endfor %}
</tr> </tr>
<!-- wind_direction --> <!-- wind_direction -->
<tr> <tr>
<td colspan="{{response.values | length}}" <td colspan="{{response.values | length}}"
class="header"> class="header">
Направление ветра Направление ветра
</td> </td>
</tr> </tr>
<tr> <tr>
{% for value in response.values %} {% for value in response.values %}
<td class="wind"> <td class="wind">
<span class="icon">{{value.wind_direction | wind_direction_icon}}</span> <span class="icon">{{value.wind_direction | wind_direction_icon}}</span>
</td> </td>
{% endfor %} {% endfor %}
</tr> </tr>
<!-- wind_speed --> <!-- wind_speed -->
<tr> <tr>
<td colspan="{{response.values | length}}" <td colspan="{{response.values | length}}"
class="header"> class="header">
Скорость ветра, м/с Скорость ветра, м/с
</td> </td>
</tr> </tr>
<tr> <tr>
{% for value in response.values %} {% for value in response.values %}
<td class="wind" <td class="wind"
style="background-color: rgba(128, 128, 128, {{value.wind_speed * 0.05}});"> style="background-color: rgba(128, 128, 128, {{value.wind_speed * 0.05}});">
<span class="speed">{{value.wind_speed}}</span> <span class="speed">{{value.wind_speed}}</span>
{% if value.wind_gust != value.wind_speed %} {% if value.wind_gust != value.wind_speed %}
<span class="gust"> <span class="gust">
({{value.wind_gust}}) ({{value.wind_gust}})
</span> </span>
{% endif %} {% endif %}
</td> </td>
{% endfor %} {% endfor %}
</tr> </tr>
<!-- precipitation --> <!-- precipitation -->
<tr> <tr>
<td colspan="{{response.values | length}}" <td colspan="{{response.values | length}}"
class="header"> class="header">
Осадки, мм Осадки, мм
</td> </td>
</tr> </tr>
<tr> <tr>
{% for value in response.values %} {% for value in response.values %}
<td class="precipitation" <td class="precipitation"
style="background-color: rgba(0, 128, 255, {{value.precipitation * 0.1}});"> style="background-color: rgba(0, 128, 255, {{value.precipitation * 0.1}});">
<span class="value">{{value.precipitation or ''}}</span> <span class="value">{{value.precipitation or ''}}</span>
</td> </td>
{% endfor %} {% endfor %}
</tr> </tr>
<!-- pressure --> <!-- pressure -->
<tr> <tr>
<td colspan="{{response.values | length}}" <td colspan="{{response.values | length}}"
class="header"> class="header">
Давление, мм рт. ст. Давление, мм рт. ст.
</td> </td>
</tr> </tr>
<tr> <tr>
{% for value in response.values %} {% for value in response.values %}
<td class="pressure" <td class="pressure"
style="background-color: rgba(128, 0, 255, {{(value.pressure - 720) * 0.008}});"> style="background-color: rgba(128, 0, 255, {{(value.pressure - 720) * 0.008}});">
<span class="value">{{value.pressure}}</span> <span class="value">{{value.pressure}}</span>
</td> </td>
{% endfor %} {% endfor %}
</tr> </tr>
<!-- humidity --> <!-- humidity -->
<tr> <tr>
<td colspan="{{response.values | length}}" <td colspan="{{response.values | length}}"
class="header"> class="header">
Влажность, % Влажность, %
</td> </td>
</tr> </tr>
<tr> <tr>
{% for value in response.values %} {% for value in response.values %}
<td class="humidity" <td class="humidity"
style="background-color: rgba(128, 128, 255, {{value.humidity * 0.005}});"> style="background-color: rgba(128, 128, 255, {{value.humidity * 0.005}});">
<span class="value">{{value.humidity}}</span> <span class="value">{{value.humidity}}</span>
</td> </td>
{% endfor %} {% endfor %}
</tr> </tr>
</tbody> </tbody>
</table> </table>
<script src="/static/index.js"></script> <script src="/static/index.js"></script>
</body> </body>
</html> </html>