mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-12 03:48:56 +00:00
fix(home): serve the bundled whats_new.json by default so a build shows only its own launches (#45934)
* fix(home): serve the bundled whats_new.json by default so a release shows only its own launches Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> (cherry picked from commit4dd96800e8) * style: annotate whats_new_url with str | None Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> (cherry picked from commitd1ae7ea085) * test(home): pin whats_new_url to the env var and regenerate the dashboard API types Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> (cherry picked from commit4bfec53865) * test(public_endpoints): prove the /public/whats_new route serves the bundled list when no URL is configured Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(public_endpoints): drop the unused os import Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(home): pin whats_new_url to the env var so a baked-in default URL fails the suite Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * style(tests): sort the import blocks ruff flagged in test_public_endpoints Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(home): let the route test read the real whats_new_url default so a baked-in URL fails it Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: mateo <mateo@berri.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
3090f4b23e
commit
2a01fe6e5c
4 changed files with 58 additions and 20 deletions
|
|
@ -440,10 +440,7 @@ autorouter_presets_url: str = os.getenv(
|
|||
"LITELLM_AUTOROUTER_PRESETS_URL",
|
||||
"https://raw.githubusercontent.com/BerriAI/litellm/main/litellm/proxy/public_endpoints/autorouter_presets.json",
|
||||
)
|
||||
whats_new_url: str = os.getenv(
|
||||
"LITELLM_WHATS_NEW_URL",
|
||||
"https://raw.githubusercontent.com/BerriAI/litellm/main/litellm/proxy/public_endpoints/whats_new.json",
|
||||
)
|
||||
whats_new_url: str | None = os.getenv("LITELLM_WHATS_NEW_URL")
|
||||
suppress_debug_info: bool = False
|
||||
dynamodb_table_name: Optional[str] = None
|
||||
s3_callback_params: Optional[Dict] = None
|
||||
|
|
|
|||
|
|
@ -591,8 +591,8 @@ async def _fetch_remote_whats_new(url: str) -> WhatsNewResponse:
|
|||
return WhatsNewResponse.model_validate_json(await _fetch_remote_bytes(url))
|
||||
|
||||
|
||||
async def _resolve_whats_new(url: str, fetch: Callable[[str], Awaitable[WhatsNewResponse]]) -> WhatsNewResponse:
|
||||
if os.getenv("LITELLM_LOCAL_WHATS_NEW", "").lower() == "true":
|
||||
async def _resolve_whats_new(url: str | None, fetch: Callable[[str], Awaitable[WhatsNewResponse]]) -> WhatsNewResponse:
|
||||
if url is None or os.getenv("LITELLM_LOCAL_WHATS_NEW", "").lower() == "true":
|
||||
return _load_bundled_whats_new()
|
||||
try:
|
||||
return await fetch(url)
|
||||
|
|
@ -611,7 +611,7 @@ class _WhatsNewCache:
|
|||
|
||||
|
||||
async def get_whats_new(
|
||||
url: str,
|
||||
url: str | None,
|
||||
fetch: Callable[[str], Awaitable[WhatsNewResponse]] = _fetch_remote_whats_new,
|
||||
) -> WhatsNewResponse:
|
||||
cached: Final = _WhatsNewCache.launches
|
||||
|
|
@ -637,10 +637,10 @@ async def get_public_whats_new() -> WhatsNewResponse:
|
|||
"""
|
||||
Return the launches the dashboard Home page shows under What's new.
|
||||
|
||||
Resolved once per process: fetched from ``litellm.whats_new_url`` (override with ``LITELLM_WHATS_NEW_URL``)
|
||||
on the first request, falling back to the list bundled with the package on any failure, so an airgapped
|
||||
proxy serves the bundled list without retrying. Set ``LITELLM_LOCAL_WHATS_NEW=True`` to skip the fetch.
|
||||
A restart picks up a newly published list.
|
||||
Serves the list bundled with this release. Set ``LITELLM_WHATS_NEW_URL`` to fetch a remote list instead,
|
||||
resolved once per process on the first request and falling back to the bundled list on any failure, so an
|
||||
airgapped proxy serves the bundled list without retrying. ``LITELLM_LOCAL_WHATS_NEW=True`` forces the
|
||||
bundled list even when a URL is set. A restart picks up a newly published list.
|
||||
"""
|
||||
return await get_whats_new(url=litellm.whats_new_url)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import json
|
||||
import os
|
||||
import re
|
||||
from collections.abc import Iterator
|
||||
from datetime import datetime, timezone
|
||||
from importlib.resources import files
|
||||
from collections.abc import Iterator
|
||||
from typing import Final
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
from pydantic import TypeAdapter
|
||||
|
||||
import litellm
|
||||
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
|
||||
from litellm.proxy.public_endpoints import router
|
||||
from litellm.router_strategy.complexity_router.fuse_presets import get_fuse_presets
|
||||
|
|
@ -1168,8 +1168,8 @@ def test_public_mcp_hub_derives_publication_metadata_without_mutating_registry(
|
|||
stored_public: bool | None,
|
||||
) -> None:
|
||||
from litellm.proxy._experimental.mcp_server.mcp_server_manager import MCPServerManager
|
||||
from litellm.types.mcp_server.mcp_server_manager import MCPServer
|
||||
from litellm.proxy._types import MCPTransport
|
||||
from litellm.types.mcp_server.mcp_server_manager import MCPServer
|
||||
|
||||
app: Final = FastAPI()
|
||||
app.include_router(router)
|
||||
|
|
@ -1253,8 +1253,8 @@ def test_public_mcp_hub_returns_empty_when_whitelist_unset():
|
|||
def test_public_mcp_hub_does_not_expose_upstream_url():
|
||||
"""Regression: /public/mcp_hub is unauthenticated, so the gateway-internal
|
||||
upstream url must never appear in its response even when the server has one."""
|
||||
from litellm.types.mcp_server.mcp_server_manager import MCPServer
|
||||
from litellm.proxy._types import MCPTransport
|
||||
from litellm.types.mcp_server.mcp_server_manager import MCPServer
|
||||
|
||||
app = FastAPI()
|
||||
app.include_router(router)
|
||||
|
|
@ -1586,6 +1586,47 @@ def test_get_whats_new_local_mode_serves_bundled_list(
|
|||
assert len(served.launches) > 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_whats_new_serves_bundled_list_without_fetching_when_no_url_is_configured(
|
||||
monkeypatch: pytest.MonkeyPatch, reset_whats_new_cache: None
|
||||
) -> None:
|
||||
from litellm.proxy.public_endpoints.public_endpoints import _load_bundled_whats_new, get_whats_new
|
||||
|
||||
monkeypatch.delenv("LITELLM_LOCAL_WHATS_NEW", raising=False)
|
||||
|
||||
async def forbidden_fetch(url: str) -> WhatsNewResponse:
|
||||
raise AssertionError(f"no URL is configured, so nothing may be fetched, got {url}")
|
||||
|
||||
served = await get_whats_new(url=None, fetch=forbidden_fetch)
|
||||
|
||||
assert served == _load_bundled_whats_new()
|
||||
assert len(served.launches) > 0
|
||||
|
||||
|
||||
def test_whats_new_url_default_mirrors_the_env_var_so_no_remote_url_is_baked_in() -> None:
|
||||
assert litellm.whats_new_url == os.environ.get("LITELLM_WHATS_NEW_URL")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_public_whats_new_route_serves_the_bundled_list_without_fetching_when_no_url_is_configured(
|
||||
monkeypatch: pytest.MonkeyPatch, reset_whats_new_cache: None
|
||||
) -> None:
|
||||
from litellm.proxy.public_endpoints import public_endpoints
|
||||
from litellm.proxy.public_endpoints.public_endpoints import _load_bundled_whats_new, get_public_whats_new
|
||||
|
||||
monkeypatch.delenv("LITELLM_LOCAL_WHATS_NEW", raising=False)
|
||||
|
||||
async def forbidden_fetch(url: str) -> bytes:
|
||||
raise AssertionError(f"no URL is configured, so the route may not fetch, got {url}")
|
||||
|
||||
monkeypatch.setattr(public_endpoints, "_fetch_remote_bytes", forbidden_fetch)
|
||||
|
||||
served = await get_public_whats_new()
|
||||
|
||||
assert served == _load_bundled_whats_new()
|
||||
assert len(served.launches) > 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_whats_new_fetches_once_per_process(
|
||||
monkeypatch: pytest.MonkeyPatch, reset_whats_new_cache: None
|
||||
|
|
|
|||
8
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
8
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
|
|
@ -13669,10 +13669,10 @@ export interface paths {
|
|||
* Get Public Whats New
|
||||
* @description Return the launches the dashboard Home page shows under What's new.
|
||||
*
|
||||
* Resolved once per process: fetched from ``litellm.whats_new_url`` (override with ``LITELLM_WHATS_NEW_URL``)
|
||||
* on the first request, falling back to the list bundled with the package on any failure, so an airgapped
|
||||
* proxy serves the bundled list without retrying. Set ``LITELLM_LOCAL_WHATS_NEW=True`` to skip the fetch.
|
||||
* A restart picks up a newly published list.
|
||||
* Serves the list bundled with this release. Set ``LITELLM_WHATS_NEW_URL`` to fetch a remote list instead,
|
||||
* resolved once per process on the first request and falling back to the bundled list on any failure, so an
|
||||
* airgapped proxy serves the bundled list without retrying. ``LITELLM_LOCAL_WHATS_NEW=True`` forces the
|
||||
* bundled list even when a URL is set. A restart picks up a newly published list.
|
||||
*/
|
||||
get: operations["get_public_whats_new_public_whats_new_get"];
|
||||
put?: never;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue