agnes-the-ai-analyst/tests/test_packaging.py
ZdenekSrotyr d2104555c6 fix(deps): promote anthropic + openai to core dependencies (#176)
LLM provider SDKs are imported by services/corporate_memory and
services/verification_detector — both production code paths. Listing
them only in [project.optional-dependencies].dev caused the scheduler
container to boot-loop with ModuleNotFoundError on default
`docker compose up` deploys, because the Dockerfile installs core
deps only (`uv pip install --system --no-cache .`).

Adds tests/test_packaging.py to lock the contract: anthropic + openai
must live in [project].dependencies, not in dev extras.
2026-05-04 23:52:30 +02:00

83 lines
3 KiB
Python

"""Packaging regression tests — guard against silent prod-vs-dev dep drift.
`anthropic` and `openai` are imported by `connectors/llm/anthropic_provider.py`
and `connectors/llm/openai_compat.py`. Those modules run in production from
`services/corporate_memory` and `services/verification_detector`. If they
slip back into `[project.optional-dependencies].dev` the Dockerfile (which
only installs core deps) will boot-loop on `ModuleNotFoundError`. See #176.
"""
from __future__ import annotations
from pathlib import Path
import pytest
def _read_pyproject() -> dict:
"""Load pyproject.toml from the repo root."""
try:
import tomllib # py3.11+
except ImportError: # pragma: no cover
import tomli as tomllib # type: ignore
root = Path(__file__).resolve().parent.parent
with (root / "pyproject.toml").open("rb") as f:
return tomllib.load(f)
def test_anthropic_is_a_core_dependency():
"""anthropic must live in [project].dependencies, not [dev].
Production code (connectors/llm/anthropic_provider.py) imports the SDK
unconditionally. Demoting it to dev resurrects the #176 boot loop.
"""
cfg = _read_pyproject()
core = cfg["project"]["dependencies"]
assert any(dep.startswith("anthropic") for dep in core), (
"anthropic must be in [project].dependencies — see #176"
)
def test_openai_is_a_core_dependency():
"""openai must live in [project].dependencies, not [dev]."""
cfg = _read_pyproject()
core = cfg["project"]["dependencies"]
assert any(dep.startswith("openai") for dep in core), (
"openai must be in [project].dependencies — see #176"
)
def test_anthropic_not_in_optional_dev_extras():
"""Belt-and-suspenders: dev extras should not double-list anthropic."""
cfg = _read_pyproject()
dev = cfg["project"].get("optional-dependencies", {}).get("dev", [])
assert not any(dep.startswith("anthropic") for dep in dev), (
"anthropic should not be duplicated in [dev] — keep it core-only"
)
def test_openai_not_in_optional_dev_extras():
"""Belt-and-suspenders: dev extras should not double-list openai."""
cfg = _read_pyproject()
dev = cfg["project"].get("optional-dependencies", {}).get("dev", [])
assert not any(dep.startswith("openai") for dep in dev), (
"openai should not be duplicated in [dev] — keep it core-only"
)
def test_llm_provider_modules_import_cleanly():
"""A fresh interpreter with only core deps installed must import the
LLM provider modules without ImportError. This is the actual behavior
that breaks the scheduler container when anthropic/openai are dev-only.
"""
# Just importing here proves the deps resolve in the active env. The
# pyproject.toml assertions above keep the contract going forward.
import importlib
for mod in (
"connectors.llm.anthropic_provider",
"connectors.llm.openai_compat",
"connectors.llm.factory",
):
importlib.import_module(mod)