- NATSSettings: subject'ы запроса и ответа из окружения (имена контракта по умолчанию), проверка формата и попарного различия; NATS_SEED и NATS_JWT — SecretStr; request_subjects() для request_subject(pipeline, subjects). - hide_input_in_errors во всех классах настроек: ошибка «нет поля» не печатает вход группы с JWT и seed. - build_nats_auth читает секреты через get_secret_value(). - Схемы контракта скопированы из docs/contracts (request, result-message). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
55 lines
2.4 KiB
Python
55 lines
2.4 KiB
Python
"""Subject'ы NATS, заголовок дедупликации и идентификаторы сообщений контракта v1.0.
|
||
|
||
Источник: `docs/context/03-architecture.md`, «Контракт обмена через NATS»; схема recognition-request
|
||
(`Nats-Msg-Id = <job_id>:<attempt>`) и recognition-result-message
|
||
(`ack:<job_id>:<attempt>` / `result:<job_id>:<attempt>`).
|
||
|
||
Модуль намеренно совпадает с копией в arm-core. `request_subject`, `REQUEST_SUBJECTS`, `IMAGE_REQUEST_SUBJECT` и
|
||
значение `"image"` в `Pipeline` tabular-parser не использует (он слушает только `tabular`) — это сознательный паритет
|
||
с arm-core, а не мёртвый код.
|
||
"""
|
||
|
||
import uuid
|
||
from collections.abc import Mapping
|
||
from typing import Literal, get_args
|
||
|
||
Pipeline = Literal["tabular", "image"]
|
||
|
||
NATS_MSG_ID_HEADER = "Nats-Msg-Id"
|
||
RESULT_SUBJECT = "files.tabular_parsed"
|
||
TABULAR_REQUEST_SUBJECT = "files.tabular_uploaded"
|
||
IMAGE_REQUEST_SUBJECT = "files.img_uploaded"
|
||
|
||
REQUEST_SUBJECTS: Mapping[str, str] = {
|
||
"tabular": TABULAR_REQUEST_SUBJECT,
|
||
"image": IMAGE_REQUEST_SUBJECT,
|
||
}
|
||
|
||
|
||
def request_subject(pipeline: str, subjects: Mapping[str, str] = REQUEST_SUBJECTS) -> str:
|
||
"""Subject запроса по pipeline. `subjects` — карта `pipeline → subject` окружения
|
||
(`NATSSettings.request_subjects()`); по умолчанию — имена контракта: `tabular` → `files.tabular_uploaded`,
|
||
`image` → `files.img_uploaded`."""
|
||
try:
|
||
return subjects[pipeline]
|
||
except KeyError:
|
||
raise ValueError(f"неизвестный pipeline {pipeline!r}: допустимы {', '.join(get_args(Pipeline))}") from None
|
||
|
||
|
||
def request_msg_id(job_id: uuid.UUID | str, attempt: int) -> str:
|
||
"""`Nats-Msg-Id` запроса: `<job_id>:<attempt>`."""
|
||
return f"{job_id}:{attempt}"
|
||
|
||
|
||
def ack_msg_id(job_id: uuid.UUID | str, attempt: int) -> str:
|
||
"""`Nats-Msg-Id` ack (`status = processing`) на `files.tabular_parsed`: `ack:<job_id>:<attempt>`."""
|
||
return f"ack:{job_id}:{attempt}"
|
||
|
||
|
||
def result_msg_id(job_id: uuid.UUID | str, attempt: int) -> str:
|
||
"""`Nats-Msg-Id` результата (`done | needs_check | error`) на `files.tabular_parsed`.
|
||
|
||
Форма: `result:<job_id>:<attempt>`.
|
||
"""
|
||
return f"result:{job_id}:{attempt}"
|