feat(app): add html weather view
This commit is contained in:
@@ -3,9 +3,10 @@ from typing import Any, Dict, List, NamedTuple
|
|||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from . import dateutil
|
||||||
|
|
||||||
from .location import LOCATION_BUNDLE
|
from .location import LOCATION_BUNDLE
|
||||||
from .parser import ROW_PARSERS, OneDayParser
|
from .parser import ROW_PARSERS, ONE_DAY_PARSER
|
||||||
|
|
||||||
|
|
||||||
class WeatherValue(NamedTuple):
|
class WeatherValue(NamedTuple):
|
||||||
@@ -32,7 +33,7 @@ class GismeteoApi:
|
|||||||
def _parse_oneday(self, data: str) -> List[WeatherValue]:
|
def _parse_oneday(self, data: str) -> List[WeatherValue]:
|
||||||
result: List[Dict[str, Any]] = []
|
result: List[Dict[str, Any]] = []
|
||||||
soup = BeautifulSoup(data, features="html.parser")
|
soup = BeautifulSoup(data, features="html.parser")
|
||||||
widget = OneDayParser().parse_widget(soup)
|
widget = ONE_DAY_PARSER.parse_widget(soup)
|
||||||
for parser in ROW_PARSERS:
|
for parser in ROW_PARSERS:
|
||||||
for index, value in enumerate(parser.parse_row(widget)):
|
for index, value in enumerate(parser.parse_row(widget)):
|
||||||
while len(result) < index + 1:
|
while len(result) < index + 1:
|
||||||
@@ -40,12 +41,7 @@ class GismeteoApi:
|
|||||||
result[index][parser.KEY] = value
|
result[index][parser.KEY] = value
|
||||||
return [WeatherValue(**item) for item in result]
|
return [WeatherValue(**item) for item in result]
|
||||||
|
|
||||||
async def today(self, location_id: str) -> List[WeatherValue]:
|
async def get_day(self, location_id: str, date: datetime.date) -> List[WeatherValue]:
|
||||||
location = LOCATION_BUNDLE.parse(location_id)
|
location = LOCATION_BUNDLE.parse(location_id)
|
||||||
data = await self._request(f"weather-{location}/today")
|
data = await self._request(f"weather-{location}/{dateutil.dump(date)}")
|
||||||
return self._parse_oneday(data)
|
|
||||||
|
|
||||||
async def tomorrow(self, location_id: str) -> List[WeatherValue]:
|
|
||||||
location = LOCATION_BUNDLE.parse(location_id)
|
|
||||||
data = await self._request(f"weather-{location}/tomorrow")
|
|
||||||
return self._parse_oneday(data)
|
return self._parse_oneday(data)
|
||||||
|
|||||||
@@ -1,26 +1,17 @@
|
|||||||
|
import locale
|
||||||
from os import environ
|
from os import environ
|
||||||
|
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
|
||||||
from gismeteo import custom_doc
|
from gismeteo.route import api, doc, view
|
||||||
from gismeteo.api import GismeteoApi
|
|
||||||
|
|
||||||
|
locale.setlocale(locale.LC_TIME, "ru_RU.UTF-8")
|
||||||
|
|
||||||
app = FastAPI(docs_url=None, redoc_url=None)
|
app = FastAPI(docs_url=None, redoc_url=None)
|
||||||
custom_doc.apply(app)
|
doc.mount(app)
|
||||||
|
api.mount(app)
|
||||||
|
view.mount(app)
|
||||||
@app.get("/")
|
|
||||||
async def root():
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/weather/{location_id}")
|
|
||||||
async def get_weather(location_id: str):
|
|
||||||
api = GismeteoApi()
|
|
||||||
result = await api.today(location_id)
|
|
||||||
return [item._asdict() for item in result]
|
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
|
|||||||
30
gismeteo/dateutil.py
Normal file
30
gismeteo/dateutil.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
|
import dateparser
|
||||||
|
import dateparser.date_parser
|
||||||
|
|
||||||
|
|
||||||
|
def parse(value: str) -> datetime.date:
|
||||||
|
if value == "today" or value == "mock":
|
||||||
|
return datetime.date.today()
|
||||||
|
elif value == "tomorrow":
|
||||||
|
return datetime.date.today() + datetime.timedelta(days=1)
|
||||||
|
elif value.endswith("-day"):
|
||||||
|
days = int(value.split("-")[0]) - 1
|
||||||
|
return datetime.date.today() + datetime.timedelta(days=days)
|
||||||
|
else:
|
||||||
|
date = dateparser.parse(value)
|
||||||
|
if date is None:
|
||||||
|
raise ValueError(value)
|
||||||
|
return date.date()
|
||||||
|
|
||||||
|
|
||||||
|
def dump(date: datetime.date) -> str:
|
||||||
|
today = datetime.date.today()
|
||||||
|
days = (date - today).days
|
||||||
|
if days == 0:
|
||||||
|
return "today"
|
||||||
|
elif days == 1:
|
||||||
|
return "tomorrow"
|
||||||
|
else:
|
||||||
|
return f"{days + 1}-day"
|
||||||
25
gismeteo/mock/__init__.py
Normal file
25
gismeteo/mock/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
import dateparser
|
||||||
|
|
||||||
|
from gismeteo.api import WeatherValue
|
||||||
|
|
||||||
|
|
||||||
|
class MockData:
|
||||||
|
|
||||||
|
@property
|
||||||
|
def html(self) -> str:
|
||||||
|
return (Path(__file__).parent / "data/weather.html").read_text()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def values(self) -> List[WeatherValue]:
|
||||||
|
data = json.loads((Path(__file__).parent / "data/weather.json").read_text())
|
||||||
|
return [
|
||||||
|
WeatherValue(**{**item, "date": dateparser.parse(item["date"])})
|
||||||
|
for item in data
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
MOCK_DATA = MockData()
|
||||||
1
gismeteo/mock/data/weather.json
Normal file
1
gismeteo/mock/data/weather.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[{"date":"2024-07-25T00:00:00","cloudness":"Ясно","temperature":14,"wind_speed":1,"wind_gust":1,"wind_direction":"С","precipitation":0.0,"pressure":739,"humidity":85},{"date":"2024-07-25T03:00:00","cloudness":"Ясно","temperature":13,"wind_speed":1,"wind_gust":2,"wind_direction":"СЗ","precipitation":0.0,"pressure":739,"humidity":92},{"date":"2024-07-25T06:00:00","cloudness":"Малооблачно, без осадков","temperature":14,"wind_speed":1,"wind_gust":2,"wind_direction":"СЗ","precipitation":0.0,"pressure":738,"humidity":89},{"date":"2024-07-25T09:00:00","cloudness":"Малооблачно, без осадков","temperature":23,"wind_speed":3,"wind_gust":5,"wind_direction":"С","precipitation":0.0,"pressure":738,"humidity":58},{"date":"2024-07-25T12:00:00","cloudness":"Малооблачно, без осадков","temperature":26,"wind_speed":3,"wind_gust":6,"wind_direction":"СЗ","precipitation":0.0,"pressure":738,"humidity":47},{"date":"2024-07-25T15:00:00","cloudness":"Облачно, без осадков","temperature":26,"wind_speed":2,"wind_gust":6,"wind_direction":"С","precipitation":0.0,"pressure":737,"humidity":46},{"date":"2024-07-25T18:00:00","cloudness":"Малооблачно, небольшой дождь","temperature":24,"wind_speed":3,"wind_gust":7,"wind_direction":"СВ","precipitation":0.3,"pressure":737,"humidity":54},{"date":"2024-07-25T21:00:00","cloudness":"Ясно","temperature":19,"wind_speed":1,"wind_gust":5,"wind_direction":"С","precipitation":0.0,"pressure":738,"humidity":80}]
|
||||||
@@ -6,9 +6,7 @@ from bs4 import Tag
|
|||||||
|
|
||||||
from .core import BaseWidgetParser, RowParser
|
from .core import BaseWidgetParser, RowParser
|
||||||
|
|
||||||
|
ONE_DAY_PARSER = BaseWidgetParser(".widget.widget-oneday .widget-items")
|
||||||
class OneDayParser(BaseWidgetParser):
|
|
||||||
SELECT = ".widget.widget-oneday .widget-items"
|
|
||||||
|
|
||||||
|
|
||||||
class DateParser(RowParser[datetime.datetime]):
|
class DateParser(RowParser[datetime.datetime]):
|
||||||
|
|||||||
0
gismeteo/route/__init__.py
Normal file
0
gismeteo/route/__init__.py
Normal file
12
gismeteo/route/api.py
Normal file
12
gismeteo/route/api.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
from gismeteo import dateutil
|
||||||
|
from gismeteo.api import GismeteoApi
|
||||||
|
|
||||||
|
|
||||||
|
def mount(app: FastAPI):
|
||||||
|
@app.get("/api/weather/{location}/{date}")
|
||||||
|
async def get_weather(location: str, date: str):
|
||||||
|
api = GismeteoApi()
|
||||||
|
result = await api.get_day(location, dateutil.parse(date))
|
||||||
|
return [item._asdict() for item in result]
|
||||||
@@ -8,9 +8,10 @@ from fastapi.openapi.docs import (
|
|||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
|
|
||||||
def apply(app: FastAPI):
|
def mount(app: FastAPI):
|
||||||
app.mount(
|
app.mount(
|
||||||
"/docs/static", StaticFiles(directory=Path(__file__).parent.parent / "static/docs")
|
"/docs/static",
|
||||||
|
StaticFiles(directory=Path(__file__).parent / "static"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@app.get("/docs", include_in_schema=False)
|
@app.get("/docs", include_in_schema=False)
|
||||||
43
gismeteo/route/view/__init__.py
Normal file
43
gismeteo/route/view/__init__.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
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 dateutil
|
||||||
|
from gismeteo.api import GismeteoApi
|
||||||
|
from gismeteo.mock import MOCK_DATA
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
@app.get("/weather/{location}")
|
||||||
|
async def get_weather_base(location: str):
|
||||||
|
return RedirectResponse(f"{location}/today")
|
||||||
|
|
||||||
|
@app.get("/weather/{location}/{date}", response_class=HTMLResponse)
|
||||||
|
async def get_weather(request: Request, location: str, date: str):
|
||||||
|
if date == "mock":
|
||||||
|
values = MOCK_DATA.values
|
||||||
|
else:
|
||||||
|
api = GismeteoApi()
|
||||||
|
values = await api.get_day(location, dateutil.parse(date))
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
request=request,
|
||||||
|
name="weather.html",
|
||||||
|
context={
|
||||||
|
"datetime": datetime,
|
||||||
|
"location": location,
|
||||||
|
"date": dateutil.parse(date),
|
||||||
|
"values": values,
|
||||||
|
},
|
||||||
|
)
|
||||||
27
gismeteo/route/view/filters.py
Normal file
27
gismeteo/route/view/filters.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
def wind_direction_icon(wind_direction: str) -> str:
|
||||||
|
return {
|
||||||
|
"С": "🡫",
|
||||||
|
"СВ": "🡯",
|
||||||
|
"В": "🡨",
|
||||||
|
"ЮВ": "🡬",
|
||||||
|
"Ю": "🡡",
|
||||||
|
"ЮЗ": "🡭",
|
||||||
|
"З": "🡪",
|
||||||
|
"СЗ": "🡦",
|
||||||
|
"штиль": "",
|
||||||
|
}.get(wind_direction, wind_direction)
|
||||||
|
|
||||||
|
|
||||||
|
def cloudness_icon(cloudness: str) -> str:
|
||||||
|
return {
|
||||||
|
"Ясно": "☀️",
|
||||||
|
"Малооблачно, без осадков": "🌤️",
|
||||||
|
"Облачно, без осадков": "⛅",
|
||||||
|
"Малооблачно, небольшой дождь": "🌦️",
|
||||||
|
"Пасмурно, без осадков": "☁️",
|
||||||
|
"Облачно, небольшой дождь": "🌧️",
|
||||||
|
"Облачно, дождь": "🌧️",
|
||||||
|
"Облачно, небольшой дождь, гроза": "⛈️",
|
||||||
|
"Малооблачно, дождь": "🌦️",
|
||||||
|
"Пасмурно, небольшой дождь": "🌧️",
|
||||||
|
}.get(cloudness, cloudness)
|
||||||
BIN
gismeteo/route/view/static/favicon.ico
Normal file
BIN
gismeteo/route/view/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
0
gismeteo/route/view/static/index.js
Normal file
0
gismeteo/route/view/static/index.js
Normal file
81
gismeteo/route/view/static/style.css
Normal file
81
gismeteo/route/view/static/style.css
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.date.now {
|
||||||
|
background: rgba(0, 128, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cloudness .icon {
|
||||||
|
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;
|
||||||
|
}
|
||||||
151
gismeteo/route/view/templates/weather.html
Normal file
151
gismeteo/route/view/templates/weather.html
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
<!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>Weather - {{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 date == datetime.date.today() else ''}}"
|
||||||
|
href="{{date - datetime.timedelta(days=1)}}">🡨</a>
|
||||||
|
<span>{{date.strftime('%a, %d %B %Y')}}</span>
|
||||||
|
<a class="button"
|
||||||
|
href="{{date + datetime.timedelta(days=1)}}">🡪</a>
|
||||||
|
</h3>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<!-- date -->
|
||||||
|
<tr>
|
||||||
|
{% for value in 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="{{values | length}}"
|
||||||
|
class="header">
|
||||||
|
Облачность
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
{% for value in values %}
|
||||||
|
<td class="cloudness">
|
||||||
|
<span class="icon">{{value.cloudness | cloudness_icon}}</span>
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
<!-- temperature -->
|
||||||
|
<tr>
|
||||||
|
<td colspan="{{values | length}}"
|
||||||
|
class="header">
|
||||||
|
Температура, °C
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
{% for value in 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="{{values | length}}"
|
||||||
|
class="header">
|
||||||
|
Направление ветра
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
{% for value in values %}
|
||||||
|
<td class="wind">
|
||||||
|
<span class="icon">{{value.wind_direction | wind_direction_icon}}</span>
|
||||||
|
<span class="direction">{{value.wind_direction}}</span>
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
<!-- wind_speed -->
|
||||||
|
<tr>
|
||||||
|
<td colspan="{{values | length}}"
|
||||||
|
class="header">
|
||||||
|
Скорость ветра, м/с
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
{% for value in 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="{{values | length}}"
|
||||||
|
class="header">
|
||||||
|
Осадки, мм
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
{% for value in 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="{{values | length}}"
|
||||||
|
class="header">
|
||||||
|
Давление, мм рт. ст.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
{% for value in 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="{{values | length}}"
|
||||||
|
class="header">
|
||||||
|
Влажность, %
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
{% for value in 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>
|
||||||
2
poetry.lock
generated
2
poetry.lock
generated
@@ -1795,4 +1795,4 @@ multidict = ">=4.0"
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.12"
|
python-versions = "^3.12"
|
||||||
content-hash = "1fd060f04380ffd2a390e2940573687736a03a7d668bc774f4942d9b63bcac3d"
|
content-hash = "6940ed3fa5467b63dde6410fcb427cbcf93c4366f967e07cf24601d51b43a28f"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ dateparser = "^1.2.0"
|
|||||||
|
|
||||||
[tool.poetry.group.app.dependencies]
|
[tool.poetry.group.app.dependencies]
|
||||||
fastapi = "^0.111.1"
|
fastapi = "^0.111.1"
|
||||||
|
jinja2 = "^3.1.4"
|
||||||
|
|
||||||
[tool.poetry.group.test.dependencies]
|
[tool.poetry.group.test.dependencies]
|
||||||
pytest = "^8.3.1"
|
pytest = "^8.3.1"
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
from pathlib import Path
|
import datetime
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from gismeteo.api import GismeteoApi
|
from gismeteo.api import GismeteoApi
|
||||||
|
from gismeteo.mock import MOCK_DATA
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="gismeteo_api", scope="module")
|
@pytest.fixture(name="gismeteo_api", scope="module")
|
||||||
@@ -10,13 +11,14 @@ def gismeteo_api_fixture() -> GismeteoApi:
|
|||||||
api = GismeteoApi()
|
api = GismeteoApi()
|
||||||
|
|
||||||
async def _request(endpoint: str) -> str:
|
async def _request(endpoint: str) -> str:
|
||||||
target = endpoint.split("/")[-1]
|
return MOCK_DATA.html
|
||||||
return (Path(__file__).parent / f"{target}.html").read_text()
|
|
||||||
|
|
||||||
api._request = _request
|
api._request = _request
|
||||||
return api
|
return api
|
||||||
|
|
||||||
|
|
||||||
async def test_api(gismeteo_api: GismeteoApi):
|
async def test_api(gismeteo_api: GismeteoApi):
|
||||||
result = await gismeteo_api.tomorrow("zmiyevka")
|
result = await gismeteo_api.get_day(
|
||||||
|
"zmiyevka", datetime.date.today() + datetime.timedelta(days=1)
|
||||||
|
)
|
||||||
assert len(result) == 8
|
assert len(result) == 8
|
||||||
|
|||||||
Reference in New Issue
Block a user