diff --git a/tests/test_litellm/proxy/openai_files_endpoint/test_files_endpoint.py b/tests/test_litellm/proxy/openai_files_endpoint/test_files_endpoint.py index ed5b8cbd81c..ab1fd795d8a 100644 --- a/tests/test_litellm/proxy/openai_files_endpoint/test_files_endpoint.py +++ b/tests/test_litellm/proxy/openai_files_endpoint/test_files_endpoint.py @@ -1,6 +1,8 @@ +import io import json import os import sys +from typing import Union from unittest.mock import ANY import pytest @@ -21,6 +23,19 @@ from litellm.proxy.proxy_server import app from litellm.types.llms.openai import OpenAIFileObject client = TestClient(app) + + +def _get_file_size(file_content: Union[bytes, io.IOBase]) -> int: + """Return file size without loading IO objects fully into memory.""" + if isinstance(file_content, io.IOBase): + pos = file_content.tell() + file_content.seek(0, 2) + size = file_content.tell() + file_content.seek(pos) + return size + return len(file_content) # type: ignore[arg-type] + + from litellm.caching.caching import DualCache from litellm.proxy.proxy_server import hash_token from litellm.proxy.utils import ProxyLogging @@ -106,16 +121,18 @@ def test_mock_create_audio_file(mocker: MockerFixture, monkeypatch, llm_router: Asserts 'create_file' is called with the correct arguments """ import litellm + import litellm.proxy.proxy_server as ps from litellm import Router from litellm.proxy._types import LitellmUserRoles - import litellm.proxy.proxy_server as ps from litellm.proxy.utils import ProxyLogging monkeypatch.setattr("litellm.proxy.proxy_server.master_key", None) monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None) # Mock create_file as an async function - mock_create_file = mocker.patch("litellm.files.main.create_file", new=mocker.AsyncMock()) + mock_create_file = mocker.patch( + "litellm.files.main.create_file", new=mocker.AsyncMock() + ) proxy_logging_obj = ProxyLogging( user_api_key_cache=DualCache(default_in_memory_ttl=1) @@ -127,7 +144,14 @@ def test_mock_create_audio_file(mocker: MockerFixture, monkeypatch, llm_router: from litellm.llms.base_llm.files.transformation import BaseFileEndpoints class DummyManagedFiles(BaseFileEndpoints): - async def acreate_file(self, llm_router, create_file_request, target_model_names_list, litellm_parent_otel_span, user_api_key_dict): + async def acreate_file( + self, + llm_router, + create_file_request, + target_model_names_list, + litellm_parent_otel_span, + user_api_key_dict, + ): # Handle both dict and object forms of create_file_request if isinstance(create_file_request, dict): file_data = create_file_request.get("file") @@ -135,7 +159,7 @@ def test_mock_create_audio_file(mocker: MockerFixture, monkeypatch, llm_router: else: file_data = create_file_request.file purpose_data = create_file_request.purpose - + # Call the mocked litellm.files.main.create_file to ensure asserts work await litellm.files.main.create_file( custom_llm_provider="azure", @@ -153,26 +177,31 @@ def test_mock_create_audio_file(mocker: MockerFixture, monkeypatch, llm_router: ) # Return a dummy response object as needed by the test from litellm.types.llms.openai import OpenAIFileObject + return OpenAIFileObject( id="dummy-id", object="file", - bytes=len(file_data[1]) if file_data else 0, + bytes=_get_file_size(file_data[1]) if file_data else 0, created_at=1234567890, filename=file_data[0] if file_data else "test.wav", purpose=purpose_data, status="uploaded", ) - + async def afile_retrieve(self, file_id, litellm_parent_otel_span, llm_router): raise NotImplementedError("Not implemented for test") - + async def afile_list(self, purpose, litellm_parent_otel_span): raise NotImplementedError("Not implemented for test") - - async def afile_delete(self, file_id, litellm_parent_otel_span, llm_router, **data): + + async def afile_delete( + self, file_id, litellm_parent_otel_span, llm_router, **data + ): raise NotImplementedError("Not implemented for test") - - async def afile_content(self, file_id, litellm_parent_otel_span, llm_router, **data): + + async def afile_content( + self, file_id, litellm_parent_otel_span, llm_router, **data + ): raise NotImplementedError("Not implemented for test") # Manually add the hook to the proxy_hook_mapping @@ -245,8 +274,8 @@ def test_target_storage_invokes_storage_backend( """ Ensure target_storage is parsed and invokes the storage backend service. """ - from litellm.proxy._types import LitellmUserRoles import litellm.proxy.proxy_server as ps + from litellm.proxy._types import LitellmUserRoles monkeypatch.setattr("litellm.proxy.proxy_server.master_key", None) monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None) @@ -304,8 +333,8 @@ def test_target_storage_with_target_models( """ Ensure target_storage and target_model_names are parsed and passed through. """ - from litellm.proxy._types import LitellmUserRoles import litellm.proxy.proxy_server as ps + from litellm.proxy._types import LitellmUserRoles monkeypatch.setattr("litellm.proxy.proxy_server.master_key", None) monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None) @@ -611,7 +640,9 @@ def test_create_file_for_each_model( assert openai_call_found, "OpenAI call not found with expected parameters" -def test_create_file_with_expires_after(mocker: MockerFixture, monkeypatch, llm_router: Router): +def test_create_file_with_expires_after( + mocker: MockerFixture, monkeypatch, llm_router: Router +): """ Test that expires_after is properly parsed and passed through when creating a file """ @@ -624,18 +655,25 @@ def test_create_file_with_expires_after(mocker: MockerFixture, monkeypatch, llm_ proxy_logging_obj._add_proxy_hooks(llm_router) class DummyManagedFiles(BaseFileEndpoints): - async def acreate_file(self, llm_router, create_file_request, target_model_names_list, litellm_parent_otel_span, user_api_key_dict): + async def acreate_file( + self, + llm_router, + create_file_request, + target_model_names_list, + litellm_parent_otel_span, + user_api_key_dict, + ): # Verify expires_after is in the request if isinstance(create_file_request, dict): expires_after = create_file_request.get("expires_after") else: expires_after = getattr(create_file_request, "expires_after", None) - + # Verify expires_after was passed correctly assert expires_after is not None, "expires_after should be in the request" assert expires_after["anchor"] == "created_at" assert expires_after["seconds"] == 2592000 - + # Return a dummy response return OpenAIFileObject( id="file-abc123", @@ -646,17 +684,21 @@ def test_create_file_with_expires_after(mocker: MockerFixture, monkeypatch, llm_ purpose="fine-tune", status="uploaded", ) - + async def afile_retrieve(self, file_id, litellm_parent_otel_span, llm_router): raise NotImplementedError("Not implemented for test") - + async def afile_list(self, purpose, litellm_parent_otel_span): raise NotImplementedError("Not implemented for test") - - async def afile_delete(self, file_id, litellm_parent_otel_span, llm_router, **data): + + async def afile_delete( + self, file_id, litellm_parent_otel_span, llm_router, **data + ): raise NotImplementedError("Not implemented for test") - - async def afile_content(self, file_id, litellm_parent_otel_span, llm_router, **data): + + async def afile_content( + self, file_id, litellm_parent_otel_span, llm_router, **data + ): raise NotImplementedError("Not implemented for test") proxy_logging_obj.proxy_hook_mapping["managed_files"] = DummyManagedFiles() @@ -688,7 +730,9 @@ def test_create_file_with_expires_after(mocker: MockerFixture, monkeypatch, llm_ assert result["purpose"] == "fine-tune" -def test_create_file_with_expires_after_missing_anchor(mocker: MockerFixture, monkeypatch, llm_router: Router): +def test_create_file_with_expires_after_missing_anchor( + mocker: MockerFixture, monkeypatch, llm_router: Router +): """ Test that an error is returned when expires_after[anchor] is missing """ @@ -717,10 +761,15 @@ def test_create_file_with_expires_after_missing_anchor(mocker: MockerFixture, mo assert response.status_code == 400 error_detail = response.json() - assert "expires_after" in error_detail["error"]["message"].lower() or "both" in error_detail["error"]["message"].lower() + assert ( + "expires_after" in error_detail["error"]["message"].lower() + or "both" in error_detail["error"]["message"].lower() + ) -def test_create_file_with_expires_after_missing_seconds(mocker: MockerFixture, monkeypatch, llm_router: Router): +def test_create_file_with_expires_after_missing_seconds( + mocker: MockerFixture, monkeypatch, llm_router: Router +): """ Test that an error is returned when expires_after[seconds] is missing """ @@ -749,10 +798,15 @@ def test_create_file_with_expires_after_missing_seconds(mocker: MockerFixture, m assert response.status_code == 400 error_detail = response.json() - assert "expires_after" in error_detail["error"]["message"].lower() or "both" in error_detail["error"]["message"].lower() + assert ( + "expires_after" in error_detail["error"]["message"].lower() + or "both" in error_detail["error"]["message"].lower() + ) -def test_create_file_with_expires_after_valid_values(mocker: MockerFixture, monkeypatch, llm_router: Router): +def test_create_file_with_expires_after_valid_values( + mocker: MockerFixture, monkeypatch, llm_router: Router +): """ Test that expires_after works with valid anchor and seconds values """ @@ -765,18 +819,25 @@ def test_create_file_with_expires_after_valid_values(mocker: MockerFixture, monk proxy_logging_obj._add_proxy_hooks(llm_router) class DummyManagedFiles(BaseFileEndpoints): - async def acreate_file(self, llm_router, create_file_request, target_model_names_list, litellm_parent_otel_span, user_api_key_dict): + async def acreate_file( + self, + llm_router, + create_file_request, + target_model_names_list, + litellm_parent_otel_span, + user_api_key_dict, + ): # Verify expires_after is in the request if isinstance(create_file_request, dict): expires_after = create_file_request.get("expires_after") else: expires_after = getattr(create_file_request, "expires_after", None) - + # Verify expires_after was passed correctly assert expires_after is not None, "expires_after should be in the request" assert expires_after["anchor"] == "created_at" assert expires_after["seconds"] == 3600 - + return OpenAIFileObject( id="file-abc123", object="file", @@ -786,17 +847,21 @@ def test_create_file_with_expires_after_valid_values(mocker: MockerFixture, monk purpose="fine-tune", status="uploaded", ) - + async def afile_retrieve(self, file_id, litellm_parent_otel_span, llm_router): raise NotImplementedError("Not implemented for test") - + async def afile_list(self, purpose, litellm_parent_otel_span): raise NotImplementedError("Not implemented for test") - - async def afile_delete(self, file_id, litellm_parent_otel_span, llm_router, **data): + + async def afile_delete( + self, file_id, litellm_parent_otel_span, llm_router, **data + ): raise NotImplementedError("Not implemented for test") - - async def afile_content(self, file_id, litellm_parent_otel_span, llm_router, **data): + + async def afile_content( + self, file_id, litellm_parent_otel_span, llm_router, **data + ): raise NotImplementedError("Not implemented for test") proxy_logging_obj.proxy_hook_mapping["managed_files"] = DummyManagedFiles() @@ -827,7 +892,9 @@ def test_create_file_with_expires_after_valid_values(mocker: MockerFixture, monk assert result["purpose"] == "fine-tune" -def test_create_file_without_expires_after(mocker: MockerFixture, monkeypatch, llm_router: Router): +def test_create_file_without_expires_after( + mocker: MockerFixture, monkeypatch, llm_router: Router +): """ Test that file creation works normally without expires_after """ @@ -840,16 +907,25 @@ def test_create_file_without_expires_after(mocker: MockerFixture, monkeypatch, l proxy_logging_obj._add_proxy_hooks(llm_router) class DummyManagedFiles(BaseFileEndpoints): - async def acreate_file(self, llm_router, create_file_request, target_model_names_list, litellm_parent_otel_span, user_api_key_dict): + async def acreate_file( + self, + llm_router, + create_file_request, + target_model_names_list, + litellm_parent_otel_span, + user_api_key_dict, + ): # Verify expires_after is None when not provided if isinstance(create_file_request, dict): expires_after = create_file_request.get("expires_after") else: expires_after = getattr(create_file_request, "expires_after", None) - + # expires_after should be None when not provided - assert expires_after is None, "expires_after should be None when not provided" - + assert ( + expires_after is None + ), "expires_after should be None when not provided" + return OpenAIFileObject( id="file-abc123", object="file", @@ -859,17 +935,21 @@ def test_create_file_without_expires_after(mocker: MockerFixture, monkeypatch, l purpose="fine-tune", status="uploaded", ) - + async def afile_retrieve(self, file_id, litellm_parent_otel_span, llm_router): raise NotImplementedError("Not implemented for test") - + async def afile_list(self, purpose, litellm_parent_otel_span): raise NotImplementedError("Not implemented for test") - - async def afile_delete(self, file_id, litellm_parent_otel_span, llm_router, **data): + + async def afile_delete( + self, file_id, litellm_parent_otel_span, llm_router, **data + ): raise NotImplementedError("Not implemented for test") - - async def afile_content(self, file_id, litellm_parent_otel_span, llm_router, **data): + + async def afile_content( + self, file_id, litellm_parent_otel_span, llm_router, **data + ): raise NotImplementedError("Not implemented for test") proxy_logging_obj.proxy_hook_mapping["managed_files"] = DummyManagedFiles() @@ -898,11 +978,13 @@ def test_create_file_without_expires_after(mocker: MockerFixture, monkeypatch, l assert result["purpose"] == "fine-tune" -def test_managed_files_with_loadbalancing(mocker: MockerFixture, monkeypatch, llm_router: Router): +def test_managed_files_with_loadbalancing( + mocker: MockerFixture, monkeypatch, llm_router: Router +): """ Test that managed files work with loadbalancing when both target_model_names and enable_loadbalancing_on_batch_endpoints are enabled. - + This ensures that the priority order is correct: - managed files should take precedence over deprecated loadbalancing - managed files internally use llm_router.acreate_file() which provides loadbalancing @@ -912,28 +994,34 @@ def test_managed_files_with_loadbalancing(mocker: MockerFixture, monkeypatch, ll # Enable loadbalancing on batch endpoints monkeypatch.setattr("litellm.enable_loadbalancing_on_batch_endpoints", True) - + proxy_logging_obj = ProxyLogging( user_api_key_cache=DualCache(default_in_memory_ttl=1) ) proxy_logging_obj._add_proxy_hooks(llm_router) - + # Track calls to verify loadbalancing through router router_acreate_file_calls = [] - + class ManagedFilesWithLoadbalancing(BaseFileEndpoints): - async def acreate_file(self, llm_router, create_file_request, target_model_names_list, litellm_parent_otel_span, user_api_key_dict): + async def acreate_file( + self, + llm_router, + create_file_request, + target_model_names_list, + litellm_parent_otel_span, + user_api_key_dict, + ): # Verify we receive the target model names - assert len(target_model_names_list) > 0, "Should have target_model_names_list" - + assert ( + len(target_model_names_list) > 0 + ), "Should have target_model_names_list" + # Simulate what managed files does - call llm_router.acreate_file for each model # This is where loadbalancing happens internally for model in target_model_names_list: - router_acreate_file_calls.append({ - "model": model, - "via_router": True - }) - + router_acreate_file_calls.append({"model": model, "via_router": True}) + # Return a managed file ID (base64 encoded) return OpenAIFileObject( id="litellm_managed_file_abc123", @@ -944,23 +1032,29 @@ def test_managed_files_with_loadbalancing(mocker: MockerFixture, monkeypatch, ll purpose="batch", status="uploaded", ) - + async def afile_retrieve(self, file_id, litellm_parent_otel_span, llm_router): raise NotImplementedError("Not implemented for test") - + async def afile_list(self, purpose, litellm_parent_otel_span): raise NotImplementedError("Not implemented for test") - - async def afile_delete(self, file_id, litellm_parent_otel_span, llm_router, **data): + + async def afile_delete( + self, file_id, litellm_parent_otel_span, llm_router, **data + ): raise NotImplementedError("Not implemented for test") - - async def afile_content(self, file_id, litellm_parent_otel_span, llm_router, **data): + + async def afile_content( + self, file_id, litellm_parent_otel_span, llm_router, **data + ): raise NotImplementedError("Not implemented for test") - + import litellm.proxy.proxy_server as ps from litellm.proxy._types import LitellmUserRoles - proxy_logging_obj.proxy_hook_mapping["managed_files"] = ManagedFilesWithLoadbalancing() + proxy_logging_obj.proxy_hook_mapping["managed_files"] = ( + ManagedFilesWithLoadbalancing() + ) monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", llm_router) monkeypatch.setattr( "litellm.proxy.proxy_server.proxy_logging_obj", proxy_logging_obj @@ -971,12 +1065,12 @@ def test_managed_files_with_loadbalancing(mocker: MockerFixture, monkeypatch, ll app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth( api_key="test-key", user_role=LitellmUserRoles.PROXY_ADMIN ) - + try: # Create batch file content test_file_content = b'{"custom_id": "request-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": "Hello"}]}}' test_file = ("batch_data.jsonl", test_file_content, "application/jsonl") - + # Make request with both target_model_names AND enable_loadbalancing_on_batch_endpoints response = client.post( "/v1/files", @@ -987,7 +1081,7 @@ def test_managed_files_with_loadbalancing(mocker: MockerFixture, monkeypatch, ll }, headers={"Authorization": "Bearer test-key"}, ) - + # Verify success assert response.status_code == 200, response.text finally: @@ -995,13 +1089,17 @@ def test_managed_files_with_loadbalancing(mocker: MockerFixture, monkeypatch, ll result = response.json() assert result["id"] == "litellm_managed_file_abc123" assert result["purpose"] == "batch" - + # Verify that managed files was called (via router for loadbalancing) # This proves that managed files took precedence over deprecated loadbalancing - assert len(router_acreate_file_calls) == 2, "Should have called router for both models" + assert ( + len(router_acreate_file_calls) == 2 + ), "Should have called router for both models" assert router_acreate_file_calls[0]["model"] == "azure-gpt-3-5-turbo" assert router_acreate_file_calls[1]["model"] == "gpt-3.5-turbo" - assert all(call["via_router"] for call in router_acreate_file_calls), "All calls should go through router" + assert all( + call["via_router"] for call in router_acreate_file_calls + ), "All calls should go through router" def test_create_file_with_nested_litellm_metadata( @@ -1009,22 +1107,29 @@ def test_create_file_with_nested_litellm_metadata( ): """ Test that nested litellm_metadata is correctly parsed from form data in bracket notation. - + Regression test for: litellm_metadata[spend_logs_metadata][owner] format should be correctly parsed into nested dictionary structure. """ from litellm.llms.base_llm.files.transformation import BaseFileEndpoints from litellm.types.llms.openai import OpenAIFileObject - + proxy_logging_obj = ProxyLogging( user_api_key_cache=DualCache(default_in_memory_ttl=1) ) proxy_logging_obj._add_proxy_hooks(llm_router) - + captured_litellm_metadata = {} - + class DummyManagedFiles(BaseFileEndpoints): - async def acreate_file(self, llm_router, create_file_request, target_model_names_list, litellm_parent_otel_span, user_api_key_dict): + async def acreate_file( + self, + llm_router, + create_file_request, + target_model_names_list, + litellm_parent_otel_span, + user_api_key_dict, + ): # Capture litellm_metadata for verification if isinstance(create_file_request, dict): captured_litellm_metadata.update( @@ -1034,7 +1139,7 @@ def test_create_file_with_nested_litellm_metadata( captured_litellm_metadata.update( getattr(create_file_request, "litellm_metadata", {}) ) - + return OpenAIFileObject( id="file-test-123", object="file", @@ -1044,28 +1149,32 @@ def test_create_file_with_nested_litellm_metadata( purpose="fine-tune", status="uploaded", ) - + async def afile_retrieve(self, file_id, litellm_parent_otel_span, llm_router): raise NotImplementedError("Not implemented for test") - + async def afile_list(self, purpose, litellm_parent_otel_span): raise NotImplementedError("Not implemented for test") - - async def afile_delete(self, file_id, litellm_parent_otel_span, llm_router, **data): + + async def afile_delete( + self, file_id, litellm_parent_otel_span, llm_router, **data + ): raise NotImplementedError("Not implemented for test") - - async def afile_content(self, file_id, litellm_parent_otel_span, llm_router, **data): + + async def afile_content( + self, file_id, litellm_parent_otel_span, llm_router, **data + ): raise NotImplementedError("Not implemented for test") - + proxy_logging_obj.proxy_hook_mapping["managed_files"] = DummyManagedFiles() monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", llm_router) monkeypatch.setattr( "litellm.proxy.proxy_server.proxy_logging_obj", proxy_logging_obj ) - + test_file_content = b'{"prompt": "Hello", "completion": "Hi"}' test_file = ("test.jsonl", test_file_content, "application/jsonl") - + # Test with nested litellm_metadata in bracket notation response = client.post( "/v1/files", @@ -1080,12 +1189,12 @@ def test_create_file_with_nested_litellm_metadata( }, headers={"Authorization": "Bearer test-key"}, ) - + # Verify success assert response.status_code == 200 result = response.json() assert result["id"] == "file-test-123" - + # Verify nested metadata was correctly parsed assert "spend_logs_metadata" in captured_litellm_metadata assert captured_litellm_metadata["spend_logs_metadata"]["owner"] == "john_doe" @@ -1099,26 +1208,33 @@ def test_create_file_with_deep_nested_litellm_metadata( ): """ Test that deeply nested litellm_metadata is correctly parsed from form data. - + Regression test for: litellm_metadata[a][b][c] format should be correctly parsed. """ + import litellm.proxy.proxy_server as ps from litellm.llms.base_llm.files.transformation import BaseFileEndpoints from litellm.proxy._types import LitellmUserRoles - import litellm.proxy.proxy_server as ps from litellm.types.llms.openai import OpenAIFileObject - + monkeypatch.setattr("litellm.proxy.proxy_server.master_key", None) monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None) - + proxy_logging_obj = ProxyLogging( user_api_key_cache=DualCache(default_in_memory_ttl=1) ) proxy_logging_obj._add_proxy_hooks(llm_router) - + captured_litellm_metadata = {} - + class DummyManagedFiles(BaseFileEndpoints): - async def acreate_file(self, llm_router, create_file_request, target_model_names_list, litellm_parent_otel_span, user_api_key_dict): + async def acreate_file( + self, + llm_router, + create_file_request, + target_model_names_list, + litellm_parent_otel_span, + user_api_key_dict, + ): if isinstance(create_file_request, dict): captured_litellm_metadata.update( create_file_request.get("litellm_metadata", {}) @@ -1127,7 +1243,7 @@ def test_create_file_with_deep_nested_litellm_metadata( captured_litellm_metadata.update( getattr(create_file_request, "litellm_metadata", {}) ) - + return OpenAIFileObject( id="file-test-456", object="file", @@ -1137,33 +1253,37 @@ def test_create_file_with_deep_nested_litellm_metadata( purpose="batch", status="uploaded", ) - + async def afile_retrieve(self, file_id, litellm_parent_otel_span, llm_router): raise NotImplementedError("Not implemented for test") - + async def afile_list(self, purpose, litellm_parent_otel_span): raise NotImplementedError("Not implemented for test") - - async def afile_delete(self, file_id, litellm_parent_otel_span, llm_router, **data): + + async def afile_delete( + self, file_id, litellm_parent_otel_span, llm_router, **data + ): raise NotImplementedError("Not implemented for test") - - async def afile_content(self, file_id, litellm_parent_otel_span, llm_router, **data): + + async def afile_content( + self, file_id, litellm_parent_otel_span, llm_router, **data + ): raise NotImplementedError("Not implemented for test") - + proxy_logging_obj.proxy_hook_mapping["managed_files"] = DummyManagedFiles() monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", llm_router) monkeypatch.setattr( "litellm.proxy.proxy_server.proxy_logging_obj", proxy_logging_obj ) - + app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth( user_role=LitellmUserRoles.PROXY_ADMIN, user_id="test-user" ) - + try: test_file_content = b'{"custom_id": "req-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-3.5-turbo"}}' test_file = ("nested.jsonl", test_file_content, "application/jsonl") - + # Test with deeply nested metadata response = client.post( "/v1/files", @@ -1177,12 +1297,12 @@ def test_create_file_with_deep_nested_litellm_metadata( }, headers={"Authorization": "Bearer test-key"}, ) - + # Verify success assert response.status_code == 200, response.text result = response.json() assert result["id"] == "file-test-456" - + # Verify deeply nested metadata was correctly parsed assert "config" in captured_litellm_metadata assert "database" in captured_litellm_metadata["config"] @@ -1356,7 +1476,9 @@ def test_file_team_injects_when_caller_sends_nothing( # --------------------------------------------------------------------------- -def _post_file_raw(monkeypatch, llm_router: Router, team_metadata: dict, form_data: dict): +def _post_file_raw( + monkeypatch, llm_router: Router, team_metadata: dict, form_data: dict +): """POST /v1/files and return the raw response (no status assertion).""" from litellm.proxy.auth.user_api_key_auth import user_api_key_auth