fix(ci): hoist imports to satisfy E402 + declare runtime/test deps in pyproject
All checks were successful
CI / lint (push) Successful in 5s
CI / test (push) Successful in 7s
CI / lint (pull_request) Successful in 4s
CI / test (pull_request) Successful in 7s
CI / build (pull_request) Successful in 2s
CI / build (push) Successful in 18s

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) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 22:48:27 +00:00
parent eea6c846c2
commit 7d8407a378
2 changed files with 23 additions and 20 deletions

View File

@@ -10,6 +10,8 @@ dependencies = [
"shapely==2.0.4", "shapely==2.0.4",
"mapbox-vector-tile==2.2.0", "mapbox-vector-tile==2.2.0",
"httpx==0.27.0", "httpx==0.27.0",
"defusedxml==0.7.1",
"pyyaml==6.0.1",
] ]
[project.optional-dependencies] [project.optional-dependencies]
@@ -18,6 +20,7 @@ dev = [
"pytest>=8.0", "pytest>=8.0",
"httpx>=0.27", "httpx>=0.27",
"pytest-asyncio>=0.23", "pytest-asyncio>=0.23",
"lxml==5.2.2",
] ]
[build-system] [build-system]

View File

@@ -11,25 +11,31 @@ import os
import math import math
import struct import struct
import sqlite3 import sqlite3
import itertools 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.environ.get(
"GPS_TRACKS_DB_PATH", "GPS_TRACKS_DB_PATH",
os.path.join(os.path.dirname(__file__), "../../data/gps_tracks.sqlite"), os.path.join(os.path.dirname(__file__), "../../data/gps_tracks.sqlite"),
) )
from shapely.geometry import LineString # ET-011 / ADR-015: путь к config/gps_sources.yaml — содержит per-source
from typing import List # флаг `download_allowed`, который router читает один раз при старте.
GPS_SOURCES_CONFIG_PATH = os.environ.get(
"GPS_SOURCES_CONFIG_PATH",
from fastapi import FastAPI, HTTPException, Response os.path.join(os.path.dirname(__file__), "../../config/gps_sources.yaml"),
from fastapi.responses import FileResponse )
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import httpx
import uvicorn
# ─── Tile cache ────────────────────────────────────────────────────────────── # ─── Tile cache ──────────────────────────────────────────────────────────────
@@ -1251,14 +1257,8 @@ async def terrain_tile(layer: str, z: int, x: int, y: int):
# ─── Static files ───────────────────────────────────────────────────────────── # ─── Static files ─────────────────────────────────────────────────────────────
from src.api.gps_tracks.endpoint import create_gps_router # ET-011 / ADR-015: GPS_SOURCES_CONFIG_PATH объявлен в начале файла рядом с
# GPS_TRACKS_DB_PATH; здесь только создаём router после того, как `app` определён.
# 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"),
)
gps_router = create_gps_router(GPS_TRACKS_DB_PATH, GPS_SOURCES_CONFIG_PATH) gps_router = create_gps_router(GPS_TRACKS_DB_PATH, GPS_SOURCES_CONFIG_PATH)
app.include_router(gps_router) app.include_router(gps_router)