test: say whether a match= pattern is a regex or a literal (ruff RUF043)

This commit is contained in:
ryan-crabbe-berri 2026-08-21 16:25:33 -07:00
parent 5ed230701a
commit 91599aef69
18 changed files with 37 additions and 25 deletions

View file

@ -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",
]

View file

@ -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",

View file

@ -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,

View file

@ -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)

View file

@ -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")

View file

@ -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")

View file

@ -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):

View file

@ -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")

View file

@ -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={},

View file

@ -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",
[

View file

@ -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,

View file

@ -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)

View file

@ -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",

View file

@ -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()

View file

@ -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"),

View file

@ -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

View file

@ -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
)

View file

@ -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)})