Andrey Epifancev 9cdc62b26d chore: упрощены app.py, config.py и requirements.txt после выреза шаблона
Убраны SPA, CORS, SMB, РАИСА, SQLAlchemy-обработчики и зависимости БД/SMB;
настройки: ENV_FILE, SERVICE_NAME, NATS_ENABLED, WORKER_CONCURRENCY,
PUBLISH_RETRIES, DONE_CACHE_SIZE, MINIO_SECURE.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 02:25:35 +04:00

117 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Точка входа FastAPI сервиса tabular-parser.
Что здесь есть: настройки, подключение S3 (MinIO), NATS (nkeys JWT; пустые seed/jwt — без аутентификации),
middleware (gzip, proxy headers), healthcheck, обработчики исключений. Сервис без БД и фронта (DECISION-4).
Воркер обработки `recognition.request` и поле `nats` в healthcheck добавляются позже (src/core/nats.py).
"""
import base64
from contextlib import asynccontextmanager
from datetime import UTC, datetime
import nkeys
from fastapi import FastAPI
from loguru import logger
from miniopy_async import Minio
from nats.aio.client import Client as NATS
from starlette.middleware.gzip import GZipMiddleware
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
from src.api.router import router
from src.core.clients import S3Storage
from src.core.config import get_settings
from src.core.exception_handlers import register_exception_handlers
from src.core.logger import setup_logger
settings = get_settings()
VERSION = settings.VERSION
UP_TIME = datetime.now(UTC)
setup_logger(level="DEBUG" if settings.DEBUG else "INFO")
async def _connect_nats() -> NATS | None:
"""Подключение к NATS (core, без JetStream); при ошибке — ``None``, приложение всё равно стартует."""
nats_settings = settings.OPENBAO.NATS
options: dict = {"max_reconnect_attempts": 2}
if nats_settings.NATS_SEED and nats_settings.NATS_JWT:
kp = nkeys.from_seed(nats_settings.NATS_SEED.encode("utf-8"))
def sig_cb(nonce: bytes | str) -> bytes:
if isinstance(nonce, str):
nonce = nonce.encode("utf-8")
return base64.b64encode(kp.sign(nonce))
options |= {"user_jwt_cb": lambda: nats_settings.NATS_JWT.encode("utf-8"), "signature_cb": sig_cb}
nc = NATS()
try:
await nc.connect(nats_settings.NATS_URL, **options)
except Exception as exc:
logger.exception(
"Не удалось подключиться к NATS. error_category=nats_error, error_type={error_type}, error={error}",
error_type=type(exc).__name__,
error=repr(exc),
)
return None
logger.info("Подключение к NATS произошло")
return nc
async def _connect_s3() -> S3Storage | None:
"""Клиент S3 с проверкой бакета; при недоступности — ``None`` (приложение всё равно стартует)."""
minio = settings.OPENBAO.MINIO
s3_client = Minio(
minio.MINIO_URL,
access_key=minio.MINIO_ADMIN_LOGIN,
secret_key=minio.MINIO_ADMIN_PASSWORD,
secure=minio.MINIO_SECURE,
cert_check=False,
)
try:
bucket_exists = await s3_client.bucket_exists(minio.MINIO_BUCKET)
except Exception as exc:
logger.exception(
"Не удалось проверить наличие бакета. error_category=s3_error, error_type={error_type}, error={error}",
error_type=type(exc).__name__,
error=repr(exc),
)
await s3_client.close_session()
return None
logger.info("Бакет проверен. bucket_exists={bucket_exists}", bucket_exists=bucket_exists)
return S3Storage(client=s3_client, bucket_name=minio.MINIO_BUCKET)
@asynccontextmanager
async def lifespan(app: FastAPI):
app.state.storage = await _connect_s3()
app.state.nats = await _connect_nats() if settings.NATS_ENABLED else None
if not settings.NATS_ENABLED:
logger.info("NATS выключен (NATS_ENABLED=false)")
try:
logger.info("Приложение запущено")
yield
finally:
logger.info("Приложение закрывается")
if app.state.nats:
await app.state.nats.close()
if app.state.storage:
await app.state.storage.client.close_session()
app = FastAPI(title="tabular-parser", version=VERSION, root_path=settings.ROOT_PATH, lifespan=lifespan)
@app.get("/healthcheck", status_code=200)
async def healthcheck():
return {"uptime": UP_TIME, "version": VERSION}
app.include_router(router)
register_exception_handlers(app)
app.add_middleware(GZipMiddleware, minimum_size=1000, compresslevel=1)
app.add_middleware(ProxyHeadersMiddleware, trusted_hosts=["*"])