mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
fix(proxy): allow request_scope import without fastapi (#2562)
## Description Base installs without the `proxy` extra crash during CLI command registration because `headroom.proxy.request_scope` imported FastAPI at module import time. That import is only needed for typing on `normalize_request_path`. This change keeps the FastAPI `Request` import under `TYPE_CHECKING` so the CLI path used by `headroom --help` no longer requires FastAPI. Closes #2561 ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Documentation update - [ ] Refactoring (no functional changes) - [ ] Performance improvement - [ ] Test update - [ ] Build/CI change - [ ] Other (please describe): ## Changes Made - Make the FastAPI `Request` import type-checking only in `headroom/proxy/request_scope.py` - Add a subprocess regression test that imports `request_scope` and `project_context` with FastAPI blocked and verifies `normalize_scope_path` ## Testing ### Test commands run ```bash PYTHONPATH=. python3 -m pytest tests/test_proxy_request_scope.py tests/test_request_scope_no_fastapi.py -q ruff format --check headroom/proxy/request_scope.py tests/test_request_scope_no_fastapi.py ruff check headroom/proxy/request_scope.py tests/test_request_scope_no_fastapi.py ``` ### Test Output ```text ========================= 5 passed, 1 warning in 0.93s ========================= 2 files already formatted All checks passed! ``` ## Real Behavior Proof ### Environment - Linux x86_64, Python 3.11.15 - Shallow sparse checkout of headroom main at commit parent of this PR - System/Hermes venv Python with pytest and ruff available ### Exact command ```bash PYTHONPATH=. python3 - <<'PY' import builtins, sys real = builtins.__import__ def imp(name, *a, **k): if name == "fastapi" or name.startswith("fastapi."): raise ModuleNotFoundError("No module named 'fastapi'") return real(name, *a, **k) builtins.__import__ = imp import headroom.proxy.request_scope as rs import headroom.proxy.project_context as pc rs.normalize_scope_path({"path": "/a"}, "/b") print("ok", "fastapi" not in sys.modules, hasattr(pc, "with_project_prefix")) PY ``` ### Observed result ```text ok True True ``` Importing the request-scope helpers no longer requires FastAPI, and scope path normalization still works. ### Not tested - Full base `pip install headroom-ai` (no extras) end-to-end on a clean venv without the monorepo source tree - Full monorepo `make ci-precheck` / cargo workspace - Live proxy traffic or FastAPI request path behavior beyond the existing unit test for `normalize_request_path` ## Review Readiness - [x] I have tested these changes locally - [x] I have added/updated tests where applicable - [x] I have updated documentation if needed (N/A) - [x] My code follows the project's style guidelines - [x] I have run linting/formatting checks - [x] I have considered security implications - [x] This PR is ready for review
This commit is contained in:
parent
58555c5be0
commit
4bd121493d
2 changed files with 45 additions and 2 deletions
|
|
@ -3,10 +3,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import MutableMapping
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from urllib.parse import quote
|
||||
|
||||
from fastapi import Request
|
||||
if TYPE_CHECKING:
|
||||
from fastapi import Request
|
||||
|
||||
|
||||
def normalize_scope_path(scope: MutableMapping[str, Any], path: str) -> None:
|
||||
|
|
|
|||
42
tests/test_request_scope_no_fastapi.py
Normal file
42
tests/test_request_scope_no_fastapi.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
"""Regression tests for importing request-scope helpers without FastAPI."""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
|
||||
def test_request_scope_and_project_context_import_without_fastapi() -> None:
|
||||
script = textwrap.dedent(
|
||||
"""
|
||||
import builtins
|
||||
import sys
|
||||
|
||||
real_import = builtins.__import__
|
||||
|
||||
def import_without_fastapi(name, *args, **kwargs):
|
||||
if name == "fastapi" or name.startswith("fastapi."):
|
||||
raise ModuleNotFoundError("No module named 'fastapi'")
|
||||
return real_import(name, *args, **kwargs)
|
||||
|
||||
builtins.__import__ = import_without_fastapi
|
||||
|
||||
import headroom.proxy.request_scope as request_scope
|
||||
import headroom.proxy.project_context
|
||||
|
||||
assert not any(
|
||||
name == "fastapi" or name.startswith("fastapi.") for name in sys.modules
|
||||
)
|
||||
scope = {"path": "/a", "raw_path": b"/a"}
|
||||
request_scope.normalize_scope_path(scope, "/b c")
|
||||
assert scope == {"path": "/b c", "raw_path": b"/b%20c"}
|
||||
"""
|
||||
)
|
||||
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-c", script],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
Loading…
Add table
Add a link
Reference in a new issue