From 8d7feabf9fa25c9641565b0022c8ff014e30b0d3 Mon Sep 17 00:00:00 2001 From: lekss361 Date: Thu, 2 Jul 2026 14:21:38 +0000 Subject: [PATCH] chore(scraper-kit): docker-context wiring + restore backend workspace dep (#2137) --- .forgejo/workflows/deploy-tradein.yml | 5 ++- tradein-mvp/.dockerignore | 49 +++++++++++++++++++++++++++ tradein-mvp/backend/Dockerfile | 43 +++++++++++++++++------ tradein-mvp/backend/pyproject.toml | 8 +++-- tradein-mvp/docker-compose.yml | 6 ++-- tradein-mvp/uv.lock | 2 ++ 6 files changed, 97 insertions(+), 16 deletions(-) create mode 100644 tradein-mvp/.dockerignore diff --git a/.forgejo/workflows/deploy-tradein.yml b/.forgejo/workflows/deploy-tradein.yml index 6ae7a76c..da73d74b 100644 --- a/.forgejo/workflows/deploy-tradein.yml +++ b/.forgejo/workflows/deploy-tradein.yml @@ -183,7 +183,10 @@ jobs: - name: Build & push tradein-backend uses: docker/build-push-action@v6 with: - context: ./tradein-mvp/backend + # Context = tradein-mvp/ (uv workspace root): образу нужен packages/scraper-kit + # для editable install (#2137). Dockerfile — в backend/. + context: ./tradein-mvp + file: ./tradein-mvp/backend/Dockerfile push: true cache-from: type=registry,ref=${{ env.IMAGE_BACKEND }}:buildcache cache-to: type=registry,ref=${{ env.IMAGE_BACKEND }}:buildcache,mode=max diff --git a/tradein-mvp/.dockerignore b/tradein-mvp/.dockerignore new file mode 100644 index 00000000..222545a0 --- /dev/null +++ b/tradein-mvp/.dockerignore @@ -0,0 +1,49 @@ +# Docker build context = tradein-mvp/ (uv workspace root) для backend/Dockerfile (#2137). +# Этот .dockerignore применяется только к backend-билду (context = tradein-mvp); +# frontend/browser собираются из своих под-контекстов (./frontend, ./browser). +# +# Держим context тонким: образу нужны лишь pyproject.toml, uv.lock, backend/, +# packages/. Всё остальное — исключаем. + +# --- прочие подпроекты, не нужные backend-образу --- +frontend/ +browser/ +deploy/ +docs/ +ops/ +scripts/ + +# --- backend: не нужны в образе (Dockerfile копирует только app/ + scripts/) --- +backend/tests/ +backend/data/sql/ +backend/.env +backend/.env.runtime +backend/.env.local +backend/Dockerfile +backend/.dockerignore + +# --- инфра-манифесты (не нужны внутри образа) --- +docker-compose*.yml +Makefile +*.md + +# --- Python / build junk --- +**/.venv/ +**/__pycache__/ +**/*.egg-info/ +**/.pytest_cache/ +**/.ruff_cache/ +**/.mypy_cache/ +**/*.pyc + +# --- VCS / env / IDE / OS --- +.git/ +.gitignore +**/.env +**/.env.local +**/.env.*.local +.vscode/ +.idea/ +.DS_Store +Thumbs.db +*.log diff --git a/tradein-mvp/backend/Dockerfile b/tradein-mvp/backend/Dockerfile index 2b5fcd35..4958161c 100644 --- a/tradein-mvp/backend/Dockerfile +++ b/tradein-mvp/backend/Dockerfile @@ -1,5 +1,10 @@ # Trade-In MVP backend — lean image (FastAPI + WeasyPrint). # Не содержит Playwright/Chromium/Celery/Redis — только то что нужно для trade-in feature. +# +# Build context = tradein-mvp/ (uv workspace root), НЕ ./backend (#2137): образу нужны +# workspace-манифест (pyproject.toml + uv.lock) и packages/scraper-kit для editable install. +# Собирается через `dockerfile: backend/Dockerfile` (см. docker-compose*.yml + +# .forgejo/workflows/deploy-tradein.yml build-backend). # ---- builder ---- FROM python:3.12-slim AS builder @@ -17,18 +22,28 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ RUN pip install --no-cache-dir uv +# WORKDIR = uv workspace root. backend/ и packages/* — члены workspace. WORKDIR /app -COPY pyproject.toml ./ -# NB: команда содержит `import lxml`-assert — это И bust'ит build-cache этого слоя -# (защита от stale-кэша, который однажды задеплоил образ без lxml), И валидирует, -# что зависимость реально установлена (иначе сборка падает громко, а не в рантайме). -RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --no-dev \ - && /app/.venv/bin/python -c "import lxml; print('lxml', lxml.__version__)" +# Манифесты workspace + members сначала (кэшируемый слой зависимостей). +# packages/ копируем ДО uv sync целиком: editable-install scraper-kit требует +# src/ на этапе резолва (hatchling билдит editable-wheel по pyproject + src). +COPY pyproject.toml uv.lock ./ +COPY backend/pyproject.toml backend/pyproject.toml +COPY packages/ packages/ -COPY app ./app -COPY scripts ./scripts +# uv sync --frozen ставит зависимости backend ВКЛЮЧАЯ scraper-kit (editable member, +# .pth → /app/packages/scraper-kit/src). --no-dev: без pytest/ruff. +# NB: команда содержит import-assert'ы — они И bust'ят build-cache этого слоя +# (защита от stale-кэша без нужной deps), И валидируют что зависимость реально +# установлена (сборка падает громко, а не в рантайме). +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --no-dev \ + && /app/.venv/bin/python -c "import lxml; print('lxml', lxml.__version__)" \ + && /app/.venv/bin/python -c "import scraper_kit; print('scraper_kit', scraper_kit.__file__)" + +COPY backend/app backend/app +COPY backend/scripts backend/scripts # ---- runner ---- @@ -51,9 +66,15 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ WORKDIR /app +# Runtime-layout сохраняем прежним (app в /app/app, scripts в /app/scripts), чтобы +# bind-mount'ы compose не менялись: ./backend/app:/app/app (dev hot-reload) и +# ../auth/roles.yaml:/app/auth/roles.yaml (prod, app/core/auth.py читает /app/auth/roles.yaml). +# backend = virtual-пакет (НЕ в site-packages) → import app идёт из CWD=/app. +# packages/ ОБЯЗАТЕЛЕН в рантайме: editable .pth ссылается на /app/packages/scraper-kit/src. COPY --from=builder --chown=app:app /app/.venv /app/.venv -COPY --from=builder --chown=app:app /app/app /app/app -COPY --from=builder --chown=app:app /app/scripts /app/scripts +COPY --from=builder --chown=app:app /app/packages /app/packages +COPY --from=builder --chown=app:app /app/backend/app /app/app +COPY --from=builder --chown=app:app /app/backend/scripts /app/scripts USER app diff --git a/tradein-mvp/backend/pyproject.toml b/tradein-mvp/backend/pyproject.toml index 6fe4043e..b94fdad8 100644 --- a/tradein-mvp/backend/pyproject.toml +++ b/tradein-mvp/backend/pyproject.toml @@ -25,10 +25,14 @@ dependencies = [ "redis>=5.0.0", # async hot cache для /api/v1/search (Phase 3.2) "pyyaml>=6.0.0", # RBAC roles.yaml loader (app/core/auth.py) "playwright>=1.45", # Playwright client для connect к tradein-browser (#905) - # scraper-kit — internal package; подключается в шаге C (перенос core + docker-wiring #2137), - # чтобы standalone backend docker-context (COPY pyproject.toml + uv sync) не ломался на build. + "scraper-kit", # internal workspace-package (#2137) — общие утилиты скрапперов; + # резолвится из workspace (см. [tool.uv.sources]), не с PyPI. + # Docker-context = tradein-mvp root, uv sync ставит editable. ] +[tool.uv.sources] +scraper-kit = { workspace = true } + [dependency-groups] dev = [ "pytest>=8.0.0", diff --git a/tradein-mvp/docker-compose.yml b/tradein-mvp/docker-compose.yml index 86b42825..341f27fe 100644 --- a/tradein-mvp/docker-compose.yml +++ b/tradein-mvp/docker-compose.yml @@ -36,8 +36,10 @@ services: backend: build: - context: ./backend - dockerfile: Dockerfile + # Context = tradein-mvp root (uv workspace): образу нужен packages/scraper-kit + # для editable install (#2137). Dockerfile лежит в backend/. + context: . + dockerfile: backend/Dockerfile container_name: tradein-backend restart: unless-stopped environment: diff --git a/tradein-mvp/uv.lock b/tradein-mvp/uv.lock index 0491e9f8..7e6fb1f4 100644 --- a/tradein-mvp/uv.lock +++ b/tradein-mvp/uv.lock @@ -1295,6 +1295,7 @@ dependencies = [ { name = "python-multipart" }, { name = "pyyaml" }, { name = "redis" }, + { name = "scraper-kit" }, { name = "segno" }, { name = "selectolax" }, { name = "sentry-sdk" }, @@ -1327,6 +1328,7 @@ requires-dist = [ { name = "python-multipart", specifier = ">=0.0.9" }, { name = "pyyaml", specifier = ">=6.0.0" }, { name = "redis", specifier = ">=5.0.0" }, + { name = "scraper-kit", editable = "packages/scraper-kit" }, { name = "segno", specifier = ">=1.6.0" }, { name = "selectolax", specifier = ">=0.3.0" }, { name = "sentry-sdk", specifier = ">=2.0.0" },