refactor: rename project to gallery

This commit is contained in:
2024-08-11 20:46:04 +03:00
parent 3785b8ce5d
commit 72fd378c40
48 changed files with 117 additions and 92 deletions
+1
View File
@@ -0,0 +1 @@
# Gallery
@@ -19,15 +19,15 @@
"version": "0.2.1", "version": "0.2.1",
"configurations": [ "configurations": [
{ {
"name": "app", "name": "gallery:app",
"type": "debugpy", "type": "debugpy",
"request": "launch", "request": "launch",
"module": "uvicorn", "module": "uvicorn",
"args": [ "args": [
"weather.main:app", "gallery.main:app",
"--reload", "--reload",
"--log-config", "--log-config",
"weather/logging.yaml" "gallery/logging.yaml"
] ]
} }
] ]
@@ -2,7 +2,7 @@ import locale
from fastapi import FastAPI from fastapi import FastAPI
from weather.api import WeatherApi from gallery.sketch.weather.api import WeatherApi
from .route import api, doc, view from .route import api, doc, view
@@ -2,8 +2,8 @@ import datetime
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from weather.api import WeatherApi from gallery.sketch.weather.api import WeatherApi
from weather.model import WeatherResponse from gallery.sketch.weather.model import WeatherResponse
def mount(app: FastAPI): def mount(app: FastAPI):
@@ -1,6 +1,6 @@
import datetime import datetime
from enum import Enum from enum import Enum
from typing import NamedTuple, Optional from typing import NamedTuple
class TagType(str, Enum): class TagType(str, Enum):
@@ -6,12 +6,11 @@ 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.location import LOCATION_BUNDLE from gallery.sketch.weather.api import WeatherApi
from gismeteo.mock import MOCK_DATA from gallery.sketch.weather.mock import WEATHER_MOCK_DATA
from weather.api import WeatherApi from gallery.sketch.weather.model import WeatherResponse
from weather.app.route.util import TagType, TagUtil
from weather.model import WeatherResponse
from ..util import TagType, TagUtil
from .filters import cloudness_icon, wind_direction_icon from .filters import cloudness_icon, wind_direction_icon
@@ -39,11 +38,13 @@ def mount(app: FastAPI):
@app.get("/weather", response_class=HTMLResponse) @app.get("/weather", response_class=HTMLResponse)
async def get_weather_list(request: Request): async def get_weather_list(request: Request):
weather_api: WeatherApi = request.app.state.weather_api
locations = await weather_api.get_locations()
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, request=request,
name="index.html", name="index.html",
context={ context={
"locations": LOCATION_BUNDLE._values, "locations": locations,
}, },
) )
@@ -53,12 +54,12 @@ def mount(app: FastAPI):
@app.get("/weather/{location}/day/mock", response_class=HTMLResponse) @app.get("/weather/{location}/day/mock", response_class=HTMLResponse)
async def get_weather_day_mock(request: Request): async def get_weather_day_mock(request: Request):
response = MOCK_DATA.get_response("day") response = WEATHER_MOCK_DATA.get_response("day")
return build_weather_response(request, response) return build_weather_response(request, response)
@app.get("/weather/{location}/days/mock", response_class=HTMLResponse) @app.get("/weather/{location}/days/mock", response_class=HTMLResponse)
async def get_weather_days_mock(request: Request): async def get_weather_days_mock(request: Request):
response = MOCK_DATA.get_response("days") response = WEATHER_MOCK_DATA.get_response("days")
return build_weather_response(request, response) return build_weather_response(request, response)
@app.get("/weather/{location}/day/{date}", response_class=HTMLResponse) @app.get("/weather/{location}/day/{date}", response_class=HTMLResponse)
@@ -1,4 +1,4 @@
from weather.model import Cloudness, Precipitation, Sky, WindDirection from gallery.sketch.weather.model import Cloudness, Precipitation, Sky, WindDirection
def wind_direction_icon(wind_direction: WindDirection) -> str: def wind_direction_icon(wind_direction: WindDirection) -> str:

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

@@ -18,7 +18,7 @@
<body class="app-container"> <body class="app-container">
<ul> <ul>
{% for location in locations %} {% for location in locations %}
<li><a href="weather/{{location.name}}-{{location.location_id}}">{{location.name}}</a></li> <li><a href="weather/{{location}}">{{location}}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
</body> </body>
+3 -3
View File
@@ -3,15 +3,15 @@ from pathlib import Path
import uvicorn import uvicorn
from gismeteo.api import GismeteoApi from gallery.easel import build_app
from weather.app import build_app from gallery.painting.gismeteo.api import GismeteoApi
app = build_app(GismeteoApi()) app = build_app(GismeteoApi())
def run(): def run():
uvicorn.run( uvicorn.run(
"weather.main:app", "gallery.main:app",
host="0.0.0.0", host="0.0.0.0",
port=8000, port=8000,
log_config=str(Path(__file__).parent / "logging.yaml"), log_config=str(Path(__file__).parent / "logging.yaml"),
@@ -6,11 +6,10 @@ import aiohttp
from aiocache import cached from aiocache import cached
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from weather.api import WeatherApi from gallery.sketch.weather.api import WeatherApi
from weather.model import WeatherResponse, WeatherValue from gallery.sketch.weather.model import WeatherResponse, WeatherValue
from . import datehelp from . import datehelp
from .location import LOCATION_BUNDLE
from .parser import DAYS_PARSER, LOCATION_PARSER, ONE_DAY_PARSER, ROW_PARSERS from .parser import DAYS_PARSER, LOCATION_PARSER, ONE_DAY_PARSER, ROW_PARSERS
logger = logging.getLogger("gismeteo") logger = logging.getLogger("gismeteo")
@@ -80,14 +79,18 @@ class GismeteoApi(WeatherApi):
values=values, values=values,
) )
async def get_locations(self) -> list[str]:
return [
"orel-4432",
"zmiyevka-184640",
]
@cached(ttl=CACHE_TTL) @cached(ttl=CACHE_TTL)
async def get_day(self, location_id: str, date: datetime.date) -> WeatherResponse: async def get_day(self, location_id: str, date: datetime.date) -> WeatherResponse:
location = LOCATION_BUNDLE.parse(location_id) data = await self._request(f"weather-{location_id}/{datehelp.dump(date)}")
data = await self._request(f"weather-{location}/{datehelp.dump(date)}")
return self._parse_oneday(date, data) return self._parse_oneday(date, data)
@cached(ttl=CACHE_TTL) @cached(ttl=CACHE_TTL)
async def get_days(self, location_id: str, days: int) -> WeatherResponse: async def get_days(self, location_id: str, days: int) -> WeatherResponse:
location = LOCATION_BUNDLE.parse(location_id) data = await self._request(f"weather-{location_id}/{days}-days")
data = await self._request(f"weather-{location}/{days}-days")
return self._parse_manydays(data) return self._parse_manydays(data)
@@ -14,7 +14,7 @@ class Day(str, Enum):
def parse(value: str) -> datetime.date: def parse(value: str) -> datetime.date:
if value == "today" or value == "mock": if value in ["today", "mock"]:
return datetime.date.today() return datetime.date.today()
elif value == "tomorrow": elif value == "tomorrow":
return datetime.date.today() + datetime.timedelta(days=1) return datetime.date.today() + datetime.timedelta(days=1)
@@ -0,0 +1,5 @@
from pathlib import Path
from gallery.sketch.mock import MockData
GISMETEO_MOCK_DATA = MockData(Path(__file__).parent / "data")
@@ -5,7 +5,7 @@ from typing import Iterable
import dateparser import dateparser
from bs4 import Tag from bs4 import Tag
from weather.model import Cloudness, Precipitation, Sky, WindDirection from gallery.sketch.weather.model import Cloudness, Precipitation, Sky, WindDirection
from .core import BaseWidgetParser, RowParser from .core import BaseWidgetParser, RowParser
+15
View File
@@ -0,0 +1,15 @@
import json
from pathlib import Path
class MockData:
def __init__(self, data_dir) -> None:
self._data_dir = data_dir
def get_html(self, key: str) -> str:
return (self._data_dir / f"{key}.html").read_text()
def get_json(self, key: str) -> dict:
data = json.loads((Path(__file__).parent / f"data/{key}.json").read_text())
return data
View File
+26
View File
@@ -0,0 +1,26 @@
import datetime
from pydantic import BaseModel
class Model(BaseModel):
class Config:
use_enum_values = True
class Channel(Model):
id: str
name: str
class ScheduleItem(Model):
start: datetime.datetime
end: datetime.datetime
label: str
category: str | None
class Schedule(Model):
channel: Channel
date: datetime.date
items: list[ScheduleItem]
View File
@@ -4,6 +4,9 @@ from .model import WeatherResponse
class WeatherApi: class WeatherApi:
async def get_locations(self) -> list[str]:
raise NotImplementedError
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
+12
View File
@@ -0,0 +1,12 @@
from pathlib import Path
from gallery.sketch.mock import MockData
from gallery.sketch.weather.model import WeatherResponse
class WeatherMockData(MockData):
def get_response(self, key: str) -> WeatherResponse:
return WeatherResponse(**self.get_json(key))
WEATHER_MOCK_DATA = WeatherMockData(Path(__file__).parent / "data")
@@ -1,6 +1,6 @@
import datetime import datetime
from weather.model import Cloudness, Precipitation, Sky, WeatherValue, WindDirection from .model import Cloudness, Precipitation, Sky, WeatherValue, WindDirection
def build_weather_value(date: datetime.datetime) -> WeatherValue: def build_weather_value(date: datetime.datetime) -> WeatherValue:
-34
View File
@@ -1,34 +0,0 @@
from typing import NamedTuple
class LocationValue(NamedTuple):
location_id: int
name: str
def __str__(self) -> str:
return f"{self.name}-{self. location_id}"
@classmethod
def parse(cls, source: str) -> "LocationValue":
name, location = source.split("-")
return cls(int(location), name)
class LocationBundle:
def __init__(self, *values: LocationValue) -> None:
self._values = values
self._values_by_name = {value.name: value for value in self._values}
self._values_by_id = {value.location_id: value for value in self._values}
def parse(self, value: str) -> LocationValue:
if str.isdigit(value):
return self._values_by_id[int(value)]
if value in self._values_by_name:
return self._values_by_name[value]
return LocationValue.parse(value)
LOCATION_BUNDLE = LocationBundle(
LocationValue(4432, "orel"),
LocationValue(184640, "zmiyevka"),
)
-17
View File
@@ -1,17 +0,0 @@
import json
from pathlib import Path
from gismeteo.api import WeatherResponse
class MockData:
def get_html(self, key: str) -> str:
return (Path(__file__).parent / f"data/{key}.html").read_text()
def get_response(self, key: str) -> WeatherResponse:
data = json.loads((Path(__file__).parent / f"data/{key}.json").read_text())
return WeatherResponse(**data)
MOCK_DATA = MockData()
+3 -3
View File
@@ -1,10 +1,10 @@
[tool.poetry] [tool.poetry]
name = "weather" name = "gallery"
version = "0.1.0" version = "0.1.0"
description = "" description = ""
authors = ["shmyga <shmyga.z@gmail.com>"] authors = ["shmyga <shmyga.z@gmail.com>"]
readme = "README.md" readme = "README.md"
packages = [{ include = "weather" }, { include = "gismeteo" }] packages = [{ include = "gallery" }]
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.12" python = "^3.12"
@@ -32,7 +32,7 @@ requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts] [tool.poetry.scripts]
app = 'weather.main:run' gallery = "gallery.main:run"
[tool.pytest.ini_options] [tool.pytest.ini_options]
addopts = "-p no:warnings" addopts = "-p no:warnings"
+1 -1
View File
@@ -2,4 +2,4 @@
set -e set -e
cd "$(dirname $(dirname "$0"))" || exit cd "$(dirname $(dirname "$0"))" || exit
docker build -t shmyga/weather . docker build -t shmyga/gallery .
Executable
+5
View File
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -e
cd "$(dirname $(dirname "$0"))" || exit
poetry run pylint gallery
+1 -1
View File
@@ -2,7 +2,7 @@
set -e set -e
cd "$(dirname $(dirname "$0"))" || exit cd "$(dirname $(dirname "$0"))" || exit
IMAGE_NAME=shmyga/weather IMAGE_NAME=shmyga/gallery
docker tag $IMAGE_NAME instreamatic.com:8083/$IMAGE_NAME docker tag $IMAGE_NAME instreamatic.com:8083/$IMAGE_NAME
docker push instreamatic.com:8083/$IMAGE_NAME docker push instreamatic.com:8083/$IMAGE_NAME
+1 -1
View File
@@ -2,4 +2,4 @@
set -e set -e
cd "$(dirname $(dirname "$0"))" || exit cd "$(dirname $(dirname "$0"))" || exit
docker run --rm -p 8000:80 shmyga/weather docker run --rm -p 8000:80 shmyga/gallery
Executable
+5
View File
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -e
cd "$(dirname $(dirname "$0"))" || exit
poetry run pytest tests
+5 -5
View File
@@ -2,8 +2,8 @@ import datetime
import pytest import pytest
from gismeteo.api import GismeteoApi from gallery.painting.gismeteo.api import GismeteoApi
from gismeteo.mock import MOCK_DATA from gallery.painting.gismeteo.mock import GISMETEO_MOCK_DATA
@pytest.fixture(name="gismeteo_api", scope="module") @pytest.fixture(name="gismeteo_api", scope="module")
@@ -11,17 +11,17 @@ def gismeteo_api_fixture() -> GismeteoApi:
api = GismeteoApi() api = GismeteoApi()
async def _request(endpoint: str) -> str: async def _request(endpoint: str) -> str:
return MOCK_DATA.get_html(endpoint.split("/")[-1]) return GISMETEO_MOCK_DATA.get_html(endpoint.split("/")[-1])
api._request = _request api._request = _request
return api return api
async def test_day(gismeteo_api: GismeteoApi): async def test_day(gismeteo_api: GismeteoApi):
result = await gismeteo_api.get_day("zmiyevka", datetime.date.today()) result = await gismeteo_api.get_day("test", datetime.date.today())
assert len(result.values) == 8 assert len(result.values) == 8
async def test_days(gismeteo_api: GismeteoApi): async def test_days(gismeteo_api: GismeteoApi):
result = await gismeteo_api.get_days("zmiyevka", 10) result = await gismeteo_api.get_days("test", 10)
assert len(result.values) == 10 assert len(result.values) == 10