diff --git a/litellm/proxy/config_resolvers/settings_store.py b/litellm/proxy/config_resolvers/settings_store.py index 3fe869ee2ce..b6e10937eab 100644 --- a/litellm/proxy/config_resolvers/settings_store.py +++ b/litellm/proxy/config_resolvers/settings_store.py @@ -33,6 +33,9 @@ class SettingsStore(MutableMapping[str, JsonValue]): self._yaml_values = MappingProxyType(dict(mapping)) self._clear_runtime() + def config_value(self, key: str) -> JsonValue: + return self._yaml_values.get(key) + def apply_db_row(self, row: DbRow, db_row: Mapping[str, JsonValue]) -> None: previous_row: Final = self._database_rows.get(row, _EMPTY_VALUES) self._database_rows = MappingProxyType({**self._database_rows, row: MappingProxyType(dict(db_row))}) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 6a720a066b4..599e9353574 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -4858,9 +4858,16 @@ class ProxyConfig: ) def _load_yaml_settings_stores(self, config: Mapping[str, object]) -> None: + global config_passthrough_endpoints for section, store in self._settings_stores.items(): store.load_yaml(_as_settings_mapping(config.get(section))) store.apply_db_row(section, _EMPTY_SETTINGS_MAPPING) + yaml_endpoints: Final = self.settings.config_value("pass_through_endpoints") + config_passthrough_endpoints = ( + [dict(endpoint) for endpoint in yaml_endpoints if isinstance(endpoint, dict)] + if isinstance(yaml_endpoints, list) + else None + ) def _config_with_resolved_settings(self, config: Mapping[str, object]) -> dict[str, object]: return { # mutable-ok: get_config preserves the mutable mapping contract used by existing loaders @@ -6161,7 +6168,6 @@ class ProxyConfig: ## pass through endpoints if general_settings.get("pass_through_endpoints", None) is not None: - config_passthrough_endpoints = general_settings["pass_through_endpoints"] await initialize_pass_through_endpoints( pass_through_endpoints=general_settings["pass_through_endpoints"], config_file_path=config_file_path, @@ -7265,17 +7271,7 @@ class ProxyConfig: db_values: Mapping[str, SettingsJsonValue], previous_retention_values: tuple[SettingsJsonValue | None, ...], ) -> None: - if ( - any( - key in db_values - for key in ( - "maximum_spend_logs_retention_period", - "maximum_autorouter_session_retention_period", - "maximum_health_check_retention_period", - ) - ) - and previous_retention_values != self._resolved_retention_values() - ): + if previous_retention_values != self._resolved_retention_values(): await self._reschedule_spend_log_cleanup_job() async def _apply_ssrf_settings(self, db_values: Mapping[str, SettingsJsonValue]) -> None: diff --git a/tests/test_litellm/proxy/proxy_server/test_proxy_config.py b/tests/test_litellm/proxy/proxy_server/test_proxy_config.py index 245ce0b4b21..c8d1f9b2f05 100644 --- a/tests/test_litellm/proxy/proxy_server/test_proxy_config.py +++ b/tests/test_litellm/proxy/proxy_server/test_proxy_config.py @@ -3775,6 +3775,23 @@ async def test_ProxyConfig__update_general_settings_skips_redundant_retention_re reschedule.assert_not_awaited() +@pytest.mark.asyncio +async def test_ProxyConfig__update_general_settings_reschedules_after_retention_key_deletion(monkeypatch): + from litellm.proxy import proxy_server + + pc = ProxyConfig() + reschedule: Final = AsyncMock() + monkeypatch.setattr(proxy_server, "general_settings", {}) + monkeypatch.setattr(pc, "_reschedule_spend_log_cleanup_job", reschedule) + + await pc._update_general_settings({"maximum_health_check_retention_period": "30d"}) + reschedule.reset_mock() + + await pc._update_general_settings({}) + + reschedule.assert_awaited_once() + + @pytest.mark.asyncio async def test_ProxyConfig__update_general_settings_dispatches_every_side_effect_handler(monkeypatch): pc = ProxyConfig() @@ -3870,6 +3887,19 @@ async def test_ProxyConfig__update_config_from_db_resolves_through_settings_stor assert pc.settings.source("max_parallel_requests") == "db" +def test_ProxyConfig_load_yaml_settings_stores_keeps_db_endpoints_out_of_config_baseline(): + from litellm.proxy import proxy_server + + pc = ProxyConfig() + config_endpoint: Final = {"path": "/config", "target": "https://config.example"} + db_endpoint: Final = {"id": "db-endpoint", "path": "/db", "target": "https://db.example"} + + pc._load_yaml_settings_stores({"general_settings": {"pass_through_endpoints": [config_endpoint]}}) + pc.settings.apply_db_row("general_settings", {"pass_through_endpoints": [db_endpoint]}) + + assert proxy_server.config_passthrough_endpoints == [config_endpoint] + + @pytest.mark.asyncio async def test_ProxyConfig_add_deployment_continues_after_null_pass_through_endpoints(monkeypatch): from litellm.proxy import proxy_server