headroom/tests/test_proxy_package_init.py
Andrew Rich 93c69372e6
fix(proxy): lazy-import server to avoid fastapi crash (#442)
## Summary

- Lazy-import `create_app`/`run_server` in `headroom/proxy/__init__.py`
via PEP 562 `__getattr__` to prevent CLI crash when `fastapi` is not
installed (i.e., installed without `[proxy]` extras)
- Fix `.pre-commit-config.yaml` to use `python3` instead of `python`
(unavailable on macOS Homebrew)
- Add graceful `ImportError` skip in `scripts/sync-plugin-versions.py`
for environments without dev dependencies

Fixes #441

## Test plan

- [x] `headroom --help` works without `[proxy]` extras installed
- [x] `headroom proxy --help` works with `[proxy]` extras installed
- [x] `headroom proxy --port 18787` starts and serves traffic
- [x] Lazy imports resolve correctly: `from headroom.proxy import
create_app, run_server`
- [x] `AttributeError` raised for invalid attributes on `headroom.proxy`
- [x] Pre-commit hooks pass (ruff, ruff-format, mypy,
sync-plugin-versions)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Code Bot <claude-code@smartwatermelon.github>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 12:44:23 -05:00

54 lines
1.9 KiB
Python

"""Unit tests for headroom.proxy lazy __getattr__ (PEP 562)."""
from __future__ import annotations
import importlib
import sys
import types
import pytest
def test_proxy_getattr_resolves_create_app_and_caches_it(monkeypatch) -> None:
sentinel = object()
fake_server = types.SimpleNamespace(create_app=sentinel, run_server=object())
monkeypatch.setitem(sys.modules, "headroom.proxy.server", fake_server)
import headroom.proxy as proxy
module = importlib.reload(proxy)
module.__dict__.pop("create_app", None)
module.__dict__.pop("run_server", None)
# Register teardown: monkeypatch notes these keys are absent now and will
# remove them after the test, preventing sentinel leakage to later tests.
monkeypatch.delitem(module.__dict__, "create_app", raising=False)
monkeypatch.delitem(module.__dict__, "run_server", raising=False)
result = module.__getattr__("create_app")
assert result is sentinel
assert module.__dict__["create_app"] is sentinel
def test_proxy_getattr_resolves_run_server(monkeypatch) -> None:
sentinel = object()
fake_server = types.SimpleNamespace(create_app=object(), run_server=sentinel)
monkeypatch.setitem(sys.modules, "headroom.proxy.server", fake_server)
import headroom.proxy as proxy
module = importlib.reload(proxy)
module.__dict__.pop("create_app", None)
module.__dict__.pop("run_server", None)
monkeypatch.delitem(module.__dict__, "create_app", raising=False)
monkeypatch.delitem(module.__dict__, "run_server", raising=False)
result = module.__getattr__("run_server")
assert result is sentinel
assert module.__dict__["run_server"] is sentinel
def test_proxy_getattr_raises_for_unknown_attribute() -> None:
import headroom.proxy as proxy
with pytest.raises(AttributeError, match="has no attribute 'nonexistent'"):
proxy.__getattr__("nonexistent")