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 commit 4dd96800e8)

* 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 commit d1ae7ea085)

* 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 commit 4bfec53865)

* 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:
devin-ai-integration[bot] 2026-10-11 02:01:56 -07:00 • committed by GitHub
parent 3090f4b23e
commit 2a01fe6e5c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 58 additions and 20 deletions

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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;