Compare commits

..
8 Commits
Author SHA1 Message Date
shmyga 160e1f9156 ci(version): 0.10.5
release / release (push) Successful in 3m53s
test / test (push) Successful in 52s
2026-09-30 20:57:43 +03:00
shmyga ad8f29c5ef feat(easel): add api link button to views
test / test (push) Successful in 59s
2026-09-30 20:56:17 +03:00
shmyga 4fc1c8c8e5 docs(readme): add test status badge
test / test (push) Successful in 56s
2026-09-30 19:43:27 +03:00
shmyga e414a93242 ci(action): add deploy action
release / release (push) Successful in 46s
test / test (push) Successful in 44s
2026-09-30 19:23:07 +03:00
shmyga 714fd1567c ci(action): use project-manager action
release / release (push) Successful in 39s
test / test (push) Successful in 49s
2026-09-30 16:03:23 +03:00
shmyga 1d1ba69cc8 ci(version): 0.10.4
release / release (push) Successful in 8m42s
test / test (push) Successful in 56s
2026-09-29 22:54:33 +03:00
shmyga f35d3f8cb8 build(docker): fix playwright version 2026-09-29 22:42:41 +03:00
shmyga b0187506fd ci(action): add setup composite action
test / test (push) Successful in 35s
2026-09-28 19:10:52 +03:00
16 changed files with 113 additions and 110 deletions
-42
View File
@@ -1,42 +0,0 @@
name: build
on:
push:
tags: ["*"]
jobs:
build:
runs-on: ubuntu-latest
steps:
# repo
- name: Check out repository
uses: actions/checkout@v7
# python
- name: Set up python
id: setup-python
uses: actions/setup-python@v7
with:
python-version: "3.14"
cache: "pip"
# project-manager
- name: Install Project Manager
run: |
pip install project-manager \
--index-url https://git.shmyga.ru/api/packages/InfernalGames/pypi/simple/ \
--extra-index-url https://pypi.org/simple
# setup
- name: Setup config
run: pm setup:config -vv
# build
- name: Build docker
run: pm build:docker -vv
# login docker
- name: Login to Docker Hub
uses: docker/login-action@v4
with:
registry: git.shmyga.ru
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# pubish
- name: Publish docker
run: pm publish:docker -vv
+22
View File
@@ -0,0 +1,22 @@
name: deploy
on:
workflow_run:
workflows: [release]
types: [completed]
jobs:
deploy:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USERNAME }}
key: ${{ secrets.DEPLOY_KEY }}
passphrase: ${{ secrets.DEPLOY_PASSPHRASE }}
script: |
cd /opt/gallery
docker compose pull
docker compose up -d
+20
View File
@@ -0,0 +1,20 @@
name: release
on:
push:
tags:
- "[0-9]+.[0-9]+.[0-9]+"
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: https://git.shmyga.ru/InfernalGames/project-manager@master
- run: pm build:docker -vv
- uses: docker/login-action@v4
with:
registry: git.shmyga.ru
username: ${{ vars.PUBLISH_USERNAME }}
password: ${{ secrets.PUBLISH_TOKEN }}
- run: pm publish:docker -vv
+5 -41
View File
@@ -6,45 +6,9 @@ jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
# repo - uses: actions/checkout@v7
- name: Check out repository - uses: https://git.shmyga.ru/InfernalGames/project-manager@master
uses: actions/checkout@v7
# python
- name: Set up python
id: setup-python
uses: actions/setup-python@v7
with: with:
python-version: "3.14" setup-python: true
# poetry - run: pm lint -vv
- name: Load cached Poetry installation - run: pm test -vv
id: cached-poetry
uses: actions/cache@v5
with:
path: ~/.local
key: poetry-1 # increment to reset cache
- name: Install Poetry
if: steps.cached-poetry.outputs.cache-hit != 'true'
uses: snok/install-poetry@v1
with:
virtualenvs-create: true
virtualenvs-in-project: true
virtualenvs-path: .venv
installer-parallel: true
# venv
- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v5
with:
path: .venv
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: ~/.local/bin/poetry install --no-interaction --no-root
- name: Install project
run: ~/.local/bin/poetry install --no-interaction
# test
- name: Run tests
run: |
source .venv/bin/activate
coverage run -m pytest tests/
coverage report
+1 -1
View File
@@ -27,7 +27,7 @@ FROM python:3.14-slim
ENV PLAYWRIGHT_BROWSERS_PATH="/opt/playwright-browsers" ENV PLAYWRIGHT_BROWSERS_PATH="/opt/playwright-browsers"
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \
pip install playwright && \ pip install playwright==1.62 && \
playwright install --with-deps chromium playwright install --with-deps chromium
ENV PATH="/app/.venv/bin:$PATH" ENV PATH="/app/.venv/bin:$PATH"
+2
View File
@@ -1,5 +1,7 @@
# API Gallery # API Gallery
[![test](https://git.shmyga.ru/InfernalGames/gallery/actions/workflows/test.yaml/badge.svg?branch=master)](https://git.shmyga.ru/InfernalGames/gallery/actions?workflow=test.yaml)
Currently implemented APIs: Currently implemented APIs:
- Weather forecast - Weather forecast
+23 -11
View File
@@ -1,4 +1,3 @@
import datetime
from pathlib import Path from pathlib import Path
from urllib import parse from urllib import parse
@@ -13,9 +12,7 @@ from gallery.easel.depends.schedule import (
ScheduleApiDepends, ScheduleApiDepends,
ScheduleGroupChannelsDepends, ScheduleGroupChannelsDepends,
) )
from gallery.sketch.bundle import ApiBundle
from gallery.sketch.schedule.api import ScheduleApi from gallery.sketch.schedule.api import ScheduleApi
from gallery.sketch.schedule.model import Schedule
from ..common.utils.template import build_templates from ..common.utils.template import build_templates
from .filters import timedelta_format from .filters import timedelta_format
@@ -52,12 +49,6 @@ async def get_schedule_index(request: AppRequest, provider: str | None = None, q
) )
async def _get_group_schedule(api: ApiBundle, channels: list[tuple[str, str]], date: datetime.date) -> list[Schedule]:
return [
await api.get_api(ScheduleApi, provider).get_schedule(channel_id, date) for provider, channel_id in channels
]
@router.get("/group/{group_id}", response_class=RedirectResponse) @router.get("/group/{group_id}", response_class=RedirectResponse)
async def get_group_default(group_id: str): async def get_group_default(group_id: str):
return RedirectResponse(f"{parse.quote(group_id)}/today") return RedirectResponse(f"{parse.quote(group_id)}/today")
@@ -72,7 +63,16 @@ async def get_group(request: AppRequest, channels: ScheduleGroupChannelsDepends,
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, request=request,
name="group.html", name="group.html",
context={"responses": responses}, context={
"responses": responses,
"api_route": "/".join(
[
"/api/schedule/group",
request.path_params["group_id"],
request.path_params["date"],
]
),
},
) )
@@ -87,5 +87,17 @@ async def get_channel(request: AppRequest, schedule_api: ScheduleApiDepends, cha
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, request=request,
name="channel.html", name="channel.html",
context={"response": response}, context={
"response": response,
"api_route": (
"/".join(
[
"/api/schedule",
request.path_params["provider"],
request.path_params["channel"],
request.path_params["date"],
]
)
),
},
) )
@@ -10,6 +10,9 @@ locale=request.state.language)}}
{% block header %} {% block header %}
<app-link href="/schedule" <app-link href="/schedule"
icon="tv">{{_("TV program")}}</app-link> icon="tv">{{_("TV program")}}</app-link>
<app-link target="_blank"
href="{{ api_route }}"
icon="filetype-json"></app-link>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@@ -44,7 +47,8 @@ locale=request.state.language)}}
{% for value in response.values %} {% for value in response.values %}
<tr class="{{'d-none' if (live and not value.live) else 'table-success' if (not live and value.live) else ''}}"> <tr class="{{'d-none' if (live and not value.live) else 'table-success' if (not live and value.live) else ''}}">
<td> <td>
<span>{{value.start.strftime('%H:%M')}}</span>&nbsp;<span class="small ms-1">({{(value.end - value.start) | timedelta_format}})</span> <span>{{value.start.strftime('%H:%M')}}</span>&nbsp;<span class="small ms-1">({{(value.end - value.start) |
timedelta_format}})</span>
</td> </td>
<td>{{value.label}}</td> <td>{{value.label}}</td>
</tr> </tr>
@@ -9,6 +9,9 @@
{% block header %} {% block header %}
<app-link href="/schedule" <app-link href="/schedule"
icon="tv">{{_("TV program")}}</app-link> icon="tv">{{_("TV program")}}</app-link>
<app-link target="_blank"
href="{{ api_route }}"
icon="filetype-json"></app-link>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
+12 -1
View File
@@ -37,7 +37,18 @@ def build_weather_response(request: AppRequest, response: WeatherResponse):
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, request=request,
name="weather.html", name="weather.html",
context={"response": response}, context={
"response": response,
"api_route": "/".join(
[
"/api/weather",
request.path_params["provider"],
request.path_params["location"],
response.period,
request.path_params.get("date") or request.path_params.get("days") or "",
]
),
},
) )
@@ -7,6 +7,9 @@ locale=request.state.language)}}{% endblock %}
{% block header %} {% block header %}
<app-link href="/weather" <app-link href="/weather"
icon="brightness-high">{{_("Weather")}}</app-link> icon="brightness-high">{{_("Weather")}}</app-link>
<app-link target="_blank"
href="{{ api_route }}"
icon="filetype-json"></app-link>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
+2 -1
View File
@@ -29,6 +29,7 @@ class YandexTVApi(ScheduleApi):
return self.channel_parser.parse(data) return self.channel_parser.parse(data)
async def get_schedule(self, channel_id: str, date: datetime.date) -> Schedule: async def get_schedule(self, channel_id: str, date: datetime.date) -> Schedule:
endpoint = f"{self.lr}/channel/{channel_id}?date={date:%Y-%m-%d}" channel_path_part = "channel" if "-" in channel_id else "channels"
endpoint = f"{self.lr}/{channel_path_part}/{channel_id}?date={date:%Y-%m-%d}"
data = await self.source.request(endpoint) data = await self.source.request(endpoint)
return self.schedule_parser.parse(ScheduleData(date, channel_id, data)) return self.schedule_parser.parse(ScheduleData(date, channel_id, data))
+9 -9
View File
@@ -1,16 +1,16 @@
[project] [project]
name = "gallery" name = "gallery"
description = "" description = ""
version = "0.10.3" version = "0.10.5"
readme = "README.md" readme = "README.md"
authors = [{ name = "shmyga", email = "shmyga.z@gmail.com" }] authors = [{ name = "shmyga", email = "shmyga.z@gmail.com" }]
requires-python = '>=3.14,<4.0' requires-python = ">=3.14,<4.0"
dependencies = [ dependencies = [
'aiohttp (>=3.14,<4.0)', "aiohttp (>=3.14,<4.0)",
'beautifulsoup4 (>=4.15,<5.0)', "beautifulsoup4 (>=4.15,<5.0)",
'dateparser (>=1.4,<2.0)', "dateparser (>=1.4,<2.0)",
'pydantic (>=2.13,<3.0)', "pydantic (>=2.13,<3.0)",
'aiocache[redis] (>=0.12,<0.13)', "aiocache[redis] (>=0.12,<0.13)",
"playwright (>=1.62.0,<2.0.0)", "playwright (>=1.62.0,<2.0.0)",
] ]
@@ -30,10 +30,10 @@ gallery = "gallery.main:run"
[tool.poetry] [tool.poetry]
packages = [{ include = "gallery" }] packages = [{ include = "gallery" }]
requires-poetry = '>=2.0.0,<3.0.0' requires-poetry = ">=2.0.0,<3.0.0"
[build-system] [build-system]
requires = ['poetry-core (>=2.0.0,<3.0.0)'] requires = ["poetry-core (>=2.0.0,<3.0.0)"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"
[tool.black] [tool.black]
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "gallery", "name": "gallery",
"version": "0.10.3", "version": "0.10.5",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "gallery", "name": "gallery",
"version": "0.10.3", "version": "0.10.5",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "gallery", "name": "gallery",
"version": "0.10.3", "version": "0.10.5",
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "webpack --mode production", "build": "webpack --mode production",
+3
View File
@@ -48,4 +48,7 @@
&.bi-cast { &.bi-cast {
mask-image: url(bootstrap-icons/icons/cast.svg); mask-image: url(bootstrap-icons/icons/cast.svg);
} }
&.bi-filetype-json {
mask-image: url(bootstrap-icons/icons/filetype-json.svg);
}
} }