refactor: rename project to gallery
This commit is contained in:
@@ -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,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"),
|
||||||
0
gallery/painting/gismeteo/__init__.py
Normal file
0
gallery/painting/gismeteo/__init__.py
Normal file
@@ -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)
|
||||||
5
gallery/painting/gismeteo/mock/__init__.py
Normal file
5
gallery/painting/gismeteo/mock/__init__.py
Normal file
@@ -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
gallery/sketch/mock.py
Normal file
15
gallery/sketch/mock.py
Normal 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
|
||||||
0
gallery/sketch/schedule/__init_.py
Normal file
0
gallery/sketch/schedule/__init_.py
Normal file
26
gallery/sketch/schedule/model.py
Normal file
26
gallery/sketch/schedule/model.py
Normal 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]
|
||||||
0
gallery/sketch/weather/__init__.py
Normal file
0
gallery/sketch/weather/__init__.py
Normal 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
gallery/sketch/weather/mock/__init__.py
Normal file
12
gallery/sketch/weather/mock/__init__.py
Normal 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:
|
||||||
@@ -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"),
|
|
||||||
)
|
|
||||||
@@ -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()
|
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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 .
|
||||||
|
|||||||
5
scripts/lint
Executable file
5
scripts/lint
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
cd "$(dirname $(dirname "$0"))" || exit
|
||||||
|
|
||||||
|
poetry run pylint gallery
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
5
scripts/test
Executable file
5
scripts/test
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
cd "$(dirname $(dirname "$0"))" || exit
|
||||||
|
|
||||||
|
poetry run pytest tests
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user