Второй CI-гейт после тестов: ruff как статический анализатор всего Python-кода (services + scripts). Раньше ни линта, ни проверки типов в CI не было вовсе. Конфиг ruff.toml: правила E/F/W/I/UP/B/SIM/C4, line-length 100. Осознанно выключены E501 (длину держит форматтер; длинные RU-комментарии — норма), B008 (Depends()/Query() в дефолтах — идиома FastAPI, не баг) и UP042 ((str, Enum)→StrEnum меняет __str__/сериализацию — не трогаем). Починено под ноль находок: - B904 (11): raise ... from exc / from None — читаемые цепочки исключений в Celery-ретраях и HTTPException, ошибки обработки не маскируют исходные. - SIM105 (5): try/except/pass → contextlib.suppress (faiss remove_ids, lsh.remove, сброс кэша, ws-disconnect, парс года). - C416/SIM108/B905/F841/UP035/UP017/F401/I001: dict(rows), тернарник, zip strict, мёртвая переменная, устаревшие импорты, timezone.utc→UTC, чистка/сортировка. Обвязка: scripts/run_lint.sh (ruff в изолированном python:3.11-slim), шаг «Линт» в job test перед юнит-тестами (падаем раньше). make lint / make lint-fix. Все 41 юнит-тест по-прежнему зелёные, изменённые файлы компилируются. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
142 lines
4.8 KiB
Python
142 lines
4.8 KiB
Python
"""Celery задача форматирования библиографии по ГОСТ."""
|
||
|
||
from typing import Any
|
||
|
||
from celery.utils.log import get_task_logger
|
||
from sqlalchemy import select
|
||
|
||
from app.celery_app import celery_app
|
||
from app.db import db_session
|
||
from app.formatters.gost_7_0_5 import GOST705Formatter
|
||
from app.formatters.gost_7_1 import GOST71Formatter, _get_sort_key
|
||
|
||
logger = get_task_logger(__name__)
|
||
|
||
|
||
@celery_app.task(
|
||
name="gost.format_bibliography",
|
||
bind=True,
|
||
max_retries=3,
|
||
default_retry_delay=30,
|
||
)
|
||
def format_bibliography(
|
||
self,
|
||
task_id: str,
|
||
doc_ids: list[int],
|
||
style: str = "7.1",
|
||
) -> dict[str, Any]:
|
||
"""
|
||
Форматировать список литературы по ГОСТ для переданных doc_ids.
|
||
|
||
Args:
|
||
task_id: ID задачи в PostgreSQL
|
||
doc_ids: Список ID документов из PostgreSQL
|
||
style: Стиль форматирования — "7.1" (полная) или "7.0.5" (краткая)
|
||
|
||
Returns:
|
||
dict с отформатированной библиографией
|
||
"""
|
||
from app.models import Document, Task
|
||
|
||
logger.info(
|
||
f"Форматирование библиографии для задачи {task_id!r}: "
|
||
f"{len(doc_ids)} документов, стиль ГОСТ {style}"
|
||
)
|
||
|
||
try:
|
||
with db_session() as session:
|
||
# Обновить статус
|
||
task = session.get(Task, task_id)
|
||
if task:
|
||
task.status = "processing"
|
||
session.commit()
|
||
|
||
# Получить документы
|
||
docs = session.execute(
|
||
select(Document).where(Document.id.in_(doc_ids))
|
||
).scalars().all()
|
||
|
||
if not docs:
|
||
raise ValueError(f"Документы не найдены: {doc_ids}")
|
||
|
||
# Выбрать форматтер
|
||
formatter = GOST71Formatter() if style == "7.1" else GOST705Formatter()
|
||
|
||
# Преобразовать ORM объекты в словари для форматирования
|
||
docs_dicts = []
|
||
for doc in docs:
|
||
docs_dicts.append({
|
||
"id": doc.id,
|
||
"title": doc.title,
|
||
"authors": doc.authors or [],
|
||
"year": doc.year,
|
||
"journal": doc.journal,
|
||
"volume": doc.volume,
|
||
"issue": doc.issue,
|
||
"pages": doc.pages,
|
||
"doi": doc.doi,
|
||
"url": doc.url,
|
||
"lang": doc.lang or "ru",
|
||
"source": doc.source,
|
||
})
|
||
|
||
# Сортировать: кириллица (рус. авторы) → латиница (иностр.)
|
||
sorted_docs = sorted(docs_dicts, key=lambda d: _get_sort_key(d))
|
||
|
||
bibliography = []
|
||
for i, doc_dict in enumerate(sorted_docs, 1):
|
||
if style == "7.1":
|
||
citation = formatter.format_full(doc_dict)
|
||
else:
|
||
citation = formatter.format_short(doc_dict)
|
||
|
||
bibliography.append({
|
||
"number": i,
|
||
"citation": citation,
|
||
"doc_id": doc_dict["id"],
|
||
})
|
||
|
||
result = {
|
||
"bibliography": bibliography,
|
||
"style": style,
|
||
"total": len(bibliography),
|
||
}
|
||
|
||
# Сохранить результат
|
||
task = session.get(Task, task_id)
|
||
if task:
|
||
task.result = result
|
||
task.status = "done"
|
||
task.queue_position = None
|
||
session.commit()
|
||
|
||
logger.info(
|
||
f"Библиография сформирована для задачи {task_id!r}: "
|
||
f"{len(bibliography)} записей по ГОСТ {style}"
|
||
)
|
||
|
||
# Уведомить пользователя
|
||
celery_app.send_task(
|
||
"notify.send_task_done",
|
||
args=[task_id],
|
||
queue="queue.notify",
|
||
)
|
||
|
||
return result
|
||
|
||
except Exception as exc:
|
||
logger.error(f"Ошибка при форматировании библиографии для задачи {task_id!r}: {exc}", exc_info=True)
|
||
|
||
try:
|
||
from app.models import Task
|
||
with db_session() as session:
|
||
task = session.get(Task, task_id)
|
||
if task:
|
||
task.status = "failed"
|
||
task.error = str(exc)
|
||
session.commit()
|
||
except Exception as db_exc:
|
||
logger.error(f"Не удалось обновить статус задачи: {db_exc}")
|
||
|
||
raise self.retry(exc=exc, countdown=30) from exc
|