From 7d8407a37853fbc412d2b9781382a9a5ef541e11 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Wed, 3 Jun 2026 22:48:27 +0000 Subject: [PATCH] fix(ci): hoist imports to satisfy E402 + declare runtime/test deps in pyproject MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI failures на feature/ET-011 были вызваны двумя проблемами: 1. ruff `E402 Module level import not at top of file` × 10 в src/api/main.py: - 9 ошибок от ET-008 (GPS_TRACKS_DB_PATH между импортами) + 1 новая от ET-011 (`from src.api.gps_tracks.endpoint import ...` после определения `app`). Перенёс все импорты наверх; константы GPS_TRACKS_DB_PATH и GPS_SOURCES_CONFIG_PATH теперь сразу после import-блока, а создание router-а остаётся в нижней части файла (зависит от `app`). 2. pyproject.toml не объявлял runtime-deps, которые реально импортируются в src/ (defusedxml, pyyaml) и в тестах (lxml). Dockerfile брал их из src/api/requirements.txt, но CI jobs `lint`/`test` ставят `.[dev]` — поэтому `pytest tests/` падал на ModuleNotFoundError при коллекции тестов из ET-008/ET-009/ET-011. Добавил недостающие пины в pyproject (defusedxml/pyyaml в основные deps, lxml — только в dev, нужен для XSD-валидации в test_gps_tracks_download/_gpx_builder). Проверено локально в чистом venv после `pip install .[dev]`: - `ruff check src/` → All checks passed - `pytest tests/` → 200 passed, 2 deselected Refs: ET-011 Co-Authored-By: Claude Opus 4.7 (1M context) --- pyproject.toml | 3 +++ src/api/main.py | 40 ++++++++++++++++++++-------------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 03bb965..fe87d40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,8 @@ dependencies = [ "shapely==2.0.4", "mapbox-vector-tile==2.2.0", "httpx==0.27.0", + "defusedxml==0.7.1", + "pyyaml==6.0.1", ] [project.optional-dependencies] @@ -18,6 +20,7 @@ dev = [ "pytest>=8.0", "httpx>=0.27", "pytest-asyncio>=0.23", + "lxml==5.2.2", ] [build-system] diff --git a/src/api/main.py b/src/api/main.py index 13e4a57..d7dd216 100644 --- a/src/api/main.py +++ b/src/api/main.py @@ -11,25 +11,31 @@ import os import math import struct import sqlite3 - import itertools +from typing import List + +import httpx +import uvicorn +from fastapi import FastAPI, HTTPException, Response +from fastapi.responses import FileResponse +from fastapi.staticfiles import StaticFiles +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel +from shapely.geometry import LineString + +from src.api.gps_tracks.endpoint import create_gps_router GPS_TRACKS_DB_PATH = os.environ.get( "GPS_TRACKS_DB_PATH", os.path.join(os.path.dirname(__file__), "../../data/gps_tracks.sqlite"), ) -from shapely.geometry import LineString -from typing import List - - -from fastapi import FastAPI, HTTPException, Response -from fastapi.responses import FileResponse -from fastapi.staticfiles import StaticFiles -from fastapi.middleware.cors import CORSMiddleware -from pydantic import BaseModel -import httpx -import uvicorn +# ET-011 / ADR-015: путь к config/gps_sources.yaml — содержит per-source +# флаг `download_allowed`, который router читает один раз при старте. +GPS_SOURCES_CONFIG_PATH = os.environ.get( + "GPS_SOURCES_CONFIG_PATH", + os.path.join(os.path.dirname(__file__), "../../config/gps_sources.yaml"), +) # ─── Tile cache ────────────────────────────────────────────────────────────── @@ -1251,14 +1257,8 @@ async def terrain_tile(layer: str, z: int, x: int, y: int): # ─── Static files ───────────────────────────────────────────────────────────── -from src.api.gps_tracks.endpoint import create_gps_router - -# ET-011 / ADR-015: путь к config/gps_sources.yaml — содержит per-source -# флаг `download_allowed`, который router читает один раз при старте. -GPS_SOURCES_CONFIG_PATH = os.environ.get( - "GPS_SOURCES_CONFIG_PATH", - os.path.join(os.path.dirname(__file__), "../../config/gps_sources.yaml"), -) +# ET-011 / ADR-015: GPS_SOURCES_CONFIG_PATH объявлен в начале файла рядом с +# GPS_TRACKS_DB_PATH; здесь только создаём router после того, как `app` определён. gps_router = create_gps_router(GPS_TRACKS_DB_PATH, GPS_SOURCES_CONFIG_PATH) app.include_router(gps_router)