diff --git a/Dockerfile b/Dockerfile index 6031770..93ddc60 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,6 @@ # syntax=docker/dockerfile:1 # Образ scan-gateway для dev и `make check-full`. Контекст сборки — корень репозитория. +# Библиотека передаётся контекстом statements (make docker-build, STATEMENTS_DIR). # Платформенные пакеты raisa_* (Nexus) в образ не ставятся — та же логика, что в `make venv`. FROM python:3.11-slim @@ -9,15 +10,11 @@ ENV PYTHONUNBUFFERED=1 \ WORKDIR /app/api COPY api/requirements.txt ./requirements.txt -RUN grep -Ev '^(raisa_|pylib_ai)' requirements.txt > /tmp/req.txt \ - && pip install --no-cache-dir -r /tmp/req.txt \ +RUN --mount=type=bind,from=statements,target=/tmp/statements,rw \ + grep -v '^raisa_' requirements.txt > /tmp/req.txt \ + && pip install --no-cache-dir -r /tmp/req.txt /tmp/statements \ && rm /tmp/req.txt -# Локальная библиотека передаётся дополнительным контекстом (make docker-build). -COPY --from=statements /setup.py /setup.cfg /pyproject.toml /MANIFEST.in /VERSION /requirements.txt /README.md /tmp/statements/ -COPY --from=statements /arm_statements /tmp/statements/arm_statements -RUN pip install --no-cache-dir /tmp/statements && rm -rf /tmp/statements - COPY api/ ./ EXPOSE 8000 diff --git a/Makefile b/Makefile index c031125..40cb4cb 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ COMPOSE_FILE ?= ../docs/dev/docker-compose.yml COMPOSE_PROJECT ?= arm-ka IMAGE ?= scan-gateway:dev STATEMENTS_DIR ?= ../pylib_ai_arm_statements +STATEMENTS_TAG ?= v0.1.1 .PHONY: help venv install lint test test-integration check docker-build check-full up emulator golden-update @@ -51,7 +52,12 @@ check: lint test # Образ собирается без Nexus (raisa_* в образ не ставятся, см. Dockerfile). Тесты в образе не запускаются: # tests/ в нём есть, но api/.env.test исключён через .dockerignore, поэтому pytest внутри контейнера не пройдёт. docker-build: - docker build --build-context statements="$(STATEMENTS_DIR)" -t $(IMAGE) . + @release=$$(git -C "$(STATEMENTS_DIR)" rev-parse --verify "refs/tags/$(STATEMENTS_TAG)^{commit}") || \ + { echo "Ошибка: в библиотеке отсутствует тег $(STATEMENTS_TAG); выполните git fetch --tags в $(STATEMENTS_DIR)" >&2; exit 1; }; \ + head=$$(git -C "$(STATEMENTS_DIR)" rev-parse HEAD) || exit 1; \ + [ "$$head" = "$$release" ] || \ + { echo "Ошибка: HEAD библиотеки не совпадает с $(STATEMENTS_TAG)" >&2; exit 1; } + docker build --build-context "statements=$(STATEMENTS_DIR)" -t $(IMAGE) . # Полная проверка перед PR. Шаги вызываются последовательно через $(MAKE), а не как пререквизиты: # так порядок check -> test-integration -> docker-build и остановка на первой ошибке гарантированы и при make -j. diff --git a/README.md b/README.md index 6082dff..5311f3b 100644 --- a/README.md +++ b/README.md @@ -423,3 +423,14 @@ make golden-update # пересобрать эталоны нормализ - Реальные фикстуры: `aep_3.json` (баланс и ОФР), `aep_6.json` — успешные ответы, `aep_3_ofr.json`, `aep_5.json`, `aep_8_form4.json` — ответы-ошибки (`INVALID_DOCUMENT`; `data_ocr` — объект с пустыми полями и `table_structure: []`). - Контейнера `scan-emulator` в compose пока нет. + +Сборка Docker использует BuildKit-контекст `statements` из `STATEMENTS_DIR` +(по умолчанию `../pylib_ai_arm_statements`). Другой путь: +`make docker-build STATEMENTS_DIR=/путь/к/библиотеке`. Библиотека устанавливается +из исходников вместе с requirements; её версия должна соответствовать закреплённой +в requirements. Остальные зависимости загружаются из пакетного индекса. + +Перед локальной сборкой `make docker-build` проверяет совпадение HEAD библиотеки +с тегом `STATEMENTS_TAG` (по умолчанию `v0.1.1`). Получить тег: +`git -C ../pylib_ai_arm_statements fetch origin --tags`. +Незакоммиченные изменения разрешены и попадают в сборку из `STATEMENTS_DIR`. diff --git a/openspec/changes/use-statements-library/design.md b/openspec/changes/use-statements-library/design.md index 170e65b..8c0222f 100644 --- a/openspec/changes/use-statements-library/design.md +++ b/openspec/changes/use-statements-library/design.md @@ -17,7 +17,7 @@ 1. **Установка локального пакета через uv.** `STATEMENTS_DIR` задаёт источник. `make venv` фильтрует `^(raisa_|pylib_ai)` при установке requirements и затем ставит пакет из пути; `make install` создаёт окружение при необходимости и выполняет переустановку через `--reinstall-package`. Так изменения исходников попадают в окружение после явной команды. Editable-установка не выбрана, чтобы поведение совпадало с установкой пакета в образ; копирование и публикация в PyPI исключены выбранным способом поставки. 2. **Версия зависимости и версия результата.** `requirements.txt` фиксирует ожидаемую `0.1.1`, runtime использует `statements_version()`. Тест приложения сравнивает версию с метаданными установленного дистрибутива вместо чтения удалённого пути. Отсутствие версии остаётся ошибкой старта, текст предлагает `make install`. Структура контракта не меняется. Фильтрация requirements означает, что версия локального источника отдельно сверяется при приёмке: сама запись `==0.1.1` не ограничивает установку из пути. 3. **Разделение проверок библиотеки и сервиса.** Удаляются копия, её `testpaths`, аргумент mypy и `statements-check` из `check-full`. Ruff считает `arm_statements` сторонним импортом. Для пакета без `py.typed` mypy получает адресный `follow_untyped_imports = true`, чтобы использовать существующие аннотации вместо общего подавления ошибок. Проверки адаптера, результата и golden остаются в шлюзе; тесты самой библиотеки принадлежат библиотеке. -4. **Docker build context `statements`.** Make передаёт путь через `--build-context`; Dockerfile фильтрует зависимости и копирует только исходники пакета и необходимые файлы сборки, устанавливает пакет, затем удаляет временный источник. Исходники не монтируются при запуске. Расширение основного контекста до всего workspace не выбрано: оно привязывает сборку к структуре соседних проектов. +4. **Docker build context `statements`.** Make передаёт путь через `--build-context`; Dockerfile исключает raisa_* из requirements, временно монтирует контекст через RUN --mount и устанавливает библиотеку вместе с requirements одним вызовом pip с проверкой закреплённой версии. Исходники не монтируются при запуске. Расширение основного контекста до всего workspace не выбрано: оно привязывает сборку к структуре соседних проектов. 5. **Документация и общие контракты.** README, CLAUDE и сообщения шлюза описывают библиотеку. Основные спеки обновятся при отдельном sync/archive, исторические change'и сохраняются. Общие JSON Schema остаются побайтовой копией источника; историческое описание коммита копии внутри схемы требует отдельного согласованного изменения в docs, если будет обновляться. ## Risks / Trade-offs