Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/policy_engine/init_policies.py: 22%

110 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-10 12:01 +0000

1""" 

2Policy Initialization - Loads policies from config and validates on startup. 

3 

4Configuration structure: 

5- policies: Define WHAT guardrails to apply (with inheritance and conditions) 

6- policy_attachments: Define WHERE policies apply (teams, keys, models) 

7""" 

8 

9from collections.abc import Mapping, Sequence 

10from typing import TYPE_CHECKING, Any, Final, Optional 

11 

12from litellm._logging import verbose_proxy_logger 

13from litellm.proxy.policy_engine.attachment_registry import get_attachment_registry 

14from litellm.proxy.policy_engine.policy_registry import get_policy_registry 

15from litellm.proxy.policy_engine.policy_validator import PolicyValidator 

16from litellm.types.proxy.policy_engine import PolicyValidationResponse 

17 

18if TYPE_CHECKING: 18 ↛ 19line 18 didn't jump to line 19 because the condition on line 18 was never true

19 from litellm.proxy.utils import PrismaClient 

20 

21# ANSI color codes for terminal output 

22_green_color_code: Final = "\033[92m" 

23_blue_color_code: Final = "\033[94m" 

24_yellow_color_code: Final = "\033[93m" 

25_reset_color_code: Final = "\033[0m" 

26 

27 

28def _print_policies_on_startup( 

29 policies_config: Mapping[str, Mapping[str, object]], 

30 policy_attachments_config: Sequence[Mapping[str, object]] | None = None, 

31) -> None: 

32 """ 

33 Print loaded policies to console on startup (similar to model list). 

34 """ 

35 import sys 

36 

37 print( # noqa: T201 

38 f"{_green_color_code}\nLiteLLM Policy Engine: Loaded {len(policies_config)} policies{_reset_color_code}\n" 

39 ) 

40 sys.stdout.flush() 

41 

42 for policy_name, policy_data in policies_config.items(): 

43 guardrails = policy_data.get("guardrails", {}) 

44 inherit = policy_data.get("inherit") 

45 condition = policy_data.get("condition") 

46 description = policy_data.get("description") 

47 

48 guardrails_add = guardrails.get("add", []) if isinstance(guardrails, dict) else [] 

49 guardrails_remove = guardrails.get("remove", []) if isinstance(guardrails, dict) else [] 

50 inherit_str = f" (inherits: {inherit})" if inherit else "" 

51 

52 print( # noqa: T201 

53 f"{_blue_color_code} - {policy_name}{inherit_str}{_reset_color_code}" 

54 ) 

55 if description: 

56 print(f" description: {description}") # noqa: T201 

57 if guardrails_add: 

58 print(f" guardrails.add: {guardrails_add}") # noqa: T201 

59 if guardrails_remove: 

60 print(f" guardrails.remove: {guardrails_remove}") # noqa: T201 

61 if condition: 

62 model_condition = condition.get("model") if isinstance(condition, dict) else None 

63 if model_condition: 

64 print(f" condition.model: {model_condition}") # noqa: T201 

65 

66 # Print attachments 

67 if policy_attachments_config: 

68 print( # noqa: T201 

69 f"\n{_yellow_color_code}Policy Attachments: {len(policy_attachments_config)} attachment(s){_reset_color_code}" 

70 ) 

71 for attachment in policy_attachments_config: 

72 policy = attachment.get("policy", "unknown") 

73 scope = attachment.get("scope") 

74 teams = attachment.get("teams") 

75 keys = attachment.get("keys") 

76 models = attachment.get("models") 

77 

78 scope_parts = [] 

79 if scope == "*": 

80 scope_parts.append("scope=* (global)") 

81 if teams: 

82 scope_parts.append(f"teams={teams}") 

83 if keys: 

84 scope_parts.append(f"keys={keys}") 

85 if models: 

86 scope_parts.append(f"models={models}") 

87 scope_str = ", ".join(scope_parts) if scope_parts else "all" 

88 

89 print(f" - {policy} -> {scope_str}") # noqa: T201 

90 else: 

91 print( # noqa: T201 

92 f"\n{_yellow_color_code}Warning: No policy_attachments configured. Policies will not be applied to any requests.{_reset_color_code}" 

93 ) 

94 

95 print() # noqa: T201 

96 sys.stdout.flush() 

97 

98 

99async def init_policies( 

100 policies_config: dict[str, Any], 

101 policy_attachments_config: list[dict[str, Any]] | None = None, 

102 prisma_client: Optional["PrismaClient"] = None, 

103 validate_db: bool = True, 

104 fail_on_error: bool = True, 

105) -> PolicyValidationResponse: 

106 """ 

107 Initialize policies from configuration. 

108 

109 This function: 

110 1. Parses the policy configuration 

111 2. Validates policies (guardrails exist, teams/keys exist in DB) 

112 3. Loads policies into the global registry 

113 4. Loads attachments into the attachment registry (if provided) 

114 

115 Args: 

116 policies_config: Dictionary mapping policy names to policy definitions 

117 policy_attachments_config: Optional list of policy attachment configurations 

118 prisma_client: Optional Prisma client for database validation 

119 validate_db: Whether to validate team/key aliases against database 

120 fail_on_error: If True, raise exception on validation errors 

121 

122 Returns: 

123 PolicyValidationResponse with validation results 

124 

125 Raises: 

126 ValueError: If fail_on_error is True and validation errors are found 

127 """ 

128 verbose_proxy_logger.info("Initializing %s policies...", len(policies_config)) 

129 

130 # Print policies to console on startup 

131 _print_policies_on_startup(policies_config, policy_attachments_config) 

132 

133 # Get the global registries 

134 policy_registry: Final = get_policy_registry() 

135 attachment_registry: Final = get_attachment_registry() 

136 

137 # Create validator 

138 validator: Final = PolicyValidator(prisma_client=prisma_client) 

139 

140 # Validate the configuration 

141 validation_result: Final = await validator.validate_policy_config( 

142 policies_config, 

143 validate_db=validate_db, 

144 ) 

145 

146 # Log validation results 

147 if validation_result.errors: 

148 for error in validation_result.errors: 

149 verbose_proxy_logger.error( 

150 "Policy validation error in '%s': [%s] %s", error.policy_name, error.error_type, error.message 

151 ) 

152 

153 if validation_result.warnings: 

154 for warning in validation_result.warnings: 

155 verbose_proxy_logger.warning( 

156 "Policy validation warning in '%s': [%s] %s", warning.policy_name, warning.error_type, warning.message 

157 ) 

158 

159 # Fail if there are errors and fail_on_error is True 

160 if not validation_result.valid and fail_on_error: 

161 error_messages: Final = [f"[{e.policy_name}] {e.message}" for e in validation_result.errors] 

162 raise ValueError( 

163 f"Policy validation failed with {len(validation_result.errors)} error(s):\n" + "\n".join(error_messages) 

164 ) 

165 

166 # Load policies into registry (even with warnings) 

167 try: 

168 policy_registry.load_policies(policies_config) 

169 verbose_proxy_logger.info("Successfully loaded %s policies", len(policies_config)) 

170 except Exception as e: 

171 verbose_proxy_logger.error("Failed to load policies: %s", e) 

172 raise 

173 

174 # Load attachments if provided 

175 if policy_attachments_config: 

176 try: 

177 attachment_registry.load_attachments(policy_attachments_config) 

178 verbose_proxy_logger.info("Successfully loaded %s policy attachments", len(policy_attachments_config)) 

179 except Exception as e: 

180 verbose_proxy_logger.error("Failed to load policy attachments: %s", e) 

181 raise 

182 

183 return validation_result 

184 

185 

186def init_policies_sync( 

187 policies_config: dict[str, Any], 

188 policy_attachments_config: list[dict[str, Any]] | None = None, 

189 fail_on_error: bool = True, 

190) -> None: 

191 """ 

192 Synchronous version of init_policies (without DB validation). 

193 

194 Use this when async is not available or DB validation is not needed. 

195 

196 Args: 

197 policies_config: Dictionary mapping policy names to policy definitions 

198 policy_attachments_config: Optional list of policy attachment configurations 

199 fail_on_error: If True, raise exception on validation errors 

200 """ 

201 import asyncio 

202 

203 # Run the async function without DB validation 

204 try: 

205 loop = asyncio.get_event_loop() 

206 except RuntimeError: 

207 loop = asyncio.new_event_loop() 

208 asyncio.set_event_loop(loop) 

209 

210 loop.run_until_complete( 

211 init_policies( 

212 policies_config=policies_config, 

213 policy_attachments_config=policy_attachments_config, 

214 prisma_client=None, 

215 validate_db=False, 

216 fail_on_error=fail_on_error, 

217 ) 

218 ) 

219 

220 

221def get_policies_summary() -> dict[str, Any]: 

222 """ 

223 Get a summary of loaded policies for debugging/display. 

224 

225 Returns: 

226 Dictionary with policy information 

227 """ 

228 from litellm.proxy.policy_engine.policy_resolver import PolicyResolver 

229 

230 policy_registry: Final = get_policy_registry() 

231 attachment_registry: Final = get_attachment_registry() 

232 

233 if not policy_registry.is_initialized(): 233 ↛ 234line 233 didn't jump to line 234 because the condition on line 233 was never true

234 return {"initialized": False, "policies": {}, "attachments": []} 

235 

236 resolved: Final = PolicyResolver.get_all_resolved_policies() 

237 

238 summary: Final[dict[str, Any]] = { 

239 "initialized": True, 

240 "policy_count": len(resolved), 

241 "attachment_count": len(attachment_registry.get_all_attachments()), 

242 "policies": {}, 

243 "attachments": [], 

244 } 

245 

246 for policy_name, resolved_policy in resolved.items(): 

247 policy = policy_registry.get_policy(policy_name) 

248 summary["policies"][policy_name] = { 

249 "inherit": policy.inherit if policy else None, 

250 "description": policy.description if policy else None, 

251 "guardrails_add": policy.guardrails.get_add() if policy else [], 

252 "guardrails_remove": policy.guardrails.get_remove() if policy else [], 

253 "condition": (policy.condition.model_dump() if policy and policy.condition else None), 

254 "resolved_guardrails": resolved_policy.guardrails, 

255 "inheritance_chain": resolved_policy.inheritance_chain, 

256 } 

257 

258 # Add attachment info 

259 for attachment in attachment_registry.get_all_attachments(): 

260 summary["attachments"].append( 

261 { 

262 "policy": attachment.policy, 

263 "scope": attachment.scope, 

264 "teams": attachment.teams, 

265 "keys": attachment.keys, 

266 "models": attachment.models, 

267 } 

268 ) 

269 

270 return summary