Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import ast
import importlib.util
from pathlib import Path
from types import ModuleType
from typing import Annotated

import fastapi.dependencies.utils as fastapi_dependency_utils
import pytest
from fastapi import Depends, FastAPI, Header, Query, Request
from fastapi.testclient import TestClient

import litellm.proxy.management_endpoints.management_v1.common as common_module
from litellm.proxy.management_endpoints.management_v1.common import (
ManagementProblem,
PROBLEM_CONTENT_TYPE,
ManagementProblem,
_declared_query_params,
problem_response,
reject_unknown_query_params,
Expand Down Expand Up @@ -93,3 +100,57 @@ def test_declared_query_params_is_empty_when_the_route_has_no_dependant():
}
)
assert _declared_query_params(request) == frozenset()


# fastapi removed these in 0.140.7, which `pyproject.toml` still allows via
# `fastapi>=0.136.3,<1.0`. Add a name here whenever a supported release drops one.
FASTAPI_NAMES_REMOVED_IN_0_140_7 = frozenset({"get_flat_dependant"})

MANAGEMENT_V1_PACKAGE = Path(str(common_module.__file__)).parent


def _public_names(module: ModuleType) -> frozenset[str]:
return frozenset(name for name in vars(module) if not name.startswith("_"))


def _fastapi_names_imported_by(source_file: Path) -> frozenset[str]:
tree = ast.parse(source_file.read_text())
return frozenset(
alias.name
for node in ast.walk(tree)
if isinstance(node, ast.ImportFrom) and (node.module or "").startswith("fastapi")
for alias in node.names
)


@pytest.mark.parametrize(
"source_file", sorted(MANAGEMENT_V1_PACKAGE.glob("*.py")), ids=lambda path: path.name
)
def test_no_module_imports_a_fastapi_name_removed_in_a_supported_release(source_file: Path):
"""`pyproject.toml` allows fastapi up to <1.0, but CI only ever resolves 0.136.3.

Every other test here passes just as well against a module importing a name
fastapi has since deleted, because the pinned fastapi still has it. On a user's
fastapi>=0.140.7 that import is an ImportError, and `proxy_server` imports this
package unguarded at module level, so it takes the whole proxy down rather than
just these routes. Globbing the package means a new module is covered on sight.
"""
assert not _fastapi_names_imported_by(source_file) & FASTAPI_NAMES_REMOVED_IN_0_140_7


def test_common_still_imports_when_fastapi_has_dropped_those_names(monkeypatch: pytest.MonkeyPatch):
"""The static check above cannot prove the module actually loads; this does.

Behaviour cannot be asserted under the same simulation: on 0.136.3
`get_flat_params` calls `get_flat_dependant` internally, so it raises NameError
once the name is gone. Loading is the part this pins.
"""
for name in FASTAPI_NAMES_REMOVED_IN_0_140_7:
monkeypatch.delattr(fastapi_dependency_utils, name, raising=False)
spec = importlib.util.spec_from_file_location(
"management_v1_common__simulated_fastapi", Path(str(common_module.__file__))
)
assert spec is not None and spec.loader is not None
reimported = importlib.util.module_from_spec(spec)
spec.loader.exec_module(reimported)
assert _public_names(reimported) == _public_names(common_module)
Loading