Files
remnawave-bedolaga-telegram…/app/webapi/server.py
c0mrade 9a2aea038a chore: add uv package manager and ruff linter configuration
- Add pyproject.toml with uv and ruff configuration
- Pin Python version to 3.13 via .python-version
- Add Makefile commands: lint, format, fix
- Apply ruff formatting to entire codebase
- Remove unused imports (base64 in yookassa/simple_subscription)
- Update .gitignore for new config files
2026-01-24 17:45:27 +03:00

113 lines
4.0 KiB
Python

from __future__ import annotations
import asyncio
import logging
import uvicorn
from app.config import settings
from .app import create_web_api_app
logger = logging.getLogger(__name__)
class WebAPIServer:
"""Асинхронный uvicorn-сервер для административного API."""
def __init__(self, app: object | None = None) -> None:
self._app = app or create_web_api_app()
workers = max(1, int(settings.WEB_API_WORKERS or 1))
if workers > 1:
logger.warning('WEB_API_WORKERS > 1 не поддерживается в embed-режиме, используем 1')
workers = 1
# Кастомный конфиг логирования - скрываем спам от WebSocket
log_config = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'default': {
'()': 'uvicorn.logging.DefaultFormatter',
'fmt': '%(levelprefix)s %(message)s',
'use_colors': None,
},
},
'handlers': {
'default': {
'formatter': 'default',
'class': 'logging.StreamHandler',
'stream': 'ext://sys.stderr',
},
},
'loggers': {
'uvicorn': {'handlers': ['default'], 'level': 'WARNING', 'propagate': False},
'uvicorn.error': {'level': 'WARNING', 'propagate': False},
'uvicorn.access': {'level': 'ERROR', 'propagate': False},
'uvicorn.protocols': {'level': 'WARNING', 'propagate': False},
'uvicorn.protocols.websockets': {'level': 'WARNING', 'propagate': False},
'uvicorn.protocols.websockets.websockets_impl': {'level': 'WARNING', 'propagate': False},
'websockets': {'level': 'WARNING', 'propagate': False},
'websockets.server': {'level': 'WARNING', 'propagate': False},
},
}
self._config = uvicorn.Config(
app=self._app,
host=settings.WEB_API_HOST,
port=int(settings.WEB_API_PORT or 8080),
log_level='warning',
workers=workers,
lifespan='on',
access_log=False,
log_config=log_config,
)
self._server = uvicorn.Server(self._config)
self._task: asyncio.Task[None] | None = None
async def start(self) -> None:
if self._task and not self._task.done():
logger.info('🌐 Административное веб-API уже запущено')
return
async def _serve() -> None:
try:
await self._server.serve()
except Exception as error: # pragma: no cover - логируем ошибки сервера
logger.exception('❌ Ошибка работы веб-API: %s', error)
raise
logger.info(
'🌐 Запуск административного API на %s:%s',
settings.WEB_API_HOST,
settings.WEB_API_PORT,
)
self._task = asyncio.create_task(_serve(), name='web-api-server')
started_attr = getattr(self._server, 'started', None)
started_event = getattr(self._server, 'started_event', None)
if isinstance(started_attr, asyncio.Event):
await started_attr.wait()
elif isinstance(started_event, asyncio.Event):
await started_event.wait()
else:
while not getattr(self._server, 'started', False):
if self._task.done():
break
await asyncio.sleep(0.1)
if self._task.done() and self._task.exception():
raise self._task.exception()
async def stop(self) -> None:
if not self._task:
return
logger.info('🛑 Остановка административного API')
self._server.should_exit = True
await self._task
self._task = None