fix: настройки — SERVICE_NAME только tabular-parser, PUBLISH_RETRIES ≥ 0 (M-1, M-3, M-4, M-5)
- SERVICE_NAME: Literal["tabular-parser"] (R-35); в config.py, .env.example, README и CLAUDE.md исправлено «имя queue group» — queue group задаёт константа REQUEST_QUEUE_GROUP в core/nats.py. - PUBLISH_RETRIES: NonNegativeInt — 0 означает одну попытку без повторов, как и принимает NatsConnection. - Удалён неиспользуемый модульный settings = get_settings() из config.py. - pyproject.toml: убран override mypy для raisa_fastapi_protected_api (зависимость удалена D-1). - tests/conftest.py: фабрика minimal_settings (Settings из временного .env с минимальным окружением, окружение процесса скрыто); test_config.py — умолчания при минимальном окружении (M-8), PUBLISH_RETRIES=0/-1, SERVICE_NAME≠tabular-parser. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
bf2f60b3b5
commit
8685441fcd
@ -25,7 +25,7 @@ Stateless-сервис разбора XLS/XLSX/XLSB: ОСВ по счетам (
|
||||
|
||||
**Оставлено от шаблона:** `api/run_server.py` (точка входа платформы, `SERVER__HOST/PORT`), `api/src/app.py` (lifespan:
|
||||
подключение к MinIO и NATS, ошибка подключения логируется и не мешает старту; `GET /healthcheck`, gzip/proxy middleware),
|
||||
`core/config.py` (pydantic-settings, `SERVICE_NAME` — `service.name` конвертов и имя queue group, `OPENBAO__MINIO__*`
|
||||
`core/config.py` (pydantic-settings, `SERVICE_NAME` — только `service.name` конвертов (queue group — константа в `core/nats.py`), `OPENBAO__MINIO__*`
|
||||
включая `MINIO_SECURE`, `OPENBAO__NATS__*`, `DEBUG`, `ENV_FILE`, `NATS_ENABLED`, `WORKER_CONCURRENCY`, `PUBLISH_RETRIES`, `DONE_CACHE_SIZE`), `core/clients.py` (S3), `core/logger.py`,
|
||||
`core/exception_handlers.py` (без SQLAlchemy), `common/exceptions.py`, `pytest.ini`, `requirements*.txt`, `raisa_config.yml`,
|
||||
`setup/setup.sh` + `pip.conf` (без npm-шага). Шаблонный `core/nats_callback.py` (consumer `recognition.result` — зона
|
||||
|
||||
@ -135,9 +135,11 @@ curl -s localhost:8000/healthcheck
|
||||
| `ENV_FILE` | `.env` | путь к файлу окружения (от `api/`) |
|
||||
| `NATS_ENABLED` | `true` | `false` — не подключаться к NATS, воркер не стартует |
|
||||
| `WORKER_CONCURRENCY` | `1` | сколько заданий обрабатывается одновременно |
|
||||
| `PUBLISH_RETRIES` | `3` | повторы публикации ack и результата в NATS после первой попытки (пауза 2 с) |
|
||||
| `PUBLISH_RETRIES` | `3` | повторы публикации ack и результата в NATS после первой попытки (пауза 2 с; `0` — без повторов) |
|
||||
| `DONE_CACHE_SIZE` | `1000` | размер LRU обработанных `job_id` (дедупликация внутри процесса) |
|
||||
| `SERVER__HOST` | `0.0.0.0` | адрес привязки HTTP-сервера |
|
||||
| `SERVER__PORT` | `8000` | порт HTTP-сервера |
|
||||
|
||||
Имя сервиса в конвертах контракта (`service.name`) и имя queue group — `tabular-parser` (`SERVICE_NAME`, менять не нужно).
|
||||
`SERVICE_NAME` — имя сервиса в конвертах контракта (`service.name`); допустимо только `tabular-parser`, иное значение —
|
||||
ошибка конфигурации при старте. Queue group подписки NATS — константа `tabular-parser` в `src/core/nats.py`, настройкой
|
||||
не меняется.
|
||||
|
||||
@ -25,13 +25,14 @@ OPENBAO__NATS__NATS_JWT=
|
||||
# Префикс путей приложения за прокси и версия в OpenAPI
|
||||
ROOT_PATH=/
|
||||
VERSION=1.0.0
|
||||
# `service.name` конвертов контракта и имя queue group NATS; менять не нужно, пока контракт v1.x
|
||||
# `service.name` конвертов контракта; допустимо только tabular-parser (иное — ошибка старта). Queue group NATS —
|
||||
# константа в коде (`tabular-parser`), этой настройкой не меняется
|
||||
SERVICE_NAME=tabular-parser
|
||||
# false — не подключаться к NATS (воркер не стартует; /healthcheck отдаёт nats = off)
|
||||
NATS_ENABLED=true
|
||||
# Число заданий, обрабатываемых одновременно
|
||||
WORKER_CONCURRENCY=1
|
||||
# Повторы публикации ack и результата в NATS после первой попытки (3 — всего до 4 попыток, пауза 2 с)
|
||||
# Повторы публикации ack и результата в NATS после первой попытки (3 — всего до 4 попыток, пауза 2 с; 0 — без повторов)
|
||||
PUBLISH_RETRIES=3
|
||||
# Размер process-local LRU обработанных job_id (защита от дублей доставки)
|
||||
DONE_CACHE_SIZE=1000
|
||||
|
||||
@ -20,7 +20,7 @@ explicit_package_bases = true
|
||||
|
||||
# Сторонние пакеты без типов (или недоступные вне банка)
|
||||
[[tool.mypy.overrides]]
|
||||
module = ["nkeys.*", "nats.*", "raisa_fastapi_protected_api", "raisa_fastapi_protected_api.*"]
|
||||
module = ["nkeys.*", "nats.*"]
|
||||
ignore_missing_imports = true
|
||||
|
||||
# miniopy_async: пакет типизирован с ошибкой (Minio не виден mypy при реэкспорте) — типы не подтягиваем, Minio = Any
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import os
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import PositiveInt
|
||||
from pydantic import NonNegativeInt, PositiveInt
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
@ -41,10 +42,11 @@ class Settings(BaseSettings):
|
||||
ROOT_PATH: str = "/"
|
||||
VERSION: str = "1.0.0"
|
||||
|
||||
SERVICE_NAME: str = "tabular-parser" # `service.name` конвертов контракта и имя queue group NATS
|
||||
# `service.name` конвертов контракта (enum ServiceName); queue group NATS — константа REQUEST_QUEUE_GROUP в nats.py
|
||||
SERVICE_NAME: Literal["tabular-parser"] = "tabular-parser"
|
||||
NATS_ENABLED: bool = True # false — приложение не подключается к NATS (воркер не стартует)
|
||||
WORKER_CONCURRENCY: PositiveInt = 1 # число заданий, обрабатываемых одновременно
|
||||
PUBLISH_RETRIES: PositiveInt = 3 # повторы публикации ack/результата в NATS после первой попытки (до 4 попыток)
|
||||
PUBLISH_RETRIES: NonNegativeInt = 3 # повторы публикации ack/результата после первой попытки (0 — без повторов)
|
||||
DONE_CACHE_SIZE: PositiveInt = 1000 # размер process-local LRU обработанных job_id
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
@ -57,6 +59,3 @@ class Settings(BaseSettings):
|
||||
|
||||
def get_settings() -> Settings:
|
||||
return Settings()
|
||||
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
"""Общие фикстуры тестов."""
|
||||
|
||||
import os
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from src.core.config import Settings
|
||||
|
||||
# Переменные, которые читает Settings: убираются из окружения процесса, чтобы тест видел только свой файл
|
||||
_SETTINGS_ENV_PREFIXES = (
|
||||
"OPENBAO__",
|
||||
"SERVER__",
|
||||
"DEBUG",
|
||||
"ROOT_PATH",
|
||||
"VERSION",
|
||||
"SERVICE_NAME",
|
||||
"NATS_ENABLED",
|
||||
"WORKER_CONCURRENCY",
|
||||
"PUBLISH_RETRIES",
|
||||
"DONE_CACHE_SIZE",
|
||||
)
|
||||
|
||||
# Минимальное окружение spec «Старт с минимальным окружением»: MinIO, адрес NATS и DEBUG; без seed/jwt, БД и SMB
|
||||
MINIMAL_ENV = """\
|
||||
DEBUG=true
|
||||
OPENBAO__MINIO__MINIO_URL=localhost:9000
|
||||
OPENBAO__MINIO__MINIO_ADMIN_LOGIN=minioadmin
|
||||
OPENBAO__MINIO__MINIO_ADMIN_PASSWORD=minioadmin
|
||||
OPENBAO__MINIO__MINIO_BUCKET=arm-ka
|
||||
OPENBAO__NATS__NATS_URL=nats://localhost:4222
|
||||
"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def minimal_settings(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Callable[[str], Settings]:
|
||||
"""Фабрика Settings только из временного `.env` с `MINIMAL_ENV` и строками `extra` (окружение процесса скрыто)."""
|
||||
for name in list(os.environ):
|
||||
if name.upper().startswith(_SETTINGS_ENV_PREFIXES):
|
||||
monkeypatch.delenv(name)
|
||||
|
||||
def build(extra: str = "") -> Settings:
|
||||
env_file = tmp_path / ".env"
|
||||
env_file.write_text(MINIMAL_ENV + extra, encoding="utf-8")
|
||||
return Settings(_env_file=env_file) # type: ignore[call-arg]
|
||||
|
||||
return build
|
||||
41
api/tests/unit/test_config.py
Normal file
41
api/tests/unit/test_config.py
Normal file
@ -0,0 +1,41 @@
|
||||
"""Unit-тесты настроек `src/core/config.py` (spec «Старт с минимальным окружением»; R-35)."""
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from src.core.config import Settings
|
||||
|
||||
SettingsFactory = Callable[..., Settings]
|
||||
|
||||
|
||||
def test_minimal_environment_gives_defaults(minimal_settings: SettingsFactory) -> None:
|
||||
settings = minimal_settings()
|
||||
|
||||
assert settings.DEBUG is True
|
||||
assert settings.OPENBAO.MINIO.MINIO_BUCKET == "arm-ka"
|
||||
assert settings.OPENBAO.NATS.NATS_URL == "nats://localhost:4222"
|
||||
assert settings.OPENBAO.NATS.NATS_SEED == ""
|
||||
assert settings.OPENBAO.NATS.NATS_JWT == ""
|
||||
assert settings.NATS_ENABLED is True
|
||||
assert settings.SERVICE_NAME == "tabular-parser"
|
||||
assert settings.WORKER_CONCURRENCY == 1
|
||||
assert settings.PUBLISH_RETRIES == 3 # noqa: PLR2004 - значение по умолчанию из spec
|
||||
assert settings.DONE_CACHE_SIZE == 1000 # noqa: PLR2004 - значение по умолчанию из spec
|
||||
|
||||
|
||||
def test_publish_retries_zero_is_allowed(minimal_settings: SettingsFactory) -> None:
|
||||
"""0 — без повторов: одна попытка публикации (NatsConnection принимает publish_retries ≥ 0)."""
|
||||
assert minimal_settings("PUBLISH_RETRIES=0\n").PUBLISH_RETRIES == 0
|
||||
|
||||
|
||||
def test_publish_retries_negative_is_rejected(minimal_settings: SettingsFactory) -> None:
|
||||
with pytest.raises(ValidationError, match="PUBLISH_RETRIES"):
|
||||
minimal_settings("PUBLISH_RETRIES=-1\n")
|
||||
|
||||
|
||||
def test_service_name_other_than_contract_value_is_rejected(minimal_settings: SettingsFactory) -> None:
|
||||
"""SERVICE_NAME — `service.name` конвертов контракта: другое значение — ошибка конфигурации при старте."""
|
||||
with pytest.raises(ValidationError, match="SERVICE_NAME"):
|
||||
minimal_settings("SERVICE_NAME=scan-gateway\n")
|
||||
Loading…
x
Reference in New Issue
Block a user