litellm/tests/unit/llms/custom_httpx/test_container_handler.py
Mateo Wang d4a791d650
refactor(types): replace Any with proven types in 157 files (#44798)
* refactor(types): replace Any with proven types in 299 files

Clears 871 basedpyright Any errors (reportAny 6,703 to 6,240, reportExplicitAny 1,651 to 1,243) without adding a cast, an ignore or a suppression, and without touching any budget file

Most edits are annotation-only: a parameter, return or local goes from Any to object, Mapping[str, object] or the concrete type the value always held. Fourteen files validate untyped JSON once where it enters, through a module-level pydantic TypeAdapter or model_validate, and then use real types

No HTTP status, error type or response shape changes. Mistral speech and fal.ai Bria image generation now report a pydantic ValidationError instead of an AttributeError when the provider answers 2xx with a body that is not a JSON object

* refactor(types): make the config locals fix effective and trim no-op edits

The Mapping[str, object] annotation on `locals().copy()` removed no error,
because the checker narrows the variable back to the dict[str, Any] the call
returns. 21 provider config constructors now build the same copy with
dict(locals()), which the checker infers as object values under that
annotation, so each file loses one reportAny.

The same annotation is reverted in 31 other config files where it stayed a
no-op, together with the tests that were added only to cover those lines, and
the one MCP server manager line that no CI coverage shard executes is
reverted too. The pull request drops from 345 to 297 changed files.

* refactor(types): accept only int in the proxy state setter

get_proxy_state_variable is annotated to return int, but
set_proxy_state_variable still took Any, so the checker could not hold
callers to the type the getter promises. The setter now takes int, which is
what its only caller already passes.

* refactor(types): index the proxy state key so the getter returns int

* refactor(types): keep public annotations and provider error text unchanged

Restore every public return, public method parameter, public attribute and exported
alias to its annotation on main so code that type-checks against the package keeps
type-checking, and take the Mistral speech and fal.ai Bria changes back out so no
provider error message differs from main

* refactor(types): leave the Vertex RAG chunking read as it is on main

Take the chunking format validation back out of the Vertex RAG ingestion path. It needs the vertexai SDK, a storage bucket and a RAG corpus to execute, so nothing here could run it end to end, and it cleared only two errors

* test(integration): pin the validated provider boundaries on a live proxy

* test(integration): give the held burst a client that outlasts the gate

The fault cell holds a burst at the upstream for up to 60 seconds while it kills a worker, but sent the burst through the shared 15 second client, so a slow box could time the survivors out before the gate opened. The burst now goes through its own client whose timeout is twice the gate, and the gate length is one named constant.

* refactor(types): keep the license reply handling and experimental MCP signatures as they were

The license check validated the whole reply as a mapping, which changed the error text logged for a reply that is not an object. It now validates only the verify value, so every reply is handled and logged exactly as before while the value is still typed.

Three files under the experimental MCP server changed annotations on public functions and methods (three returns and three parameters). They go back to their previous content so no public signature in the diff is narrowed.

* test(integration): answer the proxy's model-list call in the OpenAI stand-ins

Every 300 seconds each proxy worker asks an OpenAI deployment for GET /v1/models. Four new cells own an OpenAI stand-in that accepted only the call under test, so a refresh landing inside a cell failed it. The stand-ins now answer that call through the suite's own helper and the cells count only the provider calls they drive.
2026-10-06 15:31:54 +00:00

126 lines
4 KiB
Python

from typing import Final
from unittest.mock import MagicMock
import httpx
import pytest
import litellm
from litellm.llms.base_llm.chat.transformation import BaseLLMException
from litellm.llms.custom_httpx.container_handler import generic_container_handler
from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler
from litellm.types.containers.main import DeleteContainerFileResponse
from litellm.types.router import GenericLiteLLMParams
from litellm.utils import ProviderConfigManager
FILE_NOT_FOUND_BODY = {
"error": {
"message": "File not found.",
"type": "invalid_request_error",
"param": None,
"code": None,
}
}
def _sync_client(response: httpx.Response) -> HTTPHandler:
handler = HTTPHandler()
handler.client = httpx.Client(transport=httpx.MockTransport(lambda _request: response))
return handler
def _async_client(response: httpx.Response) -> AsyncHTTPHandler:
handler = AsyncHTTPHandler()
handler.client = httpx.AsyncClient(transport=httpx.MockTransport(lambda _request: response))
return handler
def _handle(endpoint_name: str, client, **overrides):
return generic_container_handler.handle(
endpoint_name=endpoint_name,
container_provider_config=ProviderConfigManager.get_provider_container_config(
provider=litellm.LlmProviders.OPENAI
),
litellm_params=GenericLiteLLMParams(api_key="sk-test"),
logging_obj=MagicMock(),
client=client,
container_id="cntr_real",
file_id="cfile_nonexistent",
**overrides,
)
def test_binary_endpoint_raises_on_error_status():
with pytest.raises(BaseLLMException) as exc_info:
_handle(
"retrieve_container_file_content",
_sync_client(httpx.Response(404, json=FILE_NOT_FOUND_BODY)),
)
assert exc_info.value.status_code == 404
assert exc_info.value.message == "File not found."
@pytest.mark.asyncio
async def test_async_binary_endpoint_raises_on_error_status():
with pytest.raises(BaseLLMException) as exc_info:
await _handle(
"aretrieve_container_file_content",
_async_client(httpx.Response(404, json=FILE_NOT_FOUND_BODY)),
_is_async=True,
)
assert exc_info.value.status_code == 404
assert exc_info.value.message == "File not found."
def test_binary_endpoint_returns_raw_content_on_success():
content = _handle(
"retrieve_container_file_content",
_sync_client(httpx.Response(200, content=b"\x00binary-payload")),
)
assert content == b"\x00binary-payload"
def test_error_status_with_non_json_body_surfaces_response_text():
with pytest.raises(BaseLLMException) as exc_info:
_handle(
"retrieve_container_file_content",
_sync_client(httpx.Response(502, content=b"<html>bad gateway</html>")),
)
assert exc_info.value.status_code == 502
assert exc_info.value.message == "<html>bad gateway</html>"
def test_json_endpoint_still_raises_provider_error_message():
with pytest.raises(BaseLLMException) as exc_info:
_handle(
"retrieve_container_file",
_sync_client(httpx.Response(404, json=FILE_NOT_FOUND_BODY)),
)
assert exc_info.value.status_code == 404
assert exc_info.value.message == "File not found."
def test_json_endpoint_sends_the_configured_route_and_parses_its_response_model():
handler: Final = HTTPHandler()
handler.client = httpx.Client(
transport=httpx.MockTransport(
lambda request: httpx.Response(
200,
json={
"id": request.url.path,
"object": "container.file.deleted",
"deleted": request.method == "DELETE",
},
)
)
)
response: Final = _handle("delete_container_file", handler)
assert type(response) is DeleteContainerFileResponse
assert response.id.endswith("/containers/cntr_real/files/cfile_nonexistent")
assert response.deleted is True