mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
test: say whether a match= pattern is a regex or a literal (ruff RUF043)
This commit is contained in:
parent
5ed230701a
commit
91599aef69
18 changed files with 37 additions and 25 deletions
|
|
@ -32,6 +32,10 @@
|
|||
# PT017 an `assert` on the caught error inside `except`. Nothing runs the handler when
|
||||
# the call stops raising, so the test goes green on the exact regression it was
|
||||
# written to catch. `pytest.raises` fails when the call succeeds
|
||||
# RUF043 a `match=` pattern carrying regex metacharacters in a plain string. `match=` is
|
||||
# `re.search`, so a `.` copied out of an error message is a wildcard and the block
|
||||
# accepts messages the author never meant to accept. Mark a real regex raw, wrap a
|
||||
# literal message in `re.escape`, and the pattern says which one it is
|
||||
#
|
||||
# No target-version here on purpose: it resolves from requires-python (>=3.10), so
|
||||
# 3.11-only builtins like BaseExceptionGroup are correctly flagged in a tree that
|
||||
|
|
@ -53,4 +57,5 @@ lint.select = [
|
|||
"PT017",
|
||||
"PLR0133",
|
||||
"PLW0127",
|
||||
"RUF043",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
|
|
@ -536,13 +537,13 @@ def test_demo_tokens_as_input_to_embeddings_fails_for_titan():
|
|||
|
||||
with pytest.raises(
|
||||
litellm.BadRequestError,
|
||||
match='litellm.BadRequestError: BedrockException - {"message":"Malformed input request: expected type: String, found: JSONArray, please reformat your input and try again."}',
|
||||
match=re.escape('litellm.BadRequestError: BedrockException - {"message":"Malformed input request: expected type: String, found: JSONArray, please reformat your input and try again."}'),
|
||||
):
|
||||
litellm.embedding(model="amazon.titan-embed-text-v1", input=[[1]])
|
||||
|
||||
with pytest.raises(
|
||||
litellm.BadRequestError,
|
||||
match='litellm.BadRequestError: BedrockException - {"message":"Malformed input request: expected type: String, found: Integer, please reformat your input and try again."}',
|
||||
match=re.escape('litellm.BadRequestError: BedrockException - {"message":"Malformed input request: expected type: String, found: Integer, please reformat your input and try again."}'),
|
||||
):
|
||||
litellm.embedding(
|
||||
model="amazon.titan-embed-text-v1",
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ async def test_team_blocking_behavior_multi_instance():
|
|||
assert team_info_4001["blocked"] is True, "Team should be blocked after update"
|
||||
|
||||
# 8. Make a chat completion request on port 4000 with a new prompt; expect it to be blocked.
|
||||
with pytest.raises(Exception, match="(?i)blocked") as excinfo:
|
||||
with pytest.raises(Exception, match=r"(?i)blocked") as excinfo:
|
||||
await chat_completion_on_port(
|
||||
session,
|
||||
key=key,
|
||||
|
|
@ -157,7 +157,7 @@ async def test_team_blocking_behavior_multi_instance():
|
|||
), f"Expected error indicating team blocked, got: {error_msg}"
|
||||
|
||||
# 9. Make a chat completion request on port 4000 with a new prompt; expect it to be blocked.
|
||||
with pytest.raises(Exception, match="(?i)blocked") as excinfo:
|
||||
with pytest.raises(Exception, match=r"(?i)blocked") as excinfo:
|
||||
await chat_completion_on_port(
|
||||
session,
|
||||
key=key,
|
||||
|
|
@ -171,7 +171,7 @@ async def test_team_blocking_behavior_multi_instance():
|
|||
), f"Expected error indicating team blocked, got: {error_msg}"
|
||||
|
||||
# 9. Repeat the chat completion request with another new prompt; expect it to be blocked.
|
||||
with pytest.raises(Exception, match="(?i)blocked") as excinfo_second:
|
||||
with pytest.raises(Exception, match=r"(?i)blocked") as excinfo_second:
|
||||
await chat_completion_on_port(
|
||||
session,
|
||||
key=key,
|
||||
|
|
|
|||
|
|
@ -1832,7 +1832,7 @@ def test_init_auto_router_deployment_duplicate_model_name(mock_auto_router, mode
|
|||
)
|
||||
|
||||
with pytest.raises(
|
||||
ValueError, match="Auto-router deployment test-auto-router with tags .* already exists"
|
||||
ValueError, match=r"Auto-router deployment test-auto-router with tags .* already exists"
|
||||
):
|
||||
router.init_auto_router_deployment(deployment)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
|
|
@ -158,7 +159,7 @@ def test_bitbucket_client_get_file_content_access_denied(mock_get):
|
|||
|
||||
client = BitBucketClient(config)
|
||||
|
||||
with pytest.raises(Exception, match="Access denied to file 'test.prompt'"):
|
||||
with pytest.raises(Exception, match=re.escape("Access denied to file 'test.prompt'")):
|
||||
client.get_file_content("test.prompt")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
import re
|
||||
import sys
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
|
|
@ -172,7 +173,7 @@ def test_gitlab_client_get_file_content_access_denied(mock_get):
|
|||
mock_get.side_effect = err
|
||||
|
||||
client = GitLabClient({"project": "g/s/r", "access_token": "tok"})
|
||||
with pytest.raises(Exception, match="Access denied to file 'test.prompt'"):
|
||||
with pytest.raises(Exception, match=re.escape("Access denied to file 'test.prompt'")):
|
||||
client.get_file_content("test.prompt")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class TestOpenMeterIntegration:
|
|||
def test_openmeter_logger_missing_api_key(self):
|
||||
"""Test that OpenMeterLogger raises exception when API key is missing"""
|
||||
os.environ.pop("OPENMETER_API_KEY", None)
|
||||
with pytest.raises(Exception, match="Missing keys.*OPENMETER_API_KEY"):
|
||||
with pytest.raises(Exception, match=r"Missing keys.*OPENMETER_API_KEY"):
|
||||
OpenMeterLogger()
|
||||
|
||||
def test_common_logic_with_string_user(self):
|
||||
|
|
|
|||
|
|
@ -100,12 +100,12 @@ class TestEncodeUrlPathSegment:
|
|||
|
||||
@pytest.mark.parametrize("value", ["", ".", "..", None])
|
||||
def test_rejects_empty_and_dot_segments(self, value):
|
||||
with pytest.raises(ValueError, match="resource_id (is required|cannot be a dot path segment)"):
|
||||
with pytest.raises(ValueError, match=r"resource_id (is required|cannot be a dot path segment)"):
|
||||
encode_url_path_segment(value, field_name="resource_id")
|
||||
|
||||
@pytest.mark.parametrize("value", ["../model", "model/../other", "/model"])
|
||||
def test_rejects_dot_segments_in_multi_segment_paths(self, value):
|
||||
with pytest.raises(ValueError, match="model (is required|cannot be a dot path segment)"):
|
||||
with pytest.raises(ValueError, match=r"model (is required|cannot be a dot path segment)"):
|
||||
encode_url_path_segments(value, field_name="model")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -929,7 +929,7 @@ class TestValidateEnvironmentAuthToken:
|
|||
config = AnthropicModelInfo()
|
||||
with mock_patch.dict("os.environ", {}, clear=True):
|
||||
with pytest.raises(
|
||||
Exception, match="ANTHROPIC_API_KEY.*ANTHROPIC_AUTH_TOKEN"
|
||||
Exception, match=r"ANTHROPIC_API_KEY.*ANTHROPIC_AUTH_TOKEN"
|
||||
):
|
||||
config.validate_environment(
|
||||
headers={},
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ class TestGenericToolCallErrors:
|
|||
)
|
||||
|
||||
def test_non_string_id_raises(self):
|
||||
with pytest.raises(OCIError, match="id.*must be a string"):
|
||||
with pytest.raises(OCIError, match=r"id.*must be a string"):
|
||||
adapt_messages_to_generic_oci_standard_tool_call(
|
||||
"assistant",
|
||||
[
|
||||
|
|
@ -126,7 +126,7 @@ class TestGenericToolCallErrors:
|
|||
)
|
||||
|
||||
def test_non_string_function_name_raises(self):
|
||||
with pytest.raises(OCIError, match="function.name.*must be a string"):
|
||||
with pytest.raises(OCIError, match=r"function\.name.*must be a string"):
|
||||
adapt_messages_to_generic_oci_standard_tool_call(
|
||||
"assistant",
|
||||
[
|
||||
|
|
@ -139,7 +139,7 @@ class TestGenericToolCallErrors:
|
|||
)
|
||||
|
||||
def test_non_string_arguments_raises(self):
|
||||
with pytest.raises(OCIError, match="arguments.*must be a JSON string"):
|
||||
with pytest.raises(OCIError, match=r"arguments.*must be a JSON string"):
|
||||
adapt_messages_to_generic_oci_standard_tool_call(
|
||||
"assistant",
|
||||
[
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ Test Vertex AI files handler functionality
|
|||
"""
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
from types import MappingProxyType
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
|
@ -180,7 +181,7 @@ class TestVertexAIFilesHandler:
|
|||
# Should raise ValueError for failed download
|
||||
with pytest.raises(
|
||||
ValueError,
|
||||
match="Failed to download file from GCS: gs://test-bucket/litellm-vertex-files/uploads/abc-test-file.txt",
|
||||
match=re.escape("Failed to download file from GCS: gs://test-bucket/litellm-vertex-files/uploads/abc-test-file.txt"),
|
||||
):
|
||||
await self.handler.afile_content(
|
||||
file_content_request=file_content_request,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ def test_validate_vertex_location_accepts_valid(location):
|
|||
["attacker.example/", "evil.com#", "us.attacker.example", "us/../..", "US", "us_central1", "-us", "", None],
|
||||
)
|
||||
def test_validate_vertex_location_rejects_invalid(location):
|
||||
with pytest.raises(ValueError, match="vertex_location is required|Invalid vertex_location format"):
|
||||
with pytest.raises(ValueError, match=r"vertex_location is required|Invalid vertex_location format"):
|
||||
validate_vertex_location(location)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ class TestRunAgent:
|
|||
assert calls["args"] == ("claude", "--resume")
|
||||
|
||||
def test_missing_binary_raises_with_install_hint(self):
|
||||
with pytest.raises(AgentRunError, match="claude.*Install it first"):
|
||||
with pytest.raises(AgentRunError, match=r"claude.*Install it first"):
|
||||
run_agent(
|
||||
"http://localhost:4000",
|
||||
"sk-key",
|
||||
|
|
|
|||
|
|
@ -504,7 +504,7 @@ def test_apply_to_env_rejects_pinned_sqlite_direct_url(monkeypatch):
|
|||
monkeypatch.setenv("DATABASE_URL", "postgresql://u:p@writer.example.com:5432/db")
|
||||
monkeypatch.setenv("DIRECT_URL", "sqlite:///data/litellm.db")
|
||||
|
||||
with pytest.raises(RuntimeError, match="DIRECT_URL.*sqlite"):
|
||||
with pytest.raises(RuntimeError, match=r"DIRECT_URL.*sqlite"):
|
||||
_apply()
|
||||
|
||||
|
||||
|
|
@ -514,7 +514,7 @@ def test_apply_to_env_rejects_pinned_non_postgres_reader(monkeypatch):
|
|||
"DATABASE_URL_READ_REPLICA", "mysql://u:p@reader.example.com:3306/db"
|
||||
)
|
||||
|
||||
with pytest.raises(RuntimeError, match="DATABASE_URL_READ_REPLICA.*mysql"):
|
||||
with pytest.raises(RuntimeError, match=r"DATABASE_URL_READ_REPLICA.*mysql"):
|
||||
_apply()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from __future__ import annotations
|
|||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from types import SimpleNamespace
|
||||
from typing import Any, Dict
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
|
@ -303,7 +304,7 @@ def test_resolve_routing_plugins_rejects_non_routing_plugin(tmp_path):
|
|||
plugin_file = tmp_path / "bad_rs_plugin.py"
|
||||
plugin_file.write_text("not_a_plugin = object()\n")
|
||||
|
||||
with pytest.raises(ValueError, match="router_settings.plugins"):
|
||||
with pytest.raises(ValueError, match=re.escape("router_settings.plugins")):
|
||||
resolve_routing_plugins(
|
||||
plugin_paths=["bad_rs_plugin.not_a_plugin"],
|
||||
config_file_path=str(tmp_path / "config.yaml"),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import asyncio
|
|||
import importlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
|
|
@ -2683,7 +2684,7 @@ async def test_get_config_from_file(tmp_path, monkeypatch):
|
|||
with open(empty_file, "w") as f:
|
||||
f.write("") # Write empty content which will result in None when loaded
|
||||
|
||||
with pytest.raises(Exception, match="Config cannot be None or Empty."):
|
||||
with pytest.raises(Exception, match=re.escape("Config cannot be None or Empty.")):
|
||||
await proxy_config._get_config_from_file(str(empty_file))
|
||||
|
||||
# Test Case 5: Using global user_config_file_path when no config_file_path provided
|
||||
|
|
|
|||
|
|
@ -490,7 +490,7 @@ async def test_create_waits_for_endpoint_resolution(monkeypatch):
|
|||
async def test_create_raises_when_endpoint_is_missing():
|
||||
client = FakeHTTPClient(endpoint_json={"headers": {"X": "y"}})
|
||||
|
||||
with pytest.raises(TimeoutError, match="execd endpoint.*not ready"):
|
||||
with pytest.raises(TimeoutError, match=r"execd endpoint.*not ready"):
|
||||
await OpenSandboxSandboxConfig().acreate_sandbox(
|
||||
api_key="", api_base=TEST_API_BASE, ready_timeout=0, client=client
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ should still use the built-in pricing.
|
|||
|
||||
import copy
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from unittest.mock import patch
|
||||
|
||||
|
|
@ -1892,7 +1893,7 @@ def test_a_reservation_without_a_declared_id_is_refused():
|
|||
duplicate is permanent."""
|
||||
anonymous = {k: v for k, v in _PTU_MODEL_INFO.items() if k != "id"}
|
||||
|
||||
with pytest.raises(ValueError, match="model_info.id is required"):
|
||||
with pytest.raises(ValueError, match=re.escape("model_info.id is required")):
|
||||
_ptu_router(model_info=anonymous)
|
||||
|
||||
|
||||
|
|
@ -1976,7 +1977,7 @@ def test_a_bare_yaml_date_bound_does_not_escape_the_id_rule():
|
|||
|
||||
windowed = {k: v for k, v in _PTU_MODEL_INFO.items() if k != "id"}
|
||||
|
||||
with pytest.raises(ValueError, match="model_info.id is required"):
|
||||
with pytest.raises(ValueError, match=re.escape("model_info.id is required")):
|
||||
_ptu_router(model_info={**windowed, "ptu_effective_to": _dt.date(2027, 1, 1)})
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue