diff --git a/litellm/integrations/arize/arize_phoenix_prompt_manager.py b/litellm/integrations/arize/arize_phoenix_prompt_manager.py index 0c9e868c146..fbf25ea87fd 100644 --- a/litellm/integrations/arize/arize_phoenix_prompt_manager.py +++ b/litellm/integrations/arize/arize_phoenix_prompt_manager.py @@ -379,7 +379,7 @@ class ArizePhoenixPromptManager(CustomPromptManagement): """Reload prompts from Arize Phoenix.""" if self.prompt_id: self._prompt_manager = None # Reset to force reload - self.prompt_manager # This will trigger reload + _ = self.prompt_manager # access triggers lazy reload def should_run_prompt_management( self, diff --git a/litellm/integrations/bitbucket/bitbucket_prompt_manager.py b/litellm/integrations/bitbucket/bitbucket_prompt_manager.py index ff34bd91e31..e98fa77a562 100644 --- a/litellm/integrations/bitbucket/bitbucket_prompt_manager.py +++ b/litellm/integrations/bitbucket/bitbucket_prompt_manager.py @@ -406,7 +406,7 @@ class BitBucketPromptManager(CustomPromptManagement): """Reload prompts from BitBucket.""" if self.prompt_id: self._prompt_manager = None # Reset to force reload - self.prompt_manager # This will trigger reload + _ = self.prompt_manager # access triggers lazy reload def should_run_prompt_management( self, diff --git a/litellm/integrations/weights_biases.py b/litellm/integrations/weights_biases.py index 97a2acbac08..d1a8ec098cf 100644 --- a/litellm/integrations/weights_biases.py +++ b/litellm/integrations/weights_biases.py @@ -4,18 +4,10 @@ imported_openAIResponse = True try: import io import logging - import sys - from typing import Any, TypeVar + from typing import Any, Literal, Protocol, TypeVar from wandb.sdk.data_types import trace_tree - if sys.version_info >= (3, 8): - from typing import Literal, Protocol - else: - from typing import Literal - - from typing_extensions import Protocol - logger: Final = logging.getLogger(__name__) K = TypeVar("K", bound=str) diff --git a/litellm/litellm_core_utils/coroutine_checker.py b/litellm/litellm_core_utils/coroutine_checker.py index 52fc44ba8dc..9e15b4e870d 100644 --- a/litellm/litellm_core_utils/coroutine_checker.py +++ b/litellm/litellm_core_utils/coroutine_checker.py @@ -36,7 +36,9 @@ class CoroutineChecker: target = callback if not inspect.isfunction(target) and not inspect.ismethod(target): try: - call_attr: Final = getattr(target, "__call__", None) + # Value unwrap so iscoroutinefunction can see through functors; + # B004's callable() advice does not apply here. + call_attr: Final = getattr(target, "__call__", None) # noqa: B004 if call_attr is not None: target = call_attr except Exception: diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index ece619e3883..21ae8b001dd 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -1757,7 +1757,7 @@ def convert_to_anthropic_tool_invoke( anthropic_tool_invoke: Final[list[AnthropicMessagesToolUseParam | dict[str, object]]] = [] for tool in tool_calls: - if not get_attribute_or_key(tool, "type") == "function": + if get_attribute_or_key(tool, "type") != "function": continue tool_id = cast(str, get_attribute_or_key(tool, "id")) diff --git a/litellm/litellm_core_utils/token_counter.py b/litellm/litellm_core_utils/token_counter.py index 3b128899f45..4c61fac82bb 100644 --- a/litellm/litellm_core_utils/token_counter.py +++ b/litellm/litellm_core_utils/token_counter.py @@ -454,7 +454,7 @@ def token_counter( params: Final = _MessageCountParams(model, custom_tokenizer) num_tokens = _count_messages(params, new_messages, use_default_image_token_count, default_token_count) if count_response_tokens is False: - includes_system_message: Final = any([message.get("role", None) == "system" for message in new_messages]) + includes_system_message: Final = any(message.get("role", None) == "system" for message in new_messages) num_tokens += _count_extra(params.count_function, tools, tool_choice, includes_system_message) else: diff --git a/litellm/llms/azure_ai/common_utils.py b/litellm/llms/azure_ai/common_utils.py index 0665b3f64c5..d68a55873bd 100644 --- a/litellm/llms/azure_ai/common_utils.py +++ b/litellm/llms/azure_ai/common_utils.py @@ -144,10 +144,9 @@ class AzureFoundryModelInfo(BaseLLMModelInfo): def get_api_key(api_key: str | None = None) -> str | None: return api_key or litellm.api_key or get_secret_str("AZURE_AI_API_KEY") - @property - def api_version(self, api_version: str | None = None) -> str | None: - api_version = api_version or litellm.api_version or get_secret_str("AZURE_API_VERSION") - return api_version + @staticmethod + def get_api_version(api_version: str | None = None) -> str | None: + return api_version or litellm.api_version or get_secret_str("AZURE_API_VERSION") def get_token_counter(self) -> BaseTokenCounter | None: """ diff --git a/litellm/proxy/guardrails/guardrail_hooks/javelin/javelin.py b/litellm/proxy/guardrails/guardrail_hooks/javelin/javelin.py index 3e5d8fb311d..e54e07b6a1b 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/javelin/javelin.py +++ b/litellm/proxy/guardrails/guardrail_hooks/javelin/javelin.py @@ -44,7 +44,7 @@ class JavelinGuardrail(CustomGuardrail): application: str | None = None, **kwargs, ): - f""" + """ Initialize the JavelinGuardrail class. This calls: {api_base}/{api_version}/guardrail/{guardrail_name}/apply diff --git a/litellm/proxy/guardrails/guardrail_hooks/mcp_end_user_permission/__init__.py b/litellm/proxy/guardrails/guardrail_hooks/mcp_end_user_permission/__init__.py index a911c78ddc3..95db4bd4f77 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/mcp_end_user_permission/__init__.py +++ b/litellm/proxy/guardrails/guardrail_hooks/mcp_end_user_permission/__init__.py @@ -15,7 +15,7 @@ def initialize_guardrail(litellm_params: "LitellmParams", guardrail: "Guardrail" # We check the raw guardrail dict because LitellmParams normalizes None → False, # making it impossible to distinguish "not set" from "explicitly false" via litellm_params. _raw_default_on: Final = cast(dict[str, Any], guardrail).get("litellm_params", {}).get("default_on") - _default_on: Final = False if _raw_default_on is False else True + _default_on: Final = _raw_default_on is not False _callback: Final = MCPEndUserPermissionGuardrail( guardrail_name=guardrail.get("guardrail_name", ""), diff --git a/litellm/proxy/management_helpers/team_metadata_validation.py b/litellm/proxy/management_helpers/team_metadata_validation.py index 7bc66c240c7..31960442b24 100644 --- a/litellm/proxy/management_helpers/team_metadata_validation.py +++ b/litellm/proxy/management_helpers/team_metadata_validation.py @@ -115,9 +115,10 @@ async def run_team_metadata_validation( "error": f"custom_team_metadata_validate is an Enterprise feature. {CommonProxyErrors.not_premium_user.value}" }, ) - if not ( - inspect.iscoroutinefunction(validator) or inspect.iscoroutinefunction(getattr(validator, "__call__", None)) - ): + # Value unwrap so iscoroutinefunction can see through functors; + # B004's callable() advice does not apply here. + validator_call = getattr(validator, "__call__", None) # noqa: B004 + if not (inspect.iscoroutinefunction(validator) or inspect.iscoroutinefunction(validator_call)): raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail={ # mutable-ok: HTTPException.detail has no immutable form diff --git a/litellm/proxy/spend_tracking/spend_tracking_utils.py b/litellm/proxy/spend_tracking/spend_tracking_utils.py index 3ef21996b9c..4bcdf6aad22 100644 --- a/litellm/proxy/spend_tracking/spend_tracking_utils.py +++ b/litellm/proxy/spend_tracking/spend_tracking_utils.py @@ -159,7 +159,7 @@ def _get_spend_logs_metadata( requester_ip_address=None, additional_usage_values=None, applied_guardrails=None, - status=None or "success", + status="success", error_information=None, proxy_server_request=None, batch_models=None, diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index c095586b6c9..d201ac4bc88 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -4,6 +4,7 @@ import copy import hashlib import inspect import json +import math import os import smtplib import ssl @@ -6404,7 +6405,7 @@ class PrismaClient: return None try: value: Final = float(response_time_ms) - return value if value == value and value not in (float("inf"), float("-inf")) else None + return value if math.isfinite(value) else None except (ValueError, TypeError): verbose_proxy_logger.warning("Invalid response_time_ms value: %s", response_time_ms) return None diff --git a/litellm/router.py b/litellm/router.py index 8865543badd..27bff0f6fb0 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -4108,16 +4108,16 @@ class Router: models: Final = [m.strip() for m in model.split(",")] async def _async_completion_no_exceptions( - model: str, messages: list[dict[str, str]], stream: bool, **kwargs: Any + model_name: str, messages: list[dict[str, str]], stream: bool, **kwargs: Any ) -> ModelResponse | CustomStreamWrapper | Exception: """ Wrapper around self.acompletion that catches exceptions and returns them as a result """ try: - result = await self.acompletion(model=model, messages=messages, stream=stream, **kwargs) + result = await self.acompletion(model=model_name, messages=messages, stream=stream, **kwargs) return result except asyncio.CancelledError: - verbose_router_logger.debug("Received 'task.cancel'. Cancelling call w/ model=%s.", model) + verbose_router_logger.debug("Received 'task.cancel'. Cancelling call w/ model=%s.", model_name) raise except Exception as e: return e @@ -4144,9 +4144,9 @@ class Router: except KeyError: pass - for model in models: + for model_name in models: task = asyncio.create_task( - _async_completion_no_exceptions(model=model, messages=messages, stream=stream, **kwargs) + _async_completion_no_exceptions(model_name=model_name, messages=messages, stream=stream, **kwargs) ) pending_tasks.append(task) diff --git a/litellm/utils.py b/litellm/utils.py index 7732cd88cb5..9502af33a0b 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -6264,7 +6264,7 @@ def function_to_dict(input_function) -> dict: "enum": param_enum, } - parameters[param_name] = dict([(k, v) for k, v in param_dict.items() if isinstance(v, str)]) + parameters[param_name] = {k: v for k, v in param_dict.items() if isinstance(v, str)} # Check if the parameter has no default value (i.e., it's required) if param.default == param.empty: diff --git a/ruff-strict-budget.json b/ruff-strict-budget.json index d63a69de76f..dafdc3d10aa 100644 --- a/ruff-strict-budget.json +++ b/ruff-strict-budget.json @@ -30,7 +30,7 @@ "limit": 11 }, "B004": { - "limit": 2 + "limit": 0 }, "B006": { "limit": 176 @@ -45,13 +45,13 @@ "limit": 187 }, "B018": { - "limit": 2 + "limit": 0 }, "B019": { "limit": 1 }, "B021": { - "limit": 1 + "limit": 0 }, "B026": { "limit": 3 @@ -63,7 +63,7 @@ "limit": 8 }, "C404": { - "limit": 1 + "limit": 0 }, "C405": { "limit": 19 @@ -75,7 +75,7 @@ "limit": 4 }, "C419": { - "limit": 1 + "limit": 0 }, "C901": { "limit": 306 @@ -138,13 +138,13 @@ "limit": 46 }, "PLR0124": { - "limit": 1 + "limit": 0 }, "PLR0206": { - "limit": 1 + "limit": 0 }, "PLR1704": { - "limit": 1 + "limit": 0 }, "PLR1714": { "limit": 253 @@ -213,16 +213,16 @@ "limit": 6 }, "SIM201": { - "limit": 1 + "limit": 0 }, "SIM210": { "limit": 8 }, "SIM211": { - "limit": 1 + "limit": 0 }, "SIM222": { - "limit": 1 + "limit": 0 }, "SIM401": { "limit": 11 @@ -255,6 +255,6 @@ "limit": 2 }, "UP036": { - "limit": 1 + "limit": 0 } } diff --git a/ruff.toml b/ruff.toml index 3ac4c1fc94d..fab3fe27aed 100644 --- a/ruff.toml +++ b/ruff.toml @@ -4,11 +4,12 @@ lint.ignore = ["F405", "E402", "F403"] # That gives editors and `ruff check --fix` the diagnostic, which the gate script cannot. lint.extend-select = [ "T20", "PGH004", "RUF008", "RUF009", "RUF100", - "B033", "FURB136", "FURB168", "FURB188", "I001", "PERF402", "PIE790", "PIE800", "PLC0208", - "PLR0402", "PLR1711", "PLR1730", "PLR2044", "PLW0133", "PYI030", "PYI041", "PYI064", "RET501", - "RUF010", "RUF022", "RUF023", "RUF051", "S113", "SIM114", "SIM118", "TC005", "UP006", "UP007", - "UP008", - "UP012", "UP018", "UP024", "UP032", "UP034", "UP035", "UP037", "UP045", + "B004", "B018", "B021", "B033", "FURB136", "FURB168", "FURB188", "I001", "PERF402", "PIE790", + "PIE800", "PLC0208", "PLR0124", "PLR0402", "PLR0206", "PLR1704", "PLR1711", "PLR1730", "PLR2044", + "PLW0133", "PYI030", "PYI041", "PYI064", "RET501", "RUF010", "RUF022", "RUF023", "RUF051", "S113", + "SIM114", "SIM118", "SIM201", "SIM211", "SIM222", "TC005", "UP006", "UP007", "UP008", + "UP012", "UP018", "UP024", "UP032", "UP034", "UP035", "UP036", "UP037", "UP045", + "C404", "C419", ] # RUF100 (unused-noqa) only knows the rules enabled in THIS config, so it would strip # `# noqa` directives that protect rules enforced elsewhere. List those codes as external