Skip to content

Commit 64e7d82

Browse files
committed
test fixes
Signed-off-by: Ian Eaves <[email protected]>
1 parent 62ed7c5 commit 64e7d82

File tree

4 files changed

+204
-1
lines changed

4 files changed

+204
-1
lines changed

litellm/types/llms/base.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,12 @@ def __setitem__(self, key: str, value: Any) -> None:
4242

4343
def __contains__(self, key: str) -> bool:
4444
"""Dict-style 'key in settings' checks"""
45-
return hasattr(self, key)
45+
# Match dict behavior: return False if value is None (like missing key)
46+
try:
47+
value = getattr(self, key)
48+
return value is not None
49+
except AttributeError:
50+
return False
4651

4752
def keys(self):
4853
"""Dict-style .keys() method"""
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
"""
2+
Tests for ConfigGeneralSettings type coercion and dict compatibility.
3+
4+
This test ensures that the ConfigGeneralSettings Pydantic model properly
5+
coerces types from YAML/dict input, providing consistent behavior with
6+
environment variable type coercion.
7+
"""
8+
9+
import pytest
10+
from litellm.proxy._types import ConfigGeneralSettings
11+
12+
13+
def test_config_general_settings_integer_type_coercion():
14+
"""Test that integer fields are properly coerced from string values."""
15+
config_dict = {
16+
"proxy_batch_write_at": "10", # String should be coerced to int
17+
"proxy_batch_polling_interval": "6000",
18+
"proxy_budget_rescheduler_min_time": "597",
19+
"proxy_budget_rescheduler_max_time": "605",
20+
"health_check_interval": "300",
21+
"alerting_threshold": "600",
22+
"max_parallel_requests": "100",
23+
"maximum_spend_logs_retention_period": "30",
24+
}
25+
26+
settings = ConfigGeneralSettings(**config_dict)
27+
28+
# Verify integers are properly coerced
29+
assert settings.proxy_batch_write_at == 10
30+
assert isinstance(settings.proxy_batch_write_at, int)
31+
assert settings.proxy_batch_polling_interval == 6000
32+
assert isinstance(settings.proxy_batch_polling_interval, int)
33+
assert settings.proxy_budget_rescheduler_min_time == 597
34+
assert isinstance(settings.proxy_budget_rescheduler_min_time, int)
35+
assert settings.health_check_interval == 300
36+
assert isinstance(settings.health_check_interval, int)
37+
assert settings.alerting_threshold == 600
38+
assert settings.max_parallel_requests == 100
39+
assert settings.maximum_spend_logs_retention_period == 30
40+
41+
42+
def test_config_general_settings_boolean_type_coercion():
43+
"""Test that boolean fields are properly coerced from string values."""
44+
config_dict = {
45+
"disable_spend_logs": "true",
46+
"store_model_in_db": "false",
47+
"use_shared_health_check": "True",
48+
"disable_reset_budget": "False",
49+
"health_check_details": "yes", # Pydantic accepts yes/no
50+
"infer_model_from_keys": "1", # Pydantic accepts 1/0
51+
}
52+
53+
settings = ConfigGeneralSettings(**config_dict)
54+
55+
# Verify booleans are properly coerced
56+
assert settings.disable_spend_logs is True
57+
assert isinstance(settings.disable_spend_logs, bool)
58+
assert settings.store_model_in_db is False
59+
assert isinstance(settings.store_model_in_db, bool)
60+
assert settings.use_shared_health_check is True
61+
assert settings.disable_reset_budget is False
62+
assert settings.health_check_details is True
63+
assert settings.infer_model_from_keys is True
64+
65+
66+
def test_config_general_settings_float_type_coercion():
67+
"""Test that float fields are properly coerced from string values."""
68+
config_dict = {
69+
"user_api_key_cache_ttl": "60.5",
70+
"database_connection_timeout": "120",
71+
}
72+
73+
settings = ConfigGeneralSettings(**config_dict)
74+
75+
assert settings.user_api_key_cache_ttl == 60.5
76+
assert isinstance(settings.user_api_key_cache_ttl, float)
77+
assert settings.database_connection_timeout == 120.0
78+
assert isinstance(settings.database_connection_timeout, float)
79+
80+
81+
def test_config_general_settings_get_method_with_none():
82+
"""Test that .get() method returns default when value is None."""
83+
settings = ConfigGeneralSettings()
84+
85+
# When field is None, .get() should return the default
86+
assert settings.get("proxy_batch_write_at", 10) == 10
87+
assert settings.get("master_key", "default_key") == "default_key"
88+
assert settings.get("disable_spend_logs", True) is True
89+
assert settings.get("moderation_model", "gpt-4") == "gpt-4"
90+
91+
92+
def test_config_general_settings_get_method_with_values():
93+
"""Test that .get() method returns actual value when set."""
94+
settings = ConfigGeneralSettings(
95+
proxy_batch_write_at=15,
96+
master_key="test_key",
97+
disable_spend_logs=False,
98+
)
99+
100+
assert settings.get("proxy_batch_write_at", 10) == 15
101+
assert settings.get("master_key", "default_key") == "test_key"
102+
assert settings.get("disable_spend_logs", True) is False
103+
104+
105+
def test_config_general_settings_extra_fields_allowed():
106+
"""Test that extra fields (not defined in model) are allowed and preserved."""
107+
config_dict = {
108+
"proxy_batch_write_at": 10,
109+
"custom_field_not_in_model": "some_value",
110+
"another_custom_field": 123,
111+
}
112+
113+
settings = ConfigGeneralSettings(**config_dict)
114+
115+
# Defined field should work
116+
assert settings.proxy_batch_write_at == 10
117+
118+
# Extra fields should be accessible
119+
assert settings.get("custom_field_not_in_model") == "some_value"
120+
assert settings.get("another_custom_field") == 123
121+
122+
123+
def test_config_general_settings_dict_style_access():
124+
"""Test dict-style access methods work correctly."""
125+
settings = ConfigGeneralSettings(proxy_batch_write_at=20, master_key="test")
126+
127+
# __getitem__
128+
assert settings["proxy_batch_write_at"] == 20
129+
assert settings["master_key"] == "test"
130+
131+
# __contains__
132+
assert "proxy_batch_write_at" in settings
133+
assert "master_key" in settings
134+
assert "nonexistent_field" not in settings
135+
136+
# __setitem__
137+
settings["new_field"] = "new_value"
138+
assert settings.get("new_field") == "new_value"
139+
140+
# items()
141+
items = dict(settings.items())
142+
assert "proxy_batch_write_at" in items
143+
assert items["proxy_batch_write_at"] == 20
144+
145+
146+
def test_config_general_settings_update_method():
147+
"""Test that .update() method works like dict.update()."""
148+
settings = ConfigGeneralSettings(proxy_batch_write_at=10)
149+
150+
# Update with dict
151+
settings.update({"proxy_batch_write_at": 20, "master_key": "new_key"})
152+
153+
assert settings.proxy_batch_write_at == 20
154+
assert settings.master_key == "new_key"
155+
156+
# Update should raise TypeError for non-dict
157+
with pytest.raises(TypeError):
158+
settings.update("not a dict")
159+
160+
161+
def test_config_general_settings_end_to_end_yaml_simulation():
162+
"""
163+
End-to-end test simulating loading from YAML config.
164+
This is the key test that validates the original issue is fixed.
165+
"""
166+
# Simulate YAML config being loaded as dict (YAML parsers return strings)
167+
yaml_config = {
168+
"general_settings": {
169+
"proxy_batch_write_at": "10", # String from YAML
170+
"master_key": "sk-1234",
171+
"disable_spend_logs": "false", # String from YAML
172+
"user_api_key_cache_ttl": "60.5", # String from YAML
173+
}
174+
}
175+
176+
# Instantiate ConfigGeneralSettings from the dict
177+
general_settings = ConfigGeneralSettings(**yaml_config["general_settings"])
178+
179+
# Verify type coercion happened correctly
180+
assert general_settings.proxy_batch_write_at == 10
181+
assert isinstance(general_settings.proxy_batch_write_at, int)
182+
183+
# This is the critical part - can be used in arithmetic without errors
184+
# (Original issue was that strings from YAML weren't coerced)
185+
batch_interval = general_settings.proxy_batch_write_at + 5
186+
assert batch_interval == 15
187+
188+
# Verify other coercions
189+
assert general_settings.disable_spend_logs is False
190+
assert isinstance(general_settings.disable_spend_logs, bool)
191+
assert general_settings.user_api_key_cache_ttl == 60.5
192+
assert isinstance(general_settings.user_api_key_cache_ttl, float)
193+
194+
# Verify .get() works with fallback to defaults
195+
assert general_settings.get("proxy_batch_write_at", 999) == 10
196+
assert general_settings.get("nonexistent", "default") == "default"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Tests for LLM provider implementations."""
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Tests for RAGFlow provider."""

0 commit comments

Comments
 (0)