Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/config_resolvers/settings_rules.py: 89%
48 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
1from __future__ import annotations
3from collections.abc import Mapping
4from dataclasses import dataclass
5from types import MappingProxyType
6from typing import Final, Literal, TypeAlias
8from litellm.proxy.config_resolvers._descriptors import FieldSource
10JsonValue: TypeAlias = None | bool | int | float | str | list["JsonValue"] | dict[str, "JsonValue"]
11Section: TypeAlias = Literal[
12 "general_settings",
13 "router_settings",
14 "litellm_settings",
15 "environment_variables",
16 "ui_settings",
17]
18DbRow: TypeAlias = Section
21@dataclass(frozen=True, slots=True)
22class Absent:
23 pass
26ABSENT: Final = Absent()
27SettingValue: TypeAlias = JsonValue | Absent
30@dataclass(frozen=True, slots=True)
31class KeyRule:
32 """Which stored row carries this key. Precedence no longer varies per key."""
34 db_row: DbRow
37@dataclass(frozen=True, slots=True)
38class Resolved:
39 value: SettingValue
40 source: FieldSource
43_UI_SETTINGS_FIELDS: Final[tuple[str, ...]] = (
44 "allow_public_health_readiness_details",
45 "forward_client_headers_to_llm_api",
46 "forward_llm_provider_auth_headers",
47 "disable_agents_for_internal_users",
48 "allow_agents_for_team_admins",
49 "disable_vector_stores_for_internal_users",
50 "allow_vector_stores_for_team_admins",
51 "disable_custom_api_keys",
52 "disable_key_generate_for_org_admin",
53 "team_admin_editable_team_fields",
54)
57def _rules_for(
58 section: Section, keys: tuple[str, ...], db_row: DbRow
59) -> tuple[tuple[tuple[Section, str], KeyRule], ...]:
60 return tuple(((section, key), KeyRule(db_row=db_row)) for key in keys)
63def _build_dual_source_keys() -> Mapping[tuple[Section, str], KeyRule]:
64 """Maps a key to the stored row that carries it, for the keys whose row is not their own section."""
65 return MappingProxyType(
66 dict(
67 (
68 *_rules_for("general_settings", _UI_SETTINGS_FIELDS, "ui_settings"),
69 *(
70 ((section, "*"), KeyRule(db_row=section))
71 for section in ("general_settings", "router_settings", "litellm_settings", "environment_variables")
72 ),
73 )
74 )
75 )
78DUAL_SOURCE_KEYS: Final[Mapping[tuple[Section, str], KeyRule]] = _build_dual_source_keys()
81RESOURCE_LIST_KEYS: Final[frozenset[tuple[Section, str]]] = frozenset({("general_settings", "pass_through_endpoints")})
84def is_resource_list(section: Section, key: str) -> bool:
85 return (section, key) in RESOURCE_LIST_KEYS
88def rule_for(section: Section, key: str) -> KeyRule:
89 return DUAL_SOURCE_KEYS.get((section, key), DUAL_SOURCE_KEYS[(section, "*")])
92def coerce_bool(value: JsonValue) -> JsonValue:
93 if value is None or isinstance(value, bool): 93 ↛ 95line 93 didn't jump to line 95 because the condition on line 93 was always true
94 return value
95 if isinstance(value, str):
96 return value.lower() == "true"
97 return bool(value)
100def resolve(yaml_value: SettingValue, db_value: SettingValue) -> Resolved:
101 """Config wins. A key the config file declares is config-owned, whatever the database holds.
103 A stored ``null`` still counts as absent, so clearing a row does not erase a value
104 the file never declared.
105 """
106 if yaml_value is not ABSENT:
107 return Resolved(value=yaml_value, source="config")
108 if _db_is_present(db_value):
109 return Resolved(value=db_value, source="db")
110 return Resolved(value=ABSENT, source="unset")
113def is_absent(value: SettingValue) -> bool:
114 return value is ABSENT
117def _db_is_present(value: SettingValue) -> bool:
118 return not is_absent(value) and value is not None