Harden AI Docker runtime imports
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-07-05 00:40:08 +02:00
parent 8b09bee84a
commit 7dcce9c2b8
9 changed files with 53 additions and 5 deletions
+7
View File
@@ -7,10 +7,17 @@ ARG GEOINTEL_INSTALL_AI=false
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
gdal-bin \
libgl1 \
libglib2.0-0 \
libgdal-dev \
libgeos-dev \
libproj-dev \
libpq-dev \
libsm6 \
libx11-6 \
libxcb1 \
libxext6 \
libxrender1 \
proj-bin \
&& rm -rf /var/lib/apt/lists/*
+5
View File
@@ -227,6 +227,11 @@ The default remains `false` so normal GIS deployments do not install the large A
runtime. GeoIntel still requires an explicit local model path and never downloads
weights automatically.
AI-enabled Docker images include the native OpenCV runtime libraries required by
Ultralytics. Dependency availability is checked with real `torch` and
`ultralytics` imports, so missing shared libraries are reported as
`dependency_unavailable` instead of being treated as configured.
Configured YOLO requires:
```bash
+6 -2
View File
@@ -1,6 +1,5 @@
from __future__ import annotations
import importlib.util
from pathlib import Path
from typing import Any
@@ -14,7 +13,12 @@ class YoloDetectionAdapter:
@staticmethod
def dependencies_available() -> bool:
return importlib.util.find_spec("ultralytics") is not None and importlib.util.find_spec("torch") is not None
try:
import torch # noqa: F401
import ultralytics # noqa: F401
except Exception:
return False
return True
def load_model(self, model_path: Path):
if not model_path.exists() or not model_path.is_file():
@@ -27,6 +27,9 @@ def test_backend_dockerfile_installs_approved_gis_runtime_stack() -> None:
assert "libgeos-dev" in dockerfile
assert "libproj-dev" in dockerfile
assert "proj-bin" in dockerfile
assert "libxcb1" in dockerfile
assert "libgl1" in dockerfile
assert "libglib2.0-0" in dockerfile
def test_backend_pyproject_exposes_gis_optional_dependency_group() -> None:
@@ -47,6 +50,9 @@ def test_all_in_one_dockerfile_can_opt_into_ai_dependencies_without_base_install
assert 'extras=".[gis,ai]"' in dockerfile
assert "python scripts/gis_import_smoke.py" in dockerfile
assert "yolo_preflight.py" in dockerfile
assert "libxcb1" in dockerfile
assert "libgl1" in dockerfile
assert "libglib2.0-0" in dockerfile
def test_compose_does_not_require_missing_root_env_file() -> None:
@@ -12,6 +12,8 @@ from app.services.detection_georeferencing import pixel_bbox_to_epsg4326_polygon
from app.services.detection_service import DetectionService
from app.services.model_registry_service import ModelRegistryService
ROOT = Path(__file__).resolve().parents[2]
class FakeSession:
def __init__(self, objects=None) -> None:
@@ -171,6 +173,14 @@ def test_yolo_configured_model_reports_configured_with_local_model_and_dependenc
assert model.version == settings.yolo_model_version
def test_yolo_dependency_check_uses_real_imports_not_find_spec() -> None:
source = (ROOT / "backend" / "app" / "services" / "yolo_adapter.py").read_text(encoding="utf-8")
assert 'find_spec("ultralytics")' not in source
assert "import ultralytics" in source
assert "import torch" in source
def test_yolo_run_requires_tile_manifest_path(tmp_path: Path) -> None:
db, project_id, dataset_id = _project_and_dataset()
settings = _settings(tmp_path)