Skip to content

Commit 200d79e

Browse files
strawgateclaude
andauthored
Enable PERF and T20 ruff rules (#3845)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 82f310f commit 200d79e

10 files changed

Lines changed: 45 additions & 38 deletions

File tree

pyproject.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ error-on-warning = true
180180
fixable = ["ALL"]
181181
ignore = [
182182
"COM812",
183+
"PERF203", # try-except in loop — all existing hits are intentional (retry loops, error skipping)
183184
"PLR0913", # Too many arguments, MCP Servers have a lot of arguments, OKAY?!
184185
"SIM102", # Dont require combining if statements
185186
]
@@ -194,12 +195,14 @@ extend-select = [
194195
"INP", # flake8-no-pep420: Require __init__.py in namespace packages
195196
"ISC", # flake8-implicit-str-concat: Prevent accidental string concatenation
196197
"LOG", # flake8-logging: Catches logging module misuse
198+
"PERF", # perflint: Performance anti-patterns (unnecessary copies, allocations)
197199
"PIE", # flake8-pie: More idiomatic Python code
198200
"PLE", # pylint-error: Catches actual errors (invalid operations, syntax issues)
199201
"RSE", # flake8-raise: Unnecessary parentheses on raise
200202
"RUF", # Ruff-specific: Modern best practices unique to Ruff
201203
"SIM", # flake8-simplify: Simplifies verbose code patterns
202204
"SLOT", # flake8-slots: Enforce __slots__ where applicable
205+
"T20", # flake8-print: Catch accidental print() in library code
203206
"TID", # flake8-tidy-imports: Banned imports and relative import enforcement
204207
"UP", # pyupgrade: Modernize syntax for newer Python versions
205208
]
@@ -211,6 +214,10 @@ known-first-party = ["fastmcp"]
211214
"__init__.py" = ["F401", "I001", "RUF013"]
212215
# allow imports not at the top of the file
213216
"src/fastmcp/__init__.py" = ["E402"]
217+
# CLI and example code legitimately uses print() for user-facing output
218+
"src/fastmcp/cli/**.py" = ["T20"]
219+
"src/fastmcp/client/oauth_callback.py" = ["T20"]
220+
"src/fastmcp/contrib/**/example.py" = ["T20"]
214221
"!src/**.py" = [ # Only enforce extended ruff rules for code in src/
215222
"B", # flake8-bugbear
216223
"C4", # flake8-comprehensions
@@ -221,12 +228,14 @@ known-first-party = ["fastmcp"]
221228
"INP", # flake8-no-pep420
222229
"ISC", # flake8-implicit-str-concat
223230
"LOG", # flake8-logging
231+
"PERF", # perflint
224232
"PIE", # flake8-pie
225233
"PLE", # pylint-error
226234
"RSE", # flake8-raise
227235
"RUF", # Ruff-specific
228236
"SIM", # flake8-simplify
229237
"SLOT", # flake8-slots
238+
"T20", # flake8-print
230239
"TID", # flake8-tidy-imports
231240
]
232241

src/fastmcp/cli/generate.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ def _tool_function_source(tool: mcp.types.Tool) -> str:
261261

262262
# Build call arguments, using parsed versions for JSON params
263263
call_arg_parts = []
264-
for prop_name, _ in properties.items():
264+
for prop_name in properties:
265265
safe_name = _to_python_identifier(prop_name)
266266
if any(pn == prop_name for pn, _ in json_params):
267267
call_arg_parts.append(f"{prop_name!r}: {safe_name}_parsed")
@@ -313,8 +313,7 @@ def generate_cli_script(
313313
lines.append("from rich.console import Console")
314314
lines.append("")
315315
lines.append("from fastmcp import Client")
316-
for imp in sorted(extra_imports):
317-
lines.append(imp)
316+
lines.extend(sorted(extra_imports))
318317
lines.append("")
319318

320319
# --- Transport config ---
@@ -506,8 +505,7 @@ async def get_prompt(
506505
"# ---------------------------------------------------------------------------"
507506
)
508507

509-
for tool in tools:
510-
lines.append(_tool_function_source(tool))
508+
lines.extend(_tool_function_source(tool) for tool in tools)
511509

512510
# --- Entry point ---
513511
lines.append("")

src/fastmcp/cli/install/goose.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@ def generate_goose_deeplink(
4747
extension_id = _slugify(name)
4848

4949
params: list[str] = [f"cmd={quote(command, safe='')}"]
50-
for arg in args:
51-
params.append(f"arg={quote(arg, safe='')}")
50+
params.extend(f"arg={quote(arg, safe='')}" for arg in args)
5251
params.append(f"id={quote(extension_id, safe='')}")
5352
params.append(f"name={quote(name, safe='')}")
5453
params.append(f"description={quote(description, safe='')}")

src/fastmcp/client/sampling/handlers/anthropic.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -216,12 +216,11 @@ def _convert_to_anthropic_messages(
216216
# Extract text content from the result
217217
result_content: str | list[TextBlockParam] = ""
218218
if item.content:
219-
text_blocks: list[TextBlockParam] = []
220-
for sub_item in item.content:
221-
if isinstance(sub_item, TextContent):
222-
text_blocks.append(
223-
TextBlockParam(type="text", text=sub_item.text)
224-
)
219+
text_blocks: list[TextBlockParam] = [
220+
TextBlockParam(type="text", text=sub_item.text)
221+
for sub_item in item.content
222+
if isinstance(sub_item, TextContent)
223+
]
225224
if len(text_blocks) == 1:
226225
result_content = text_blocks[0]["text"]
227226
elif text_blocks:
@@ -270,12 +269,11 @@ def _convert_to_anthropic_messages(
270269
if isinstance(content, ToolResultContent):
271270
result_content_str: str | list[TextBlockParam] = ""
272271
if content.content:
273-
text_parts: list[TextBlockParam] = []
274-
for item in content.content:
275-
if isinstance(item, TextContent):
276-
text_parts.append(
277-
TextBlockParam(type="text", text=item.text)
278-
)
272+
text_parts: list[TextBlockParam] = [
273+
TextBlockParam(type="text", text=item.text)
274+
for item in content.content
275+
if isinstance(item, TextContent)
276+
]
279277
if len(text_parts) == 1:
280278
result_content_str = text_parts[0]["text"]
281279
elif text_parts:

src/fastmcp/client/sampling/handlers/google_genai.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -280,9 +280,9 @@ def _convert_messages_to_google_genai_content(
280280

281281
# Handle list content (tool calls + results)
282282
if isinstance(content, list):
283-
parts: list[Part] = []
284-
for item in content:
285-
parts.append(_sampling_content_to_google_genai_part(item))
283+
parts: list[Part] = [
284+
_sampling_content_to_google_genai_part(item) for item in content
285+
]
286286

287287
if message.role == "user":
288288
google_messages.append(UserContent(parts=parts))

src/fastmcp/client/sampling/handlers/openai.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,11 @@ def _convert_to_openai_messages(
231231
# Collect tool results (added after assistant message)
232232
content_text = ""
233233
if item.content:
234-
result_texts = []
235-
for sub_item in item.content:
236-
if isinstance(sub_item, TextContent):
237-
result_texts.append(sub_item.text)
234+
result_texts = [
235+
sub_item.text
236+
for sub_item in item.content
237+
if isinstance(sub_item, TextContent)
238+
]
238239
content_text = "\n".join(result_texts)
239240
tool_messages.append(
240241
ChatCompletionToolMessageParam(

src/fastmcp/server/providers/prefab_synthesis.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,11 @@ def _walk_provider(provider: Provider) -> None:
173173
if isinstance(inner, FastMCPApp):
174174
sources.append(inner._local)
175175
for src in sources:
176-
for component in src._components.values():
177-
if isinstance(component, Tool) and _is_prefab_tool(component):
178-
results.append(component)
176+
results.extend(
177+
component
178+
for component in src._components.values()
179+
if isinstance(component, Tool) and _is_prefab_tool(component)
180+
)
179181

180182
# Recurse into aggregate children
181183
from fastmcp.server.providers.aggregate import AggregateProvider

src/fastmcp/server/transforms/prompts_as_tools.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ async def list_prompts() -> str:
104104

105105
result: list[dict[str, Any]] = []
106106
for p in prompts:
107-
result.append(
107+
result.append( # noqa: PERF401
108108
{
109109
"name": p.name,
110110
"description": p.description,

src/fastmcp/server/transforms/resources_as_tools.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ async def list_resources() -> str:
110110
result: list[dict[str, Any]] = []
111111

112112
for r in resources:
113-
result.append(
113+
result.append( # noqa: PERF401
114114
{
115115
"uri": str(r.uri),
116116
"name": r.name,
@@ -120,7 +120,7 @@ async def list_resources() -> str:
120120
)
121121

122122
for t in templates:
123-
result.append(
123+
result.append( # noqa: PERF401
124124
{
125125
"uri_template": t.uri_template,
126126
"name": t.name,

src/fastmcp/utilities/inspect.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
136136
# Extract detailed prompt information
137137
prompt_infos = []
138138
for prompt in prompts_list:
139-
prompt_infos.append(
139+
prompt_infos.append( # noqa: PERF401
140140
PromptInfo(
141141
key=prompt.key,
142142
name=prompt.name or prompt.key,
@@ -156,7 +156,7 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
156156
# Extract detailed resource information
157157
resource_infos = []
158158
for resource in resources_list:
159-
resource_infos.append(
159+
resource_infos.append( # noqa: PERF401
160160
ResourceInfo(
161161
key=resource.key,
162162
uri=str(resource.uri),
@@ -178,7 +178,7 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
178178
# Extract detailed template information
179179
template_infos = []
180180
for template in templates_list:
181-
template_infos.append(
181+
template_infos.append( # noqa: PERF401
182182
TemplateInfo(
183183
key=template.key,
184184
uri_template=template.uri_template,
@@ -258,7 +258,7 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
258258
# Extract detailed tool information from MCP Tool objects
259259
tool_infos = []
260260
for mcp_tool in mcp_tools:
261-
tool_infos.append(
261+
tool_infos.append( # noqa: PERF401
262262
ToolInfo(
263263
key=mcp_tool.name,
264264
name=mcp_tool.name,
@@ -301,7 +301,7 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
301301
# Extract detailed resource information from MCP Resource objects
302302
resource_infos = []
303303
for mcp_resource in mcp_resources:
304-
resource_infos.append(
304+
resource_infos.append( # noqa: PERF401
305305
ResourceInfo(
306306
key=str(mcp_resource.uri),
307307
uri=str(mcp_resource.uri),
@@ -321,7 +321,7 @@ async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
321321
# Extract detailed template information from MCP ResourceTemplate objects
322322
template_infos = []
323323
for mcp_template in mcp_templates:
324-
template_infos.append(
324+
template_infos.append( # noqa: PERF401
325325
TemplateInfo(
326326
key=str(mcp_template.uriTemplate),
327327
uri_template=str(mcp_template.uriTemplate),

0 commit comments

Comments
 (0)