Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/container_endpoints/endpoints.py: 86%
77 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
1#### Container Endpoints #####
3from typing import Final
5from fastapi import APIRouter, Depends, HTTPException, Request, Response
6from fastapi.responses import ORJSONResponse
8from litellm._logging import verbose_proxy_logger
9from litellm.proxy._types import *
10from litellm.proxy.auth.user_api_key_auth import UserAPIKeyAuth, user_api_key_auth
11from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing
12from litellm.proxy.common_utils.http_parsing_utils import _read_request_body
13from litellm.proxy.common_utils.openai_endpoint_utils import (
14 get_custom_llm_provider_from_request_body,
15 get_custom_llm_provider_from_request_headers,
16 get_custom_llm_provider_from_request_query,
17)
18from litellm.proxy.common_utils.resource_ownership import is_proxy_admin
19from litellm.proxy.container_endpoints.ownership import (
20 assert_user_can_access_container,
21 get_container_forwarding_params,
22 list_owned_containers,
23 record_container_owner,
24)
26router: Final = APIRouter()
29@router.post(
30 "/v1/containers",
31 dependencies=[Depends(user_api_key_auth)],
32 response_class=ORJSONResponse,
33 tags=["containers"],
34)
35@router.post(
36 "/containers",
37 dependencies=[Depends(user_api_key_auth)],
38 response_class=ORJSONResponse,
39 tags=["containers"],
40)
41async def create_container(
42 request: Request,
43 fastapi_response: Response,
44 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
45):
46 """
47 Container creation endpoint for creating new containers.
49 Follows the OpenAI Containers API spec:
50 https://platform.openai.com/docs/api-reference/containers
52 Example:
53 ```bash
54 curl -X POST "http://localhost:4000/v1/containers" \
55 -H "Authorization: Bearer sk-1234" \
56 -H "Content-Type: application/json" \
57 -d '{
58 "name": "My Container",
59 "expires_after": {
60 "anchor": "last_active_at",
61 "minutes": 20
62 }
63 }'
64 ```
66 Or specify provider via header:
67 ```bash
68 curl -X POST "http://localhost:4000/v1/containers" \
69 -H "Authorization: Bearer sk-1234" \
70 -H "custom-llm-provider: azure" \
71 -H "Content-Type: application/json" \
72 -d '{
73 "name": "My Container"
74 }'
75 ```
76 """
77 from litellm.proxy.proxy_server import (
78 general_settings,
79 llm_router,
80 proxy_config,
81 proxy_logging_obj,
82 select_data_generator,
83 user_api_base,
84 user_max_tokens,
85 user_model,
86 user_request_timeout,
87 user_temperature,
88 version,
89 )
91 # Read request body
92 data: Final = await _read_request_body(request=request)
94 # Extract custom_llm_provider using priority chain
95 # Priority: headers > query params > request body > default
96 custom_llm_provider: Final = (
97 get_custom_llm_provider_from_request_headers(request=request)
98 or get_custom_llm_provider_from_request_query(request=request)
99 or await get_custom_llm_provider_from_request_body(request=request)
100 or "openai"
101 )
103 # Add custom_llm_provider to data
104 data["custom_llm_provider"] = custom_llm_provider
106 # Process request using ProxyBaseLLMRequestProcessing
107 processor: Final = ProxyBaseLLMRequestProcessing(data=data)
108 try:
109 response: Final[object] = await processor.base_process_llm_request(
110 request=request,
111 fastapi_response=fastapi_response,
112 user_api_key_dict=user_api_key_dict,
113 route_type="acreate_container",
114 proxy_logging_obj=proxy_logging_obj,
115 llm_router=llm_router,
116 general_settings=general_settings,
117 proxy_config=proxy_config,
118 select_data_generator=select_data_generator,
119 model=None,
120 user_model=user_model,
121 user_temperature=user_temperature,
122 user_request_timeout=user_request_timeout,
123 user_max_tokens=user_max_tokens,
124 user_api_base=user_api_base,
125 version=version,
126 )
127 except Exception as e:
128 raise await processor._handle_llm_api_exception(
129 e=e,
130 user_api_key_dict=user_api_key_dict,
131 proxy_logging_obj=proxy_logging_obj,
132 version=version,
133 )
135 # Ownership recording sits between the upstream create and the response
136 # to the caller. The recorder swallows DB errors internally (falling
137 # back to in-memory tracking) and only surfaces HTTPException on auth
138 # conflicts; we let those propagate verbatim so the client sees the
139 # real status code rather than a generic LLM error wrapper.
140 try:
141 return await record_container_owner(
142 response=response,
143 user_api_key_dict=user_api_key_dict,
144 custom_llm_provider=custom_llm_provider,
145 )
146 except HTTPException:
147 raise
148 except Exception as e:
149 # Unexpected (non-HTTPException) failure after upstream create.
150 # The container exists upstream but is now untracked — log loudly
151 # so an operator can reconcile, and return the response so the
152 # caller does not get charged for a resource they cannot use.
153 verbose_proxy_logger.exception(
154 "Container ownership recording failed after upstream create; "
155 "returning response with untracked ownership: %s",
156 e,
157 )
158 return response
161@router.get(
162 "/v1/containers",
163 dependencies=[Depends(user_api_key_auth)],
164 response_class=ORJSONResponse,
165 tags=["containers"],
166)
167@router.get(
168 "/containers",
169 dependencies=[Depends(user_api_key_auth)],
170 response_class=ORJSONResponse,
171 tags=["containers"],
172)
173async def list_containers(
174 request: Request,
175 fastapi_response: Response,
176 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
177 after: str | None = None,
178 limit: int | None = None,
179 order: str | None = None,
180):
181 """
182 Container list endpoint for retrieving a list of containers.
184 Follows the OpenAI Containers API spec:
185 https://platform.openai.com/docs/api-reference/containers
187 Example:
188 ```bash
189 curl -X GET "http://localhost:4000/v1/containers?limit=20&order=desc" \
190 -H "Authorization: Bearer sk-1234"
191 ```
193 Or specify provider via header or query param:
194 ```bash
195 curl -X GET "http://localhost:4000/v1/containers?custom_llm_provider=azure" \
196 -H "Authorization: Bearer sk-1234"
197 ```
198 """
199 from litellm.proxy.proxy_server import (
200 general_settings,
201 llm_router,
202 proxy_config,
203 proxy_logging_obj,
204 select_data_generator,
205 user_api_base,
206 user_max_tokens,
207 user_model,
208 user_request_timeout,
209 user_temperature,
210 version,
211 )
213 query_params: Final = dict(request.query_params)
214 custom_llm_provider: Final = (
215 get_custom_llm_provider_from_request_headers(request=request)
216 or get_custom_llm_provider_from_request_query(request=request)
217 or "openai"
218 )
219 data: Final[dict[str, object]] = {
220 "query_params": query_params,
221 "model": query_params.get("model"),
222 "order": order,
223 "custom_llm_provider": custom_llm_provider,
224 }
226 async def fetch_page(page_after: str | None, page_limit: int | None) -> object:
227 processor: Final = ProxyBaseLLMRequestProcessing(data={**data, "after": page_after, "limit": page_limit})
228 try:
229 return await processor.base_process_llm_request(
230 request=request,
231 fastapi_response=fastapi_response,
232 user_api_key_dict=user_api_key_dict,
233 route_type="alist_containers",
234 proxy_logging_obj=proxy_logging_obj,
235 llm_router=llm_router,
236 general_settings=general_settings,
237 proxy_config=proxy_config,
238 select_data_generator=select_data_generator,
239 model=None,
240 user_model=user_model,
241 user_temperature=user_temperature,
242 user_request_timeout=user_request_timeout,
243 user_max_tokens=user_max_tokens,
244 user_api_base=user_api_base,
245 version=version,
246 )
247 except Exception as e:
248 raise await processor._handle_llm_api_exception(
249 e=e,
250 user_api_key_dict=user_api_key_dict,
251 proxy_logging_obj=proxy_logging_obj,
252 version=version,
253 )
255 if is_proxy_admin(user_api_key_dict): 255 ↛ 257line 255 didn't jump to line 257 because the condition on line 255 was always true
256 return await fetch_page(after, limit)
257 return await list_owned_containers(
258 fetch_page=fetch_page,
259 after=after,
260 limit=limit,
261 user_api_key_dict=user_api_key_dict,
262 custom_llm_provider=custom_llm_provider,
263 )
266@router.get(
267 "/v1/containers/{container_id}",
268 dependencies=[Depends(user_api_key_auth)],
269 response_class=ORJSONResponse,
270 tags=["containers"],
271)
272@router.get(
273 "/containers/{container_id}",
274 dependencies=[Depends(user_api_key_auth)],
275 response_class=ORJSONResponse,
276 tags=["containers"],
277)
278async def retrieve_container(
279 request: Request,
280 container_id: str,
281 fastapi_response: Response,
282 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
283):
284 """
285 Container retrieve endpoint for getting details of a specific container.
287 Follows the OpenAI Containers API spec:
288 https://platform.openai.com/docs/api-reference/containers
290 Example:
291 ```bash
292 curl -X GET "http://localhost:4000/v1/containers/cntr_123" \
293 -H "Authorization: Bearer sk-1234"
294 ```
296 Or specify provider via header:
297 ```bash
298 curl -X GET "http://localhost:4000/v1/containers/cntr_123" \
299 -H "Authorization: Bearer sk-1234" \
300 -H "custom-llm-provider: azure"
301 ```
302 """
303 from litellm.proxy.proxy_server import (
304 general_settings,
305 llm_router,
306 proxy_config,
307 proxy_logging_obj,
308 select_data_generator,
309 user_api_base,
310 user_max_tokens,
311 user_model,
312 user_request_timeout,
313 user_temperature,
314 version,
315 )
317 # Include container_id in request data
318 data: Final[dict[str, object]] = {"container_id": container_id}
320 # Extract custom_llm_provider using priority chain
321 custom_llm_provider = (
322 get_custom_llm_provider_from_request_headers(request=request)
323 or get_custom_llm_provider_from_request_query(request=request)
324 or "openai"
325 )
327 # Add custom_llm_provider to data
328 original_container_id, custom_llm_provider = await assert_user_can_access_container(
329 container_id=container_id,
330 user_api_key_dict=user_api_key_dict,
331 custom_llm_provider=custom_llm_provider,
332 )
333 data.update(
334 await get_container_forwarding_params(
335 container_id,
336 original_container_id,
337 custom_llm_provider,
338 )
339 )
341 # Process request using ProxyBaseLLMRequestProcessing
342 processor: Final = ProxyBaseLLMRequestProcessing(data=data)
343 try:
344 container: Final[object] = await processor.base_process_llm_request(
345 request=request,
346 fastapi_response=fastapi_response,
347 user_api_key_dict=user_api_key_dict,
348 route_type="aretrieve_container",
349 proxy_logging_obj=proxy_logging_obj,
350 llm_router=llm_router,
351 general_settings=general_settings,
352 proxy_config=proxy_config,
353 select_data_generator=select_data_generator,
354 model=None,
355 user_model=user_model,
356 user_temperature=user_temperature,
357 user_request_timeout=user_request_timeout,
358 user_max_tokens=user_max_tokens,
359 user_api_base=user_api_base,
360 version=version,
361 )
362 except Exception as e:
363 raise await processor._handle_llm_api_exception(
364 e=e,
365 user_api_key_dict=user_api_key_dict,
366 proxy_logging_obj=proxy_logging_obj,
367 version=version,
368 )
369 return container
372@router.delete(
373 "/v1/containers/{container_id}",
374 dependencies=[Depends(user_api_key_auth)],
375 response_class=ORJSONResponse,
376 tags=["containers"],
377)
378@router.delete(
379 "/containers/{container_id}",
380 dependencies=[Depends(user_api_key_auth)],
381 response_class=ORJSONResponse,
382 tags=["containers"],
383)
384async def delete_container(
385 request: Request,
386 container_id: str,
387 fastapi_response: Response,
388 user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
389):
390 """
391 Container delete endpoint for deleting a specific container.
393 Follows the OpenAI Containers API spec:
394 https://platform.openai.com/docs/api-reference/containers
396 Example:
397 ```bash
398 curl -X DELETE "http://localhost:4000/v1/containers/cntr_123" \
399 -H "Authorization: Bearer sk-1234"
400 ```
402 Or specify provider via header:
403 ```bash
404 curl -X DELETE "http://localhost:4000/v1/containers/cntr_123" \
405 -H "Authorization: Bearer sk-1234" \
406 -H "custom-llm-provider: azure"
407 ```
408 """
409 from litellm.proxy.proxy_server import (
410 general_settings,
411 llm_router,
412 proxy_config,
413 proxy_logging_obj,
414 select_data_generator,
415 user_api_base,
416 user_max_tokens,
417 user_model,
418 user_request_timeout,
419 user_temperature,
420 version,
421 )
423 # Include container_id in request data
424 data: Final[dict[str, object]] = {"container_id": container_id}
426 # Extract custom_llm_provider using priority chain
427 custom_llm_provider = (
428 get_custom_llm_provider_from_request_headers(request=request)
429 or get_custom_llm_provider_from_request_query(request=request)
430 or "openai"
431 )
433 # Add custom_llm_provider to data
434 original_container_id, custom_llm_provider = await assert_user_can_access_container(
435 container_id=container_id,
436 user_api_key_dict=user_api_key_dict,
437 custom_llm_provider=custom_llm_provider,
438 )
439 data.update(
440 await get_container_forwarding_params(
441 container_id,
442 original_container_id,
443 custom_llm_provider,
444 )
445 )
447 # Process request using ProxyBaseLLMRequestProcessing
448 processor: Final = ProxyBaseLLMRequestProcessing(data=data)
449 try:
450 deleted_container: Final[object] = await processor.base_process_llm_request(
451 request=request,
452 fastapi_response=fastapi_response,
453 user_api_key_dict=user_api_key_dict,
454 route_type="adelete_container",
455 proxy_logging_obj=proxy_logging_obj,
456 llm_router=llm_router,
457 general_settings=general_settings,
458 proxy_config=proxy_config,
459 select_data_generator=select_data_generator,
460 model=None,
461 user_model=user_model,
462 user_temperature=user_temperature,
463 user_request_timeout=user_request_timeout,
464 user_max_tokens=user_max_tokens,
465 user_api_base=user_api_base,
466 version=version,
467 )
468 except Exception as e:
469 raise await processor._handle_llm_api_exception(
470 e=e,
471 user_api_key_dict=user_api_key_dict,
472 proxy_logging_obj=proxy_logging_obj,
473 version=version,
474 )
475 return deleted_container
478# Register JSON-configured container file endpoints
479from litellm.proxy.container_endpoints.handler_factory import (
480 register_container_file_endpoints,
481)
483register_container_file_endpoints(router)