Commit 8459f362 authored by Kirill Unitsaev's avatar Kirill Unitsaev

Merge branch 'api-refactoring' into 'master'

Resolve "Рефакторинг модуля api" Closes #2 See merge request !1
parents 70582fc5 97b5bb62
import aiohttp
from .api import ALTRepoAPI
from .parser import ALTRepoParser
class ALTRepo:
def __init__(self):
self._session: aiohttp.ClientSession | None = None
self.api: ALTRepoAPI | None = None
self.parser: ALTRepoParser | None = None
async def init(self):
if self._session is None:
self._session = aiohttp.ClientSession()
self.api = ALTRepoAPI(self._session)
self.parser = ALTRepoParser(self._session)
async def close(self):
await self._session.close()
altrepo = ALTRepo()
__all__ = ("altrepo")
from .methods import ALTRepoAPI
class RequestValidationError(Exception):
"""Ошибка валидации параметров запроса (400)"""
pass
class DataNotFoundError(Exception):
"""Данные не найдены в базе (404)"""
pass
class TooManyRequests(Exception):
"""Cлишком много запросо (429)"""
pass
class UnexpectedResponseError(Exception):
"""Непредвиденный ответ от сервера"""
def __init__(self, status_code: int, message: str = ""):
self.status_code = status_code
self.message = message or f"Unexpected response status: {status_code}"
super().__init__(self.message)
...@@ -4,17 +4,32 @@ from urllib.parse import urlencode ...@@ -4,17 +4,32 @@ from urllib.parse import urlencode
from typing import List from typing import List
from . import models from . import models
from . import errors
class BaseAPI: class BaseAPI:
BASE_URL = "https://rdb.altlinux.org/api" BASE_URL = "https://rdb.altlinux.org/api"
def __init__(self): def __init__(self, session: aiohttp.ClientSession):
self.session: aiohttp.ClientSession | None = None self.session = session
async def init(self): async def _handle_response(self, resp: aiohttp.ClientResponse):
if self.session is None:
self.session = aiohttp.ClientSession() match resp.status:
case 200 | 201:
return await resp.json()
case 400:
raise errors.RequestValidationError("Request parameters validation error (400)")
case 404:
raise errors.DataNotFoundError("Requested data not found in database (404)")
case 429:
raise errors.TooManyRequests("Too Many Requests (429)")
case _:
try:
error_text = await resp.text()
except:
error_text = ""
raise errors.UnexpectedResponseError(resp.status, error_text)
async def get(self, path: str, params: dict = None): async def get(self, path: str, params: dict = None):
url = f"{self.BASE_URL}/{path.lstrip('/')}" url = f"{self.BASE_URL}/{path.lstrip('/')}"
...@@ -23,18 +38,13 @@ class BaseAPI: ...@@ -23,18 +38,13 @@ class BaseAPI:
v, list) else v for k, v in params.items()}) v, list) else v for k, v in params.items()})
url += f"?{query}" url += f"?{query}"
async with self.session.get(url) as resp: async with self.session.get(url) as resp:
resp.raise_for_status() return await self._handle_response(resp)
return await resp.json()
async def post(self, path: str, data: dict = None, json: dict = None): async def post(self, path: str, data: dict = None, json: dict = None):
url = f"{self.BASE_URL}/{path.lstrip('/')}" url = f"{self.BASE_URL}/{path.lstrip('/')}"
async with self.session.post(url, data=data, json=json) as resp: async with self.session.post(url, data=data, json=json) as resp:
resp.raise_for_status() return await self._handle_response(resp)
return await resp.json()
async def close(self):
await self.session.close()
class APIInfo: class APIInfo:
...@@ -59,6 +69,7 @@ class PackageInfo: ...@@ -59,6 +69,7 @@ class PackageInfo:
data = await self.client.post("/package/packages_by_file_names", json=data) data = await self.client.post("/package/packages_by_file_names", json=data)
return models.PackageByFileNameModel(**data) return models.PackageByFileNameModel(**data)
class PackagesetInfo: class PackagesetInfo:
def __init__(self, client: BaseAPI): def __init__(self, client: BaseAPI):
self.client = client self.client = client
...@@ -137,18 +148,15 @@ class SiteInfo: ...@@ -137,18 +148,15 @@ class SiteInfo:
return models.SiteWatchByMaintainerModel(**data) return models.SiteWatchByMaintainerModel(**data)
class PackagesAPI: class ALTRepoAPI:
def __init__(self): def __init__(self, session: aiohttp.ClientSession):
self._client = BaseAPI() self._client = BaseAPI(session)
self.api = APIInfo(self._client) self.api = APIInfo(self._client)
self.package = PackageInfo(self._client) self.package = PackageInfo(self._client)
self.packageset = PackagesetInfo(self._client) self.packageset = PackagesetInfo(self._client)
self.bug = BugInfo(self._client) self.bug = BugInfo(self._client)
self.file = FileInfo(self._client) self.file = FileInfo(self._client)
self.site = SiteInfo(self._client) self.site = SiteInfo(self._client)
async def init(self): async def version(self) -> models.APIVersion:
await self._client.init() return await self.api.version()
async def close(self):
await self._client.close()
from .methods import ALTRepoParser
import aiohttp
from typing import Literal
from . import models
class BaseParser:
def __init__(self, session: aiohttp.ClientSession):
self.session = session
async def get(self, url: str):
async with self.session.get(url) as resp:
resp.raise_for_status()
return await resp.text()
class NewsInfo:
def __init__(self, client: BaseParser):
self.client = client
async def news_url(self, news_type: Literal["sisyphus", "p11", "bugs"]):
...
async def sisyphus(self):
...
class ALTRepoParser:
def __init__(self, session: aiohttp.ClientSession):
self._client = BaseParser(session)
self.news = NewsInfo(self._client)
from pydantic import BaseModel
from typing import List, Dict, Any
from .methods import PackagesAPI
api = PackagesAPI()
...@@ -2,7 +2,7 @@ from telegrinder import Dispatch, Message ...@@ -2,7 +2,7 @@ from telegrinder import Dispatch, Message
from telegrinder.rules import Command, Argument, Text, IsPrivate from telegrinder.rules import Command, Argument, Text, IsPrivate
from telegrinder.tools.formatting import HTMLFormatter from telegrinder.tools.formatting import HTMLFormatter
from api import api from altrepo import altrepo
from database.models import User from database.models import User
from data.keyboards import bugs_keyboards from data.keyboards import bugs_keyboards
...@@ -18,7 +18,7 @@ async def bugs_handler(m: Message, user: User | None, maintainer: str | None = N ...@@ -18,7 +18,7 @@ async def bugs_handler(m: Message, user: User | None, maintainer: str | None = N
if maintainer: if maintainer:
maintainer = maintainer.lower() maintainer = maintainer.lower()
maintainer_data = await api.site.all_maintainers("sisyphus") maintainer_data = await altrepo.api.site.all_maintainers("sisyphus")
if not any( if not any(
_m.packager_nickname == maintainer for _m in maintainer_data.maintainers _m.packager_nickname == maintainer for _m in maintainer_data.maintainers
): ):
...@@ -29,7 +29,7 @@ async def bugs_handler(m: Message, user: User | None, maintainer: str | None = N ...@@ -29,7 +29,7 @@ async def bugs_handler(m: Message, user: User | None, maintainer: str | None = N
else: else:
return return
bugs_data = await api.bug.bugzilla_by_maintainer(maintainer) bugs_data = await altrepo.api.bug.bugzilla_by_maintainer(maintainer)
unresolved_bugs = [bug for bug in bugs_data.bugs if bug.status not in ["RESOLVED", "CLOSED"]] unresolved_bugs = [bug for bug in bugs_data.bugs if bug.status not in ["RESOLVED", "CLOSED"]]
if not len(unresolved_bugs): if not len(unresolved_bugs):
......
from telegrinder import Dispatch, Message from telegrinder import Dispatch, Message
from telegrinder.rules import Command from telegrinder.rules import Command
from api import api from altrepo import altrepo
dp = Dispatch() dp = Dispatch()
@dp.message(Command("altrepo_info")) @dp.message(Command("altrepo_info"))
async def info_handler(m: Message) -> None: async def info_handler(m: Message) -> None:
api_data = await api.api.version() api_data = await altrepo.api.version()
await m.answer( await m.answer(
"Telegram бот для портала packages.altlinux.org\n" "Telegram бот для портала packages.altlinux.org\n"
f"Версия API: {api_data.version}" f"Версия API: {api_data.version}"
......
...@@ -4,8 +4,7 @@ from telegrinder.rules import Command, PayloadEqRule, PayloadMarkupRule, Text, I ...@@ -4,8 +4,7 @@ from telegrinder.rules import Command, PayloadEqRule, PayloadMarkupRule, Text, I
from database.func import DB from database.func import DB
from data.keyboards import profile_keyboards from data.keyboards import profile_keyboards
from api import api from altrepo import altrepo
from api.models import BugzillaInfoModel
from services.menu import send_menu from services.menu import send_menu
...@@ -21,10 +20,10 @@ async def profile_handler(m: Message, user: User | None) -> None: ...@@ -21,10 +20,10 @@ async def profile_handler(m: Message, user: User | None) -> None:
if user is None: if user is None:
return return
maintainer_data = await api.site.maintainer_info(user.default_branch, user.maintainer) maintainer_data = await altrepo.api.site.maintainer_info(user.default_branch, user.maintainer)
maintainer = maintainer_data.information maintainer = maintainer_data.information
bugs_data: BugzillaInfoModel = await api.bug.bugzilla_by_maintainer(user.maintainer) bugs_data = await altrepo.api.bug.bugzilla_by_maintainer(user.maintainer)
unresolved_bugs = [bug for bug in bugs_data.bugs if bug.status not in ["RESOLVED", "CLOSED"]] unresolved_bugs = [bug for bug in bugs_data.bugs if bug.status not in ["RESOLVED", "CLOSED"]]
await m.answer( await m.answer(
"Профиль:\n\n" "Профиль:\n\n"
...@@ -51,7 +50,7 @@ async def callback_confirm_handler(cb: CallbackQuery) -> None: ...@@ -51,7 +50,7 @@ async def callback_confirm_handler(cb: CallbackQuery) -> None:
text="Введите никнейм сопровождающего:", text="Введите никнейм сопровождающего:",
) )
api_data = await api.site.all_maintainers("sisyphus") api_data = await altrepo.api.site.all_maintainers("sisyphus")
while True: while True:
msg, _ = await wm.wait(MESSAGE_FROM_USER, cb.from_user.id, release=HasText()) msg, _ = await wm.wait(MESSAGE_FROM_USER, cb.from_user.id, release=HasText())
maintainer = msg.text.unwrap().lower() maintainer = msg.text.unwrap().lower()
...@@ -99,9 +98,9 @@ async def callback_confirm_handler(cb: CallbackQuery, branch: str) -> None: ...@@ -99,9 +98,9 @@ async def callback_confirm_handler(cb: CallbackQuery, branch: str) -> None:
) )
@dp.callback_query(PayloadEqRule("command/menu")) @dp.callback_query(PayloadEqRule("command/menu"))
async def menu_handler(cb: CallbackQuery, user: User | None): async def menu_handler(cb: CallbackQuery):
send_menu(cb, user) send_menu(cb)
@dp.message(Command(["menu", "меню"]) | Text(["меню", "menu"]), IsPrivate()) @dp.message(Command(["menu", "меню"]) | Text(["меню", "menu"]), IsPrivate())
async def menu_handler(m: Message, user: User | None): async def menu_handler(m: Message):
await send_menu(m, user) await send_menu(m)
...@@ -7,7 +7,7 @@ from aiohttp import ClientResponseError ...@@ -7,7 +7,7 @@ from aiohttp import ClientResponseError
from database.models import User from database.models import User
from collections import defaultdict from collections import defaultdict
from api import api from altrepo import altrepo
dp = Dispatch() dp = Dispatch()
wm = WaiterMachine(dp) wm = WaiterMachine(dp)
...@@ -22,10 +22,10 @@ async def search_file_handler(m: Message, user: User | None, file_name: str, bra ...@@ -22,10 +22,10 @@ async def search_file_handler(m: Message, user: User | None, file_name: str, bra
if not branch: if not branch:
branch = user.default_branch \ branch = user.default_branch \
if user.default_branch \ if user.default_branch \
else (await api.packageset.active_packagesets()).packagesets[0] else (await altrepo.api.packageset.active_packagesets()).packagesets[0]
try: try:
search_file_data = await api.file.packages_by_file(branch, file_name) search_file_data = await altrepo.api.file.packages_by_file(branch, file_name)
except ClientResponseError: except ClientResponseError:
await m.answer("Ничего не найдено.") await m.answer("Ничего не найдено.")
return return
...@@ -49,16 +49,16 @@ async def search_file_handler(m: Message, user: User | None, file_name: str, bra ...@@ -49,16 +49,16 @@ async def search_file_handler(m: Message, user: User | None, file_name: str, bra
if not branch: if not branch:
branch = user.default_branch \ branch = user.default_branch \
if user.default_branch \ if user.default_branch \
else (await api.packageset.active_packagesets()).packagesets[0] else (await altrepo.api.packageset.active_packagesets()).packagesets[0]
try: try:
search_file_data: FilesModel = await api.file.search(branch, file_name) search_file_data: FilesModel = await altrepo.api.file.search(branch, file_name)
except ClientResponseError: except ClientResponseError:
await m.answer("Ничего не найдено.") await m.answer("Ничего не найдено.")
return return
list_files = [file.file_name for file in search_file_data.files] list_files = [file.file_name for file in search_file_data.files]
search_packages_data: PackageByFileNameModel = await api.package.packages_by_file_names( search_packages_data: PackageByFileNameModel = await altrepo.api.package.packages_by_file_names(
list_files, branch, "x86_64" list_files, branch, "x86_64"
) )
......
...@@ -5,7 +5,7 @@ from database.func import DB ...@@ -5,7 +5,7 @@ from database.func import DB
from database.models import User from database.models import User
from data.keyboards import start_keyboards from data.keyboards import start_keyboards
from api import api from altrepo import altrepo
from services.menu import send_menu from services.menu import send_menu
...@@ -23,7 +23,7 @@ async def start_handler(m: Message, user: User | None) -> None: ...@@ -23,7 +23,7 @@ async def start_handler(m: Message, user: User | None) -> None:
await m.answer( await m.answer(
"Введите никнейм сопровождающего:" "Введите никнейм сопровождающего:"
) )
api_data = await api.site.all_maintainers("sisyphus") api_data = await altrepo.api.site.all_maintainers("sisyphus")
while True: while True:
msg, _ = await wm.wait(MESSAGE_FROM_USER, m.from_user.id, release=HasText()) msg, _ = await wm.wait(MESSAGE_FROM_USER, m.from_user.id, release=HasText())
maintainer = msg.text.unwrap().lower() maintainer = msg.text.unwrap().lower()
...@@ -66,4 +66,4 @@ async def start_handler(m: Message, user: User | None) -> None: ...@@ -66,4 +66,4 @@ async def start_handler(m: Message, user: User | None) -> None:
maintainer, maintainer,
branch branch
) )
await send_menu(m, user) await send_menu(m)
...@@ -4,9 +4,7 @@ from telegrinder.rules import Command, Text, IsPrivate, Argument ...@@ -4,9 +4,7 @@ from telegrinder.rules import Command, Text, IsPrivate, Argument
from datetime import datetime from datetime import datetime
import locale import locale
from api import api from altrepo import altrepo
from services.menu import send_menu
from database.models import User from database.models import User
...@@ -17,7 +15,7 @@ wm = WaiterMachine(dp) ...@@ -17,7 +15,7 @@ wm = WaiterMachine(dp)
@dp.message(Text(["statistics", "статистика"], ignore_case=True), IsPrivate()) @dp.message(Text(["statistics", "статистика"], ignore_case=True), IsPrivate())
async def statistics_handler(m: Message, user: User | None, branch: str | None = None) -> None: async def statistics_handler(m: Message, user: User | None, branch: str | None = None) -> None:
active_branches = (await api.packageset.active_packagesets()).packagesets active_branches = (await altrepo.api.packageset.active_packagesets()).packagesets
if branch: if branch:
branch = branch.strip().lower() branch = branch.strip().lower()
if branch not in active_branches: if branch not in active_branches:
...@@ -29,7 +27,7 @@ async def statistics_handler(m: Message, user: User | None, branch: str | None = ...@@ -29,7 +27,7 @@ async def statistics_handler(m: Message, user: User | None, branch: str | None =
else: else:
branch = active_branches[0] branch = active_branches[0]
statistics_data = (await api.packageset.repository_statistics(branch)).branches[0] statistics_data = (await altrepo.api.packageset.repository_statistics(branch)).branches[0]
locale.setlocale(locale.LC_TIME, 'ru_RU.UTF-8') locale.setlocale(locale.LC_TIME, 'ru_RU.UTF-8')
......
...@@ -2,7 +2,7 @@ from telegrinder import Dispatch, Message ...@@ -2,7 +2,7 @@ from telegrinder import Dispatch, Message
from telegrinder.rules import Command, Argument, Text, IsUser from telegrinder.rules import Command, Argument, Text, IsUser
from telegrinder.tools.formatting import HTMLFormatter from telegrinder.tools.formatting import HTMLFormatter
from api import api from altrepo import altrepo
from database.models import User from database.models import User
from data.keyboards import watch_keyboards from data.keyboards import watch_keyboards
...@@ -16,7 +16,7 @@ async def watch_handler(m: Message, user: User | None, maintainer: str | None = ...@@ -16,7 +16,7 @@ async def watch_handler(m: Message, user: User | None, maintainer: str | None =
if maintainer: if maintainer:
maintainer = maintainer.lower() maintainer = maintainer.lower()
maintainer_data = await api.site.all_maintainers("sisyphus") maintainer_data = await altrepo.api.site.all_maintainers("sisyphus")
if not any( if not any(
_m.packager_nickname == maintainer for _m in maintainer_data.maintainers _m.packager_nickname == maintainer for _m in maintainer_data.maintainers
): ):
...@@ -27,7 +27,7 @@ async def watch_handler(m: Message, user: User | None, maintainer: str | None = ...@@ -27,7 +27,7 @@ async def watch_handler(m: Message, user: User | None, maintainer: str | None =
else: else:
return return
watch_data = await api.site.watch_by_maintainer(maintainer) watch_data = await altrepo.api.site.watch_by_maintainer(maintainer)
if not len(watch_data.packages): if not len(watch_data.packages):
await m.answer("Нет устаревших пакетов") await m.answer("Нет устаревших пакетов")
......
from telegrinder import API, Telegrinder, Token from telegrinder import API, Telegrinder, Token, Message
from telegrinder.modules import logger from telegrinder.modules import logger
from telegrinder.tools.formatting import HTMLFormatter from telegrinder.tools.formatting import HTMLFormatter
from telegrinder.types import LinkPreviewOptions from telegrinder.types import LinkPreviewOptions
from telegrinder.node import Error
from config import config from config import config
from api import api from altrepo import altrepo
from altrepo.api.errors import TooManyRequests
from database.models import db, User from database.models import db, User
from middlewares import UserMiddleware from middlewares import UserMiddleware
...@@ -21,10 +23,16 @@ bot.on.message.register_middleware(UserMiddleware) ...@@ -21,10 +23,16 @@ bot.on.message.register_middleware(UserMiddleware)
@bot.loop_wrapper.lifespan.on_startup @bot.loop_wrapper.lifespan.on_startup
async def startup(): async def startup():
db.create_tables([User]) db.create_tables([User])
await api.init() logger.info("initializing ALTRepo")
await altrepo.init()
@bot.loop_wrapper.lifespan.on_shutdown @bot.loop_wrapper.lifespan.on_shutdown
async def shutdown(): async def shutdown():
await api.close() logger.info("stopping ALTRepo")
await altrepo.close()
@bot.on.error()
async def error_handler(err: Error[TooManyRequests], m: Message):
await m.answer(f"Слишком много запросов!")
bot.run_forever(skip_updates=True) bot.run_forever(skip_updates=True)
from telegrinder import API, ABCMiddleware, Message from telegrinder import ABCMiddleware, Message
from telegrinder.bot import Context from telegrinder.bot import Context
from database.func import DB from database.func import DB
......
from telegrinder import Message from telegrinder import Message
from data.keyboards import menu_keyboards from data.keyboards import menu_keyboards
from api import api
from database.models import User async def send_menu(m: Message) -> None:
async def send_menu(m: Message, user: User | None) -> None:
await m.answer( await m.answer(
"menu:", "menu:",
reply_markup=menu_keyboards.menu_kb reply_markup=menu_keyboards.menu_kb
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment