refactor(weather.app): move routes to weather.app module

This commit is contained in:
2024-07-29 00:50:25 +03:00
parent 1fb627110d
commit 22fab7a15a
17 changed files with 31 additions and 9 deletions

22
weather/app/__init__.py Normal file
View File

@@ -0,0 +1,22 @@
import locale
from fastapi import FastAPI
from weather.api import WeatherApi
from .route import api, doc, view
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,
)
app.state.weather_api = weather_api
doc.mount(app)
api.mount(app)
view.mount(app)
return app

View File

13
weather/app/route/api.py Normal file
View File

@@ -0,0 +1,13 @@
import datetime
from fastapi import FastAPI, Request
from weather.model import WeatherResponse
def mount(app: FastAPI):
@app.get("/api/weather/{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)

View File

@@ -0,0 +1,37 @@
from anyio import Path
from fastapi import FastAPI
from fastapi.openapi.docs import (
get_redoc_html,
get_swagger_ui_html,
get_swagger_ui_oauth2_redirect_html,
)
from fastapi.staticfiles import StaticFiles
def mount(app: FastAPI):
app.mount(
"/docs/static",
StaticFiles(directory=Path(__file__).parent / "static"),
)
@app.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html():
return get_swagger_ui_html(
openapi_url=app.openapi_url,
title=app.title + " - Swagger UI",
oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
swagger_js_url="docs/static/swagger-ui-bundle.js",
swagger_css_url="docs/static/swagger-ui.css",
)
@app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False)
async def swagger_ui_redirect():
return get_swagger_ui_oauth2_redirect_html()
@app.get("/redoc", include_in_schema=False)
async def redoc_html():
return get_redoc_html(
openapi_url=app.openapi_url,
title=app.title + " - ReDoc",
redoc_js_url="docs/static/redoc.standalone.js",
)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,66 @@
import datetime
from pathlib import Path
from fastapi import FastAPI, Request
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.model import WeatherResponse
from .filters import cloudness_icon, wind_direction_icon
def mount(app: FastAPI):
base_dir = Path(__file__).parent
app.mount("/static", StaticFiles(directory=base_dir / "static"), name="static")
templates = Jinja2Templates(directory=base_dir / "templates")
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()}")
@app.get("/weather/{location}/mock", response_class=HTMLResponse)
async def get_weather_mock(request: Request):
response = MOCK_DATA.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 request.app.state.weather_api.get_day(location, date)
return build_weather_response(request, response)

View File

@@ -0,0 +1,39 @@
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.CALM: "",
}.get(wind_direction, wind_direction)
def cloudness_icon(sky: Sky) -> list[str]:
main_icon = ""
if sky.thunder:
if sky.cloudness == Cloudness.CLEAR:
main_icon = "🌩️"
if sky.precipitation == Precipitation.NO:
main_icon = ""
else:
main_icon = "⛈️"
elif sky.precipitation == Precipitation.NO:
main_icon = {
Cloudness.CLEAR: "☀️",
Cloudness.PARTLY_CLOUDY: "🌤️",
Cloudness.CLOUDY: "",
Cloudness.MAINLY_CLOUDY: "☁️",
}[sky.cloudness]
else:
main_icon = "🌧️"
icons = [main_icon]
if sky.fog:
icons.append("🌫️")
return icons

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

View File

@@ -0,0 +1,91 @@
body {
font-size: 1.5rem;
}
h3 {
margin: 0.5rem 0;
}
a.button {
text-decoration: none;
color: inherit;
}
.button.disabled {
pointer-events: none;
cursor: default;
color: gray;
filter: grayscale(100%);
}
table {
/* width: 100%; */
table-layout: fixed;
border-collapse: collapse;
}
table,
th,
td {
/* border: 1px solid rgba(0, 0, 0, 0.2); */
text-align: center;
}
td {
padding: 0.1rem 0.4rem;
}
.header {
font-size: 1rem;
text-align: left;
padding-top: 0.25rem;
}
.date {
font-size: 1.5rem;
background: rgba(0, 0, 0, 0.1);
}
.date.now {
background: rgba(0, 128, 255, 0.2);
}
.cloudness {
vertical-align: top;
}
.cloudness .icon {
font-size: 1rem;
}
.cloudness .icon:first-child {
font-size: 2rem;
}
.temperature.positive .value {
color: orangered;
}
.temperature.negative .value {
color: blue;
}
.wind .direction {
font-size: 1rem;
}
.wind .gust {
font-size: 1rem;
}
.precipitation .value {
color: blue;
}
.pressure .value {
color: blueviolet;
}
.humidity .value {
color: blue;
}

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

@@ -0,0 +1,152 @@
<!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>Погода | {{response.location}} | {{response.date.strftime('%a, %d %B %Y')}}</title>
<link rel="stylesheet"
href="/static/style.css">
<link rel="icon"
href="/static/favicon.ico"
type="image/x-icon">
</head>
<body>
<h3>
<a class="button {{'disabled' if response.date == datetime.date.today() else ''}}"
href="{{response.date - datetime.timedelta(days=1)}}">⬅️</a>
<span>{{response.location}} | {{response.date.strftime('%a, %d %B %Y')}}</span>
<a class="button"
href="{{response.date + datetime.timedelta(days=1)}}">➡️</a>
</h3>
<table>
<tbody>
<!-- date -->
<tr>
{% for value in response.values %}
<td
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>
</td>
{% endfor %}
</tr>
<!-- cloudness -->
<tr>
<td colspan="{{response.values | length}}"
class="header">
Облачность
</td>
</tr>
<tr>
{% for value in response.values %}
<td class="cloudness">
{% for icon in value.sky | cloudness_icon %}
<div class="icon">{{icon}}</div>
{% endfor %}
</td>
{% endfor %}
</tr>
<!-- temperature -->
<tr>
<td colspan="{{response.values | length}}"
class="header">
Температура, °C
</td>
</tr>
<tr>
{% for value in response.values %}
<td class="temperature {{'positive' if value.temperature > 0 else 'negative'}}"
style="background-color: rgba(255, 128, 128, {{(value.temperature - 10) * 0.015}});">
<span class="value">{{value.temperature}}</span>
</td>
{% endfor %}
</tr>
<!-- wind_direction -->
<tr>
<td colspan="{{response.values | length}}"
class="header">
Направление ветра
</td>
</tr>
<tr>
{% for value in response.values %}
<td class="wind">
<span class="icon">{{value.wind_direction | wind_direction_icon}}</span>
</td>
{% endfor %}
</tr>
<!-- wind_speed -->
<tr>
<td colspan="{{response.values | length}}"
class="header">
Скорость ветра, м/с
</td>
</tr>
<tr>
{% for value in response.values %}
<td class="wind"
style="background-color: rgba(128, 128, 128, {{value.wind_speed * 0.05}});">
<span class="speed">{{value.wind_speed}}</span>
{% if value.wind_gust != value.wind_speed %}
<span class="gust">
({{value.wind_gust}})
</span>
{% endif %}
</td>
{% endfor %}
</tr>
<!-- precipitation -->
<tr>
<td colspan="{{response.values | length}}"
class="header">
Осадки, мм
</td>
</tr>
<tr>
{% for value in response.values %}
<td class="precipitation"
style="background-color: rgba(0, 128, 255, {{value.precipitation * 0.1}});">
<span class="value">{{value.precipitation or ''}}</span>
</td>
{% endfor %}
</tr>
<!-- pressure -->
<tr>
<td colspan="{{response.values | length}}"
class="header">
Давление, мм рт. ст.
</td>
</tr>
<tr>
{% for value in response.values %}
<td class="pressure"
style="background-color: rgba(128, 0, 255, {{(value.pressure - 720) * 0.008}});">
<span class="value">{{value.pressure}}</span>
</td>
{% endfor %}
</tr>
<!-- humidity -->
<tr>
<td colspan="{{response.values | length}}"
class="header">
Влажность, %
</td>
</tr>
<tr>
{% for value in response.values %}
<td class="humidity"
style="background-color: rgba(128, 128, 255, {{value.humidity * 0.005}});">
<span class="value">{{value.humidity}}</span>
</td>
{% endfor %}
</tr>
</tbody>
</table>
<script src="/static/index.js"></script>
</body>
</html>